网络编程中的select实现超时检测和通用API

来源:互联网 发布:知乎 匿名用户 编辑:程序博客网 时间:2024/05/16 19:39

关于select的基础知识在上一篇文章已经初步总结了,他的应用主要分为三个方向:

  1. 用于完成超时检测(connect、accept、read以及write)
  2. 用于处理客户端的普通文件描述符和套接字文件描述符,监控这些描述符可以避免在服务器死掉以后,客户端阻塞在等待标准输入或者其他文件的读取。
  3. 用于服务器构造多并发,使用select可以实现单进程实现多并发!只不过是自己管理所有建立的用于通信的链接和用于监听的套接字,有别于poll和epoll(底层自己管理)

实现connect超时检测

首先要明确以下知识点:

  • 在建立套接字(fd)以后默认是阻塞的
  • 如果客户端连接服务器发生异常,则默认阻塞的情况下,返回时间是1.5RTT,大约100秒!–软件质量低下
  • 先把套接字通过fcntl变为非阻塞模型,再调用connect函数
    • 如果网络顺利,直接建立链接
    • 如果网络不好,则根据返回值判断,connect的返回值如果是-1而且errno==EINPROGRESS表示客户端和服务器正在建立连接,需要等待一段时间才能建立链接
    • 等待的时间我们可以自己设定
    • 利用select监控该套接字—提高软件质量
  • 尽管select返回了套接字的可写状态(套接字默认是可写入状态,正常建立连接以后也是表返回可写入状态)但不一定表示就是正确建立链接
    • select返回大于零的值
    • 真正建立链接
    • 建立套接字产生错误,会返回写失败信息,造成可写入的状态。 此时错误信息不会保存至errno变量中,因此,需要调用getsockopt来获取。
/** * activate_noblock - 设置I/O为非阻塞模式 * @fd: 文件描符符 */int activate_nonblock(int fd){  int ret = 0;  int flags = fcntl(fd,F_GETFL);  if(-1 == flags)  {    ret =flags;    perror("fcntl");    return ret;  }  flags |= O_NONBLOCK;  ret = fcntl(fd,F_SETFL,flags);  if(ret == -1)  {    perror("fcntl(fd,F_SETFL,flags)");    return ret;  }  return ret;}/** * deactivate_nonblock - 设置I/O为阻塞模式 * @fd: 文件描符符 */int deactivate_nonblock(int fd){  int ret = 0;  int flags = fcntl(fd,F_GETFL);  if(-1 == flags)  {    ret =flags;    perror("fcntl");    return ret;  }  flags &= ~O_NONBLOCK;  ret = fcntl(fd,F_SETFL,flags);  if(ret == -1)  {    perror("fcntl(fd,F_SETFL,flags)");    return ret;  }  return ret;}/** * connect_timeout - connect * @fd: 套接字 * @addr: 要连接的对方地址 * @wait_seconds: 等待超时秒数,如果为0表示正常模式 * 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT */static int connect_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds){  int ret = 0;  socklen_t addrlen = sizeof(struct sockaddr_in);  fd_set connect_fdset;  struct timeval timeout;  int err;    socklen_t socklen = sizeof(err);    int sockoptret;   if(wait_seconds > 0)//设置成非阻塞--因为文件描述符默认是阻塞的  {    activate_nonblock(fd);  }  /*非阻塞  --成功:立马建立连接  --失败:ret < 0 && errno == EINPROGRESS,表示没有获取到链接  */  ret = connect(fd,(struct sockaddr*)addr,addrlen);  if(ret < 0 && errno == EINPROGRESS)  {    FD_ZERO(&connect_fdset);    FD_SET(fd,&connect_fdset);    timeout.tv_sec = wait_seconds;    timeout.tv_usec = 0;    do    {      // 一但连接建立,则套接字就可写  所以connect_fdset放在了写集合中      ret = select(fd + 1,NULL,&connect_fdset,NULL,&timeout);//在规定时间内监控链接    }while(ret < 0 && errno == EINTR);    if(ret == 0)//超时    {      ret = -1;      errno = ETIMEDOUT;    }    else if (ret < 0)//select出错    {      return -1;    }    else if( ret == 1)//有两种情况会导致文件描述符变为可写入的状态/准备好的状态    {      /* ret返回为1(表示套接字可写),可能有两种情况,一种是连接建立成功,一种是套接字产生错误,*/            /* 此时错误信息不会保存至errno变量中,因此,需要调用getsockopt来获取。 */      sockoptret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &socklen);//获取套接字的状态      if(sockoptret == -1)//getsockopt(可以修改十一种状态,诸如TIME_WAIT的时间等)调用失败      {        return -1;      }      if(err == 0)//真正可写入/准备好        ret = 0;      else{//-1表示套接字产生错误        errno = err;        ret = -1;      }    }  }  if (wait_seconds > 0)    {        deactivate_nonblock(fd);    }  return ret;}

实现accept超时检测

/** * accept_timeout - 带超时的accept * @fd: 套接字 * @addr: 输出参数,返回对方地址 * @wait_seconds: 等待超时秒数,如果为0表示正常模式 * 成功(未超时)返回已连接套接字,超时返回-1并且errno = ETIMEDOUT */int accept_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds){    int ret;    socklen_t addrlen = sizeof(struct sockaddr_in);    if (wait_seconds > 0)    {        fd_set accept_fdset;        struct timeval timeout;        FD_ZERO(&accept_fdset);        FD_SET(fd, &accept_fdset);        timeout.tv_sec = wait_seconds;        timeout.tv_usec = 0;        do        {            ret = select(fd + 1, &accept_fdset, NULL, NULL, &timeout);        } while (ret < 0 && errno == EINTR);        if (ret == -1)            return -1;        else if (ret == 0)        {            errno = ETIMEDOUT;            return -1;        }    }

实现read超时检测

/** * read_timeout - 读超时检测函数,不含读操作 * @fd: 文件描述符 * @wait_seconds: 等待超时秒数,如果为0表示不检测超时 * 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT */int read_timeout(int fd, unsigned int wait_seconds){    int ret = 0;    if (wait_seconds > 0)    {        fd_set read_fdset;        struct timeval timeout;        FD_ZERO(&read_fdset);        FD_SET(fd, &read_fdset);        timeout.tv_sec = wait_seconds;        timeout.tv_usec = 0;        //select返回值三态        //1 若timeout时间到(超时),没有检测到读事件 ret返回=0        //2 若ret返回<0 &&  errno == EINTR 说明select的过程中被别的信号中断(可中断睡眠原理)        //2-1 若返回-1,select出错        //3 若ret返回值>0 表示有read事件发生,返回事件发生的个数        do        {            ret = select(fd + 1, &read_fdset, NULL, NULL, &timeout);        } while (ret < 0 && errno == EINTR);         if (ret == 0)        {            ret = -1;            errno = ETIMEDOUT;        }        else if (ret == 1)            ret = 0;    }    return ret;}

实现write超时检测

/** * write_timeout - 写超时检测函数,不含写操作 * @fd: 文件描述符 * @wait_seconds: 等待超时秒数,如果为0表示不检测超时 * 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT */int write_timeout(int fd, unsigned int wait_seconds){    int ret = 0;    if (wait_seconds > 0)    {        fd_set write_fdset;        struct timeval timeout;        FD_ZERO(&write_fdset);        FD_SET(fd, &write_fdset);        timeout.tv_sec = wait_seconds;        timeout.tv_usec = 0;        do        {            ret = select(fd + 1, NULL, &write_fdset, NULL, &timeout);        } while (ret < 0 && errno == EINTR);        if (ret == 0)        {            ret = -1;            errno = ETIMEDOUT;        }        else if (ret == 1)            ret = 0;    }    return ret;}

利用select实现的通用套接字编程库

  • 头文件
#ifndef __COMSOCKET_H__#define __COMSOCKET_H__#ifdef __cplusplusextern "C"{#endif#include <sys/types.h>          /* See NOTES */#include <sys/socket.h>#include <netinet/in.h>#include <netinet/ip.h> /* superset of previous */#include <arpa/inet.h>#include <unistd.h>#include <stdlib.h>#include <stdio.h>#include <string.h>#include <signal.h>#include <errno.h>#include <fcntl.h>//错误码定义  #define Sck_Ok              0#define Sck_BaseErr         3000#define Sck_ErrParam                    (Sck_BaseErr+1)#define Sck_ErrTimeOut                  (Sck_BaseErr+2)#define Sck_ErrPeerClosed               (Sck_BaseErr+3)#define Sck_ErrMalloc                   (Sck_BaseErr+4)//函数声明//客户端环境低级初始化int sckClient_init01(void **handle,char * ip,int port,int con_time,int send_time,int recv_time);//客户端环境高级初始化int sckClient_init(void **handle,int con_time,int send_time,int recv_time,int cnt);//客户端获取链接int sckClient_getConn(void *handle,char * ip,int port,int * con_fd);//客户端关闭连接int sckClient_closeConn(int  con_fd);//客户端发送报文int sckClient_send(void *handle, int con_fd,unsigned char *data, int datalen);//客户端端接受报文int sckClient_rcv(void *handle,int con_fd, unsigned char *out, int *outlen); //1// 客户端环境释放 void sckClient_destroy(void **handle);/*=========================================================================================*///函数声明//服务器端初始化int sckServer_init(int port, int *listenfd);int sckServer_accept(int listenfd, int *con_fd,  int timeout);//服务器端发送报文int sckServer_send(int con_fd,  unsigned char *data, int datalen, int timeout);//服务器端端接受报文int sckServer_rcv(int  con_fd, unsigned char *out, int *outlen,  int timeout); //服务器端环境释放 int sckServer_destroy(int con_fd);#ifdef __cplusplus}#endif#endif
  • 实现文件
#include "comsocket.h"typedef struct _sckHandle{  int sock_fd;  int arrayNum;  int con_time;  int send_time;  int recv_time;}SckHandle;/** * readn - 读取固定字节数 * @fd: 文件描述符 * @buf: 接收缓冲区 * @count: 要读取的字节数 * 成功返回count,失败返回-1,读到EOF返回<count */ssize_t readn(int fd, void *buf, size_t count){    size_t nleft = count;//剩余字节数    ssize_t nread;//已经读取的字节数    char *bufp = (char*)buf;    while (nleft > 0)    {        if ((nread = read(fd, bufp, nleft)) < 0)        {            if (errno == EINTR)                continue;            return -1;        }        else if (nread == 0)            return count - nleft;        bufp += nread;        nleft -= nread;    }    return count;}/** * writen - 发送固定字节数 * @fd: 文件描述符 * @buf: 发送缓冲区 * @count: 要读取的字节数 * 成功返回count,失败返回-1 */ssize_t writen(int fd, const void *buf, size_t count){    size_t nleft = count;    ssize_t nwritten;    char *bufp = (char*)buf;    while (nleft > 0)    {        if ((nwritten = write(fd, bufp, nleft)) < 0)        {            if (errno == EINTR)                continue;            return -1;        }        else if (nwritten == 0)            continue;        bufp += nwritten;        nleft -= nwritten;    }    return count;}/** * recv_peek - 仅仅查看套接字缓冲区数据,但不移除数据 * @sockfd: 套接字 * @buf: 接收缓冲区 * @len: 长度 * 成功返回>=0,失败返回-1 */ssize_t recv_peek(int sockfd, void *buf, size_t len){    while (1)    {        int ret = recv(sockfd, buf, len, MSG_PEEK);        if (ret == -1 && errno == EINTR)            continue;        return ret;    }}/** * activate_noblock - 设置I/O为非阻塞模式 * @fd: 文件描符符 */int activate_nonblock(int fd){  int ret = 0;  int flags = fcntl(fd,F_GETFL);  if(-1 == flags)  {    ret =flags;    perror("fcntl");    return ret;  }  flags |= O_NONBLOCK;  ret = fcntl(fd,F_SETFL,flags);  if(ret == -1)  {    perror("fcntl(fd,F_SETFL,flags)");    return ret;  }  return ret;}/** * deactivate_nonblock - 设置I/O为阻塞模式 * @fd: 文件描符符 */int deactivate_nonblock(int fd){  int ret = 0;  int flags = fcntl(fd,F_GETFL);  if(-1 == flags)  {    ret =flags;    perror("fcntl");    return ret;  }  flags &= ~O_NONBLOCK;  ret = fcntl(fd,F_SETFL,flags);  if(ret == -1)  {    perror("fcntl(fd,F_SETFL,flags)");    return ret;  }  return ret;}/** * connect_timeout - connect * @fd: 套接字 * @addr: 要连接的对方地址 * @wait_seconds: 等待超时秒数,如果为0表示正常模式 * 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT */static int connect_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds){  int ret = 0;  socklen_t addrlen = sizeof(struct sockaddr_in);  fd_set connect_fdset;  struct timeval timeout;  int err;    socklen_t socklen = sizeof(err);    int sockoptret;   if(wait_seconds > 0)//设置成非阻塞--因为文件描述符默认是阻塞的  {    activate_nonblock(fd);  }  /*非阻塞  --成功:立马建立连接  --失败:ret < 0 && errno == EINPROGRESS,表示没有获取到链接  */  ret = connect(fd,(struct sockaddr*)addr,addrlen);  if(ret < 0 && errno == EINPROGRESS)  {    FD_ZERO(&connect_fdset);    FD_SET(fd,&connect_fdset);    timeout.tv_sec = wait_seconds;    timeout.tv_usec = 0;    do    {      // 一但连接建立,则套接字就可写  所以connect_fdset放在了写集合中      ret = select(fd + 1,NULL,&connect_fdset,NULL,&timeout);//在规定时间内监控链接    }while(ret < 0 && errno == EINTR);    if(ret == 0)//超时    {      ret = -1;      errno = ETIMEDOUT;    }    else if (ret < 0)//select出错    {      return -1;    }    else if( ret == 1)//有两种情况会导致文件描述符变为可写入的状态/准备好的状态    {      /* ret返回为1(表示套接字可写),可能有两种情况,一种是连接建立成功,一种是套接字产生错误,*/            /* 此时错误信息不会保存至errno变量中,因此,需要调用getsockopt来获取。 */      sockoptret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &socklen);//获取套接字的状态      if(sockoptret == -1)//getsockopt(可以修改十一种状态,诸如TIME_WAIT的时间等)调用失败      {        return -1;      }      if(err == 0)//真正可写入/准备好        ret = 0;      else{//-1表示套接字产生错误        errno = err;        ret = -1;      }    }  }  if (wait_seconds > 0)    {        deactivate_nonblock(fd);    }  return ret;}/** * write_timeout - 写超时检测函数,不含写操作 * @fd: 文件描述符 * @wait_seconds: 等待超时秒数,如果为0表示不检测超时 * 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT */int write_timeout(int fd, unsigned int wait_seconds){    int ret = 0;    if (wait_seconds > 0)    {        fd_set write_fdset;        struct timeval timeout;        FD_ZERO(&write_fdset);        FD_SET(fd, &write_fdset);        timeout.tv_sec = wait_seconds;        timeout.tv_usec = 0;        do        {            ret = select(fd + 1, NULL, &write_fdset, NULL, &timeout);        } while (ret < 0 && errno == EINTR);        if (ret == 0)        {            ret = -1;            errno = ETIMEDOUT;        }        else if (ret == 1)            ret = 0;    }    return ret;}/** * read_timeout - 读超时检测函数,不含读操作 * @fd: 文件描述符 * @wait_seconds: 等待超时秒数,如果为0表示不检测超时 * 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT */int read_timeout(int fd, unsigned int wait_seconds){    int ret = 0;    if (wait_seconds > 0)    {        fd_set read_fdset;        struct timeval timeout;        FD_ZERO(&read_fdset);        FD_SET(fd, &read_fdset);        timeout.tv_sec = wait_seconds;        timeout.tv_usec = 0;        //select返回值三态        //1 若timeout时间到(超时),没有检测到读事件 ret返回=0        //2 若ret返回<0 &&  errno == EINTR 说明select的过程中被别的信号中断(可中断睡眠原理)        //2-1 若返回-1,select出错        //3 若ret返回值>0 表示有read事件发生,返回事件发生的个数        do        {            ret = select(fd + 1, &read_fdset, NULL, NULL, &timeout);        } while (ret < 0 && errno == EINTR);         if (ret == 0)        {            ret = -1;            errno = ETIMEDOUT;        }        else if (ret == 1)            ret = 0;    }    return ret;}//客户端环境低级初始化int sckClient_init01(void **handle,char * ip,int port,int con_time,int send_time,int recv_time){  int ret = 0;  SckHandle * tmp = NULL;  int sock;  int con;  struct sockaddr_in serv_addr;  if(handle == NULL || con_time < 0 || send_time < 0 || recv_time < 0    || ip == NULL|| port < 0||port > 65535)  {    ret = Sck_ErrParam;    perror("sckClient_init:(handle == NULL || con_time < 0 || send_time < 0 || recv_time < 0)");    return ret;  }  tmp = (SckHandle *)malloc(sizeof(SckHandle));  if(tmp == NULL)  {    ret =  Sck_ErrMalloc;    perror("malloc(sizeof(SckHandle)");    return ret;  }  tmp->con_time = con_time;  tmp->send_time = send_time;  tmp->recv_time = recv_time;  sock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);  if(sock < 0)  {    ret = errno;    perror("socket");    return ret;  }  bzero(&serv_addr,sizeof(serv_addr));  serv_addr.sin_family = AF_INET;  serv_addr.sin_port = htons(port);  serv_addr.sin_addr.s_addr = inet_addr(ip);  if((con = connect(sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr))) < 0)  {    ret = errno;    perror("connect");    return ret;  }  //tmp->con_fd = con;  tmp->sock_fd = sock;  *handle = tmp;  return ret;}//客户端环境高级初始化//成功返回0,失败返回相应的错误码//参数分别是SckHandle的结构体成员int sckClient_init(void **handle,int con_time,int send_time,int recv_time,int cnt){  int ret = 0;  SckHandle * tmp = NULL;  if(handle == NULL || con_time < 0 || send_time < 0 || recv_time < 0)//合法性检测  {    ret = Sck_ErrParam;    printf("sckClient_init:(handle == NULL || con_time < 0 || send_time < 0 || recv_time < 0)");    return ret;  }  /*分配堆空间内存--临时变量*/  tmp = (SckHandle *)malloc(sizeof(SckHandle));  if(tmp == NULL)  {    ret =  Sck_ErrMalloc;    perror("malloc(sizeof(SckHandle)");    return ret;  }  //进行初始化  tmp->con_time = con_time;  tmp->send_time = send_time;  tmp->recv_time = recv_time;  tmp->arrayNum = cnt;  //将分配好的结果甩出去  *handle = tmp;  return ret;}//客户端获取链接//成功:返回0,失败返回错误代码//参数是服务器地址以及已经初始化好的句柄和准备建立的套接字int sckClient_getConn(void *handle,char * ip,int port,int * con_fd){  int ret = 0;  struct sockaddr_in serv_addr;  SckHandle * tmp = (SckHandle *)handle;  int sock;  //合法性检测  if(handle == NULL || ip == NULL || con_fd == NULL     || port < 0 || port  > 65535)    {      ret = Sck_ErrParam;      printf("sckClient_getConn(handle == NULL || ip == NULL || con_fd == NULL port < 0 || port  > 65535)\n");      return ret;    }  //建立套接字  sock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);  if(sock < 0)  {    ret = errno;    perror("socket");    return ret;  }  //将建立好的套接字赋值给临时变量  tmp->sock_fd = sock;  //设置要连接的服务器地址  bzero(&serv_addr,sizeof(serv_addr));  serv_addr.sin_family = AF_INET;  serv_addr.sin_port = htons(port);  serv_addr.sin_addr.s_addr = inet_addr(ip);  /*if(connect(tmp->sock_fd,(struct sockaddr*)&serv_addr,sizeof(serv_addr)) < 0)  {    ret = errno;    perror("connect");    return ret;  }*/  //连接服务器  // 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT  ret = connect_timeout(tmp->sock_fd, (struct sockaddr_in*) (&serv_addr), (unsigned int )tmp->con_time);  if (ret < 0)//连接服务器失败  {    if (ret==-1 && errno == ETIMEDOUT)//连接超时    {        ret = Sck_ErrTimeOut;        return ret;    }    else//其他情况的连接失败    {        printf("func connect_timeout() err:  %d\n", ret);    }  }  *con_fd = tmp->sock_fd;  //将建立好的套接字甩出去  return ret;}//客户端关闭连接//参数:要关闭的套接字int sckClient_closeConn(int  con_fd){  if(con_fd > 0)  {    close(con_fd);  }  return 0;}//客户端发送报文//成功:返回0,失败:返回错误代码(-1或者其他非零值)//参数:句柄、已经建立的套接字、要发送数据缓冲区的地址、要发送的数据长度int sckClient_send(void *handle, int con_fd,unsigned char *data, int datalen){  int ret = 0;  SckHandle * tmp = (SckHandle *)handle;  unsigned char * pkt = NULL;  int nwrite = 0;  int pkt_len = 0;  //合法性检测  if(handle == NULL || data == NULL    || con_fd < 0 || con_fd > 65535 || datalen <0)    {      ret = Sck_ErrParam;      perror("Sck_ErrParam");      return ret;    }  //写超时检测  // 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT  ret = write_timeout(con_fd,tmp->send_time);  if(ret == 0)//没有超时  {    pkt = (unsigned char *)malloc(datalen + sizeof(int));//分配堆空间内存--临时数据包    if(pkt == NULL)    {      ret = Sck_ErrMalloc;      perror("malloc(datalen + sizeof(int))");      return ret;    }    pkt_len = htonl(datalen);//包头(数据包长度)字节序转换    memcpy(pkt,&pkt_len,sizeof(int));//复制转换后表示长度的数据到包头    memcpy(pkt+sizeof(int),data,datalen);//复制有效数据到包体    nwrite = writen(con_fd,pkt,datalen + sizeof(int));//向服务器发送数据    if(nwrite < (datalen + sizeof(int)))//写入出错    {      if(pkt != NULL)//回收内存      {        free(pkt);        pkt = NULL;      }      return nwrite;    }  }  else if(ret < 0)//在检测超时的时候调用select失败  {    if(ret == -1 && errno == ETIMEDOUT)//确实超时了    {      ret = Sck_ErrTimeOut;      printf("write_timeout(con_fd,tmp->send_time)");      return ret;    }     return ret;//其他失败的情况  }  //内存回收  free(pkt);  pkt = NULL;  return ret;}/*typedef struct _pkt{  int len;  unsigned char buf[1024];}pkt_t;*///客户端端接受报文//成功:返回0,失败:返回错误代码或者实际读取到的字节数,但是该数字小于包头存储的数字//参数:句柄、已经建立好的套接字,目的缓冲区地址、正确读取以后读取到的字节数int sckClient_rcv(void *handle,int con_fd, unsigned char *out, int *outlen){  int ret = 0;  SckHandle * tmp = (SckHandle *)handle;  int tmp_len ;  //合法性检测  if(handle == NULL || out == NULL    || outlen == NULL||con_fd < 0 || con_fd > 65535)    {      ret = Sck_ErrParam;      perror("Sck_ErrParam");      return ret;    }  //超时检测    ret = read_timeout(con_fd, tmp->recv_time);  if(ret == -1)//调用select失败  {    if(errno == ETIMEDOUT)//确实超时了    {      ret = Sck_ErrTimeOut;      perror("read_timeout");      return ret;    }    perror("read_timeout");//其他情况的失败    return ret;  }  else if(ret == 0)//没有超时  {    //成功返回count,失败返回-1,读到EOF返回<count    ret = readn(con_fd,&tmp_len,sizeof(tmp_len));//读取包头--表示有效数据长度    if(ret == -1)//读取失败     {      printf("func readn() err:%d\n",ret);      return ret;    }    else if(ret < sizeof(tmp_len))//读取结束--没有数据可读--对方关闭连接    {      ret = Sck_ErrPeerClosed;      printf("client close\n");      return ret ;//返回读取到的字节数--没用的字节数--表示错误    }    tmp_len = ntohl(tmp_len);//字节序转换得到包体长度    ret = readn(con_fd,out,tmp_len);//读取指定长度的有效数据    if(ret == -1)//读取失败    {      printf("func readn() err:%d\n",ret);      return ret;    }    else if(ret < tmp_len)//对方关闭连接    {      ret = Sck_ErrPeerClosed;      printf("client close\n");      return ret ;//返回读取到的字节数--没用的字节数--表示错误    }  }  *outlen = tmp_len;  return 0;//最后一定要返回0,因为ret在最后变成了readn函数的返回结果--读取到的字节数}// 客户端环境释放 //参数:句柄void sckClient_destroy(void **handle){  if(handle != NULL)  {    free(*handle);    *handle = NULL;  }}/*===================================================================================*///服务器端初始化//成功:返回0,失败:返回错误码//参数:要绑定的端口号、准备建立的监听套接字int sckServer_init(int port, int *listenfd){  int   ret = 0;    int mylistenfd;    struct sockaddr_in servaddr;  //初始化服务器地址    memset(&servaddr, 0, sizeof(servaddr));    servaddr.sin_family = AF_INET;    servaddr.sin_port = htons(port);    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);    //建立监听用的套接字     mylistenfd = socket(AF_INET, SOCK_STREAM, 0);    if (mylistenfd < 0)    {        ret = errno ;        printf("func socket() err:%d \n", ret);        return ret;    }  //设置地址复用    int on = 1;    ret = setsockopt(mylistenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) );    if (ret < 0)    {        ret = errno ;        printf("func setsockopt() err:%d \n", ret);        return ret;    }  //绑定地址/端口和监听用的套接字    ret = bind(mylistenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));    if (ret < 0)    {        ret = errno ;        printf("func bind() err:%d \n", ret);        return ret;    }  //开始监听        ret = listen(mylistenfd, SOMAXCONN);    if (ret < 0)    {        ret = errno ;        printf("func listen() err:%d \n", ret);        return ret;    }  //将建立好的监听套接字甩出去       *listenfd = mylistenfd;    return ret;//成功初始化返回0,否则返回出错步骤返回的错误代码}/** * accept_timeout - 带超时的accept * @fd: 套接字 * @addr: 输出参数,返回对方地址 * @wait_seconds: 等待超时秒数,如果为0表示正常模式 * 成功(未超时)返回已连接套接字,超时返回-1并且errno = ETIMEDOUT */int accept_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds){    int ret;    socklen_t addrlen = sizeof(struct sockaddr_in);    if (wait_seconds > 0)    {        fd_set accept_fdset;        struct timeval timeout;        FD_ZERO(&accept_fdset);        FD_SET(fd, &accept_fdset);        timeout.tv_sec = wait_seconds;        timeout.tv_usec = 0;        do        {            ret = select(fd + 1, &accept_fdset, NULL, NULL, &timeout);        } while (ret < 0 && errno == EINTR);        if (ret == -1)            return -1;        else if (ret == 0)        {            errno = ETIMEDOUT;            return -1;        }   return ret ;    }    //一但检测出 有select事件发生,表示对等方完成了三次握手,客户端有新连接建立    //此时再调用accept将不会堵塞    if (addr != NULL)        ret = accept(fd, (struct sockaddr*)addr, &addrlen); //返回已连接套接字    else        ret = accept(fd, NULL, NULL);        if (ret == -1)        {            ret = errno;            printf("func accept() err:%d \n", ret);            return ret;        }    return ret;}//服务器端接受客户端连接请求//成功:返回0,失败:返回错误码//参数:已经建立好的用于监听的套接字,准备建立的用于连接的套接字文件描述符--连接、超时时间值int sckServer_accept(int listenfd, int *con_fd,  int timeout){  int   ret = 0;  //超时检测  //成功(未超时)返回已连接套接字,超时返回-1并且errno = ETIMEDOUT    ret = accept_timeout(listenfd, NULL, (unsigned int) timeout);//这里可以改进--这里没有保存客户端的地址    if (ret < 0)    {        if (ret == -1 && errno == ETIMEDOUT)//确实超时        {            ret = Sck_ErrTimeOut;            printf("func accept_timeout() timeout err:%d \n", ret);            return ret;        }        else//其他select失败的情况        {            ret = errno;            printf("func accept_timeout() err:%d \n", ret);            return ret;        }    }    *con_fd = ret;//将从完成三次握手的队列里面取出可用的连接甩出去    return ret;//成功返回0,失败返回错误代码}//服务器端发送报文//成功:返回0,失败:返回错误代码或者实际写入的字节数//参数:已经建立好的连接,待发送数据、要发送长度、超时时间int sckServer_send(int con_fd,  unsigned char *data, int datalen, int timeout){  int   ret = 0;  int writed = 0;  unsigned char *netdata = NULL;  //超时检测  // 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT    ret = write_timeout(con_fd, timeout);    if (ret == 0)//没有超时    {        writed = 0;        netdata = ( unsigned char *)malloc(datalen + 4);//分配空间        if ( netdata == NULL)        {            ret = Sck_ErrMalloc;            printf("func sckServer_send() mlloc Err:%d\n ", ret);            return ret;        }    //数据打包        int netlen = htonl(datalen);        memcpy(netdata, &netlen, 4);        memcpy(netdata+4, data, datalen);    //数据发送        writed = writen(con_fd, netdata, datalen + 4);        if (writed < (datalen + 4) )//对方关闭        {            if (netdata != NULL)             {                free(netdata);//内存回收                netdata = NULL;            }            return writed;//返回实际写入的数据--非零都表示发送失败        }    }    else if (ret < 0)    {        //失败返回-1,超时返回-1并且errno = ETIMEDOUT        if (ret == -1 && errno == ETIMEDOUT)//确实超时        {            ret = Sck_ErrTimeOut;            printf("func sckServer_send() mlloc Err:%d\n ", ret);            return ret;        }        return ret;//其他情况的select失败。ret非零,但不一定是-1    }  free(netdata);//内存回收    netdata = NULL;    return ret;}//服务器端端接受报文//成功:返回0,失败:返回错误代码或者实际读取到的字节数,但是该数字小于包头存储的数字//参数:已经建立好的连接,目的缓冲区地址、正确读取以后读取到的字节数,超时时间int sckServer_rcv(int  con_fd, unsigned char *out, int *outlen,  int timeout){  int       ret = 0;  //合法性检测    if (out==NULL || outlen==NULL)    {        ret = Sck_ErrParam;        printf("func sckClient_rev() timeout , err:%d \n", Sck_ErrTimeOut);        return ret;    }    ret =  read_timeout(con_fd, timeout); //超时检测    if (ret != 0)//select调用失败    {        if (ret==-1 || errno == ETIMEDOUT)//确实超时了        {            ret = Sck_ErrTimeOut;            printf("func sckClient_rev() timeout , err:%d \n", Sck_ErrTimeOut);            return ret;        }        else//其他情况的失败        {            printf("func sckClient_rev() timeout , err:%d \n", Sck_ErrTimeOut);            return ret;        }       }    //没有超时    int netdatalen = 0;  ret = readn(con_fd, &netdatalen,  4); //读包头 4个字节    if (ret == -1)//读取失败    {        printf("func readn() err:%d \n", ret);        return ret;    }    else if (ret < 4)//对方关闭    {        ret = Sck_ErrPeerClosed;        printf("func readn() err peer closed:%d \n", ret);        return ret;    }    int n;    n = ntohl(netdatalen);//字节序转换    ret = readn(con_fd, out, n); //根据长度读数据    if (ret == -1)//读取失败    {        printf("func readn() err:%d \n", ret);        return ret;    }    else if (ret < n)//对方关闭    {        ret = Sck_ErrPeerClosed;        printf("func readn() err peer closed:%d \n", ret);        return ret;//返回读取到的字节数--没用的字节数--表示错误    }    *outlen = n;//将正确读取以后读取到的字节数甩出去    return 0;//最后一定要返回0,因为ret在最后变成了readn函数的返回结果--读取到的实际字节数}//服务器端环境释放 //成功:返回,失败:返回-1//准备关闭的链接int sckServer_destroy(int con_fd){  if(close(con_fd) == 0)    return 0;  else     return -1;}
  • 客户端测试代码
#include "comsocket.h"int main(){  void * handle = NULL;  int ret = 0;  unsigned char *data = (unsigned char *)"asdfghjklaedas";  unsigned char out[1024] = {0};  int datalen = 10;  int outlen = 1024;  int con_fd;  //客户端环境初始化  ret = sckClient_init(&handle,5,5,5,1);  ret = sckClient_getConn(handle,"192.168.1.110",8001,&con_fd);  //客户端发送报文  ret = sckClient_send(handle,con_fd, data,datalen);  //客户端端接受报文  ret = sckClient_rcv(handle,con_fd,out, &outlen);   out[outlen] = '\0';    printf("data: %s \nlen: %d\n", out,outlen);  // 客户端环境释放   sckClient_destroy(&handle);  return ret;}
  • 服务器端测试代码
#include "comsocket.h"#define PORT 8001void chld_handle(int sig){  pid_t my_pid;  //wait(NULL);//只能处理单进程的情况  while((my_pid = waitpid(-1, NULL, WNOHANG)) > 0)    printf("child is died,parent take care of it:%d\n", my_pid);}int main(){  int ret = 0;  int listenfd;  int con_fd;  int datalen = 1024;  int timeout = 5;  //unsigned char *data = (unsigned char *)"lzjyrm";  unsigned char recv[1024] = {0};  pid_t pid;  signal(SIGCHLD,chld_handle);  signal(SIGPIPE,SIG_IGN);  ret = sckServer_init(PORT, &listenfd);  if(ret != 0)  {    printf("sckServer_init err:%d\n ",ret);    return ret;  }  printf("Waiting for connect.....\n");  while(1)  {    ret = sckServer_accept(listenfd, &con_fd,  timeout);    if(ret == Sck_ErrTimeOut)    {      printf("sckServer_accept Sck_ErrTimeOut");      continue;    }    pid = fork();    if(pid == -1)    {      perror("fork");      return errno;    }    else if(pid == 0)    {      close(listenfd);      while(1)      {        memset(recv,0,sizeof(recv));        ret = sckServer_rcv(con_fd, recv, &datalen,  timeout);  //服务器端端接受报文        if(ret != 0)        {          printf("sckServer_rcv: %d\n",ret);          break;        }        recv[datalen] = '\0';        printf("rcv:%s\n==================\n",recv);        //服务器端发送报文        ret = sckServer_send(con_fd,recv, datalen, timeout);        if(ret != 0)        {          printf("sckServer_send: %d\n",ret);          break;        }      }      close(con_fd);      exit(ret);    }    else if(pid > 0)    {      close(con_fd);    }  }  //服务器端环境释放   //ret = sckServer_destroy(void **handle);  return 0;}
0 0