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

6.Connect的使用方式

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

 1 STATUS connectWithTimeout(int sock, struct sockaddr*addrs, int adrsLen,struct timeval* tm)
 2 {
 3     int err = 0;
 4     int len = sizeof(int);
 5     int flag;
 6     int ret;
 7     fd_set set;
 8     struct timeval mytm;
 9     
10     if(tm!=NULL){
11         memcpy(&mytm, tm, sizeof(struct timeval));
12     }
13  
14     flag = 1;
15     ioctl(sock,FIONBIO,&flag);
16 
17     ret = connect(sock, addrs, adrsLen);
18     if ( ret == -1 )
19     {
20         if ( EINPROGRESS == errno )
21         {
22             FD_ZERO(&set);
23             FD_SET(sock,&set);
24             if ( select(sock+1,NULL,&set,NULL,tm) > 0 )
25             {
26                 getsockopt(sock,SOL_SOCKET,SO_ERROR,&err,(socklen_t*)&len);
27                 if ( 0 == err )
28                     ret = OK;
29                 else
30                     ret = ERROR;
31             }
32             else
33             {
34                 ret = ERROR;
35             }
36         }
37     }
38 
39     flag = 0;
40     ioctl(sock,FIONBIO,&flag);
41 
42     if(tm!=NULL){
43         memcpy(tm, &mytm, sizeof(struct timeval));
44     }
45 
46     return ret;
47 }
connectWithTimeout

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

 1 STATUS connectWithTimeout(int sock, struct sockaddr* addrs, int adrsLen,struct timeval* tm)
 2 {
 3     int err = 0;
 4     int len = sizeof(int);
 5     int flag;
 6     int ret = -1;
 7     int retselect = -1;
 8     fd_set set;
 9     struct timeval mytm;
10     
11     if (tm != NULL){
12         memcpy(&mytm, tm, sizeof(struct timeval));
13     }
14  
15     flag = 1;
16     ioctl(sock,FIONBIO,&flag);
17 
18     ret = connect(sock, addrs, adrsLen);
19     if (-1 == ret)
20     {
21         if (EINPROGRESS == errno)
22         {
23 reselect:
24             FD_ZERO(&set);
25             FD_SET(sock,&set);
26             if ((retselect = select(sock+1, NULL, &set, NULL, tm)) > 0)
27             {
28                 if (FD_ISSET(sock, &set))
29                 {
30                     getsockopt(sock, SOL_SOCKET, SO_ERROR, &err, (socklen_t*)&len);
31                     if (0 == err)
32                         ret = 0;
33                     else
34                         ret = -1;
35                 }
36                     
37             }
38             else if (retselect < 0)
39             {
40                 if (EINTR == errno)
41                 {
42                     printf("error! errno = %s:%d\n", strerror(errno), errno);
43                     goto reselect;
44                 }
45             }
46         }
47     }
48     else if (0 == ret)
49     {
50         ret = 0;  //OK
51     }
52 
53     flag = 0;
54     ioctl(sock, FIONBIO, &flag);
55 
56     if (tm != NULL){
57         memcpy(tm, &mytm, sizeof(struct timeval));
58     }
59 
60     return ret;
61 }
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 }
server_select.c

   客户端代码:

 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 }
client_select.c

  书上提到,有些实现内核会抛一个错误给应用进程,应用进程在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                 else
170                 {
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 }
server_last.c

  客户端:

  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;  //OK
100     }
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         else
161         {
162             printf("%dth send success!\n", iSendTime);
163             iSendTime++;
164         }
165     }
166     
167     close(sockfd);
168     
169     return 0;
170 }
client_last.c

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

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

  在此要说OVER了!

 

你可能感兴趣的:(socket)