linux网络编程之socket(九):使用select函数改进客户端/服务器端程序

您所在的位置:网站首页 网络编程select函数 linux网络编程之socket(九):使用select函数改进客户端/服务器端程序

linux网络编程之socket(九):使用select函数改进客户端/服务器端程序

2023-09-14 10:29| 来源: 网络整理| 查看: 265

一、当我们使用单进程单连接且使用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