linux网络编程 | c | select实现多路IO转接服务器

select实现多路IO转接服务器

基于该视频完成

15-select实现多路IO转接设计思路_哔哩哔哩_bilibili

通过响应式–多路IO转接实现

1.思路&功能

**功能:**客户端输入小写字符串,服务器转成大写返回给客户端

思路:

image-20241212201548394

allset是用来更新rset的,因为rest是传入传出参数,allset是记录传出的rest的,因为rest传出以后,监听列表就变了,可能不会原来的了

2.代码实现

warp.h

#ifndef __WRAP_H_
#define __WRAP_H_
#include<sys/epoll.h>
//#include<event2/event.h>
#include<sys/select.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<errno.h>
#include<string.h>
#include<dirent.h>
#include<sys/stat.h>
#include<wait.h>
#include<sys/mman.h>
#include<signal.h>
#include<pthread.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<ctype.h>
#include<strings.h>
#include<netinet/ip.h>
#define SRV_PORT 1234


void perr_exit(const char *s);
int Accept(int fd,struct sockaddr *sa,socklen_t * salenptr);
int Bind(int fd, const struct sockaddr *sa, socklen_t salen);
int Connect(int fd, const struct sockaddr *sa, socklen_t addrlen);
int Listen(int fd, int backlog);
int Socket(int family, int type, int protocol);
size_t Read(int fd, void *ptr, size_t nbytes);
ssize_t Write(int fd,const void *ptr,size_t nbytes);
int Close(int fd);
ssize_t Readn(int fd, void *vptr, size_t n);
ssize_t Writen(int fd, const void *vptr, size_t n);
ssize_t my_read(int fd, char  *ptr);
ssize_t Readline(int fd, void *vptr, size_t maxlen);



#endif

warp.c

#include"warp.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((errno==ECONNABORTED)||(errno==EINTR))
			goto again;
		else
			perr_exit("accept error");
	}
	return n;
}

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

	return n;
}

int Connect(int fd, const struct sockaddr *sa, socklen_t addrlen)
{
	int n;
	n=connect(fd,sa,addrlen);
	if(n<0)
	{
		perr_exit("connect error");
	}
	return n;
}

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

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

size_t Read(int fd, void *ptr, size_t nbytes)
{
	ssize_t n;
again:
	if((n=read(fd,ptr,nbytes))==-1)
	{
		if(errno==EINTR)
			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(errno==EINTR)
			goto again;
		else
			return -1;
	}

	return 0;
}

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

	return n;
}

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

	ptr=vptr;
	nleft=n;

	while(nleft>0)
	{
		if((nread=read(fd,ptr,nleft))<0)
		{
			if(errno==EINTR)
				nread=0;
			else
				return -1;
		}
		else if(nread==0)
			break;
	
	}
}
ssize_t Writen(int fd, const void *vptr, size_t n)
{
	size_t nleft;
	ssize_t nwritten;
	char *ptr;

	ptr=(char *)vptr;
	nleft=n;

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

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;
}

multi_select_sever.c

#include"warp.h"
#define SERV_PORT 1234


int main(int argc,char * argv[])
{
	int listenfd=0,connfd=0;
	int ret,maxfd=0,i,n,j;
	char buf[4096];

	listenfd=Socket(AF_INET,SOCK_STREAM,0);	
	
	struct sockaddr_in serv_addr,clie_addr;
	socklen_t clie_addr_len;

	serv_addr.sin_family=AF_INET;
	serv_addr.sin_port=htons(SERV_PORT);
	serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);

	Bind(listenfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
	Listen(listenfd,128);

	fd_set rset,allset;
	FD_ZERO(&allset);
	FD_SET(listenfd,&allset);
	maxfd=listenfd;

	while(1){
		rset=allset;	
		ret=select(maxfd+1,&rset,NULL,NULL,NULL);
		if(ret<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);

			FD_SET(connfd,&allset);

			if(maxfd<connfd)
				maxfd=connfd;
			if(ret==1)
				continue;
		}
		for(i=listenfd+1;i<=maxfd;i++)
		{
			if(FD_ISSET(i,&rset))
			{
				n=read(i,buf,sizeof(buf));
				if(n==0)
				{
					close(i);
					FD_CLR(i,&allset);
				}
				else if(n==-1)
					perr_exit("read error");
			}
			for(j=0;j<n;j++)
				buf[j]=toupper(buf[j]);
			Write(i,buf,n);
			Write(STDOUT_FILENO,buf,n);
		}
		
	}	

	Close(listenfd);

	return 0;
}

gcc warp.c multi_select_sever.c -o multi_select_sever

运行图

两个客户端访问服务器端

image-20241212205700368

3.代码解释(细节)

1.allset作用

更新rest,因为rest是传入传出参数,传出的是需要监听的集合

而需要监听的集合可能会改变,就是上一次监听的这一次来了可能就不需要监听了

2.监听套接字收到连接请求是属于读行为的,也就是说监听套接字要放到读集合中,有请求要连接的时候,传出的集合中会有监听套接字

3.如果有监听套接字,那说明有连接请求,我们就要处理连接

同时还要看传出的需要监听的描述符集合的大小,如果就是1,说明就只有一个连接请求,我们也就不需要执行下面的代码直接continue就行

4.如果传出的传出的需要监听的描述符集合的大小>1

那我们就循环遍历,从监听套接字开始,到最大

我们监听的是读集合,那如果文件描述在读集合中,那就读取数据然后小写转大写

如果对端关闭的话(read返回0),那就关闭连接同时清除掉该文件描述符

5.我们会发现我们没有用fork也没有用pthread就完成了多并发服务器

4.改进版

如果只有两个描述符,一个3,一个1023,那么效率会很低,我们现在用一个数组来把我们要监听的文件描述符存起来,然后遍历这个数组就行

其实就是比原来来说,避免了3和1023这种情况导致的效率降低的问题,总体效率并没有提升很多

#include"warp.h"
#define SERV_PORT 1234


int main(int argc,char * argv[])
{
	int listenfd=0,connfd=0,maxfd,sockfd;
	int ret,i,n,j,maxi;
	char buf[4096],str[INET_ADDRSTRLEN];
	int nready,client[FD_SETSIZE];//就是个1024

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

	struct sockaddr_in serv_addr,clie_addr;
	socklen_t clie_addr_len;

	serv_addr.sin_family=AF_INET;
	serv_addr.sin_port=htons(SERV_PORT);
	serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);

	Bind(listenfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
	Listen(listenfd,128);

	fd_set rset,allset;
	FD_ZERO(&allset);
	FD_SET(listenfd,&allset);
	
	maxfd=listenfd;
	maxi=-1;
	for(i=0;i<FD_SETSIZE;i++)
		client[i]=-1;
	while(1){
		rset=allset;	
		nready=select(maxfd+1,&rset,NULL,NULL,NULL);
		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);
			printf("received from %s at PORT %d\n",
					inet_ntop(AF_INET,&clie_addr,str,sizeof(str)),
					ntohs(clie_addr.sin_port));
			for(i=0;i<FD_SETSIZE;i++)
				if(client[i]<0)
				{
					client[i]=connfd;
					break;
				}
			if(i==FD_SETSIZE)
			{
				fputs("too many clients\n",stderr);
				exit(1);
			}
			FD_SET(connfd,&allset);

			if(maxfd<connfd)
				maxfd=connfd;
			if(i>maxi)
				maxi=i;

			if(--nready==0)
				continue;
		}
		for(i=0;i<=maxi;i++)
		{
			if((sockfd=client[i])<0)
				continue;
			
			if(FD_ISSET(sockfd,&rset))
			{
				n=read(sockfd,buf,sizeof(buf));
				if(n==0)
				{
					close(sockfd);
					FD_CLR(sockfd,&allset);
					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;
			}
		}
	}	

	Close(listenfd);

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值