Linux网络编程5——多路IO转接服务器

本文详细介绍了Linux网络编程中的多路IO服务器模型,重点讲解了select函数的工作原理、参数、配套函数及其实现代码。通过一个实际的服务器端示例展示了如何使用select进行客户端连接管理和数据传输。同时,提到了select函数在Windows环境下的应用,并分析了其优缺点。此外,还提出了通过自定义数组来提高select效率的方法。
摘要由CSDN通过智能技术生成

学习视频链接 

黑马程序员-Linux网络编程_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1iJ411S7UA?p=63&spm_id_from=333.1007.top_right_bar_window_history.content.click

目录

一、多路 IO 服务器

1.1 分类

1.2 select 原理图

二、select 函数

2.1 传入、传出参数

2.2 select函数和后续分析

2.3 配套函数

2.4 代码

2.5 linux和windows通信

2.6 select的优缺点

2.7 添加一个自己定义数组提高效率


一、多路 IO 服务器

1.1 分类

select 模型

poll 模型

epoll 模型

1.2 select 原理图

5cd0b52d989e43d2820554c5a50756d1.jpg

这里 select 不是等待客户端连接,而是客户端想要连接需要需要通知 select,然后 select 把信息交给主函数,主函数执行 accept(),然后由 select 负责去写数据。涉及到阻塞、非阻塞轮询、响应式——多路 IO 转接,上述例子是多路 IO 转接

二、select 函数

2.1 传入、传出参数

程序启动起来后 0 1 2 这 3 个文件描述符就被占用了。后面的文件描述符 3 被 lfd 占用,用于监听,后面的连接依次占用文件描述符 4、5、6 ...

客户端连接 select 上后没有发送信息的话,就不需要 select 读监听,想监听的话就把对应的文件描述符放入读集合 (r) 就可以了。写和异常的监听放在另外两个集合里面

现阶段没有这两种需求,所有传入 NULL 就行了

2.2 select函数和后续分析

1、函数

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

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

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

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

exceptfds: 监控异常发生达文件描述符集合,如带外数据到达异常,传入传出参数

timeout: 定时阻塞监控时间,3种情况。
        (1) NULL,永远等下去
        (2) 设置 timeval,等待固定时间。
        (3) 设置 timeval 里时间均为 0,检查描述字后立即返回,轮询

2、分析

select 函数最后一个参数设置轮询的时间,在监听期间有发送信息的话,会放入到传出参数中,传出参数同样使用位图

一开始监听 3、5、6 文件描述符对应的读 (r),3 在监听的时候没有读 (r),所以传出中的位图没有 3 了

2.3 配套函数

void FD_ZERO(fd_set *set); —— 清空一个文件描述符集合
        fd_set rset;
        FD_ZERO(&rset);

void FD_SET(int fd, fd_set *set); —— 将待监听的文件描述符,添加到监听集合中
        FD_SET(3, &rset);
        FD_SET(5, &rset); 
        FD_SET(6, &rset);

void FD_CLR(int fd, fd_ set *set); —— 将一个文件描述符从监听集合中移除
        FD_CLR(4, &rset);

int FD_ISSET(int fd, fd_set *set); —— 判断一个文件描述符是否在监听集合中
        返回值: 在返回1;不在返回0
        FD_ISSET(4, &rset);

2.4 代码

进入 while 前,监听的位图 allset 就是上图这个,maxfd 对应的值是 3

#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>

#define SERV_PORT 6666

void sys_err(const char* str) 
{
	perror(str);
	exit(1);
}

int main(void)
{
	int i, j, n, nready;
	int maxfd = 0;
	int listenfd, connfd;
	char buf[BUFSIZ];
	struct sockaddr_in clie_addr, serv_addr;
	socklen_t clie_addr_len;

	listenfd = socket(AF_INET, SOCK_STREAM, 0);     // 创建一个socket, 得到lfd  
	int opt = 1;
	setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); // 设置端口复用
	bzero(&serv_addr, sizeof(serv_addr));           // 地址结构清零  
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);  // 指定本地任意IP  
	serv_addr.sin_port = htons(SERV_PORT);          // 指定端口号   
	bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));  // 绑定  
	listen(listenfd, 128);     // 设置同一时刻链接服务器上限数  

	fd_set rset, allset;       // rset读事件文件描述符 allset用来暂存
	maxfd = listenfd;

	FD_ZERO(&allset);
	FD_SET(listenfd, &allset); // 构造select监控文件描述符集

	while (1) {
		rset = allset;         // 每次循环时都要重新设置select监控信号集
		nready = select(maxfd + 1, &rset, NULL, NULL, NULL);
		if (nready < 0) {
			sys_err("select error");
		}
		if (FD_ISSET(listenfd, &rset)) {  // 说明有新的客户端链接请求
			clie_addr_len = sizeof(clie_addr);
			connfd = accept(listenfd, (struct sockaddr *)&clie_addr, &clie_addr_len);  // accept不会阻塞  
			FD_SET(connfd, &allset);      // 向监控文件描述符集合allset添加新的文件描述法connfd
			if (maxfd < connfd) {
				maxfd = connfd;
			}		
			if (0 == --nready) {
				continue;
			}
		}

		for (i = listenfd + 1; i <= maxfd; i++) {  // 检测哪个客户端有数据就绪
			if (FD_ISSET(i, &rset)) {
				if ((n = read(i, buf, sizeof(buf))) == 0) {  // 当客户端关闭链接时,服务器也关闭对应链接
					close(i);
					FD_CLR(i, &allset);
				}
				else if (n > 0) {
					for (j = 0; j < n; j++) {
						buf[j] = toupper(buf[j]);
					}
					write(i, buf, n);
				}
			}
		}
	}
	close(listenfd);

	return 0;
}

 

2.5 linux和windows通信

windows 代码

#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS

#include <iostream>
#include <WinSock2.h>
#include <windows.h>

#pragma comment(lib, "ws2_32.lib")

using namespace std;

int main()
{
	WORD ver = MAKEWORD(2, 2);  // 版本号,启动windows socket 2.x环境
	WSADATA dat;  // 数据结构,用来存储被WSAStartup函数调用后返回的Windows Sockets数据。
	// 它包含Winsock.dll执行的数据。
	WSAStartup(ver, &dat);

	// 1.建立一个socket
	SOCKET _sock = socket(AF_INET, SOCK_STREAM, 0);

	// 2.连接服务器
	sockaddr_in _sin = {};
	_sin.sin_family = AF_INET;
	_sin.sin_port = htons(6666);
	_sin.sin_addr.S_un.S_addr = inet_addr("192.168.244.137");
	int ret = connect(_sock, (sockaddr*)&_sin, sizeof(sockaddr_in));
	if (SOCKET_ERROR == ret) {
		cout << "绑定网络端口失败" << endl;
	}
	else {
		cout << "绑定网络端口成功" << endl;
	}

	char recvBuf[256] = {};
	int nlen = recv(_sock, recvBuf, 256, 0);
	if (nlen > 0) {
		cout << "接收到数据: " << recvBuf << endl;
	}

	// 4.关闭套接字closesocket
	closesocket(_sock);

	getchar();

	// 清除windows socket环境
	WSACleanup();
	return 0;
}

拓展一下思路,其他的 TCP 通信同样可以和服务端连接

2.6 select的优缺点

1、缺点

监听上限受文件描述符限制,最大 1024 个

检测满足条件的 fd,自己添加业务逻辑提高小。提高了编码难度。

2、优点

跨平台

2.7 添加一个自己定义数组提高效率

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <ctype.h>
#include "wrap.h"

#define SERV_PORT 6666
  
int main(int argc, char *argv[])  
{  
	int i, j, n, maxi;  
  
	int nready, client[FD_SETSIZE];          // 自定义数组client, 防止遍历1024个文件描述符  FD_SETSIZE默认为1024 */  
	int maxfd, listenfd, connfd, sockfd;  
	char buf[BUFSIZ], str[INET_ADDRSTRLEN];  // #define INET_ADDRSTRLEN 16 
 
	struct sockaddr_in clie_addr, serv_addr;  
	socklen_t clie_addr_len;  
	fd_set rset, allset;                     // rset 读事件文件描述符集合 allset用来暂存

	listenfd = Socket(AF_INET, SOCK_STREAM, 0);  
 
	int opt = 1;  
	setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));  

	bzero(&serv_addr, sizeof(serv_addr));  
	serv_addr.sin_family= AF_INET;  
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);  
	serv_addr.sin_port= htons(SERV_PORT);  
  
	Bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));  
	Listen(listenfd, 128);  
  
	maxfd = listenfd;           // 起初 listenfd 即为最大文件描述符

	maxi = -1;                  // 将来用作client[]的下标, 初始值指向0个元素之前下标位置
	for (i = 0; i < FD_SETSIZE; i++) {  
		client[i] = -1;         // 用-1初始化client[]  
	}
	FD_ZERO(&allset);  
	FD_SET(listenfd, &allset);  // 构造select监控文件描述符集  
 
	while (1) {     
		rset = allset;          // 每次循环时都重新设置select监控信号集 
 
		nready = select(maxfd+1, &rset, NULL, NULL, NULL);  // 2  1--lfd  1--connfd  
		if (nready < 0) {  
			perr_exit("select error");
		}  
 
		if (FD_ISSET(listenfd, &rset)) {                    // 说明有新的客户端链接请求  
			clie_addr_len = sizeof(clie_addr);  
			connfd = Accept(listenfd, (struct sockaddr *)&clie_addr, &clie_addr_len);  // Accept 不会阻塞 */  
			printf("received from %s at PORT %d\n",  
                    inet_ntop(AF_INET, &clie_addr.sin_addr, str, sizeof(str)),  
                    ntohs(clie_addr.sin_port));  
					
			for (i = 0; i < FD_SETSIZE; i++) 
				if (client[i] < 0) {     // 找client[]中没有使用的位置  
					client[i] = connfd;  // 保存accept返回的文件描述符到client[]里 
					break;  
				}

			if (i == FD_SETSIZE) {    // 达到select能监控的文件个数上限 1024  
				fputs("too many clients\n", stderr);  
				exit(1);  
			}
			FD_SET(connfd, &allset);  // 向监控文件描述符集合allset添加新的文件描述符connfd

			if (connfd > maxfd)  
				maxfd = connfd;       // select第一个参数需要

			if (i > maxi) {  
				maxi = i;             // 保证maxi存的总是client[]最后一个元素下标 
			}

			if (--nready == 0) {
					continue;  
			}
		}   
 
		for (i = 0; i <= maxi; i++) {                             // 检测哪个clients 有数据就绪 		  
			if ((sockfd = client[i]) < 0) {
				continue;  
			}
			if (FD_ISSET(sockfd, &rset)) {  
				if ((n = Read(sockfd, buf, sizeof(buf))) == 0) {  // 当client关闭链接时,服务器端也关闭对应链接 */  
					Close(sockfd);  
					FD_CLR(sockfd, &allset);                      // 解除select对此文件描述符的监控
					client[i] = -1;  
				} 
				else if (n > 0) {  
					for (j = 0; j < n; j++)  
						buf[j] = toupper(buf[j]);  
					Write(sockfd, buf, n);  
					Write(STDOUT_FILENO, buf, n);  
				}	  
				if (--nready == 0) {
					break;   // 跳出for, 但还在while中
				}
			}  
		}  
	}  
	Close(listenfd);  
	return 0;  
}  

 

wrap.h

#ifndef WRAP_H 
#define WRAP_H 


#include <stdlib.h> 
#include <errno.h> 
#include <sys/socket.h> 


void perr_exit(const char *s); 


int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr); 
void Bind(int fd, const struct sockaddr *sa, socklen_t salen); 
void Connect(int fd, const struct sockaddr *sa, socklen_t salen); 
void Listen(int fd, int backlog); 


int Socket(int family, int type, int protocol); 
void Close(int fd); 

ssize_t Readline(int fd,void *vptr,size_t maxlen);
ssize_t Read(int fd, void *ptr, size_t nbytes); 
ssize_t Write(int fd, const void *ptr, size_t nbytes); 


ssize_t Readn(int fd, void *vptr, size_t n); 
ssize_t Writen(int fd, const void *vptr, size_t n); 


#endif 

wrap.c

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "sys/socket.h"
#include "ctype.h"
#include "arpa/inet.h"
#include "wrap.h" 

void perr_exit(const char *s) 
{ 
     perror(s); 
     exit(1); 
} 


int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr) 
{ 
     int n; 


again: 
     if((n = accept(fd, sa, salenptr)) < 0) { 
            if((ECONNABORTED == errno) || (EINTR == errno)) 
                 goto again; 
            else 
                 perr_exit("accept error"); 
     } 
     
     return n; 
} 


void Bind(int fd, const struct sockaddr *sa, socklen_t salen) 
{ 
     if(bind(fd, sa, salen) < 0) 
            perr_exit("bind error"); 
} 


void Connect(int fd, const struct sockaddr *sa, socklen_t salen) 
{ 
     if(connect(fd, sa, salen) < 0) 
            perr_exit("connect error"); 
} 


void Listen(int fd, int backlog) 
{ 
     if(listen(fd, backlog) < 0) 
            perr_exit("listen error"); 
} 


int Socket(int family, int type, int protocol) 
{ 
     int n; 


     if((n = socket(family, type, protocol)) < 0) 
             perr_exit("socket error"); 
     return n; 
} 


ssize_t Read(int fd, void *ptr, size_t nbytes) 
{ 
     ssize_t n; 


again: 
     if((n = read(fd, ptr, nbytes)) == -1) { 
            if(EINTR == errno) 
                 goto again; 
            else 
                 return -1; 
     }    
     
     return n; 
} 


ssize_t Write(int fd, const void *ptr, size_t nbytes) 
{ 
     ssize_t n; 


again: 
     if((n = write(fd, ptr, nbytes)) == -1) { 
            if(EINTR == errno) 
                 goto again; 
            else 
                 return -1; 
     } 


     return n; 
} 


void Close(int fd) 
{ 
     if(close(fd) == -1) 
            perr_exit("close error");    
} 


ssize_t Readn(int fd, void *vptr, size_t nbytes) 
{ 
     size_t nleft; 
     size_t nread; 
     char *ptr; 


     ptr = vptr; 
     nleft = nbytes; 


     while(nleft > 0) { 
            if((nread = read(fd, ptr, nleft)) < 0) { 
                 if(EINTR == errno)     
                        nread = 0; 
                 else 
                        return -1; 
            } else if(nread == 0) 
                    break; 


            nleft -= nread; 
            ptr += nread; 
     } 


     return (nbytes-nleft); 
} 

static ssize_t my_read(int fd,char *ptr)
{
    static int read_cnt;
    static char * read_ptr;
    static char read_buf[100];

    if(read_cnt <= 0){
again:
        if((read_cnt == read(fd,read_buf,sizeof(read_buf))) < 0){
            if(errno == EINTR)
                goto again;
            return -1;
        }else if(read_cnt == 0)
            return 0;
        read_ptr = read_buf;
    }
    read_cnt--;
    *ptr = *read_ptr++;
    return 1;
}

ssize_t Readline(int fd,void *vptr,size_t maxlen)
{
    ssize_t n,rc;
    char c,*ptr;
    ptr = vptr;

    for(n = 1;n < maxlen;n ++){
        if((rc = my_read(fd,&c)) == 1)
        {
            *ptr++ = c;
            if(c == '\n')
                break;
        }else if(rc == 0){
            *ptr = 0;
            return n - 1;
        }else
        {
            return -1;
        }
        
    }
    *ptr = 0;
    return n;
}

ssize_t Writen(int fd, const void *vptr, size_t nbytes) 
{ 
     size_t nleft; 
     size_t nwritten; 
     const char *ptr; 


     ptr = vptr; 
     nleft = nbytes; 


     while(nleft > 0) { 
            if((nwritten = write(fd, ptr, nleft)) <= 0) {    
                 if(nwritten < 0 && EINTR == errno) 
                        nwritten = 0; 
                 else 
                        return -1; 
            } 
                
            nleft -= nwritten; 
            ptr += nwritten; 
     } 


     return nbytes; 
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

herb.dr

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值