Linux网络编程3-select模型

Linux网络编程3-select模型


1.select函数

cpp 复制代码
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

/*
函数作用:委托内核监控可读、可写、异常事件

函数参数:
	nfds:输入参数,告诉内核要监控文件描述符的范围,一般取值为最大文件描述符+1
	readfds:
		输入参数:告诉内核要监控哪些文件描述符
		输出参数:内核告诉应用程序哪些文件描述符有变化
	writefds:
		输入参数:告诉内核要监控哪些文件描述符
		输出参数:内核告诉应用程序哪些文件描述符有变化
	exceptfds:
		输入输出参数,一般表示异常事件
	timeout:超时时间:
		NULL:表示永久阻塞,直到有事件发生
		0:表示不阻塞,不管有没有事件发生,都会立刻返回
		>0:表示阻塞的时长,若没有超过超时时长,则一直阻塞
			若在时长内,有事件发生,则立刻返回,
			若超过时长,则立刻返回

返回值:
	成功返回发生变化的文件描述符个数。
	
*/


void FD_CLR(int fd, fd_set *set);    // 从set集合中清楚fd
int  FD_ISSET(int fd, fd_set *set);  // 判断fd是否在set集合中
void FD_SET(int fd, fd_set *set);    // 将fd添加到set集合中
void FD_ZERO(fd_set *set);           // 清空文件描述符集

2.使用select模型-服务器端开发流程

cpp 复制代码
//1 创建socket, 得到监听文件描述符lfd---socket()
//2 设置端口复用-----setsockopt()
//3 将lfd和IP  PORT绑定----bind()
//4 设置监听---listen()
//5 
fd_set readfds;  //定义文件描述符集变量
fd_set tmpfds;
FD_ZERO(&readfds);  //清空文件描述符集变量
FD_SET(lfd, &readfds);//将lfd加入到readfds集合中;
maxfd = lfd;
while(1)
{
    tmpfds = readfds;
    nready = select(maxfd+1, &tmpfds, NULL, NULL, NULL);
    if(nready<0)
    {
        if(errno==EINTR)//被信号中断
        {
            continue;
        }
        break;
    }
    
    //有客户端连接请求到来
    if(FD_ISSET(lfd, &tmpfds))
    {
        //接受新的客户端连接请求
        cfd = accept(lfd, NULL, NULL);
        
        //将cfd加入到readfds集合中
        FD_SET(cfd, &readfds);
        
        //修改内核监控的文件描述符的范围
        if(maxfd<cfd)
        {
            maxfd = cfd;
        }
        //如果只有连接请求,就不用执行下面的代码处理客户端的数据。
        if(--nready==0)
        {
            continue;
        }
    }
    
    
    //有客户端数据发来
    for(i=lfd+1; i<=maxfd; i++)
    {
        if(FD_ISSET(i, &tmpfds))
        {
            //read数据
            n = read(i, buf, sizeof(buf));
            if(n<=0)
            {
                close(i);
                //将文件描述符i从内核中去除
                FD_CLR(i, &readfds);
            }
            else
            {
                printf("n=[%d], buf=[%s]\n", n, buf);
                // 转大写
                //write应答数据给客户端
            	write(i, buf, n);
            }
            
            // 本次select返回的可变文件描述符处理完,提前结束for循环
        	if(--nready==0)
        	{
        	    break;
        	}
        } 
    }
    
    close(lfd);
    
    return 0;
}

3.select服务器代码

cpp 复制代码
//select版本的服务器
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <ctype.h>
#include <pthread.h>
#include "wrap.h"

int main() {
    //创建socket
    int lfd = Socket(AF_INET, SOCK_STREAM, 0);

    //设置端口复用
    int opt = 1;
    setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int));

    //绑定--将lfd 和 IP PORT绑定
    struct sockaddr_in serv;
    bzero(&serv, sizeof(serv));
    serv.sin_family = AF_INET;
    serv.sin_port = htons(8888);
    serv.sin_addr.s_addr = htonl(INADDR_ANY);
    Bind(lfd, (struct sockaddr *) &serv, sizeof(serv));

    //监听
    Listen(lfd, 128);

    fd_set readfds;  //定义文件描述符集变量
    fd_set tmpfds;
    FD_ZERO(&readfds);  //清空文件描述符集变量
    FD_ZERO(&tmpfds);
    FD_SET(lfd, &readfds);//将lfd加入到readfds集合中;
    int maxfd = lfd;
    int cfd;
    while (1) {
        tmpfds = readfds;
        int nready = select(maxfd + 1, &tmpfds, NULL, NULL, NULL);
        if (nready < 0) {
            if (errno == EINTR)//被信号中断
            {
                continue;
            }
            break;
        }

        //有客户端连接请求到来
        if (FD_ISSET(lfd, &tmpfds)) {
            //接受新的客户端连接请求
            cfd = accept(lfd, NULL, NULL);

            //将cfd加入到readfds集合中
            FD_SET(cfd, &readfds);

            //修改内核监控的文件描述符的范围
            if (maxfd < cfd) {
                maxfd = cfd;
            }
            //如果只有连接请求,就不用执行下面的代码处理客户端的数据。
            if (--nready == 0) {
                continue;
            }
        }


        //有客户端数据发来
        for (int i = lfd + 1; i <= maxfd; i++) {
            if (FD_ISSET(i, &tmpfds)) {

                char buf[1024];
                bzero(buf,sizeof(buf));

                //read数据
                int n = read(i, buf, sizeof(buf));
                if (n <= 0) {
                    close(i);
                    //将文件描述符i从内核中去除
                    FD_CLR(i, &readfds);
                } else {
                    printf("n=[%d], buf=[%s]\n", n, buf);
                    // 转大写
                    //write应答数据给客户端
                    write(i, buf, n);
                }

                // 本次select返回的可变文件描述符处理完,提前结束for循环
                if (--nready == 0) {
                    break;
                }
            }
        }

    } // end while(1)

    Close(lfd);
    return 0;
}

4.select优缺点

select优点:

1.一个进程可以支持多个客户端

2.select支持跨平台

select缺点:

1.代码编写困难

2.会涉及到用户区到内核区的来回拷贝

3.当客户端多个连接, 但少数活跃的情况, select效率较低

例如: 作为极端的一种情况, 3-1023文件描述符全部打开, 但是只有1023有发送数据, select就显得效率低下

4.最大支持1024个客户端连接

select最大支持1024个客户端连接不是有文件描述符表最多可以支持1024个文件描述符限制的, 而是由FD_SETSIZE=1024限制的.

FD_SETSIZE=1024 fd_set使用了该宏, 当然可以修改内核, 然后再重新编译内核.

相关推荐
_snowstorm_10 个月前
Linux学习之网络编程2(socket,简单C/S模型)
linux·网络·学习·linux网络编程·套接字socket
shlyyy1 年前
Linux网络编程2-多进程和多线程版本服务器
c++·c·linux网络编程
shlyyy1 年前
Linux网络编程1-简单的CS通信程序
c++·c·linux网络编程