linux—TCP_server端编写之利用select()函数编写可多用户同时访问

来源:互联网 发布:国产网络腐剧 编辑:程序博客网 时间:2024/06/05 08:13

1. 回顾多进程、多线程编写的server

之前我们编写了多进程、多线程的tcp_server,但我们发现虽然多线程和多进程的编写简单,但太占资源了,当客户端连接稍微多一点,服务器就有可能奔溃。所以我们今天要利用一种io模式:i/o复用(多路转接)

2. 什么是多路转接

我们在IO数据时,通常时间是在等,我们普通read()函数,write()函数等一次只能等一个文件描述符,这样的效率太低了。所以我们就有了多路转接的IO方式,这种IO方式是,一次等待多个文件描述符,只要有一个就绪,则返回。

3. 函数select

int select(int nfds, fd_set *readfds, fd_set *wirtefds,\             fd_set     *exceptfds, struct timeval *timeout);

返回值:有多少个描述符就绪
nfds: 需要监视的文件描述符最大值+1;
rdset, wrset, exset分别对应于需要检测的可读⽂文件描述符的集合,可写⽂文件描述符的集 合及异常⽂文件描述符的集合。
struct timeval结构⽤用于描述 —— 一段时间长度,如果在这个时间内,需要监视的描述符没有事件发生则函数返回,返回值为0。

4. 代码:

因为socket的创建,绑定,设置监听之前我已经写过了所以这里就不写了,有需要的可以点击链接参考参考:
LINUX–TCP_server端的编写 创建监听套接字 listen_socket

int main(int argc, char *argv[]){    if(argc != 3){        usarg(argv[0]);        return 0;    } //提供手册,从命令行输入本地ip和端口号    int listen_sock = startup(argv[1], atoi(argv[2]));    //listen_socke    int fds[MAX] = {0}; // select的参数都是输入输出型的,需要自己记录文件描述符    int i = 0;    fds[0] = listen_sock;//先将监听套接字添加到数组中    for(i=1; i<MAX; i++)    {        fds[i] = -1;     }//清空数组    fd_set readfds;    while(1)    {        FD_ZERO(&readfds);        int count = listen_sock;        for(i=0; i<MAX; i++)        {            if(fds[i] != -1)            {                   count = ((count>fds[i])?count:fds[i]);                FD_SET(fds[i], &readfds);            }//添加数组的文件描述到参数readfds中,我们现在只关心读事件。        }        int ret = select(count+1, &readfds, NULL, NULL, NULL);        if(ret == 0)//我们设置的是阻塞式,所以基本不会==0        {printf("timeout!!!\n");        }        else if(ret < 0)//出错        {            perror("select");            return 5;        }        else if(ret > 0)//>0时,一定有文件描述符的那个事件就绪        {               for(i=0; i<MAX; i++)//遍历判断是那个文件描述符的那个时间            {                if(fds[i] == -1)                    continue;                if(i == 0 && FD_ISSET(listen_sock, &readfds))                {//listen_socket的读事件就绪                    struct sockaddr_in client;                    int len = sizeof(client);                    int new_sock = accept(listen_sock, (struct sockaddr *)&client, &len);//读                    printf("client : [%s][%d],socket: [%d]\n"           ,inet_ntoa(client.sin_addr),ntohs(client.sin_port),new_sock);                    for(i=4; i<MAX; i++ )                    {                        if(fds[i] == -1)                            break;                    }//找数组中可以添加的位置,添加新的sock                    if(i == MAX)                        close(new_sock);                    else                        fds[i] = new_sock;                }                else if(FD_ISSET(fds[i], &readfds))                {                    char buf[1024];                    ssize_t s= read(fds[i], buf, sizeof(buf)-1);                    if(s < 0)                    {                        perror("read");                    }                    else if(s == 0)                    {                        printf("client[%d] quit!\n", fds[i]);                        close(fds[i]);                        fds[i] = -1;                    }                    else                    {                        printf("client[%d]: %s\n",fds[i],buf);                    }                }            }        }    }    return 0;}

5. select函数的优点与缺点

优点:占用资源少,当用户多时性能较好
缺点:1、select可监听的文件描述符有上限制;
2、因为select参数是输入输出型的,所以每次重新设置select时,都需遍历式设置,对性能有一定的影响
3、用户增多时,多次重复遍历和频繁内核与进程数据拷贝(多次的返回)

6.解决的办法

多路转接的函数还有poll. epoll ,
epoll可以完全解决这个问题

原创粉丝点击