打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
socket网络编程快速上手(二)——细节问题(5)(完结篇)

6.Connect的使用方式

  前面提到,connect发生EINTR错误时,是不能重新启动的。那怎么办呢,是关闭套接字还是直接退出进程呢?如果EINTR前,三次握手已经发起,我们当然希望链路就此已经建立完成,不要再重新走流程了。这个时候我们就需要为connect量身定做一个使用方案。代码如下:

connectWithTimeout

  这部分代码是从原有工程里抠出来的,在学习完前面知识后,我觉得有些地方不是很完善,按照下面的代码做了修改。此处将老代码贴出只是为了防止自己的理解有误,做了画蛇添足的事情。新代码如下:

connectWithTimeoutNew.c

  这是一个非阻塞式connect的模型,更为详细的介绍可以看《UNIX网络编程》(我真不是个做广告的)。但此处模型个人认为逻辑上还是完善的。

15-16行:设置套接口为非阻塞方式,这个步骤需要视使用环境而定,因此这里存在一个移植性问题。

18-21行:非阻塞模式下的connect,调用后立即返回,如果已经errno为EINPROGRESS,说明已经发起了三次握手。否则为异常。

23-45行:使用select去监测套接口状态。实现规定(那些乱七八糟的不同实现,我一直搞不清楚,哎,接触的不多!大家可以自己去查查):(1)当连接建立成功时,描述字变为可写,(2)当连接建立遇到错误时,描述字变为既可读又可写。我是个比较投机的人,归纳一下,描述字变为可写时说明连接有结果了。30行使用getsockopt获取描述字状态(使用SO_ERROR选项)。建立成功err为0,否则为-1。对于一个商业软件的“贡献者”,我们不能放过任何出错处理,但这里不对getsockopt的返回值进行出错处理是有原因的。在异常情况下,有些实现该调用返回0,有些则返回-1,因此我们不能根据它的返回值来判断连接是否异常。38-45行,前面已经提到,select这个阻塞的家伙很有可能发生EINTR错误,我们也必须兼容这种错误。注意reselect的位置,自己使用时位置放错了效果可是截然不同的。

48-51行:connect有时反应很快啊,一经调用就成功建立连接了,这种情况是存在的。所以我们也要兼容这种情况。

后面行:恢复调用前状态。

  又到了热血澎湃的总结时刻:以前,看到有些地方使用非阻塞的connect,真的很费解,为什么简单的connect不直接使用,还要搞那么多花样?现在其实我们应该可以想明白了,作为一个完美程序的追求者,着眼于代码长期地维护(自己太高尚了),阻塞的connect确实会存在很多的问题。那是否存在一个稳健的阻塞的connect版本,说实话,我没仔细想过,粗略地想想那应该是个很复杂的东西,比如:connect返回时是否已经开始三次握手等等?因此,阻塞的或者非阻塞的connect目前并非再是二者选其一、根据喜好选择使用的问题了,而是必须使用非阻塞的connect。这个结论或许很武断,但本人通过目前了解的知识得出了这样的结论,还是希望有大神突然出现,指点一二。

7.Accept返回前连接夭折

  这是《UNIX网络编程》上的原装问题,新发现的,按照书上的说法,貌似很严重,我要先测试一下。在我给出一个比较稳健的程序之前不允许存在已知的类似问题。又要牵涉到SO_LINGER选项了,真是有点无法自圆自说的感觉。就当初探SO_LINGER选项吧,现在它只是配角,后面有机会详细研究一下它。

  服务器代码,这个代码同样可以用来测试select发生的EINTR的问题:

  1 #include <stdio.h>  2 #include <stdlib.h>  3 #include <string.h>  4 #include <unistd.h>  5 #include <sys/types.h>  6 #include <sys/socket.h>  7 #include <netinet/in.h>  8 #include <arpa/inet.h>  9 #include <signal.h> 10 #include <errno.h> 11  12 #define  PORT         1234 13 #define  BACKLOG      5 14 #define  MAXDATASIZE  1000 15  16 void sig_chld(int signo) 17 { 18    pid_t pid; 19    int stat = 0; 20  21    pid = wait(&stat); 22    printf("child %d terminated(stat:%d)\n", pid, stat); 23     24    return; 25 } 26  27 void signal_ex(int signo, void* func) 28 { 29     struct sigaction act, oact; 30      31     act.sa_handler = func; 32     sigemptyset(&act.sa_mask); //清空此信号集 33     act.sa_flags = 0; 34      35     if (sigaction(signo, &act, &oact) < 0) 36     { 37         printf("sig err!\n"); 38     } 39  40     //sigaction(SIGINT, &oact, NULL); //恢复成原始状态 41     return; 42 } 43  44 int main() 45 { 46     int  listenfd, connectfd; 47     struct  sockaddr_in server; 48     struct  sockaddr_in client; 49     socklen_t  addrlen; 50     char    szbuf[MAXDATASIZE + 1] = {0}; 51     int     num = 0; 52     pid_t   pid_child; 53     int ret; 54     fd_set set; 55     struct timeval mytm; 56      57     if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) 58     { 59         perror("Creating  socket failed."); 60         exit(1); 61     } 62      63     int opt = SO_REUSEADDR; 64     setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); 65      66     bzero(&server, sizeof(server)); 67     server.sin_family = AF_INET; 68     server.sin_port = htons(PORT); 69     server.sin_addr.s_addr = htonl(INADDR_ANY); 70     if (bind(listenfd, (struct sockaddr *)&server, sizeof(server)) == -1)  71     { 72         perror("Bind()error."); 73         exit(1); 74     }    75     if (listen(listenfd, BACKLOG) == -1) 76     { 77         perror("listen()error\n"); 78         exit(1); 79     } 80  81     signal_ex(SIGCHLD, sig_chld); 82     while (1) 83     { 84         addrlen = sizeof(client); 85         sleep(10); 86         printf("start accept!\n"); 87         if ((connectfd = accept(listenfd, (struct sockaddr*)&client, &addrlen)) == -1)  88         { 89             #if 1 90             if (EINTR == errno) 91             { 92                 printf("EINTR!\n"); 93                 continue; 94             } 95             #endif 96              97             perror("accept()error\n"); 98             exit(1); 99         }100         printf("You got a connection from cient's ip is %s, prot is %d\n", inet_ntoa(client.sin_addr), htons(client.sin_port));101 102         if (0 == (pid_child = fork()))103         {104             close(connectfd);105             close(listenfd);106             printf("child a ha!\n");107             sleep(5);108             exit(0);109         }110 111         mytm.tv_sec  = 15;112         mytm.tv_usec = 0;113 reselect:114         FD_ZERO(&set);115         FD_SET(connectfd, &set);116         if ((ret = select(connectfd + 1, &set, NULL, NULL, &mytm)) > 0)117         {118             if(FD_ISSET(connectfd, &set))119             {120                 printf("connectfd can be readn!\n");121             }122         }123         else if (0 == ret)124         {125             printf("timeout!\n");126         }127         else if (ret < 0)128         {129             //perror("error! ");130             if (EINTR == errno)131             {132                 printf("error! errno = %s:%d\n", strerror(errno), errno);133                 goto reselect;134             }135         }136 137         close(connectfd);138         connectfd = -1;139     }140     141     close(listenfd);142     143     return 0;144 }

   客户端代码:

 1 #include <sys/types.h> 2 #include <sys/socket.h> 3 #include <netinet/in.h> 4 #include <netdb.h> 5 #include <signal.h> 6  7 #define  PORT        1234 8 #define  MAXDATASIZE 1000 9 10 int main(int argc, char *argv[])11 {12     int  sockfd = -1;13     struct sockaddr_in server;14     struct linger ling;15     16     if (argc != 2) 17     {18         printf("Usage:%s <IP Address>\n", argv[0]);19         exit(1);20     }21 22     signal(SIGPIPE, SIG_IGN);23     24     if ((sockfd=socket(AF_INET, SOCK_STREAM, 0)) == -1)25     {26         printf("socket()error\n");27         exit(1);28     }29     bzero(&server, sizeof(server));30     server.sin_family = AF_INET;31     server.sin_port = htons(PORT);32     server.sin_addr.s_addr = inet_addr(argv[1]);33     if (connect(sockfd, (struct sockaddr *)&server, sizeof(server)) == -1)34     {35         printf("connect()error\n");36         exit(1);37     }38 39     ling.l_onoff = 1;40     ling.l_linger = 0;41     setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling));42     43     close(sockfd);44     45     return 0;46 }

  书上提到,有些实现内核会抛一个错误给应用进程,应用进程在accept时会返回错误。而有些实现内核完全能够兼容这种问题,不对应用进程产生任何影响。强大的linux显然是后者,客户端发送RST后,服务器accept没有返回任何错误,运行正常。

  因此,该问题暂且略过,实际使用时还是应当注意这种极端的情况。

8.最终的代码

   写到这,赶紧要为这个标题画句号了。一边写一边学,再这么下去就成无底洞了。而UDP的相关知识自己本身用得不多,在这不敢下笔,等哪一天心中有物再来详细整理一下。因此,这边要很不负责任地给出一个最终版本的TCP代码了。

  个人认为,网络编程还有很多知识,但是该代码已经可以应付一个新手解决很多实际的问题了。之所以叫“快速上手”,干得也就是这种事,离精通还是很远的。之后,有什么问题再以专题的形式给出吧。

  先是服务器代码:

  1 #include <stdio.h>  2 #include <stdlib.h>  3 #include <string.h>  4 #include <unistd.h>  5 #include <sys/types.h>  6 #include <sys/socket.h>  7 #include <netinet/in.h>  8 #include <arpa/inet.h>  9 #include <signal.h> 10 #include <errno.h> 11  12 #define  PORT         1234 13 #define  BACKLOG      5 14 #define  MAXDATASIZE  1000 15 #define  TEST_STRING  "HELLO, WORLD!" 16 #define  TEST_STRING_LEN strlen(TEST_STRING) 17  18 int readn(int connfd, void *vptr, int n) 19 { 20     int    nleft; 21     int    nread; 22     char *ptr; 23     int ret = -1; 24     struct timeval     select_timeout; 25     fd_set rset; 26  27     ptr = (char*)vptr; 28     nleft = n; 29  30     while (nleft > 0) 31     { 32         FD_ZERO(&rset); 33         FD_SET(connfd, &rset); 34         select_timeout.tv_sec = 5; 35         select_timeout.tv_usec = 0; 36         if ((ret = select(connfd+1, &rset, NULL, NULL, &select_timeout)) < 0) 37         { 38             if (errno == EINTR) 39             { 40                 continue; 41             } 42             else 43             { 44                 return -1; 45             } 46         } 47         else if (0 == ret) 48         { 49             return -1; 50         } 51         if ((nread = recv(connfd, ptr, nleft, 0)) < 0) 52         { 53             if(errno == EINTR) 54             { 55                 nread = 0; 56             } 57             else 58             { 59                 return -1; 60             } 61         } 62         else if (nread == 0) 63         { 64             break; 65         } 66         nleft -= nread; 67         ptr   += nread; 68     } 69      70     return(n - nleft); 71 } 72  73 void sig_chld(int signo) 74 { 75    pid_t pid; 76    int stat = 0; 77  78    pid = wait(&stat); 79     80    return; 81 } 82  83 void signal_ex(int signo, void* func) 84 { 85     struct sigaction act, oact; 86      87     act.sa_handler = func; 88     sigemptyset(&act.sa_mask); //清空此信号集 89     act.sa_flags = 0; 90      91     if (sigaction(signo, &act, &oact) < 0) 92     { 93         printf("sig err!\n"); 94     } 95  96     //sigaction(SIGINT, &oact, NULL); //恢复成原始状态 97     return; 98 } 99 100 int main()101 {102     int  listenfd, connectfd;103     struct  sockaddr_in server;104     struct  sockaddr_in client;105     socklen_t  addrlen;106     char    szbuf[MAXDATASIZE + 1] = {0};107     int     num = 0;108     pid_t   pid_child;109 110     signal(SIGPIPE, SIG_IGN);111     112     if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)113     {114         perror("Creating  socket failed.");115         exit(1);116     }117     118     int opt = SO_REUSEADDR;119     setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));120     121     bzero(&server, sizeof(server));122     server.sin_family = AF_INET;123     server.sin_port = htons(PORT);124     server.sin_addr.s_addr = htonl(INADDR_ANY);125     if (bind(listenfd, (struct sockaddr *)&server, sizeof(server)) == -1) 126     {127         perror("Bind()error.");128         exit(1);129     }   130     if (listen(listenfd, BACKLOG) == -1)131     {132         perror("listen()error\n");133         exit(1);134     }135 136     signal_ex(SIGCHLD, sig_chld);137     while (1)138     {139         addrlen = sizeof(client);140         printf("start accept!\n");141         if ((connectfd = accept(listenfd, (struct sockaddr*)&client, &addrlen)) == -1) 142         {143             if (EINTR == errno)144             {145                 printf("EINTR!\n");146                 continue;147             }148             149             perror("accept()error\n");150             exit(1);151         }152         printf("You got a connection from cient's ip is %s, prot is %d\n", inet_ntoa(client.sin_addr), htons(client.sin_port));153 154         if (0 == (pid_child = fork()))155         {156             while (1)157             {158                 num = readn(connectfd, szbuf, TEST_STRING_LEN);159                 if (num < 0)160                 {161                     printf("read error!\n");162                     break;163                 }164                 else if (0 == num)165                 {166                     printf("read over!\n");167                     break;168                 }169                 else170                 {171                     printf("recv: %s\n", szbuf);172                 }173             }174             close(connectfd);175             close(listenfd);176             sleep(5);177             exit(0);178         }179         180         close(connectfd);181         connectfd = -1;182     }183     184     close(listenfd);185     186     return 0;187 }

  客户端:

  1 #include <stdio.h>  2 #include <stdlib.h>  3 #include <unistd.h>  4 #include <string.h>  5 #include <sys/types.h>  6 #include <sys/socket.h>  7 #include <netinet/in.h>  8 #include <netdb.h>  9 #include <signal.h> 10 #include <errno.h> 11 #include <fcntl.h> 12  13 #define  PORT        1234 14 #define  MAXDATASIZE 1000 15 #define  TEST_STRING  "HELLO, WORLD!" 16 #define  TEST_STRING_LEN strlen(TEST_STRING) 17  18 int writen(int connfd, void *vptr, size_t n) 19 { 20     int nleft, nwritten; 21      char    *ptr; 22  23     ptr = (char*)vptr; 24     nleft = n; 25  26     while (nleft > 0) 27     { 28         if ((nwritten = send(connfd, ptr, nleft, MSG_NOSIGNAL)) == -1) 29         { 30             if (errno == EINTR) 31             { 32                 nwritten = 0; 33             } 34             else  35             { 36                 return -1; 37             } 38         } 39         nleft -= nwritten; 40         ptr   += nwritten; 41     } 42  43     return(n); 44 } 45  46 int connectWithTimeout(int sock, struct sockaddr* addrs, int adrsLen,struct timeval* tm) 47 { 48     int err = 0; 49     int len = sizeof(int); 50     int flag; 51     int ret = -1; 52     int retselect = -1; 53     fd_set set; 54     struct timeval mytm; 55      56     if (tm != NULL){ 57         memcpy(&mytm, tm, sizeof(struct timeval)); 58     } 59  60      flag = fcntl(sock, F_GETFL, 0); 61     fcntl(sock, F_SETFL, flag | O_NONBLOCK);  //linux用这个 62   //  flag = 1; 63   //  ioctl(sock,FIONBIO,&flag); 64  65     ret = connect(sock, addrs, adrsLen); 66     if (-1 == ret) 67     { 68         if (EINPROGRESS == errno) 69         { 70 reselect: 71             printf("start check!\n"); 72             FD_ZERO(&set); 73             FD_SET(sock,&set); 74             if ((retselect = select(sock+1, NULL, &set, NULL, tm)) > 0) 75             { 76                 if (FD_ISSET(sock, &set)) 77                 { 78                     getsockopt(sock, SOL_SOCKET, SO_ERROR, &err, (socklen_t*)&len); 79                     if (0 == err) 80                         ret = 0; 81                     else 82                         ret = -1; 83                 } 84                      85             } 86             else if (retselect < 0) 87             { 88                 if (EINTR == errno) 89                 { 90                     printf("error! errno = %s:%d\n", strerror(errno), errno); 91                     goto reselect; 92                 } 93             } 94         } 95     } 96     else if (0 == ret) 97     { 98         printf("OK at right!\n"); 99         ret = 0;  //OK100     }101 102     fcntl(sock, F_SETFL, flag);103   //  flag = 0;104   //  ioctl(sock, FIONBIO, &flag);105 106     if (tm != NULL){107         memcpy(tm, &mytm, sizeof(struct timeval));108     }109 110     return ret;111 }112 113 int main(int argc, char *argv[])114 {115     int  sockfd, num;116     char  szbuf[MAXDATASIZE] = {0};117     struct sockaddr_in server;118     struct timeval timeOut;119     int  ret = -1;120     int  iSendTime = 0;121     122     if (argc != 2) 123     {124         printf("Usage:%s <IP Address>\n", argv[0]);125         exit(1);126     }127 128     signal(SIGPIPE, SIG_IGN);129     130     if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)131     {132         printf("socket()error\n");133         exit(1);134     }135     136     bzero(&server, sizeof(server));137     server.sin_family = AF_INET;138     server.sin_port = htons(PORT);139     server.sin_addr.s_addr = inet_addr(argv[1]);140 141     timeOut.tv_sec  = 5;142     timeOut.tv_usec = 0;143     ret = connectWithTimeout(sockfd, (struct sockaddr *)&server, sizeof(server), &timeOut);144     if (-1 == ret)145     {146         printf("connect()error\n");147         exit(1);148     }149 150     memset(szbuf, 0, sizeof(szbuf));151     strcpy(szbuf, TEST_STRING);152     153     while (iSendTime < 5)154     {155         ret = writen(sockfd, szbuf, TEST_STRING_LEN);156         if (TEST_STRING_LEN != ret)157         {158             break;159         }160         else161         {162             printf("%dth send success!\n", iSendTime);163             iSendTime++;164         }165     }166     167     close(sockfd);168     169     return 0;170 }

  总结的时候,有很多知识都得到了更新,最终代码对部分函数进行了完善。当然还有一些东西没有考虑进去,比如说对端掉电、路由器损坏等问题,以上程序都无法很好的适应这些问题。后续再慢慢改进吧。

  最终的例子功能很简单,最主要还是在socket编程的各个细节的处理上。

  在此要说OVER了!

 

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
Linux下TCP/IP编程
epoll 多进程实现简单的服务器端
linux下Epoll简单使用
TCP协议堵塞窗口算法攻击
网络编程2
14、socket编程实现回声客户端
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服