zoukankan      html  css  js  c++  java
  • Linux系统编程(35)—— socket编程之TCP服务器的并发处理


    我们知道,服务器通常是要同时服务多个客户端的,如果我们运行上一篇实现的server和client之后,再开一个终端运行client试试,新的client就不能能得到服务了。因为服务器之支持一个连接。

    网络服务器通常用fork来同时服务多个客户端,父进程专门负责监听端口,每次accept一个新的客户端连接就fork出一个子进程专门服务这个客户端。但是子进程退出时会产生僵尸进程,父进程要注意处理SIGCHLD信号和调用wait清理僵尸进程。

    下面是代码框架:

    listenfd = socket(...);
    bind(listenfd, ...);
    listen(listenfd, ...);
    while (1) {
             connfd= accept(listenfd, ...);
             n= fork();
             if(n == -1) {
                       perror("callto fork");
                       exit(1);
             }else if (n == 0) {
                       close(listenfd);
                       while(1) {
                                read(connfd,...);
                                ...
                                write(connfd,...);
                       }
                       close(connfd);
                       exit(0);
             }else
                       close(connfd);
    }

     

    现在做一个测试,首先启动server,然后启动client,然后用Ctrl-C使server终止,这时马上再运行server,结果是:

     binderror: Address already in use

    这是因为,虽然server的应用程序终止了,但TCP协议层的连接并没有完全断开,因此不能再次监听同样的server端口。server终止时,socket描述符会自动关闭并发FIN段给client,client收到FIN后处于CLOSE_WAIT状态,但是client并没有终止,也没有关闭socket描述符,因此不会发FIN给server,因此server的TCP连接处于FIN_WAIT2状态。

    现在用Ctrl-C把client也终止掉,再观察现象结果是:

     binderror: Address already in useclient

    终止时自动关闭socket描述符,server的TCP连接收到client发的FIN段后处于TIME_WAIT状态。TCP协议规定,主动关闭连接的一方要处于TIME_WAIT状态,等待两个MSL(maximum segment lifetime)的时间后才能回到CLOSED状态,因为我们先Ctrl-C终止了server,所以server是主动关闭连接的一方,在TIME_WAIT期间仍然不能再次监听同样的server端口。MSL在RFC1122中规定为两分钟,但是各操作系统的实现不同,在Linux上一般经过半分钟后就可以再次启动server了。

    在server的TCP连接没有完全断开之前不允许重新监听是不合理的,因为,TCP连接没有完全断开指的是connfd(127.0.0.1:8000)没有完全断开,而我们重新监听的是listenfd(0.0.0.0:8000),虽然是占用同一个端口,但IP地址不同,connfd对应的是与某个客户端通讯的一个具体的IP地址,而listenfd对应的是wildcard address。解决这个问题的方法是使用setsockopt()设置socket描述符的选项SO_REUSEADDR为1,表示允许创建端口号相同但IP地址不同的多个socket描述符。在server代码的socket()和bind()调用之间插入如下代码: 

    int opt = 1;
    setsockopt(listenfd, SOL_SOCKET,SO_REUSEADDR, &opt, sizeof(opt));

    select是网络程序中很常用的一个系统调用,它可以同时监听多个阻塞的文件描述符(例如多个网络连接),哪个有数据到达就处理哪个,这样,不需要fork和多进程就可以实现并发服务的server。

    /* server.c */
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <netinet/in.h>
    #include "wrap.h"
     
    #define MAXLINE 80
    #define SERV_PORT 8000
     
    int main(int argc, char **argv)
    {
             inti, maxi, maxfd, listenfd, connfd, sockfd;
             intnready, client[FD_SETSIZE];
             ssize_tn;
             fd_setrset, allset;
             charbuf[MAXLINE];
             charstr[INET_ADDRSTRLEN];
             socklen_tcliaddr_len;
             structsockaddr_in  cliaddr, servaddr;
     
             listenfd= Socket(AF_INET, SOCK_STREAM, 0);
     
             bzero(&servaddr,sizeof(servaddr));
             servaddr.sin_family      = AF_INET;
             servaddr.sin_addr.s_addr= htonl(INADDR_ANY);
             servaddr.sin_port        = htons(SERV_PORT);
     
             Bind(listenfd,(struct sockaddr *)&servaddr, sizeof(servaddr));
     
             Listen(listenfd,20);
     
             maxfd= listenfd;               /* initialize */
             maxi= -1;                            /* indexinto 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 */
                       nready= select(maxfd+1, &rset, NULL, NULL, NULL);
                       if(nready < 0)
                                perr_exit("selecterror");
     
                       if(FD_ISSET(listenfd, &rset)) { /* new client connection */
                                cliaddr_len= sizeof(cliaddr);
                                connfd= Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
     
                                printf("receivedfrom %s at PORT %d
    ",
                                       inet_ntop(AF_INET, &cliaddr.sin_addr,str, sizeof(str)),
                                       ntohs(cliaddr.sin_port));
     
                                for(i = 0; i < FD_SETSIZE; i++)
                                         if(client[i] < 0) {
                                                   client[i]= connfd; /* save descriptor */
                                                   break;
                                         }
                                if(i == FD_SETSIZE) {
                                         fputs("toomany clients
    ", stderr);
                                         exit(1);
                                }
     
                                FD_SET(connfd,&allset);         /* add newdescriptor 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 allclients for data */
                                if( (sockfd = client[i]) < 0)
                                         continue;
                                if(FD_ISSET(sockfd, &rset)) {
                                         if( (n = Read(sockfd, buf, MAXLINE)) == 0) {
                                                   /*connection closed by client */
                                                   Close(sockfd);
                                                   FD_CLR(sockfd,&allset);
                                                   client[i]= -1;
                                         }else {
                                                   intj;
                                                   for(j = 0; j < n; j++)
                                                            buf[j]= toupper(buf[j]);
                                                   Write(sockfd,buf, n);
                                         }
     
                                         if(--nready == 0)
                                                   break;       /* no more readable descriptors */
                                }
                       }
             }
    }


  • 相关阅读:
    软工实践个人总结
    第02组 Beta版本演示
    第02组 Beta冲刺(5/5)
    第02组 Beta冲刺(4/5)
    第02组 Beta冲刺(3/5)
    第02组 Beta冲刺(2/5)
    第02组 Beta冲刺(1/5)
    第02组 Alpha事后诸葛亮
    第02组 Alpha冲刺(6/6)
    第02组 Alpha冲刺(5/6)
  • 原文地址:https://www.cnblogs.com/new0801/p/6176967.html
Copyright © 2011-2022 走看看