一、循环服务器模型

所谓循环服务器,在单个任务执行时,只有将上一个客户端处理结束后,才能处理下一个客户端,效率较低

1> 通信模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
socket()            //创建套接字
bind(); //绑定ip地址和端口号
listen(); //将套接字设置成被动监听状态

while(1)
{
newfd = accept(); //接收客户端连接请求

send();
recv();
close(newfd); //关闭当前通信的套接字
}

close(sfd); //关闭监听


2> 程序代码

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
#include<myhead.h>
#define SER_PORT 8888 //服务器端口号
#define SER_IP "192.168.125.104" //服务器IP地址


int main(int argc, const char *argv[])
{
//1、创建用于连接的套接字
int sfd = socket(AF_INET, SOCK_STREAM, 0);
//参数1:通信域,表明使用的是ipv4协议
//参数2:通信方式,使用TCP通信
//参数3:0表示之前已经指定协议 IPPROTO_TCP

if(sfd == -1)
{
perror("socket error");
return -1;
}
printf("sfd = %d\n", sfd); //3


//将端口号快速重用函数
int reuse = 1;
if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1)
{
perror("setsockopt error");
return -1;
}
printf("端口号快速重用成功\n");



//2、给当前套接字绑定IP地址和端口号
//2.1填充要绑定的地址信息结构体
struct sockaddr_in sin;
sin.sin_family = AF_INET; //通信域
sin.sin_port = htons(SER_PORT); //端口号
sin.sin_addr.s_addr = inet_addr(SER_IP); //ip地址

//2.2 绑定
if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) == -1)
{
perror("bind error");
return -1;
}
printf("bind success %s %s %d\n", __FILE__, __func__, __LINE__);

//3、将套接字设置成监听状态
if(listen(sfd, 128) == -1)
{
perror("listen error");
return -1;
}
printf("listen success %s %s %d\n", __FILE__, __func__, __LINE__);

//4、阻塞等待客户端的链接请求
//4.1定义容器接收客户端的地址信息
struct sockaddr_in cin; //用于接收地址信息
socklen_t socklen = sizeof(cin); //用于接收地址信息的大小

int newfd = -1;

while(1)
{
//4.2 接收客户端的链接
newfd = accept(sfd, (struct sockaddr*)&cin, &socklen);
if(newfd == -1)
{
perror("accept error");
return -1;
}
printf("[%s:%d]发来链接请求 %s %s %d\n", \
inet_ntoa(cin.sin_addr), ntohs(cin.sin_port),__FILE__, __func__, __LINE__);

//5、跟客户端进行消息通信
char buf[128] = "";
while(1)
{
//将数组清空
bzero(buf, sizeof(buf));

//读取客户端发来的消息
//int res = read(newfd, buf, sizeof(buf));
int res = recv(newfd, buf, sizeof(buf), 0);
if(res == 0)
{
printf("客户端已经下线\n");
break;
}
printf("[%s:%d] : %s\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), buf);

//给客户端发消息
strcat(buf, "*_*");

//write(newfd, buf, sizeof(buf));
send(newfd, buf, sizeof(buf), 0);
printf("发送成功\n");
}

//关闭当前通信的套接字
close(newfd);
}

//6、关闭套接字
close(sfd);





return 0;
}

二、多进程实现并发服务器

由于一个服务器在同一时间想要处理多个客户端,就需要实现多任务并发执行,目前所学的多任务并发执行操作有多进程和多线程

可以使用多进程完成:思路如下

父进程可以专门用于客户端连接请求,每连接一个客户端,就创建出一个子进程用于跟客户端进行交互

1> 通信模型

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
 
void handler(int signo)
{
while(waitpid()>0); //以非阻塞配合信号完成僵尸进程的回收
}

int main()
{
//将信号与信号处理函数绑定
signal(SIGCHLD, handler);

socket(); //创建用于连接的套接字
bind(); //绑定IP地址和端口号
listen(); //将套接字设置成被动监听状态

while(1)
{
newfd = accept(); //接收客户端连接请求
fd = fork(); //创建子进程用于通信
if(pid > 0)
{
//父进程中关闭newfd
close(newfd);
} else if(pid == 0)
{
close(sfd); //关闭sfd
//子进程用于通信
recv();
send();
close(newfd);
exit(EXIT_SUCCESS); //退出子进程
}
}

close(sfd); 关闭监听

}


2> 通信代码

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
#include<myhead.h>
#define SER_PORT 8888 //服务器端口号
#define SER_IP "192.168.125.104" //服务器IP地址

//定义信号处理函数
void handler(int signo)
{
if(signo == SIGCHLD)
{
//以非阻塞的形式回收僵尸进程
while(waitpid(-1, NULL, WNOHANG) >0);
}
}


int main(int argc, const char *argv[])
{

//将SIGCHLD信号绑定到信号处理函数中,以非阻塞的形式回收僵尸进程
if(signal(SIGCHLD, handler) == SIG_ERR)
{
perror("signal error");
return -1;
}


//1、创建用于连接的套接字
int sfd = socket(AF_INET, SOCK_STREAM, 0);
//参数1:通信域,表明使用的是ipv4协议
//参数2:通信方式,使用TCP通信
//参数3:0表示之前已经指定协议 IPPROTO_TCP

if(sfd == -1)
{
perror("socket error");
return -1;
}
printf("sfd = %d\n", sfd); //3


//将端口号快速重用函数
int reuse = 1;
if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1)
{
perror("setsockopt error");
return -1;
}
printf("端口号快速重用成功\n");



//2、给当前套接字绑定IP地址和端口号
//2.1填充要绑定的地址信息结构体
struct sockaddr_in sin;
sin.sin_family = AF_INET; //通信域
sin.sin_port = htons(SER_PORT); //端口号
sin.sin_addr.s_addr = inet_addr(SER_IP); //ip地址

//2.2 绑定
if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) == -1)
{
perror("bind error");
return -1;
}
printf("bind success %s %s %d\n", __FILE__, __func__, __LINE__);

//3、将套接字设置成监听状态
if(listen(sfd, 128) == -1)
{
perror("listen error");
return -1;
}
printf("listen success %s %s %d\n", __FILE__, __func__, __LINE__);

//4、阻塞等待客户端的链接请求
//4.1定义容器接收客户端的地址信息
struct sockaddr_in cin; //用于接收地址信息
socklen_t socklen = sizeof(cin); //用于接收地址信息的大小

int newfd = -1; //存放用于跟新客户端通信的文件描述符
pid_t pid = 0; //存放子进程的pid号

while(1)
{
//4.2 接收客户端的链接
newfd = accept(sfd, (struct sockaddr*)&cin, &socklen);
if(newfd == -1)
{
perror("accept error");
return -1;
}
printf("[%s:%d:%d]发来链接请求 %s %s %d\n", \
inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), newfd,__FILE__, __func__, __LINE__);




pid = fork(); //创建子进程
if(pid > 0)
{
//父进程:用于连接客户端的请求
//关闭newfd
close(newfd);

//wait(NULL);
//waitpid(-1, NULL, WNOHANG);

}else if(pid == 0)
{
//关闭sfd
close(sfd);

//5、跟客户端进行消息通信
char buf[128] = "";
while(1)
{
//将数组清空
bzero(buf, sizeof(buf));

//读取客户端发来的消息
//int res = read(newfd, buf, sizeof(buf));
int res = recv(newfd, buf, sizeof(buf), 0);
if(res == 0)
{
printf("客户端已经下线\n");
break;
}
printf("[%s:%d] : %s\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), buf);

//给客户端发消息
strcat(buf, "*_*");

//write(newfd, buf, sizeof(buf));
send(newfd, buf, sizeof(buf), 0);
printf("发送成功\n");
}

//关闭当前通信的套接字
close(newfd);

//退出子进程
exit(EXIT_SUCCESS);


}else
{
perror("fork error");
return -1;
}


}

//6、关闭套接字
close(sfd);





return 0;
}

三、多线程实现TCP并发服务器

多线程切换所需要的资源较小,效率较高,所以可以实现使用多线程完成tcp并发服务器

思路:主线程完成对客户端的连接请求,分支线程完成对客户端的交互工作

1> 通信模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 
socket(); //创建套接字
bind(); //必须绑定
listen(); //设置被动监听
while(1)
{
newfd = accept(); //接收客户端连接请求
//创建分支线程,用于跟客户端进行交互
pthread_create(&tid, NULL, deal_cli_msg, &info);

//线程分离
pthread_detach(tid);
}
//线程处理函数的定义
void *deal_cli_msg(void *arg)
{
recv();
send();
close();
pthread_exit();
}



2> 通信代码

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
#include<myhead.h>
#define SER_PORT 8888 //服务器端口号
#define SER_IP "192.168.125.104" //服务器IP地址

//定义一个用于向线程体函数传参的结构体类型
struct MsgInfo
{
int newfd;
struct sockaddr_in cin;
};


//定义线程处理函数
void *deal_cli_msg(void *arg)
{

//解析传进来的参数
int newfd = ((struct MsgInfo*)arg)->newfd;
struct sockaddr_in cin = ((struct MsgInfo*)arg)->cin;

//5、跟客户端进行消息通信
char buf[128] = "";
while(1)
{
//将数组清空
bzero(buf, sizeof(buf));

//读取客户端发来的消息
//int res = read(newfd, buf, sizeof(buf));
int res = recv(newfd, buf, sizeof(buf), 0);
if(res == 0)
{
printf("客户端已经下线\n");
break;
}
printf("[%s:%d] : %s\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), buf);

//给客户端发消息
strcat(buf, "*_*");

//write(newfd, buf, sizeof(buf));
send(newfd, buf, sizeof(buf), 0);
printf("发送成功\n");
}

//关闭当前通信的套接字
close(newfd);

//退出线程
pthread_exit(NULL);


}




int main(int argc, const char *argv[])
{
//1、创建用于连接的套接字
int sfd = socket(AF_INET, SOCK_STREAM, 0);
//参数1:通信域,表明使用的是ipv4协议
//参数2:通信方式,使用TCP通信
//参数3:0表示之前已经指定协议 IPPROTO_TCP

if(sfd == -1)
{
perror("socket error");
return -1;
}
printf("sfd = %d\n", sfd); //3


//将端口号快速重用函数
int reuse = 1;
if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1)
{
perror("setsockopt error");
return -1;
}
printf("端口号快速重用成功\n");



//2、给当前套接字绑定IP地址和端口号
//2.1填充要绑定的地址信息结构体
struct sockaddr_in sin;
sin.sin_family = AF_INET; //通信域
sin.sin_port = htons(SER_PORT); //端口号
sin.sin_addr.s_addr = inet_addr(SER_IP); //ip地址

//2.2 绑定
if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) == -1)
{
perror("bind error");
return -1;
}
printf("bind success %s %s %d\n", __FILE__, __func__, __LINE__);

//3、将套接字设置成监听状态
if(listen(sfd, 128) == -1)
{
perror("listen error");
return -1;
}
printf("listen success %s %s %d\n", __FILE__, __func__, __LINE__);

//4、阻塞等待客户端的链接请求
//4.1定义容器接收客户端的地址信息
struct sockaddr_in cin; //用于接收地址信息
socklen_t socklen = sizeof(cin); //用于接收地址信息的大小

int newfd = -1;

pthread_t tid; //线程id号

while(1)
{
//将程序执行到accept处时,系统会给accept函数预选一个文件描述符,按最小未分配原则
//即使后期可能会有更小的文件描述符,在本次操作时,不会选择

//4.2 接收客户端的链接
newfd = accept(sfd, (struct sockaddr*)&cin, &socklen);
if(newfd == -1)
{
perror("accept error");
return -1;
}
printf("[%s:%d:%d]发来链接请求 %s %s %d\n", \
inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), newfd, __FILE__, __func__, __LINE__);

//定义用于传参的结构体变量
struct MsgInfo info;
info.newfd = newfd;
info.cin = cin;


//创建分支线程,用于跟新连接的客户端进行交互
if(pthread_create(&tid, NULL, deal_cli_msg, &info) != 0)
{
printf("分支线程创建失败\n");
return -1;
}

//回收线程资源
//pthread_join(tid, NULL);
pthread_detach(tid); //将线程分离,后期退出后,由系统回收资源

}

//6、关闭套接字
close(sfd);





return 0;
}

本章完