多进程并发服务器

基于该视频完成

11-多进程并发服务器思路分析_哔哩哔哩_bilibili

通过的是非阻塞忙轮询的方式实现的

和阻塞等待的区别就是,阻塞是真的阻塞了,而这个方式是一直在问有没有请求有没有请求

1.核心思路&功能

实现一个服务器可以连接多个客户端,每当accept函数等待到客户端进行连接时 就创建一个子进程;

核心思路:让accept循环阻塞等待客户端,每当有客户端连接时就fork子进程,让子进程去和客户端进行通信,父进程用于监听并使用信号捕捉回收子进程;(子进程关闭用于监听的套接字lfd,父进程关闭用于通信的cfd)

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

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_process_concurrency_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
71
72
73
74
75
76
77
78
79
#include"warp.h"

//回调函数,父进程用来完成子进程回收的
void catch_child(int signum)
{
while((waitpid(0,NULL,WNOHANG))>0);
return ;
}

int main(int argc,char *argv[])
{
int lfd,cfd;
pid_t pid;
int ret;
char buf[1024];
struct sockaddr_in srv_addr,clt_addr;
socklen_t clt_addr_len;
//地址结构清0
bzero(&srv_addr,sizeof(srv_addr));

srv_addr.sin_family=AF_INET;
srv_addr.sin_port=htons(SRV_PORT);
srv_addr.sin_addr.s_addr=htonl(INADDR_ANY);

lfd=Socket(AF_INET,SOCK_STREAM,0);

Bind(lfd,(struct sockaddr*)&srv_addr,sizeof(srv_addr));

Listen(lfd,128);

clt_addr_len=sizeof(clt_addr);

int i;
while(1)
{
cfd=Accept(lfd,(struct sockaddr *)&clt_addr,&clt_addr_len);
pid=fork();
if(pid<0)
{
perr_exit("fork error");
}
else if(pid==0)//子进程
{
//关闭用不到的socket
close(lfd);
for(;;)
{
ret=Read(cfd,buf,sizeof(buf));
if(ret==0)
{
Close(cfd);
exit(1);
}
//逻辑处理部分
for(i=0;i<ret;i++)
buf[i]=toupper(buf[i]);
write(cfd,buf,ret);
write(STDOUT_FILENO,buf,ret);
}
}
else//父进程
{
//注册信号
struct sigaction act;
act.sa_handler=catch_child;
sigemptyset(&act.sa_mask);
act.sa_flags=0;
ret=sigaction(SIGCHLD,&act,NULL);
if(ret!=0)
{
perr_exit("sigaction error");
}
Close(cfd);
continue;
}
}

return 0;
}
1
gcc warp.c multi_process_concurrency_sever.c -o multi_process_concurrency_sever

运行图

两个客户端访问服务器端

image-20241212182721902

3.代码解释

1.指定的固定端口号为1234

也可以用argv接受ip和port参数

2.srv_addr.sin_addr.s_addr=htonl(INADDR_ANY);

  • sin_addrstruct sockaddr_in结构体中的一个嵌套结构体,其成员s_addr用于存放 IP 地址信息(以 32 位整数形式表示)。htonl函数和htons类似,不过它是将主机字节序的 32 位整数(通常用于 IP 地址)转换为网络字节序。INADDR_ANY是一个特殊的常量,它表示服务器端套接字可以绑定到本机的任意可用 IP 地址上,通过这行代码,将转换为网络字节序后的INADDR_ANY值赋给了srv_addr结构体的sin_addr.s_addr成员,使得服务器能够监听来自本机所有网络接口上对应端口的连接请求。

3.需要用到的调用都需要错误处理,封装到warp.c后进行使用

4.父子进程是共享文件描述符表的

  1. 父子进程共享文件描述符表的含义
    • 在 Unix 和 Linux 系统中,当父进程创建子进程时,子进程会继承父进程的文件描述符表。这意味着父子进程可以通过相同的文件描述符来访问同一个文件或 I/O 资源。文件描述符表是一个进程用于管理打开文件或其他 I/O 设备的表格,其中每个文件描述符是一个整数索引,对应着一个打开的文件或设备的相关信息(如文件状态、读写位置等)。
    • 例如,父进程打开了一个文件,得到文件描述符为 3,当创建子进程后,子进程也会有一个文件描述符为 3,并且这个文件描述符在父子进程中都指向同一个打开的文件。这种共享机制使得父子进程可以方便地共享文件资源,比如共同对一个文件进行读写操作。
  2. 父进程close(5)后子进程的情况
    • 当父进程执行close(5)操作后,只是父进程自己释放了文件描述符 5 所对应的资源。子进程的文件描述符表仍然保留文件描述符 5,并且子进程仍然可以使用文件描述符 5 来访问对应的文件。
    • 原因是父子进程的文件描述符表虽然在创建子进程时是共享的,但它们在操作文件描述符时是相互独立的。父进程的close操作不会影响子进程中已经继承的相同文件描述符。
  3. 子进程是否可以close(5)以及继续访问文件描述符 5 对应的文件
    • 子进程可以执行close(5)操作。当子进程执行close(5)后,它自己释放了文件描述符 5 对应的资源,之后就不能再通过文件描述符 5 来访问原来对应的文件了。
    • 在没有执行close(5)之前,子进程可以继续访问文件描述符 5 对应的文件。它可以进行正常的读写操作(前提是该文件是以合适的模式打开的,如可读可写模式),并且文件的读写位置等状态是在父子进程间共享的。例如,如果父进程已经读取了文件的一部分内容,子进程通过相同的文件描述符 5 继续读取文件时,会从父进程读取后的位置开始读取。

5.子进程做的事情:

close(lfd)关闭监听套接字

read()

逻辑处理:小写转大写

write()

6.父进程做的事情:子进程的回收

close(cfd)

注册信号捕捉函数

在回调函数中,完成子进程的回收