linux网络编程之socket(九):使用select函数改进客户端/服务器端程序 |
您所在的位置:网站首页 › 网络编程select函数 › linux网络编程之socket(九):使用select函数改进客户端/服务器端程序 |
一、当我们使用单进程单连接且使用readline修改后的客户端程序,去连接使用readline修改后的服务器端程序,会出现一个有趣的现象,先来看输出: 先运行服务器端,再运行客户端, simba@ubuntu:~/Documents/code/linux_programming/UNP/socket$ ./echoser_recv_peek recv connect ip=127.0.0.1 port=54005 simba@ubuntu:~/Documents/code/linux_programming/UNP/socket$ ./echocli_recv_peek local ip=127.0.0.1 port=54005 可以先查看一下网络状态, simba@ubuntu:~$ netstat -an | grep tcp | grep 5188 tcp 0 0 0.0.0.0:5188 0.0.0.0:* LISTEN tcp 0 0 127.0.0.1:54005 127.0.0.1:5188 ESTABLISHED tcp 0 0 127.0.0.1:5188 127.0.0.1:54005 ESTABLISHED 可以看出建立了连接,服务器端有两个进程,一个父进程处于监听状态,另一子进程正在对客户端进行服务。 再ps 出服务器端的子进程,并kill掉它, simba@ubuntu:~$ ps -ef | grep echoser simba 4549 3593 0 15:57 pts/0 00:00:00 ./echoser_recv_peek simba 4551 4549 0 15:57 pts/0 00:00:00 ./echoser_recv_peek simba 4558 4418 0 15:57 pts/6 00:00:00 grep --color=auto echoser simba@ubuntu:~$ kill -9 4551 这时再查看一下网络状态, simba@ubuntu:~$ netstat -an | grep tcp | grep 5188 tcp 0 0 0.0.0.0:5188 0.0.0.0:* LISTEN tcp 1 0 127.0.0.1:54005 127.0.0.1:5188 CLOSE_WAIT tcp 0 0 127.0.0.1:5188 127.0.0.1:54005 FIN_WAIT2 来分析一下,我们将server子进程 kill掉,则其终止时,socket描述符会自动关闭并发FIN段给client,client收到FIN后处于CLOSE_WAIT状态,但是client并没有终止,也没有关闭socket描述符,因此不会发FIN 段给 server子进程,因此server 子进程的TCP连接处于FIN_WAIT2状态。 为什么会出现这种情况呢,来看client的部分程序: void do_echocli(int sock) { char sendbuf[1024] = {0}; char recvbuf[1024] = {0}; while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL) { writen(sock, sendbuf, strlen(sendbuf)); int ret = readline(sock, recvbuf, sizeof(recvbuf)); //按行读取 if (ret == -1) ERR_EXIT("readline error"); else if (ret == 0) //服务器关闭 { printf("server close\n"); break; } fputs(recvbuf, stdout); memset(sendbuf, 0, sizeof(sendbuf)); memset(recvbuf, 0, sizeof(recvbuf)); } close(sock); }客户端程序阻塞在了fgets 那里,即从标准输入读取数据,所以不能执行到下面的readline,也即不能返回0,不会退出循环,不会调用close关闭sock,所以出现上述的情况,即状态停滞,不能向前推进。具体的状态变化可以参见这里。 出现上述问题的根本原因在于客户端程序不能并发处理从标准输入读取数据和从套接字读取数据两个事件,我们可以使用前面讲过的select函数来完善客户端程序,如下所示: void do_echocli(int sock) { fd_set rset; FD_ZERO(&rset); int nready; int maxfd; int fd_stdin = fileno(stdin); // if (fd_stdin > sock) maxfd = fd_stdin; else maxfd = sock; char sendbuf[1024] = {0}; char recvbuf[1024] = {0}; while (1) { FD_SET(fd_stdin, &rset); FD_SET(sock, &rset); nready = select(maxfd + 1, &rset, NULL, NULL, NULL); //select返回表示检测到可读事件 if (nready == -1) ERR_EXIT("select error"); if (nready == 0) continue; if (FD_ISSET(sock, &rset)) { int ret = readline(sock, recvbuf, sizeof(recvbuf)); //按行读取 if (ret == -1) ERR_EXIT("read error"); else if (ret == 0) //服务器关闭 { printf("server close\n"); break; } fputs(recvbuf, stdout); memset(recvbuf, 0, sizeof(recvbuf)); } if (FD_ISSET(fd_stdin, &rset)) { if (fgets(sendbuf, sizeof(sendbuf), stdin) == NULL) break; writen(sock, sendbuf, strlen(sendbuf)); memset(sendbuf, 0, sizeof(sendbuf)); } } close(sock); }即将两个事件都添加进可读事件集合,在while循环中,如果select返回说明有事件发生,依次判断是哪些事件发生,如果是标准输入有数据可读,则读取后再次回到循环开头select阻塞等待事件发生,如果是套接口有数据可读,且返回为0则说明对方已经关闭连接,退出循环并调用close关闭sock。 重复前面的实验过程,把客户端换成使用select函数修改后的程序,可以看到最后的输出: simba@ubuntu:~$ netstat -an | grep tcp | grep 5188 tcp 0 0 0.0.0.0:5188 0.0.0.0:* LISTEN tcp 0 0 127.0.0.1:5188 127.0.0.1:54007 TIME_WAIT 即 client 关闭socket描述符,server 子进程的TCP连接收到client发的FIN段后处于TIME_WAIT状态,此时会再发生一个ACK段给client,client接收到之后就处于CLOSED状态,这个状态存在时间很短,所以看不到客户端的输出条目,TCP协议规定,主动关闭连接的一方要处于TIME_WAIT状态,等待两个MSL(maximum segment lifetime)的时间后才能回到CLOSED状态,需要有MSL 时间的主要原因是在这段时间内如果最后一个ack段没有发送给对方,则可以重新发送。 过一小会再次查看网络状态, simba@ubuntu:~$ netstat -an | grep tcp | grep 5188 tcp 0 0 0.0.0.0:5188 0.0.0.0:* LISTEN 可以发现只剩下服务器端父进程的监听状态了,由TIME_WAIT状态转入CLOSED状态,也很快会消失。 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 二、前面我们实现的能够并发服务的服务器端程序是使用fork出多个子进程来实现的,现在学习了select函数,可以用它来改进服务器端程序,实现单进程并发服务。先看如下程序,再来解释: /************************************************************************* > File Name: echoser.c > Author: Simba > Mail: [email protected] > Created Time: Fri 01 Mar 2013 06:15:27 PM CST ************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include "read_write.h" #define ERR_EXIT(m) \ do { \ perror(m); \ exit(EXIT_FAILURE); \ } while (0) int main(void) { signal(SIGPIPE, SIG_IGN); int listenfd; //被动套接字(文件描述符),即只可以accept, 监听套接字 if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |