常见协议头部c++结构体


同步,异步,阻塞,非阻塞的概念


TIME_WAIT问题的原理

bind()一个刚刚断开的tcp端口失败了,显示端口状态是TIME_WAIT的原因


http服务器架构学习



recvfrom和sendto

与recv和send类似,多出来的参数用于保存struct sockaddr
recvfrom 默认阻塞,读取的字节数可设为>=实际收到的字节数,可以将目标地址保存在struct sockaddr
sendto可以将目标地址设置在struct sockaddr中


recvmsg和sendmsg

ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
ssizt_t sendmsg(int sockfd, struct msghdr *msg, int flags);

struct msghdr {
    void          *msg_name;            /* protocol address */
    socklen_t     msg_namelen;          /* sieze of protocol address */
    struct iovec  *msg_iov;             /* scatter/gather array */
    int           msg_iovlen;           /* # elements in msg_iov */
    void          *msg_control;         /* ancillary data ( cmsghdr struct) */
    socklen_t     msg_conntrollen;      /* length of ancillary data */
    int           msg_flags;            /* flags returned by recvmsg() */
}

struct iovec {
    void    *iov_base;      /* starting address of buffer */
    size_t  iov_len;        /* size of buffer */
}



struct cmsghdr {
    socklen_t   cmsg_len;   /* length in bytes, including this structure */
    int         cmsg_level; /* originating protocol */
    int         cmsg_type;  /* protocol-specific type */
    /* followed by unsigned char cmsg_data[] */
}
iovec以(iov_base,iov_len)的形式指定msg_iovlen个分散的缓冲区,适合自定义网络协议字段。

msg_name和msg_namelen用于套接字未连接的时候,用来指定接收来源或者发送目的的地址(主要是未连接的UDP套接字)
两个成员分别是套接字地址及其大小,类似recvfrom和sendto的第二和第三个参数。
对于已连接套接字,则可直接将两个参数设置为NULL。
对于recvmsg,msg_name是一个值-结果参数,会返回发送端的套接字地址。
msg_iov和msg_iovlen两个成员用于指定数据缓冲区数组,即iovec结构数组。
参考:https://blog.csdn.net/u014209688/article/details/71311973

epoll

入门
epoll服务器思路
内部原理
特殊性质
如果对一个socket的文件描述符调用close()函数,使其引用计数为0的时候,epoll会自动删除该文件描述符

epoll_ctl的封装

int epfd=epoll_create1(0);
//省去了struct epoll_event赋值的操作
int add_event(int epfd,int fd,int mask)
{
    struct epoll_event ev={0};
    ev.events=mask;
    ev.data.fd=fd;
    return epoll_ctl(rt,EPOLL_CTL_ADD,fd,&ev);
}
int del_event(int rt,int fd)
{
    return epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL);
}
int mod_event(int rt,int fd,int mask)
{
    struct epoll_event ev={0};
    ev.events=mask;
    ev.data.fd=fd;
    return epoll_ctl(epfd,EPOLL_CTL_MOD,fd,&ev);
}


服务端针对socket的read

如果客户端已经关闭了scoket,服务端的read不管有没有读满指定的字节数,都会返回
但服务器read一个长连接的socket时,如果读不满指定的字节就会阻塞,一种解决方法如下
char buf[BUF_SZ];
int remote_sock_fd=socket();

......

int nread;
ioctl(remote_sock_fd,FIONREAD,&nread);

if(!nread)
{
    /*
    对方连接关闭
    */
}
else while(nread)
{
    int ndone=min(BUF_SZ,nread);
    read(remote_sock_fd,buf,ndone);
    nread-=ndone;
}

struct sockaddr_in的封装(tcp服务端)

int init_local_fd(char *ip,int port)//指定ip和端口
{//省去了struct sockaddr_in的赋值,直接得到本地监听的文件描述符
    local_fd=socket(AF_INET, SOCK_STREAM, 0);
    socklen_t sz_of_sockaddr=(socklen_t)sizeof(struct sockaddr);
    struct sockaddr_in local_adr={0};
    local_adr.sin_family=AF_INET;
    local_adr.sin_port=htons(port);
    local_adr.sin_addr.s_addr=inet_addr(ip);
    bind(local_fd,(struct sockaddr*)&local_adr,sz_of_sockaddr);
    return local_fd;//返回绑定了地址的文件描述符
} 
//bind本质就是把一个本地地址绑定到一个fd上,之后对本地地址的操作就不涉及struct sockaddr_in了

struct sockaddr_in的封装(tcp客户端)

int init_remtote_fd(char *ip,int port)
{
    int remtote_fd=socket(AF_INET,SOCK_STREAM,0);//获取一个fd
	struct sockaddr_in remote_adr={0};//填写服务器地址
	remote_adr.sin_family=AF_INET;
	remote_adr.sin_addr.s_addr=inet_addr(ip);
	remote_adr.sin_port=htons(port);
	int ret=connect(fd,(struct sockaddr*)&remote_adr,sizeof(remote_adr));//连接服务器
        if(ret<0)perror("connect");
    return remtote_fd;//返回服务器fd
}
//connect本质就是把一个远程地址绑定到一个fd上,之后对远程地址的操作就不涉及struct sockaddr_in了


struct sockaddr_in的封装(udp服务端)


int init_local_fd(char *ip,int port)//指定ip和端口
{//省去了struct sockaddr_in的赋值,直接得到本地监听的文件描述符
    int local_fd=socket(AF_INET, SOCK_DGRAM, 0);
    struct sockaddr_in local_adr={0};
    local_adr.sin_family=AF_INET;
    local_adr.sin_port=htons(port);
    local_adr.sin_addr.s_addr=inet_addr(ip);
    bind(local_fd,(struct sockaddr*)&local_adr,sz_of_sockaddr);
    if(local_fd<0)perror("bind()");
    return local_fd;//返回绑定了地址的文件描述符
} 
void recv_and_send(char *ip,int port)//接受客户端的消息,再发回去
{
    int local_fd=init_local_fd(ip,port);
    int ret;
    struct sockaddr_in remote_adr;
    printf("local_fd %d\n",local_fd);
    while(1)
    {
        ret=recvfrom(local_fd,buf,sizeof(buf)-1,0,(struct sockaddr*)&remote_adr,&sz_of_sockaddr);
        if(ret<0){perror("recvfrom");return;}
        else 
        {
            buf[ret]=0;
            puts(buf);
        }
        sendto(local_fd,buf,ret,0,(struct sockaddr*)&remote_adr,sz_of_sockaddr);
    }
} 



struct sockaddr_in的封装(udp服务端)


socklen_t sz_of_sockaddr=(socklen_t)sizeof(struct sockaddr);
char buf[1024]="hello\n";
int init_local_fd(char *ip,int port)
{
    int local_fd=socket(AF_INET,SOCK_DGRAM,0);//获取一个fd
    struct sockaddr_in remote_adr={0};//填写服务器地址
    remote_adr.sin_family=AF_INET;
    remote_adr.sin_addr.s_addr=inet_addr(ip);
    remote_adr.sin_port=htons(port);
    int ret;
    ret=sendto(local_fd,buf,strlen(buf),0,(struct sockaddr*)&remote_adr,sz_of_sockaddr);
        if(ret<0)perror("sendto()");
    //先向服务器发一条初始消息
    return local_fd;
}
void recv_and_send(char *ip,int port)//服务端ip,port
{
    int local_fd=init_local_fd(ip,port);
    int ret;
    struct sockaddr_in remote_adr;
    printf("local_fd %d\n",local_fd);
    while(1)
    {//接受服务端消息
        ret=recvfrom(local_fd,buf,sizeof(buf)-1,0,(struct sockaddr*)&remote_adr,&sz_of_sockaddr);
        if(ret<0){perror("recvfrom");return;}
        puts(buf);
     //发送自己的数据
        scanf("%s",buf);//scanf输入       
        int len=strlen(buf);
        buf[len]='\n';
        buf[++len]=0;
        sendto(local_fd,buf,len,0,(struct sockaddr*)&remote_adr,sz_of_sockaddr);
    }
}