网络编程之I_O Multiplexing总结(四)
2018-03-15 16:35
363 查看
1. 概念澄清
1.1
A socket is ready for reading if any of the following four conditions is true:The read half of the connection is closed (i.e., a TCP connection that has received a FIN). A read operation on the socket will not block and will return 0 (i.e., EOF)
A socket is ready for writing if any of the following four conditions is true:
The write half of the connection is closed. A write operation on the socket will generate SIGPIPE (Section 5.12).
StackOverflow上关于这个问题给出了答案,其实一开始看到这个问题,难以理解的就是
shutdown(xxxx,SHUT_RD),一端调用该函数代表其不再接收数据,但是如果假设
active close的一端首先调用该函数,显然是无法理解的,因为一般都是发送数据方作为
active close端,而作为
passive close端调用该函数,又多此一举,在百科中说该函数调用为空操作不权威,这个问题先按照常见的一种情形处理:
1.2
TCP的状态变化图回顾:从理论上分析首先client处于
FIN_WAITE_1,之后接收到server的
ACK后,变化到
FIN_WAITE_2,等待server的
FIN;server此时接收到client的
FIN后处于
CLOSE_WAIT状态,之后其调用
close向client发送
FIN,自己进入
LAST_ACK,等待client的
ACK。
腾讯的面经会有提问
TIME_WAIT的作用,现在回顾如下:
To implement TCP’s full-duplex connection termination reliably
TIME_WAIT状态用来重复发送
ACK,假设第一次
FIN_WAIT_2回复的
ACK丢失的话,该
TIME_WAITE状态持续
2MSL。
To allow old duplicate segments to expire in the network
相同地址和端口号建立的连接不希望接收到
lost duplicate,也就是上次连接的数据,则使用该状态让当前连接的数据全部过期。
2. Select server和Client的改进代码
2.1 Server
//6_ser.c #include <sys/socket.h> #include <sys/un.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <arpa/inet.h> #include <unistd.h> #include <errno.h> #include <err.h> #define INET_ADDRSTRLEN 16 #define LISTENQ 10 #define SERV_PORT 9877 #define MAXLINE 20 #define handle_error(msg) \ do { perror(msg); exit(EXIT_FAILURE); } while (0) void str_echo(int sockfd); ssize_t writen(int fd, const void *vptr, size_t n); int main(int argc, char **argv) { int i, maxi, maxfd, listenfd, connfd, sockfd; int nready, client[FD_SETSIZE]; char strcliaddr[INET_ADDRSTRLEN]; ssize_t n; fd_set rset, allset; char buf[MAXLINE]; socklen_t clilen; struct sockaddr_in cliaddr, servaddr; if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) handle_error("socket"); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); if(bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) == -1) handle_error("bind"); if(listen(listenfd, LISTENQ) == -1) handle_error("listen"); maxfd = listenfd; /* initialize */ maxi = -1; /* index into client[] array */ for (i = 0; i < FD_SETSIZE; i++) client[i] = -1; /* -1 indicates available entry */ FD_ZERO(&allset); FD_SET(listenfd, &allset); for ( ; ; ) { rset = allset; /* structure assignment */ if((nready=select(maxfd+1, &rset, NULL, NULL, NULL)) == -1) handle_error("select"); if (FD_ISSET(listenfd, &rset)) { /* new client connection */ clilen = sizeof(cliaddr); if((connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &clilen)) == -1 ) handle_error("accept"); for (i = 0; i < FD_SETSIZE; i++) if (client[i] < 0) { client[i] = connfd; /* save descriptor */ break; } if (i == FD_SETSIZE) errx(1,"too many clients"); if(inet_ntop(AF_INET,&cliaddr.sin_addr,strcliaddr,INET_ADDRSTRLEN) == NULL) handle_error("inet_pton"); printf("connected client number : %d, ipaddress:%s\n",i,strcliaddr); FD_SET(connfd, &allset); /* add new descriptor to set */ if (connfd > maxfd) maxfd = connfd; /* for select */ if (i > maxi) maxi = i; /* max index in client[] array */ if (--nready <= 0) continue; /* no more readable descriptors */ } for (i = 0; i <= maxi; i++) { /* check all clients for data */ if ( (sockfd = client[i]) < 0) continue; if (FD_ISSET(sockfd, &rset)) { if ( (n = read(sockfd, buf, MAXLINE)) < 0) handle_error("read"); if(n == 0){ if(getpeername(sockfd,(struct sockaddr *) &cliaddr, &clilen)== -1) handle_error("getpeername"); if(inet_ntop(AF_INET,&cliaddr.sin_addr,strcliaddr,INET_ADDRSTRLEN) == NULL) handle_error("inet_pton"); printf("closed client number : %d, ipaddress:%s\n",i,strcliaddr); if(close(sockfd) == -1) handle_error("close"); if(i == maxi) maxi-=1; FD_CLR(sockfd, &allset); client[i] = -1; } else if(writen(sockfd, buf, n) == -1) handle_error("writen"); if (--nready <= 0) break; /* no more readable descriptors */ } } } } void str_echo(int sockfd) { ssize_t n; char buf[MAXLINE]; again: while ( (n = read(sockfd, buf, MAXLINE)) > 0) if(writen(sockfd, buf, n) == -1) handle_error("writen"); if (n < 0 && errno == EINTR) goto again; else if (n < 0) errx(1,"str_echo: read error"); } ssize_t writen(int fd, const void *vptr, size_t n) { size_t nleft; ssize_t nwritten; const char *ptr; ptr = vptr; nleft = n; while (nleft > 0) { if ( (nwritten = write(fd, ptr, nleft)) <= 0) { if (nwritten < 0 && errno == EINTR) nwritten = 0; /* and call write() again */ else return (-1); /* error */ } nleft -= nwritten; ptr += nwritten; } return (n); }
2.2 Client
//6_cli.c #include <sys/socket.h> #include <sys/un.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <err.h> #include <arpa/inet.h> #include <unistd.h> #include <errno.h> #define SERV_PORT 9877 #define MAXLINE 20 #define handle_error(msg) \ do { perror(msg); exit(EXIT_FAILURE); } while (0) ssize_t writen(int fd, const void *vptr, size_t n); void str_cli(FILE *fp, int sockfd); int max(int,int); int main(int argc, char **argv) { int sockfd; struct sockaddr_in servaddr; if (argc != 2) errx(1,"usage: tcpcli <IPaddress>"); if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) handle_error("socket"); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(SERV_PORT); if(inet_pton(AF_INET, argv[1], &servaddr.sin_addr) == -1) handle_error("inet_pton"); if(connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) == -1) handle_error("connect"); str_cli(stdin, sockfd); exit(0); } int max(int a,int b) { return a < b? b:a; } void str_cli(FILE *fp, int sockfd) { int maxfdp1, stdineof; fd_set rset; char buf[MAXLINE]; int n; stdineof = 0; FD_ZERO(&rset); for ( ; ; ) { if (stdineof == 0) FD_SET(fileno(fp), &rset); FD_SET(sockfd, &rset); maxfdp1 = max(fileno(fp), sockfd) + 1; if(select(maxfdp1, &rset, NULL, NULL, NULL) == -1) handle_error("select"); if (FD_ISSET(sockfd, &rset)) { /* socket is readable */ if ( (n = read(sockfd, buf, MAXLINE)) < 0) handle_error("read"); if( n == 0){ if (stdineof == 1) return; /* normal termination */ else errx(1,"str_cli: server terminated prematurely"); } if( write(fileno(stdout), buf, n) == -1 ) handle_error("write stdio"); } if (FD_ISSET(fileno(fp), &rset)) { /* input is readable */ if ( (n = read(fileno(fp), buf, MAXLINE)) < 0) handle_error("read"); if(n == 0){ stdineof = 1; if(shutdown(sockfd, SHUT_WR)== -1) handle_error("shutdown");/* send FIN */ FD_CLR(fileno(fp), &rset); continue; } if(writen(sockfd, buf, n) == -1) handle_error("writen"); } } } ssize_t writen(int fd, const void *vptr, size_t n) { size_t nleft; ssize_t nwritten; const char *ptr; ptr = vptr; nleft = n; while (nleft > 0) { if ( (nwritten = write(fd, ptr, nleft)) <= 0) { if (nwritten < 0 && errno == EINTR) nwritten = 0; /* and call write() again */ else return (-1); /* error */ } nleft -= nwritten; ptr += nwritten; } return (n); }
3. 结果验证
//server端 [root@localhost ~]# ./6_ser connected client number : 0, ipaddress:127.0.0.1 connected client number : 1, ipaddress:192.168.182.130 closed client number : 0, ipaddress:127.0.0.1 closed client number : 1, ipaddress:192.168.182.130 //client0 [root@localhost ~]# ./6_cli 127.0.0.1 this is the first client. this is the first client. //CTRL+D [root@localhost ~]# //client1 [root@localhost ~]# ./6_cli 192.168.182.130 this is the second client. this is the second client. //CTRL+D [root@localhost ~]#
以上两个client同时连接到server,但是该server是single loop,意味着对于client数据量比较大的时候,阻塞其他client接受server的服务。然后看看状态转化,以一个client为例:
//server继续等待,此时上面两个连接已经结束,9877为server的端口。 [root@localhost ~]# netstat -a Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 0.0.0.0:9877 0.0.0.0:* LISTEN //笔者是同一台电脑多个终端,所以这里看到两个建立连接的socket,59750是clinet的端口 [root@localhost ~]# ./6_cli 192.168.182.130 //等待输入 [root@localhost ~]# ./6_ser connected client number : 0, ipaddress:127.0.0.1 connected client number : 1, ipaddress:192.168.182.130 closed client number : 0, ipaddress:127.0.0.1 closed client number : 1, ipaddress:192.168.182.130 connected client number : 0, ipaddress:192.168.182.130 //上面新连接的client //此时观察netstat [root@localhost ~]# netstat -a | grep tcp tcp 0 0 0.0.0.0:9877 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:ssh 0.0.0.0:* LISTEN tcp 0 0 localhost.localdom:9877 localhost.localdo:59750 ESTABLISHED tcp 0 0 localhost.localdo:59750 localhost.localdom:9877 ESTABLISHED //在client键入EOF,迅速查看结果,client的TCP处于TIME_WAIT状态,server端的socket已经关闭 [root@localhost ~]# netstat -a | grep tcp tcp 0 0 0.0.0.0:9877 0.0.0.0:* LISTEN tcp 0 0 localhost.localdo:59750 localhost.localdom:9877 TIME_WAIT
4.总结
client和server都采用select处理,为了在server显示连接的client,及client结束时打印相应的信息,笔者用了
inet_pton和
getpeername,对于
maxi的处理,笔者只是简单的让其减一。最后这两个代码本身的缺陷,在以前的章节有讨论过,在此不赘述。
相关文章推荐
- java基础总结25-java网络编程
- ios 网络编程总结
- 高性能网络编程总结及《TCP/IP Sockets编程(C语言实现) (第2版)》 代码下载(链接以及文件打包)
- 网络编程总结
- 网络编程之---TCP/IP UDP总结
- Android学习笔记:Android网络编程的理解和总结
- Symbian编程总结-网络与通信-套接字(1)-套接字体系结构与相关API
- Reachability 网络编程总结(解析数据,下载文件,确认网络环境)
- Java网络编程总结(转)
- 网络编程常见问题总结 2
- Java 网络IO编程总结(BIO、NIO、AIO均含完整实例代码)
- 网络编程学习总结
- 网络编程 socket简单编程总结
- Java网络编程总结
- 牛人整理分享的面试知识:操作系统、计算机网络、设计模式、Linux编程,数据结构总结 转载
- Java 网络IO编程总结(BIO、NIO、AIO均含完整实例代码)
- 今日网络编程问题总结
- *nix 网络编程 基础精华总结
- 【tcp-ip学习总结】socket编程基础/网络编程基础
- iOS 各种网络编程总结--进程、线程、Socket、HTTP、TCP/IP、TCP和UDP