好了,我们了解了tcp服务器的创建过程,就开始实现吧:
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
|
#include <stdio.h>
#include <arpa/inet.h>//inet_addr() sockaddr_in
#include <string.h>//bzero()
#include <sys/socket.h>//socket
#include <unistd.h>
#include <stdlib.h>//exit()
#define BUFFER_SIZE 1024
int
main() {
char
listen_addr_str[] =
"0.0.0.0"
;
size_t
listen_addr = inet_addr(listen_addr_str);
int
port = 8080;
int
server_socket, client_socket;
struct
sockaddr_in server_addr, client_addr;
socklen_t addr_size;
char
buffer[BUFFER_SIZE];
//缓冲区大小
int
str_length;
server_socket = socket(PF_INET, SOCK_STREAM, 0);
//创建套接字
bzero(&server_addr,
sizeof
(server_addr));
//初始化
server_addr.sin_family = INADDR_ANY;
server_addr.sin_port = htons(port);
server_addr.sin_addr.s_addr = listen_addr;
if
(bind(server_socket, (
struct
sockaddr *) &server_addr,
sizeof
(server_addr)) == -1) {
printf
(
"绑定失败\n"
);
exit
(1);
}
if
(listen(server_socket, 5) == -1) {
printf
(
"监听失败\n"
);
exit
(1);
}
printf
(
"创建tcp服务器成功\n"
);
addr_size =
sizeof
(client_addr);
client_socket = accept(server_socket, (
struct
sockaddr *) &client_addr, &addr_size);
printf
(
"%d 连接成功\n"
, client_socket);
char
msg[] =
"恭喜你连接成功"
;
write(client_socket, msg,
sizeof
(msg));
while
(1) {
str_length = read(client_socket, buffer, BUFFER_SIZE);
if
(str_length == 0)
//读取数据完毕关闭套接字
{
close(client_socket);
printf
(
"连接已经关闭: %d \n"
, client_socket);
break
;
}
else
{
printf
(
"客户端发送数据:%s"
,buffer);
write(client_socket, buffer, str_length);
//发送数据
}
}
return
0;
}
|
多客户端TCP服务器
以上代码实现了一个服务器,并且可以接收一个客户端连接,和它互相收发信息,但是看代码很容易发现不支持多客户端,只支持一个,那么怎么才能实现支持多个客户端呢?我们稍微改一改这份代码:
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
|
#include <stdio.h>
#include <arpa/inet.h>//inet_addr() sockaddr_in
#include <string.h>//bzero()
#include <sys/socket.h>//socket
#include <unistd.h>
#include <stdlib.h>//exit()
#define BUFFER_SIZE 1024
int
main() {
char
listen_addr_str[] =
"0.0.0.0"
;
size_t
listen_addr = inet_addr(listen_addr_str);
int
port = 8080;
int
server_socket, client_socket;
struct
sockaddr_in server_addr, client_addr;
socklen_t addr_size;
char
buffer[BUFFER_SIZE];
//缓冲区大小
size_t
client_arr[100];
//存储客户端数组
int
client_length=0;
//记录客户端数量
int
str_length;
server_socket = socket(PF_INET, SOCK_STREAM, 0);
//创建套接字
bzero(&server_addr,
sizeof
(server_addr));
//初始化
server_addr.sin_family = INADDR_ANY;
server_addr.sin_port = htons(port);
server_addr.sin_addr.s_addr = listen_addr;
if
(bind(server_socket, (
struct
sockaddr *) &server_addr,
sizeof
(server_addr)) == -1) {
printf
(
"绑定失败\n"
);
exit
(1);
}
if
(listen(server_socket, 5) == -1) {
printf
(
"监听失败\n"
);
exit
(1);
}
printf
(
"创建tcp服务器成功\n"
);
while
(1) {
addr_size =
sizeof
(client_addr);
client_socket = accept(server_socket, (
struct
sockaddr *) &client_addr, &addr_size);
client_arr[client_length] = client_socket;
client_length++;
printf
(
"%d 连接成功\n"
, client_socket);
char
msg[] =
"恭喜你连接成功"
;
write(client_socket, msg,
sizeof
(msg));
for
(
int
i = 0; i < client_length; ++i) {
if
(client_arr[i]==0){
continue
;
}
str_length = read(client_arr[i], buffer, BUFFER_SIZE);
if
(str_length == 0)
//读取数据完毕关闭套接字
{
close(client_arr[i]);
client_arr[i]=0;
printf
(
"连接已经关闭: %d \n"
, client_arr[i]);
break
;
}
else
{
printf
(
"客户端发送数据:%s"
,buffer);
write(client_arr[i], buffer, str_length);
//发送数据
}
}
}
}
|
我们通过将client_socket存储到一个数组里,然后每次去遍历该数组,可以勉强实现一个所谓的多客户端tcp服务器,但是有个致命弱点:
由于accept,read函数是阻塞的,导致这份代码,每次运行都得客户端连接,才能到下面的遍历代码,导致代码根本就没什么卵用:
A客户端连接好了,然后发送了条消息,服务器还得等到B客户端连接,才能接收到A的消息
,然后,B客户端发送好消息,需要C客户端连接,然后还得A客户端发送了条消息,才能遍历到B客户端的消息
多进程TCP服务器
这样的话,这份代码根本没什么卵用啊!!!!!!该怎么解决这个问题呢?????
我们或许可以通过多进程去解决这个问题,每个进程只处理一条客户端,就不存在什么阻塞不阻塞的问题了:
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
|
#
include
<stdio.h>
#
include
<arpa/inet.h>
//inet_addr() sockaddr_in
#
include
<string.h>
//bzero()
#
include
<sys/socket.h>
//socket
#
include
<unistd.h>
#
include
<stdlib.h>
//exit()
#
include
<sys/wait.h>
//waitpid();
#define BUFFER_SIZE 1024
int main() {
char listen_addr_str[] =
"0.0.0.0"
;
size_t listen_addr = inet_addr(listen_addr_str);
int port = 8080;
int server_socket, client_socket;
struct sockaddr_in server_addr, client_addr;
socklen_t addr_size;
char buffer[BUFFER_SIZE];
//缓冲区大小
int str_length;
pid_t pid;
int status = 0;
//初始化状态
server_socket = socket(PF_INET, SOCK_STREAM, 0);
//创建套接字
bzero(&server_addr, sizeof(server_addr));
//初始化
server_addr.sin_family = INADDR_ANY;
server_addr.sin_port = htons(port);
server_addr.sin_addr.s_addr = listen_addr;
if
(bind(server_socket, (struct sockaddr *) &server_addr, sizeof(server_addr)) == -1) {
printf(
"绑定失败\n"
);
exit
(1);
}
if
(listen(server_socket, 5) == -1) {
printf(
"监听失败\n"
);
exit
(1);
}
printf(
"创建tcp服务器成功\n"
);
while
(1) {
addr_size = sizeof(client_addr);
client_socket = accept(server_socket, (struct sockaddr *) &client_addr, &addr_size);
printf(
"%d 连接成功\n"
, client_socket);
char msg[] =
"恭喜你连接成功"
;
write(client_socket, msg, sizeof(msg));
pid = fork();
if
(pid > 0) {
sleep(1);
//父进程,进行下次循环,读取客户端连接事件
waitpid(-1, &status, WNOHANG | WUNTRACED | WCONTINUED);
if
(WIFEXITED(status)) {
printf(
"status = %d\n"
, WEXITSTATUS(status));
}
if
(WIFSIGNALED(status)) {
//如果子进程是被信号结束了 ,则为真
printf(
"signal status = %d\n"
, WTERMSIG(status));
//R->T
}
if
(WIFSTOPPED(status)) {
printf(
"stop sig num = %d\n"
, WSTOPSIG(status));
}
//T->R
if
(WIFCONTINUED(status)) {
printf(
"continue......\n"
);
}
}
else
if
(pid == 0) {
//子进程,进行阻塞式收发客户端数据
while
(1) {
memset(buffer, 0, sizeof(buffer));
str_length = read(client_socket, buffer, BUFFER_SIZE);
if
(str_length == 0)
//读取数据完毕关闭套接字
{
close(client_socket);
printf(
"连接已经关闭: %d \n"
, client_socket);
exit
(1);
}
else
{
printf(
"%d 客户端发送数据:%s \n"
, client_socket, buffer);
write(client_socket, buffer, str_length);
//发送数据
}
}
break
;
}
else
{
printf(
"创建子进程失败\n"
);
exit
(1);
}
}
return
0;
}
|
通过多进程,我们可以实现一个较完美的多进程TCP服务器,这个服务器可以完美的去处理多个客户端的数据
但是,一个进程处理一个连接,如果连接多的时候,会造成进程的频繁创建销毁,进程开销会非常大,导致cpu占用太大
所以,直接使用多进程去处理,还是不够完美的
由第二个例子,我们可以发现,主要问题出在于accept,read函数的阻塞上面,有没有什么办法处理掉这个阻塞呢?
非阻塞式TCP服务器
在c语言中,可以使用fcntl函数,将套接字设置为非阻塞的
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
|
#include <stdio.h>
#include <arpa/inet.h>//inet_addr() sockaddr_in
#include <string.h>//bzero()
#include <sys/socket.h>//socket
#include <unistd.h>
#include <stdlib.h>//exit()
#include <fcntl.h>//非阻塞
#define BUFFER_SIZE 1024
int
set_non_block(
int
socket) {
int
flags = fcntl(socket, F_GETFL, 0);
flags |= O_NONBLOCK;
return
fcntl(socket, F_SETFL, flags);
}
int
main() {
char
listen_addr_str[] =
"0.0.0.0"
;
size_t
listen_addr = inet_addr(listen_addr_str);
int
port = 8080;
int
server_socket, client_socket;
struct
sockaddr_in server_addr, client_addr;
socklen_t addr_size;
char
buffer[BUFFER_SIZE];
//缓冲区大小
size_t
client_arr[100];
//存储客户端数组
int
client_length = 0;
//记录客户端数量
int
str_length;
server_socket = socket(PF_INET, SOCK_STREAM, 0);
//创建套接字
bzero(&server_addr,
sizeof
(server_addr));
//初始化
server_addr.sin_family = INADDR_ANY;
server_addr.sin_port = htons(port);
server_addr.sin_addr.s_addr = listen_addr;
if
(bind(server_socket, (
struct
sockaddr *) &server_addr,
sizeof
(server_addr)) == -1) {
printf
(
"绑定失败\n"
);
exit
(1);
}
if
(listen(server_socket, 5) == -1) {
printf
(
"监听失败\n"
);
exit
(1);
}
if
(set_non_block(server_socket) == -1) {
//设置非阻塞
printf
(
"设置非阻塞失败\n"
);
exit
(1);
}
printf
(
"创建tcp服务器成功\n"
);
while
(1) {
addr_size =
sizeof
(client_addr);
client_socket = accept(server_socket, (
struct
sockaddr *) &client_addr, &addr_size);
if
(client_socket > 0) {
//非阻塞下,无法读取返回-1
client_arr[client_length] = client_socket;
client_length++;
if
(set_non_block(client_socket) == -1) {
//设置非阻塞
printf
(
"设置客户端非阻塞失败\n"
);
exit
(1);
}
printf
(
"%d 连接成功\n"
, client_socket);
char
msg[] =
"恭喜你连接成功"
;
write(client_socket, msg,
sizeof
(msg));
}
for
(
int
i = 0; i < client_length; ++i) {
if
(client_arr[i] == 0) {
continue
;
}
memset
(&buffer, 0,
sizeof
(buffer));
str_length = read(client_arr[i], buffer, BUFFER_SIZE);
if
(str_length==-1){
//非阻塞下,无法读取返回-1
continue
;
}
if
(str_length == 0)
//读取数据完毕关闭套接字
{
close(client_arr[i]);
client_arr[i] = 0;
printf
(
"连接已经关闭: %d \n"
, client_arr[i]);
break
;
}
else
{
printf
(
"客户端发送数据:%s"
, buffer);
write(client_arr[i], buffer, str_length);
//发送数据
}
}
usleep(100);
//非阻塞下,如果全部socket无法读取(没有事件变化),则相当于是while(1),会使cpu繁忙
}
}
|
这样,我们就实现了一个单进程多客户端的tcp服务器了,不需要多进程也能实现多客户端,但是看最后一行注释能发现一个问题:非阻塞下,会无限循环,让代码空转,这样浪费的性能也是巨大的,那我们该怎么完善呢?或许我们可以用到I/O复用模型
select机制TCP服务器
select是系统级别的功能,它可以同时阻塞探测多个socket,并且返回可调用的socket的数量
原理图大概为:
实现代码:
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
|
#include <stdio.h>
#include <arpa/inet.h>//inet_addr() sockaddr_in
#include <string.h>//bzero()
#include <sys/socket.h>//socket
#include <unistd.h>
#include <stdlib.h>//exit()
#define BUFFER_SIZE 1024
int
main() {
char
listen_addr_str[] =
"0.0.0.0"
;
size_t
listen_addr = inet_addr(listen_addr_str);
int
port = 8080;
int
server_socket, client_socket;
struct
sockaddr_in server_addr, client_addr;
socklen_t addr_size;
char
buffer[BUFFER_SIZE];
//缓冲区大小
int
str_length;
server_socket = socket(PF_INET, SOCK_STREAM, 0);
//创建套接字
bzero(&server_addr,
sizeof
(server_addr));
//初始化
server_addr.sin_family = INADDR_ANY;
server_addr.sin_port = htons(port);
server_addr.sin_addr.s_addr = listen_addr;
if
(bind(server_socket, (
struct
sockaddr *) &server_addr,
sizeof
(server_addr)) == -1) {
printf
(
"绑定失败\n"
);
exit
(1);
}
if
(listen(server_socket, 5) == -1) {
printf
(
"监听失败\n"
);
exit
(1);
}
printf
(
"创建tcp服务器成功\n"
);
fd_set reads,copy_reads;
int
fd_max,fd_num;
struct
timeval timeout;
FD_ZERO(&reads);
//初始化清空socket集合
FD_SET(server_socket,&reads);
fd_max=server_socket;
while
(1) {
copy_reads = reads;
timeout.tv_sec = 5;
timeout.tv_usec = 5000;
//无限循环调用select 监视可读事件
if
((fd_num = select(fd_max+1, ©_reads, 0, 0, &timeout)) == -1) {
perror
(
"select error"
);
break
;
}
if
(fd_num==0){
//没有变动的socket
continue
;
}
for
(
int
i=0;i<fd_max+1;i++){
if
(FD_ISSET(i,©_reads)){
if
(i==server_socket){
//server_socket变动,代表有新客户端连接
addr_size =
sizeof
(client_addr);
client_socket = accept(server_socket, (
struct
sockaddr *) &client_addr, &addr_size);
printf
(
"%d 连接成功\n"
, client_socket);
char
msg[] =
"恭喜你连接成功"
;
write(client_socket, msg,
sizeof
(msg));
FD_SET(client_socket,&reads);
if
(fd_max < client_socket){
fd_max=client_socket;
}
}
else
{
memset
(buffer, 0,
sizeof
(buffer));
str_length = read(i, buffer, BUFFER_SIZE);
if
(str_length == 0)
//读取数据完毕关闭套接字
{
close(i);
printf
(
"连接已经关闭: %d \n"
, i);
FD_CLR(i, &reads);
//从reads中删除相关信息
}
else
{
printf
(
"%d 客户端发送数据:%s \n"
, i, buffer);
write(i, buffer, str_length);
//将数据发送回客户端
}
}
}
}
}
return
0;
}
|
上面就是select机制的tcp实现代码,可以同时处理多客户端,性能比多进程好了很多,但这并不是说明select机制没有缺点了
在这份代码中,可以发现以下几点:
1:客户端的socket标识符是存在一个fd_set类型中的集合中的,客户端大小由fd_set大小决定,开发时需要考虑到这个的最大值
2:每次调用select函数之前,都得将集合重新传给select,效率较慢;
3:每次调用完select函数,就算返回1,也会将集合全部遍历一遍,效率较慢
epoll机制TCP服务器
原理图大概为:
epoll机制提供了以下3个核心函数:
epoll_create() 创建epoll监听socket
epoll_ctl()注册,删除,修改监听
epoll_wait() 等待事件触发函数
在实现epoll机制前,我们得先了解下ET/LT模式
LT(level-trigger) 水平触发
epoll的默认工作方式,在这个模式下,只要监听的socket有可读/可写状态,都将返回该socket,例如:
当客户端给tcp服务器发送一个数据时,这个client_socket将会是可读的,调用epoll_wait函数将会返回该client_socket,
如果服务器不做处理,这个client_socket将会是一直可读的,下次调用epoll_wait函数将会继续返回client_socket
实现代码:
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
|
#include <stdio.h>
#include <arpa/inet.h>//inet_addr() sockaddr_in
#include <string.h>//bzero()
#include <sys/socket.h>//socket
#include <unistd.h>
#include <stdlib.h>//exit()
#include <sys/epoll.h> //epoll
#define BUFFER_SIZE 1024
#define CLIENT_MAX_SIZE 1024
int
main() {
char
listen_addr_str[] =
"0.0.0.0"
;
size_t
listen_addr = inet_addr(listen_addr_str);
int
port = 8080;
int
server_socket, client_socket;
struct
sockaddr_in server_addr, client_addr;
socklen_t addr_size;
char
buffer[BUFFER_SIZE];
//缓冲区大小
int
str_length;
server_socket = socket(PF_INET, SOCK_STREAM, 0);
//创建套接字
bzero(&server_addr,
sizeof
(server_addr));
//初始化
server_addr.sin_family = INADDR_ANY;
server_addr.sin_port = htons(port);
server_addr.sin_addr.s_addr = listen_addr;
if
(bind(server_socket, (
struct
sockaddr *) &server_addr,
sizeof
(server_addr)) == -1) {
printf
(
"绑定失败\n"
);
exit
(1);
}
if
(listen(server_socket, 5) == -1) {
printf
(
"监听失败\n"
);
exit
(1);
}
printf
(
"创建tcp服务器成功\n"
);
struct
epoll_event event;
//监听事件
struct
epoll_event wait_event_list[CLIENT_MAX_SIZE];
//监听结果
int
fd[CLIENT_MAX_SIZE];
int
j = 0;
int
epoll_fd = epoll_fd = epoll_create(10);
//创建epoll句柄,里面的参数10没有意义
if
(epoll_fd == -1) {
printf
(
"创建epoll句柄失败\n"
);
exit
(1);
}
event.events = EPOLLIN;
//可读事件
event.data.fd = server_socket;
//server_socket
int
result = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_socket, &event);
if
(result == -1) {
printf
(
"注册epoll 事件失败\n"
);
exit
(1);
}
while
(1) {
result = epoll_wait(epoll_fd, wait_event_list, CLIENT_MAX_SIZE, -1);
//阻塞
if
(result <= 0) {
continue
;
}
for
(j = 0; j < result; j++) {
printf
(
"%d 触发事件 %d \n"
, wait_event_list[j].data.fd, wait_event_list[j].events);
//server_socket触发事件
if
(server_socket == wait_event_list[j].data.fd && EPOLLIN == wait_event_list[j].events & EPOLLIN) {
addr_size =
sizeof
(client_addr);
client_socket = accept(server_socket, (
struct
sockaddr *) &client_addr, &addr_size);
printf
(
"%d 连接成功\n"
, client_socket);
char
msg[] =
"恭喜你连接成功"
;
write(client_socket, msg,
sizeof
(msg));
event.data.fd = client_socket;
event.events = EPOLLIN;
//可读或错误
result = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_socket, &event);
if
(result == -1) {
printf
(
"注册客户端 epoll 事件失败\n"
);
exit
(1);
}
continue
;
}
//客户端触发事件
if
((wait_event_list[j].events & EPOLLIN)
||(wait_event_list[j].events & EPOLLERR))
//可读或发生错误
{
memset
(&buffer, 0,
sizeof
(buffer));
str_length = read(wait_event_list[j].data.fd, buffer, BUFFER_SIZE);
if
(str_length == 0)
//读取数据完毕关闭套接字
{
close(wait_event_list[j].data.fd);
event.data.fd = wait_event_list[j].data.fd;
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, wait_event_list[j].data.fd, &event);
printf
(
"连接已经关闭: %d \n"
, wait_event_list[j].data.fd);
}
else
{
printf
(
"客户端发送数据:%s \n"
, buffer);
write(wait_event_list[j].data.fd, buffer, str_length);
//执行回声服务 即echo
}
}
}
}
// return 0;
}
|
lt模式下,也可以使用非阻塞模式,以上代码未使用
ET(edge-trigger) 边缘触发
通过注册监听增加EPOLLET参数可将模式转换成边缘触发,
在et模式下,socket触发的多个事件只会返回一次,必须一次性全部处理,例如:
server_socket 有10个待处理的新连接,在epoll_wait函数返回后,必须循环读取accept直到没有数据可读,
由于必须一直循环读取,所以当accept没有数据可读时,必须是非阻塞模式,否则会阻塞
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
|
#include <stdio.h>
#include <arpa/inet.h>//inet_addr() sockaddr_in
#include <string.h>//bzero()
#include <sys/socket.h>//socket
#include <unistd.h>
#include <stdlib.h>//exit()
#include <sys/epoll.h> //epoll
#define BUFFER_SIZE 1024
#define CLIENT_MAX_SIZE 1024
int
set_non_block(
int
socket) {
int
flags = fcntl(socket, F_GETFL, 0);
flags |= O_NONBLOCK;
return
fcntl(socket, F_SETFL, flags);
}
int
main() {
char
listen_addr_str[] =
"0.0.0.0"
;
size_t
listen_addr = inet_addr(listen_addr_str);
int
port = 8080;
int
server_socket, client_socket;
struct
sockaddr_in server_addr, client_addr;
socklen_t addr_size;
char
buffer[BUFFER_SIZE];
//缓冲区大小
int
str_length;
server_socket = socket(PF_INET, SOCK_STREAM, 0);
//创建套接字
bzero(&server_addr,
sizeof
(server_addr));
//初始化
server_addr.sin_family = INADDR_ANY;
server_addr.sin_port = htons(port);
server_addr.sin_addr.s_addr = listen_addr;
if
(bind(server_socket, (
struct
sockaddr *) &server_addr,
sizeof
(server_addr)) == -1) {
printf
(
"绑定失败\n"
);
exit
(1);
}
if
(listen(server_socket, 5) == -1) {
printf
(
"监听失败\n"
);
exit
(1);
}
printf
(
"创建tcp服务器成功\n"
);
set_non_block(server_socket);
//设置非阻塞
struct
epoll_event event;
//监听事件
struct
epoll_event wait_event_list[CLIENT_MAX_SIZE];
//监听结果
int
fd[CLIENT_MAX_SIZE];
int
j = 0;
int
epoll_fd = epoll_fd = epoll_create(10);
//创建epoll句柄,里面的参数10没有意义
if
(epoll_fd == -1) {
printf
(
"创建epoll句柄失败\n"
);
exit
(1);
}
event.events = EPOLLIN|EPOLLET;
//注册可读事件+et模式
event.data.fd = server_socket;
//server_socket
int
result = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_socket, &event);
if
(result == -1) {
printf
(
"注册epoll 事件失败\n"
);
exit
(1);
}
while
(1) {
result = epoll_wait(epoll_fd, wait_event_list, CLIENT_MAX_SIZE, -1);
//阻塞
if
(result <= 0) {
continue
;
}
for
(j = 0; j < result; j++) {
printf
(
"%d 触发事件 %d \n"
, wait_event_list[j].data.fd, wait_event_list[j].events);
//server_socket触发事件
if
(server_socket == wait_event_list[j].data.fd && EPOLLIN == wait_event_list[j].events & EPOLLIN) {
addr_size =
sizeof
(client_addr);
while
(1) {
client_socket = accept(server_socket, (
struct
sockaddr *) &client_addr, &addr_size);
if
(client_socket==-1){
//没有数据可读
break
;
}
printf
(
"%d 连接成功\n"
, client_socket);
char
msg[] =
"恭喜你连接成功"
;
write(client_socket, msg,
sizeof
(msg));
set_non_block(client_socket);
//设置非阻塞
event.data.fd = client_socket;
event.events = EPOLLIN|EPOLLET;
//可读+et模式
result = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_socket, &event);
if
(result == -1) {
printf
(
"注册客户端 epoll 事件失败\n"
);
exit
(1);
}
}
continue
;
}
//客户端触发事件
if
((wait_event_list[j].events & EPOLLIN)
||(wait_event_list[j].events & EPOLLERR))
//可读或发生错误
{
memset
(&buffer, 0,
sizeof
(buffer));
while
(1){
str_length = read(wait_event_list[j].data.fd, buffer, BUFFER_SIZE);
//读取多次数据
if
(str_length==-1){
//没有数据返回
break
;
}
if
(str_length == 0)
//读取数据完毕关闭套接字
{
close(wait_event_list[j].data.fd);
event.data.fd = wait_event_list[j].data.fd;
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, wait_event_list[j].data.fd, &event);
printf
(
"连接已经关闭: %d \n"
, wait_event_list[j].data.fd);
}
else
{
printf
(
"客户端发送数据:%s \n"
, buffer);
write(wait_event_list[j].data.fd, buffer, str_length);
//执行回声服务 即echo
}
}
}
}
}
// return 0;
}
|
以上说明,可看出:
1:epoll不需要遍历其他没有事件的socket,避免了select的性能浪费
2:epoll有两种工作模式,用于不同的场景,et和lt模式都可以用非阻塞,但et模式必须非阻塞,et模式编程难度较大,每次epoll_wait都得考虑必须处理掉所有事件
出自尊重,本文声明:本文为仙士可原创文章,转载来自仙士可博客www.php20.cn
5种io模型tcp服务器分为了5种io复用模型,分别是:阻塞io模型 非阻塞io模型io复用信号驱动io异步io本文会讲前面3种io模型的tcp服务器实现(本文只做tcp服务器实现,客户端逻辑处理,接收数据等缓冲区不做深入说明)简单实现首先,我们需要理解下tcp服务器的创建过程:1:通过socket函数创建一个套接字文件2:通过bind函数将本地一...
本篇目标:在之前能ping通pc机的工程基础上搭建
tcp
连接,并可以收发数据,在网络调试工具上显示材料准备:
基础工程:修改后能ping通pc机的工程(STM32官方移植lwip修改代码)
调试工具:用来调试
tcp
连接下的数据接收(网络调试助手)
搭建工程:最终搭建好
tcp
数据接收的工程(
tcp
服务器
建立工程)
搭建
TCP
服务器
之前已经能够让pc机ping通stm32了,说明PHY网卡已经正确工作了
3)将套接字设为监听,准备接收客户请求(listen)
4)等待客户请求的到来,当请求到来后,接受请求,返回一个对应于此次连接的套接字(accept)
5)用返回的套接字与客户端进行通信(send/recv)
6)返回,等待另一客户请求
7)关闭套接字
2.基于
TCP
9、Linux
网络编程
09——
TCP
编程之客户端
10、Linux
网络编程
10——
TCP
编程之
服务器
11、Linux
网络编程
11——
tcp
、udp迭代
服务器
12、Linux
网络编程
12——
tcp
三次握手、四次挥手
13、Linux
网络编程
13——connect()、listen()和accept()三者之间的关系
14、Linux
网络编程
14——I/O复用之select详解
15、Linux
网络编程
15——I/O复用之poll详解
16、Linux
网络编程
16——I/O复用之epoll详解
17、Linux
网络编程
17——
tcp
并发
服务器
(多进程)
18、Linux
网络编程
18——
tcp
并发
服务器
(多线程)
19、Linux
网络编程
——
tcp
高效并发
服务器
(select
实现
)
20、Linux
网络编程
——
tcp
高效并发
服务器
(poll
实现
)
21、Linux
网络编程
——
tcp
高效并发
服务器
(epoll
实现
)
二、网络底层编程(黑客模式)
1、Linux
网络编程
1——啥叫原始套接字
2、Linux
网络编程
2——原始套接字编程
3、Linux
网络编程
3——原始套接字实例:MAC头分析
4、Linux
网络编程
4——原始套接字实例:MAC地址扫描器
5、Linux
网络编程
5——IP数据报格式详解
6、Linux
网络编程
6——
TCP
、UDP数据包格式详解
7、Linux
网络编程
7——原始套接字实例:发送UDP数据包
8、Linux
网络编程
8——libpcap详解
9、Linux
网络编程
9——libnet详解
原创作品,允许转载,转载时请务必以超链接形式标明文章原始出处、作者信息和本声明。否则将追究法律责任。http://bluefish.blog.51cto.com/214870/158416
既然定了这么个标题,当然是要从socket的recv来讲了。这里主要...
TCP
迭代
服务器
接受一个客户端的连接,然后处理,完成了这个客户的所有请求后,断开连接。
TCP
迭代
服务器
一次只能处理一个客户端的请求,只有在这个客户的所有请求满足后,
服务器
才可以继续后面的请求。如果有一个客户端占住
服务器
不放时,其它的客户机都不能工作了,因此,
TCP
服务器
一般很少用迭代
服务器
模型的。
tcp
服务器
端框架
1.创建
tcp
套接字
2. 绑定套接字
3. 监听套接字
int main(int argc, char *argv[]) {
if (argc != 4) {
fprintf(stderr, "Usage: %s <server_ip> <server_port> <filename>\n", argv[0]);
exit(1);
char *server_ip = argv[1];
int server_port = atoi(argv[2]);
char *filename = argv[3];
// 创建套接字
int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (sock_fd == -1) {
perror("socket");
exit(1);
// 连接
服务器
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr(server_ip);
server_addr.sin_port = htons(server_port);
if (connect(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("connect");
exit(1);
// 发送请求
char request[BUFFER_SIZE];
snprintf(request, BUFFER_SIZE, "GET %s HTTP/1.0\r\n\r\n", filename);
if (send(sock_fd, request, strlen(request), 0) == -1) {
perror("send");
exit(1);
// 接收响应
char buffer[BUFFER_SIZE];
int len;
FILE *fp = fopen(filename, "wb");
while ((len = recv(sock_fd, buffer, BUFFER_SIZE, 0)) > 0) {
fwrite(buffer, sizeof(char), len, fp);
// 关闭文件和套接字
fclose(fp);
close(sock_fd);
return 0;
以上代码
实现
了从
服务器
下载指定文件的功能。需要传入
服务器
IP地址、端口号和文件名三个参数。程序通过创建套接字,连接
服务器
,发送请求和接收响应来
实现
文件下载。下载的文件将保存在当前目录下,文件名与指定的文件名相同。
需要注意的是,由于
TCP
协议保证了数据传输的可靠性,因此在接收响应时需要循环接收数据,直到接收到的数据长度为0,才表明文件已经接收完毕。同时,在写入文件时需要使用二进制模式打开文件,以免出现文件内容被截断等问题。
另外,以上代码只是一个示例,实际应用中还需要考虑网络异常、文件大小等因素,做相应的处理,以确保程序的稳定性和正确性。