多路复用
 

  在客户端/服务器模型中,服务器端需要同时处理多个客户端的连接请求,此时就需要使用多路复用。

  实现多路复用最简单的方法是采用非阻塞的方式套接字,服务器端不断的查询每一个套接字的状态,如果有数据到达则读出数据,如果没有数据到达则查看下一个套接字。这种方法虽然简单,但是轮询过程中浪费了大量的CPU时间,效率非常的低。

  另一种方法是服务器进程并不主动的询问套接字的状态,而是向系统登记希望坚实的套接字,然后阻塞。当套接字上有事件发生时(如有数据到达),系统通知服务器进程告知那个套接字上发生了什么事件,服务器进程查询对应的套接字,并进行处理。在这种工作方式下,套接字上没有事件发生时,服务器进程不会去查询套接字的状态,从而不会浪费CPU的时间,提高了效率。

  使用函数select可以实现第二种多路复用。

  int select(int n, fd_set *readfds, fd_set *writefds, fd_set *expectfds, struct timeval *timeout);

  参数n是要监视的文件描述符数,要监视的文件描述符值为0~n-1。

  参数readfds制定需要监视的可读文件描述符集合,当这个集合中的一个描述符上有数据到达的时候,系统将通知调用select函数的程序。

  参数writefds指定需要监视的可写文件描述符集合,当这个集合中的一个描述符可以发送数据时,程序将得到通知。

  参数expectfds指定需要监视的异常文件描述符集合,当有一个描述符发生异常的时候,程序将得到通知。

  参数timeout设置阻塞的时间。

  系统为文件描述符集合提供了一系列的操作宏:

  • FD_CLR(int fd,fd_set *set) 将文件描述符fd从文件描述符集合set中删除
  • FD_ISSET(int fd,fd_set *set) 测试fd是否在set中
  • FD_SET(int fd,fd_set *set) 在文件描述符集合set中增加文件描述符fd
  • FD_ZERO(fd_set *set)

  

服务器端

多路复用_Linux
int main()
{
    int sock_fd,conn_fd;
    int optval;
    int ret;
    int name_num;
    pid_t pid;
    socklen_t cli_len;
    struct sockaddr_in cli_addr, serv_addr;
    char recv_buf[128];

    //initialization a socket
    sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if(sock_fd < 0)
    {
        my_err("socket",_LINE_);
    }

    //initialization server address
    memset(&serv_addr, 0, sizeof(struct sockaddr_in));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(SERV_PORT);
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    //bind
    if(bind(sock_fd, (struct sockaddr*)&serv_addr), sizeof(struct sockaddr_in) < 0)
    {
        my_err("bind");
    }

    //listen
    if(listen(sock_fd, LISTENQ) < 0)
    {
        my_err("listen");
    }

    cli_len = sizeof(struct sockaddr_in);
    while(1)
    {
        conn_fd = accept(sock_fd, (struct sockaddr *)&cli_addr, &cli_len);
        if(conn_fd < 0)
        {
            my_err("accept",_LINE_);
        }
        printf("Accept a new client,ip:%s \n", inet_ntoa(cli_addr.sin_addr));
        if((pid = fork()) == 0)
        {    //    child process
            while(1)
            {
                if((ret = recv(conn_fd, recv_buf, sizeof(recv_buf), 0)) < 0)
                {
                    perror("recv");
                    exit(1);
                }
                recv_buf[ret-1] = '\0';
                if(flag_recv == USERNAME)
                {
                    name_num = find_name(recv_buf);
                    switch(name_num)
                    {
                        case -1:
                            send_data(conn_fd, "n\n");
                            break;
                        case -2:
                            exit(1);
                            break;
                        default:
                            send_data(conn_fd, "y\n");
                            flag_recv = PASSWORD;
                            break;
                    }
                }
                else if(flag_recv == PASSWORD)
                {
                    //check password right or wrong
                }
            }
            close(sock_fd);
            close(conn_fd);
            exit(0);// child process end
        }
        else
        {
            //parent process
            close(conn_fd);
        }
    }
    return 0;
}
多路复用_Linux

 

   服务器程序用fork函数来处理多个客户端,但是在数据库应用程序中,这可能不是最佳的解决办法。因为服务器将程序可能会相当大,而且在数据库访问方面还存在着多个服务器副本的协调问题。

  所以我们需要一个办法支持让单个服务器进程在不阻塞,不等待客户请求带大的前提下处理多个客户。所以选择select。程序如下

多路复用_Linux
 1 int main()
 2 {
 3     int server_sockfd, client_sockfd;
 4     int server_len, client_len;
 5     struct sockaddr_in server_address;
 6     struct sockaddr_in client_address;
 7     int result;
 8     fd_set readfds, testfds;
 9 
10     //create a socket on the server
11     server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
12     server_address.sin_family = AF_INET;
13     server_address.sin_addr.s_addr = htonl(INADDR_ANY);
14     server_address.sin_port = htons(9734);
15     server_len = sizeof(server_address);
16 
17     bind(server_sockfd, (struct sockaddr*)&server_address, server_len);
18 
19     //create a listen queue
20     listen(server_sockfd, 5);
21 
22     FD_ZERO(&readfds);
23     FD_SET(server sockfd, &readfds);
24 
25     while(1)
26     {
27         char ch;
28         int fd;
29         int nread;
30 
31         testfds = readfds;
32 
33         printf("server waiting \n");
34         result = select(FD_SETSIZE, &testfds, (fd_set *)0, (fd_set *)0, (struct timeval *)0);
35         if(result < 1)
36         {
37             perror("server");
38             exit(1);
39         }
40 
41         for(fd = 0; fd < FD_SETSIZE; fd++)
42         {
43             if(FD_ISSET(fd, &testfds))
44             {
45                 if(fd == server_sockfd)
46                 {
47                     client_len = sizeof(client_address);
48                     client_sockfd = accept(server_sockfd, (struct sockaddr*)&client_address, &client_len);
49                     FD_SET(client_sockfd, &readfds);
50                 }
51                 else
52                 {
53                     //如果活动不是发生在服务器套接字上,那就一定是客户端的活动
54                     ioctl(fd, FIONREAD, &nread);
55 
56                     if(nread == 0)
57                     {
58                         close(fd);
59                         FD_CLR(fd, &readfds);
60                         printf("removing client on fd %d\n", fd);
61                     }
62                     else
63                     {
64                         read(fd, &ch, 1);
65                         sleep(5);
66                         printf("serving client on fd %d\n", fd);
67                         ch++;
68                         write(fd, &ch, 1);
69                     }
70                 }
71             }
72         }
73     }
74 }
多路复用_Linux