做网络服务的时候并发服务端程序的编写必不可少。前端客户端应用程序是否稳定一部分取决于客户端自身,而更多的取决于服务器是否相应时间够迅速,够稳定.

    常见的linux并发服务器模型;


    • 多进程并发服务器

    • 多线程并发服务器

    • select多路I/O转接服务器

    • poll多路I/O转接服务器

    • epool多路I/O转接服务器.


    本次主要讨论select多路I/O转接服务器模型:

    linux网络编程----->高并发--->select多路I/O复用服务器_第1张图片

    使用select多路I/O转接服务器模型要考虑到以下几点:

    1. select能监听的文件描述符个数受限于FD_SETSIZE, 一般为1024, 单纯改变进程打开的文件描述符个数并不能改变select监听文件个数

    2. 解决1024以下客户端请求使用select是很合适的, 但如果链接客户端过多, select采用的是轮询模型, 会大大降低服务器响应效率, 不应在select上投入更多精力.

    主要API:

        int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* execptfds, struct timeval* timmeout);

            nfds: 监控的文件描述符集里最大文件描述符加1, 因为此参数会告诉内核横测前多少个文件描述符的状态

            readfds: 监控有读数据到达文件描述符集合, 传入传出参数

            writefds: 监控写数据到达文件描述符集合, 传入传出参数

            execptfds: 监控异常发生达文件描述符集合, 如带外数据到达异常,

            timeout: 设置阻塞监控时间,3种情况

                    1. NULL 永远等下去

                    2. 设置timeval, 等待固定的时间

                    3. 设置timeval里时间均为0, 检查描述字后立即返回, 轮询

        void FD_ZERO(fd_set* set);            //把文件描述符集合里初始化为0

        void FD_SET(int fd, fd_set* set);     //把文件描述符集合里fd位置1

        void FD_CLR(int fd, fd_set* set);     //把文件描述符集合里fd位置0

        int FD_ISSET(int fd, fd_set* set);    //测试文件描述符集合里fd是否为1 有返回1, 没有返回0


    代码:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define SERV_PORT 9096			//服务所占用端口
#define SERV_ADDR "10.10.101.105"	//服务所占用ip

int main(int argc, char* argv[]){
	int listenfd, connfd, maxfd;
	struct sockaddr_in serv_addr, clie_addr;
	socklen_t clie_addr_len;
	int opt, i, j, n, nready;
	char str[INET_ADDRSTRLEN];	//INET_ADDRSTRLEN  内置宏  16
	char buf[BUFSIZ];		//BUFSIZ 内置宏 8192
	int client[FD_SETSIZE];		//保存客户端描述符
	int maxi;			//记录客户端数组最大的下标
	fd_set rset, allset;

	//创建监听套接字
	//AF_INET:	ipv4
	//SOCK_STREAM	tcp流类型
	//IPPROTO_TCP	tcp协议
	listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	//设置端口复用
	opt = 1;
	setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
	
	//初始化为0
	bzero(&serv_addr, sizeof(serv_addr));
	//指定族: ipv4
	serv_addr.sin_family = AF_INET;
	//指定端口号并转换成网络字节序
	serv_addr.sin_port = htons(SERV_PORT);
	//指定ip并转换为网络字节序
	inet_pton(AF_INET, SERV_ADDR, &serv_addr.sin_addr.s_addr);
	//绑定到监听套接字
	bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

	//设置同时连接请求上限
	listen(listenfd, SOMAXCONN);

	//将客户端数组全部置为-1
	for(i = 0; i < FD_SETSIZE; i++){
		client[i] = -1;
	}
	maxi = -1;
	
	//监听描述集初始化
	FD_ZERO(&allset);
	//将监听套接字添加至监听集
	FD_SET(listenfd, &allset);

	maxfd = listenfd;
	for(;;){
            rset = allset;
	    nready = select(maxfd + 1, &rset, NULL, NULL, NULL);
		
	    //判断是否有连接请求
	    if(FD_ISSET(listenfd, &rset)){
		clie_addr_len = sizeof(clie_addr);
		//获取连接请求
		connfd = accept(listenfd, (struct sockaddr*)&clie_addr, &clie_addr_len);
		//打印客户端信息
		printf("%s:%d connect successfully!\n", inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, str, sizeof(str)), ntohs(clie_addr.sin_port));

		//添加至客户端集合
		for(i = 0; i < FD_SETSIZE; i++){
		    if(0 > client[i]){
	                client[i] = connfd;
			    break;
			}
		}

		//客户端数组已满
		if(FD_SETSIZE == i){
		    printf("clients at full strength!\n");
			continue;;
		}
		//添加至监听集
		FD_SET(connfd, &allset);

		//判断i下标是否大于maxi
		if(i > maxi){
		    maxi = i;
		}
		//判断新的描述符是否大于maxfd
		if(connfd > maxfd){
		    maxfd = connfd;
		}
		//判断是否还有事件
		if(0 == (--nready)){
		    continue;
		}
	}
	for(i = 0; i <= maxi; i++){
	    if(0 > client[i])
    		continue;
		if(FD_ISSET(client[i], &rset)){
    		    bzero(buf, sizeof(buf));
		    n = read(client[i], buf, sizeof(buf));
		    //当对方关闭
		    if(0 == n){
		    clie_addr_len = sizeof(clie_addr);
		    //获取客户端信息
		    getpeername(client[i], (struct sockaddr*)&clie_addr, &clie_addr_len);
		    printf("%s:%d disconnect successfully!\n", inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, str, sizeof(str)), ntohs(clie_addr.sin_port));
		    //关闭连接
		    close(client[i]);
		    //从监听中清除
		    FD_CLR(client[i], &allset);
		    //客户端数组位置恢复为-1
		    client[i] = -1;
		}else if(0 < n){
		    //全部转为大写
		    for(j = 0; j < n; j++){
		        buf[j] = toupper(buf[j]);
		    }
		    //回写给客户端
		        write(client[i], buf, n);
		    }
		    //判断是否还有事件
		    if(0 == (--nready)){
	                break;
		    }
		}
	}
    }
    close(listenfd);
}