select实现IO多路转接服务器

基于该视频完成

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

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

1.思路&功能

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

思路:

image-20241212201548394

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

2.代码实现

warp.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
#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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#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;
}

1
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这种情况导致的效率降低的问题,总体效率并没有提升很多

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#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;
}