ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

C语言网络编程-tcp服务器实现

2021-06-22 16:35:10  阅读:146  来源: 互联网

标签:socket epoll 编程 tcp server client printf C语言 addr


5种io模型

tcp服务器分为了5种io复用模型,分别是:

阻塞io模型    

非阻塞io模型

io复用

信号驱动io

异步io

本文会讲前面3种io模型的tcp服务器实现(本文只做tcp服务器实现,客户端逻辑处理,接收数据等缓冲区不做深入说明)

简单实现

首先,我们需要理解下tcp服务器的创建过程:

1:通过socket函数创建一个套接字文件

2:通过bind函数将本地一个地址和套接字捆绑

3:使用listen函数监听外部请求

4:使用accept函数接收外部请求

5:read,write,close 用于收,发,关闭客户端数据

 

好了,我们了解了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都得考虑必须处理掉所有事件

 

 

 

 

 

 

标签:socket,epoll,编程,tcp,server,client,printf,C语言,addr
来源: https://www.cnblogs.com/endkoo/p/14919109.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有