添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

好了,我们了解了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, &copy_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,&copy_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,才表明文件已经接收完毕。同时,在写入文件时需要使用二进制模式打开文件,以免出现文件内容被截断等问题。 另外,以上代码只是一个示例,实际应用中还需要考虑网络异常、文件大小等因素,做相应的处理,以确保程序的稳定性和正确性。