并发服务器

如果同一时刻有多个客户端请求服务器处理数据,按照之前方式都是串行处理。

多进程多线程

多线程服务端代码代码

监听是一个线程,有客户端到达,再创建一个线程为客户端服务。

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
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>

#include<pthread.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>

int initsocket()
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd==-1) return -1;

struct sockaddr_in ser;
memset(&ser,0,sizeof(ser));
ser.sin_family=AF_INET;
ser.sin_port=htons(6000);
ser.sin_addr.s_addr=inet_addr("127.0.0.1");

int res=bind(sockfd,(struct sockaddr*)&ser,sizeof(ser));
if(res==-1) return -1;

res=listen(sockfd,5);
if(res==-1) return -1;

return sockfd;
}
//完成与一个客户端的交互
void * work_thread(void *arg)
{
int c=(int)arg;
while (1)
{
char buff[128]={0};
int n=recv(c,buff,127,0);
if(n<=0) break;

printf("%d: %s\n",c,buff);

send(c,"OK",2,0);
}
close(c);//必须关闭与客户端连接的文件描述符
}
int main()
{
int sockfd=initsocket();
assert(sockfd!=-1);

while (1)
{
struct sockaddr_in cli;
socklen_t len=sizeof(cli);
int c=accept(sockfd,(struct sockaddr*)&cli,&len);
if(c<0) continue;

pthread_t id;
int res = pthread_create(&id,NULL,work_thread,(void *)c);
assert(res==0);
}
close(sockfd);
}

当有100个客户端连接时,服务器端得创建100个函数线程来运行

多进程服务端代码

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<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>

#include<pthread.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<signal.h>

int initsocket()
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd==-1) return -1;

struct sockaddr_in ser;
memset(&ser,0,sizeof(ser));
ser.sin_family=AF_INET;
ser.sin_port=htons(6000);
ser.sin_addr.s_addr=inet_addr("127.0.0.1");

int res=bind(sockfd,(struct sockaddr*)&ser,sizeof(ser));
if(res==-1) return -1;

res=listen(sockfd,5);
if(res==-1) return -1;

return sockfd;
}
//完成与一个客户端的交互
void * work_process(int c)
{
while (1)
{
char buff[128]={0};
int n=recv(c,buff,127,0);
if(n<=0) break;

printf("%d: %s\n",c,buff);

send(c,"OK",2,0);
}
close(c);//必须关闭与客户端连接的文件描述符
}
void sig_funaction(int sign)//处理僵死进程
{
wait(NULL);
}
int main()
{
signal(SIGCHLD,sig_funaction);
int sockfd=initsocket();
assert(sockfd!=-1);

while (1)
{
struct sockaddr_in cli;
socklen_t len=sizeof(cli);
int c=accept(sockfd,(struct sockaddr*)&cli,&len);
if(c<0) continue;

pid_t pid=fork();
assert(pid!=-1);
if(pid==0) //新创建的子进程
{
//父子进程可以共享fork()之前打开的文件描述符
work_process(c);
exit(0); //关闭了子进程,但是父进程没有处理子进程的退出状态
}
else //父进程执行的逻辑
{
close(c); //fork()在创建子进程时,将文件描述符复制了一次,将struct file {f_count++}执行++
}
}
close(sockfd);
}

从这可以看到服务器输出的都是一个4,而不是以往的每一个客户端对应一个。服务器第一次接收数据会得到4这个文件描述符,但是发送完消息就把c给关了,所以下一次还是从4开始。所以不能用c来判断是否是一个客户端,要用IP地址和端口号来判断。

一个进程打开一个文件sys_open()->确定一个可用的文件描述符

1
2
3
4
for(fd=0;fd<20;++fd)
{
if(判断文件描述符是否被使用)
}

那为什么文件描述符都是从4开始的,以前太菜不知道为什么,查询一番后,一个进程会默认打开三个文件描述符:0 标准输入 1 标准输出 2 标准错误输出 然后3有个sockfd这个操作,所以后续会从4开始。