Linux下的socket操作

一、TCP服务端

创建一个TCP服务器的基本操作:

  1. 创建一个套接字(socket):使用socket函数
  2. 绑定套接字(socket):将套接字绑定到一个特定的IP地址和端口号上,这些信息要用结构体sockaddr_in来保存
  3. 监听请求连接:使用listen函数
  4. 接受连接:使用accept函数来实现
  5. 发送和接受信息:一旦建立了连接,服务器和客户端都可以使用套接字的send()和recv()方法来发送和接收数据。发送方使用send()方法将数据发送到套接字,而接收方使用recv()方法从套接字接收数据。
  6. 关闭连接:当通信完成后,可以调用套接字的close()方法来关闭连接。这将释放套接字占用的资源并终止连接。

1.socket:创建套接字

函数原型:

int socket(int domain, int type, int protocol);

参数说明:

  • domain:指定地址族,可以是AF_INET(用于IPv4)或AF_INET6(用于IPv6)。
  • type:指定套接字类型,可以是SOCK_STREAM(用于TCP)或SOCK_DGRAM(用于UDP)。
  • protocol:一般为0,表示使用默认的协议。

返回值: 如果成功,返回一个非负整数,表示套接字文件描述符(socket file descriptor)。如果失败,返回-1,并设置全局变量errno来指示错误类型。

2. sockaddr_in:sockaddr_in 是一个用于表示 IPv4 地址的结构体,它定义在 <netinet/in.h> 头文件中。

定义如下:

struct sockaddr_in {sa_family_t sin_family; // 地址族,一般为 AF_INETin_port_t sin_port;     // 端口号struct in_addr sin_addr; // IPv4 地址char sin_zero[8];       // 用于补齐,一般设置为全0
};

其中,sa_family_t 和 in_port_t 是整数类型,struct in_addr 是一个用于存储 IPv4 地址的结构体。sin_family 表示地址族,一般为 AF_INET,表示使用 IPv4 地址。sin_port 表示端口号,用于标识网络中的应用程序。sin_addr 存储了 IPv4 地址的信息。sin_zero 是一个用于补齐的字段,一般设置为全0。

in_addr:in_addr 是一个用于存储 IPv4 地址的结构体,它定义在 <netinet/in.h> 头文件中。

它的定义如下:

struct in_addr {in_addr_t s_addr; // IPv4 地址
};

其中,in_addr_t 是一个无符号整数类型,用于存储 IPv4 地址。

3. htons:htons 是一个用于将主机字节序(Host Byte Order)转换为网络字节序(即大端存储)的函数。它定义在 <arpa/inet.h> 头文件中,并且接受一个参数,表示要转换的 16 位无符号整数。

函数原型如下:

uint16_t htons(uint16_t hostshort);

4.bind:bind 是一个用于将一个套接字(socket)与一个特定的地址(包括 IP 地址和端口号)绑定的函数。它通常用于服务器端在监听连接之前将套接字绑定到一个特定的地址上。

在 C 语言中,bind 函数的原型如下:

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数说明:

  • sockfd 是要绑定的套接字的文件描述符。
  • addr 是一个指向 sockaddr 结构体的指针,用于指定要绑定的地址信息。
  • addrlen 是 addr 所指向的结构体的长度。

返回值:

  • 如果 bind 函数执行成功,返回值为 0。这意味着套接字成功地与指定的地址绑定在一起。
  • 如果 bind 函数执行失败,返回值为 -1。这表示绑定操作未成功。

5.listen:listen 函数用于将一个已绑定的套接字(socket)设置为监听状态,以便接受传入的连接请求

在 C 语言中,listen 函数的原型如下:

int listen(int sockfd, int backlog);

参数说明:

  • sockfd:要设置为监听状态的套接字的文件描述符。
  • backlog:定义在连接队列中等待被接受的连接的最大数量。

返回值:

  • 如果 listen 函数执行成功,返回值为 0。这意味着套接字已成功设置为监听状态,并且可以开始接受传入的连接请求。
  • 如果 listen 函数执行失败,返回值为 -1。这表示设置监听状态的操作未成功。

6.accept:accept 函数用于从已监听的套接字中接受一个传入的连接请求,并创建一个新的套接字来处理该连接。

在 C 语言中,accept 函数的原型如下:

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

参数说明:

  • sockfd:已监听的套接字的文件描述符。
  • addr:指向一个 struct sockaddr 结构的指针,用于存储接受连接的远程地址信息。
  • addrlen:指向一个 socklen_t 类型的变量,表示 addr 的大小。

返回值:

  • 如果 accept 函数执行成功,返回值为新创建的套接字的文件描述符。这个新套接字用于与客户端进行通信。
  • 如果 accept 函数执行失败,返回值为 -1。这表示接受连接请求的操作未成功。

7.recv:recv 函数用于从已连接的套接字接收数据。它是在已建立连接的套接字上进行数据交换的常用函数之一。

在 C 语言中,recv 函数的原型如下:

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

参数含义:

  • sockfd:已连接的套接字的文件描述符。
  • buf:指向接收数据的缓冲区的指针。
  • len:缓冲区的大小,即要接收的最大字节数。
  • flags:可选的标志参数,用于控制接收操作的行为。

返回值: recv 函数会阻塞程序执行,直到有数据到达为止。当有数据到达时,recv 函数会将数据从套接字读取到指定的缓冲区 buf 中,并返回实际接收到的字节数。如果返回值为 0,表示对端已关闭连接。如果返回值为 -1,表示接收数据的操作未成功。

8.创建服务器实例

#include <stdio.h>                                                                                                                                  
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>int main()
{//创建socket,参数说明://指定协议族:AF_INET(IPv4)和 AF_INET6(IPv6)//指定套接字使用的协议,通常为0,表示根据 domain 和 type 自动选择合适的协议。int sockfd = socket(AF_INET, SOCK_STREAM, 0); if(-1 == sockfd){   perror("socket");exit(1);}   //绑定//sockaddr_in 是一个用于表示 IPv4 地址的结构体struct sockaddr_in server_info; //用于保存服务器的信息(IP PROT)bzero(&server_info, sizeof(struct sockaddr_in)); //清空,bzero() 是一个函数,用于将指定内存区域的前几个字节设置为零server_info.sin_family = AF_INET; //地址族server_info.sin_port = htons(7000); //端口,大于1024都行
//  server_info.sin_addr.s_addr = inet_addr("127.0.0.1"); //表示回环IP,用于测试server_info.sin_addr.s_addr = inet_addr("192.168.175.129");if(bind(sockfd, (struct sockaddr *)&server_info, sizeof(server_info)) == -1) {   perror("bind");exit(2);}//设置监听队列if(listen(sockfd, 10) == -1){perror("listen");exit(3);}printf("等待客户端的连接...\n");//接受连接 struct sockaddr_in client_info;int length = sizeof(client_info);int fd = accept(sockfd, (struct sockaddr *)&client_info, &length);if(-1 == fd){perror("accept");exit(4);}printf("接受客户端的连接 %d\n", fd);char buf[1024] = {0};ssize_t size;while(1){size = recv(fd, buf, sizeof(buf), 0);if(size == -1){perror("recv");break;}else if(size == 0)break;if(!strcmp(buf, "bye"))break;printf("%s\n", buf);bzero(buf, 1024);}close(fd); //关闭TCP连接,不能在接受数据close(sockfd); //关闭socket,不能再处理客户端的请求//socket用于处理客户端的连接,fd用于处理客户端的消息return 0;
}       

二、TCP的客户端

客户端连接服务端的基本操作:

  1. 创建socket
  2. 发起连接
  3. 发送信息

send:send 是一个函数,用于在一个已连接的套接字上发送数据。

函数原型如下:

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

参数说明:

  • sockfd:已连接的套接字描述符。
  • buf:指向要发送数据的缓冲区的指针。
  • len:要发送的数据的长度(以字节为单位)。
  • flags:可选的标志参数,用于控制发送操作的行为。

返回值: 函数返回一个 ssize_t 类型的值,表示实际发送的字节数。如果发送失败,返回值为 -1,并且可以通过检查全局变量 errno 来获取错误代码。

创建客户端实例

#include <stdio.h>                                                                                                                                  
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>int main()
{//创建socketint sockfd = socket(AF_INET, SOCK_STREAM, 0);if(-1 == sockfd){perror("socket");exit(1);}//发起连接struct sockaddr_in server_info; // 存储服务器信息bzero(&server_info, 0); server_info.sin_family = AF_INET;  //地址族server_info.sin_port = htons(7000); //绑定端口server_info.sin_addr.s_addr = inet_addr("192.168.175.129"); //绑定ip地址if(connect(sockfd, (struct sockaddr*)&server_info, sizeof(server_info)) == -1){perror("connect");exit(2);}//发送信息char buf[1024] = {0};while(1){scanf("%s", buf);if(send(sockfd, buf, sizeof(buf), 0) == -1){perror("send");exit(3);}if(!strcmp(buf, "bye"))break;bzero(buf, sizeof(buf));}close(sockfd);return 0;
}   

三、TCP并发服务器

使用多线程来实现响应多个客户端

#include <stdio.h>                                                                                                                                  
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>void *client_recv(void *arg)
{int fd = *(int *)arg;char buf[1024] = {0};ssize_t size;while(1){   size = recv(fd, buf, sizeof(buf), 0); if(size == -1) {perror("recv");break;}else if(size == 0)break;if(!strcmp(buf, "bye"))break;printf("%s\n", buf);bzero(buf, 1024);}printf("客户端 %d 退出\n", fd);close(fd);
}int main()
{//创建socket,参数说明://指定协议族:AF_INET(IPv4)和 AF_INET6(IPv6)//指定套接字使用的协议,通常为0,表示根据 domain 和 type 自动选择合适的协议。int sockfd = socket(AF_INET, SOCK_STREAM, 0);if(-1 == sockfd) {perror("socket");exit(1);}   //绑定//sockaddr_in 是一个用于表示 IPv4 地址的结构体struct sockaddr_in server_info; //用于保存服务器的信息(IP PROT)bzero(&server_info, sizeof(struct sockaddr_in)); //清空,bzero() 是一个函数,用于将指定内存区域的前几个字节设置为零server_info.sin_family = AF_INET; //地址族server_info.sin_port = htons(7000); //端口,大于1024都行
//  server_info.sin_addr.s_addr = inet_addr("127.0.0.1"); //表示回环IP,用于测试server_info.sin_addr.s_addr = inet_addr("192.168.175.129");if(bind(sockfd, (struct sockaddr *)&server_info, sizeof(server_info)) == -1){perror("bind");exit(2);}//设置监听队列if(listen(sockfd, 10) == -1){perror("listen");exit(3);}printf("等待客户端的连接...\n");//接受连接struct sockaddr_in client_info;int length = sizeof(client_info);int fd;while(1){fd = accept(sockfd, (struct sockaddr *)&client_info, &length);if(-1 == fd){perror("accept");exit(4);}printf("接受客户端的连接 %d\n", fd);//为每个客户端创建一个线程pthread_t tid;if(pthread_create(&tid, NULL, client_recv, &fd) != 0){perror("pthread_create");exit(4);  }pthread_detach(tid);}//  close(fd); //关闭TCP连接,不能在接受数据close(sockfd); //关闭socket,不能再处理客户端的请求//socket用于处理客户端的连接,fd用于处理客户端的消息return 0}

四、服务器转发

服务器代码:

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>typedef struct Info
{char text[1024];int tofd;
}Info;void *client_recv(void *arg)
{int fd = *(int *)arg;Info buf;ssize_t size;while(1){   size = recv(fd, &buf, sizeof(buf), 0); if(size == -1) {   perror("recv");break;}   else if(size == 0)break;if(!strcmp(buf.text, "bye"))break;//转发数据if(send(buf.tofd, &buf, size, 0) == -1){perror("send");break;}bzero(&buf, sizeof(buf));}printf("客户端 %d 退出\n", fd);close(fd);
}int main()
{   //创建socket,参数说明://指定协议族:AF_INET(IPv4)和 AF_INET6(IPv6)//指定套接字使用的协议,通常为0,表示根据 domain 和 type 自动选择合适的协议。int sockfd = socket(AF_INET, SOCK_STREAM, 0);if(-1 == sockfd){   perror("socket");exit(1);}       //绑定//sockaddr_in 是一个用于表示 IPv4 地址的结构体struct sockaddr_in server_info; //用于保存服务器的信息(IP PROT)bzero(&server_info, sizeof(struct sockaddr_in)); //清空,bzero() 是一个函数,用于将指定内存区域的前几个字节设置为零server_info.sin_family = AF_INET; //地址族server_info.sin_port = htons(7000); //端口,大于1024都行
//  server_info.sin_addr.s_addr = inet_addr("127.0.0.1"); //表示回环IP,用于测试server_info.sin_addr.s_addr = inet_addr("192.168.175.129");if(bind(sockfd, (struct sockaddr *)&server_info, sizeof(server_info)) == -1){       perror("bind");exit(2);}//设置监听队列if(listen(sockfd, 10) == -1){perror("listen");exit(3);}printf("等待客户端的连接...\n");//接受连接struct sockaddr_in client_info;int length = sizeof(client_info);int fd;while(1){   fd = accept(sockfd, (struct sockaddr *)&client_info, &length);if(-1 == fd){perror("accept");exit(4);}printf("接受客户端的连接 %d\n", fd);//为每个客户端创建一个线程pthread_t tid;if(pthread_create(&tid, NULL, client_recv, &fd) != 0){perror("pthread_create");exit(4);}pthread_detach(tid);}   //  close(fd); //关闭TCP连接,不能在接受数据close(sockfd); //关闭socket,不能再处理客户端的请求//socket用于处理客户端的连接,fd用于处理客户端的消息return 0;

客户端代码:

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>typedef struct Info
{char text[1024];                                                                                                                                int tofd;
}Info;
pthread_t tid[2] = {0};void *send_thread(void *arg)
{int sockfd = *(int *)arg;Info buf;while(1){   scanf("%s%d", buf.text, &buf.tofd);if(send(sockfd, &buf, sizeof(buf), 0) == -1) {perror("send");break;}if(!strcmp(buf.text, "bye"))break;bzero(&buf, sizeof(buf));}}void *recv_thread(void *arg)
{int sockfd = *(int *)arg;Info buf;ssize_t size;while(1){size = recv(sockfd, &buf, sizeof(buf), 0);if(size == -1){perror("recv");break;}else if(size == 0)break; printf("%s\n", buf.text);bzero(&buf, sizeof(buf));}
}       int main()
{           //创建socketint sockfd = socket(AF_INET, SOCK_STREAM, 0);if(-1 == sockfd){perror("socket");exit(1);}//发起连接struct sockaddr_in server_info; // 存储服务器信息bzero(&server_info, 0);server_info.sin_family = AF_INET;  //地址族server_info.sin_port = htons(7000); //绑定端口server_info.sin_addr.s_addr = inet_addr("192.168.175.129"); //绑定ip地址if(connect(sockfd, (struct sockaddr*)&server_info, sizeof(server_info)) == -1){   perror("connect");exit(2);}       //启动2个线程,一个负责发送,一个负责接收if(pthread_create(&tid[0], NULL, send_thread, &sockfd) != 0){       perror("pthread_create");exit(3);}if(pthread_create(&tid[1], NULL, recv_thread, &sockfd) != 0){perror("pthread_create")exit(4);}void *status;pthread_join(tid[0], &status);pthread_join(tid[1], &status);close(sockfd);return 0;
}   

五、UDP服务端

创建UDP服务器的步骤:

  1. 创建socket
  2. 绑定端口、地址等

代码:

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>int main()
{//创建socketint sockfd = socket(AF_INET, SOCK_DGRAM, 0); if(-1 == sockfd){   perror("socket");exit(1);}   //绑定struct sockaddr_in server_info;bzero(&server_info, sizeof(server_info));server_info.sin_family = AF_INET;server_info.sin_port = htons(6000);server_info.sin_addr.s_addr = inet_addr("127.0.0.1");if(bind(sockfd, (struct sockaddr *)&server_info, sizeof(server_info)) == -1) {   perror("bind");exit(2);}   ssize_t size;char buf[1024] = {0};struct sockaddr_in client_info;int len = sizeof(client_info);while(1){size = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&client_info, &len);if(-1 == len){perror("recvfrom");break;}printf("收到 %s : %d 的数据 %s\n", inet_ntoa(client_info.sin_addr), client_info.sin_port, buf);bzero(buf, 1024);}close(sockfd);return 0;
}

六、UDP客户端

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>int main()
{int sockfd = socket(AF_INET, SOCK_DGRAM, 0); if(sockfd == -1) {   perror("socket");exit(1);}   char buf[1024] = {0};struct sockaddr_in server_info;bzero(&server_info, sizeof(server_info));server_info.sin_family = AF_INET;server_info.sin_port = htons(6000);server_info.sin_addr.s_addr = inet_addr("127.0.0.1");while(1){   scanf("%s", buf);ssize_t size = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&server_info, sizeof(server_info));if(-1 == size){perror("send");break;}bzero(buf, 1024);}close(sockfd);return 0;
}

七、高并发服务器select:select 是一个在 C 语言中使用的系统调用,用于实现 I/O 多路复用。它可以同时监视多个文件描述符,以确定它们中是否有可读、可写或异常等事件发生。

使用 select 的一般步骤如下:

  1. 创建并初始化 fd_set 集合,设置要监视的文件描述符。
  2. 调用 select 函数,传入需要监视的文件描述符集合和超时时间。
  3. 检查返回值,判断哪些文件描述符就绪。
  4. 根据就绪的文件描述符进行相应的操作。

select

select函数的原型:

#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

参数说明:

  • nfds:监视的文件描述符的最大值加 1。
  • readfds:指向一个 fd_set,包含要监视可读事件的文件描述符。如果为NULL,代表不监视,下2同。
  • writefds:指向一个 fd_set,包含要监视可写事件的文件描述符。
  • exceptfds:指向一个 fd_set,包含要监视异常事件的文件描述符。
  • timeout:指向一个 struct timeval 结构体,用于设置超时时间。如果为 NULL,则 select 将一直阻塞,直到有事件发生。

返回值:

  • 如果有事件发生或超时,select 返回就绪文件描述符的数量。
  • 如果出错,返回 -1,并设置 errno。

fd_set

概念: fd_set 是一个集合数据结构。它用于在 C 语言中表示一组文件描述符(file descriptor)的集合。
fd_set 是一个位向量(bit vector),用于表示文件描述符的集合。它通常是一个固定大小的数组,每个元素都是一个位字段(bit field),用于表示一个文件描述符的状态。每个位对应一个文件描述符,如果该位为 1,则表示对应的文件描述符在集合中,如果该位为 0,则表示对应的文件描述符不在集合中。

fd_set 的一些常用操作和相关的函数:

  • FD_ZERO(fd_set *set):将 fd_set 初始化为空集合,即清除所有的位。
  • FD_SET(int fd, fd_set *set):将指定的文件描述符 fd 添加到 fd_set 中,即将对应的位设置为 1。
  • FD_CLR(int fd, fd_set *set):从 fd_set 中移除指定的文件描述符 fd,即将对应的位清除为 0。
  • FD_ISSET(int fd, fd_set *set):检查指定的文件描述符 fd 是否在 fd_set 中,即对应的位是否为 1。
  • FD_COPY(fd_set *src, fd_set *dest):复制一个 fd_set,将 src 中的位复制到 dest 中。

代码实例

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <sys/select.h>
#include <sys/time.h>
#include <unistd.h>
#include <string.h>int main()
{int sockfd = socket(AF_INET, SOCK_STREAM, 0); if(-1 == sockfd){   perror("socket");exit(1);}   struct sockaddr_in server_info;bzero(&server_info, sizeof(server_info));server_info.sin_family = AF_INET;server_info.sin_port = htons(7000);server_info.sin_addr.s_addr = inet_addr("192.168.175.129");if(bind(sockfd, (struct sockaddr *)&server_info, sizeof(server_info)) == -1) {   perror("bind");exit(2);}   if(listen(sockfd, 10) == -1) {   perror("listen");exit(3);}printf("等待客户端的连接...\n");//定义两个集合fd_set readset, tmpset;//初始化FD_ZERO(&readset);FD_ZERO(&tmpset);//添加文件描述符FD_SET(sockfd, &readset);int maxfd = sockfd;int fd[1024] = {0};int ret, i;while(1){tmpset = readset;ret = select(maxfd + 1, &tmpset, NULL, NULL, NULL);if(-1 == ret){perror("select");break;}if(FD_ISSET(sockfd, &tmpset)) //判断sockfd是否留在集合中(是否有客户端发起连接){struct sockaddr_in client_info; //用于保存客户端信息int length = sizeof(client_info);//找到一个没有分配的fdfor(i = 0; i < 1024; i++){if(0 == fd[i])break;}fd[i] = accept(sockfd, (struct sockaddr *)&client_info, &length);if(-1 == fd[i]){perror("accept");break;}printf("接受客户端的连接:%d\n", fd[i]);//把新的文件描述符加入集合中FD_SET(fd[i], &readset);//更新最大文件描述符if(maxfd < fd[i])maxfd = fd[i];}else{for(i = 0; i < 1024; i++){if(FD_ISSET(fd[i], &tmpset)){char buf[1024] = {0};ssize_t size;size = recv(fd[i], buf, sizeof(buf), 0);if(size == -1){perror("recv");}else if(size == 0){printf("客户端 %d 退出\n", fd[i]);FD_CLR(fd[i], &readset); // 从集合中删掉close(fd[i]);fd[i] = 0;}elseprintf("%s\n", buf);break;}}}}close(sockfd);return 0;
}

八、高并发服务器epoll

概念: epoll 是一个在 Linux 操作系统上用于高效事件通知的 I/O 多路复用机制。它是一种替代传统的 select 和 poll 函数的方法,可以在大规模并发连接的网络服务器中提供更高的性能。

使用 epoll 的一般步骤如下:

  1. 创建一个 epoll 实例:
int epoll_create(int size);

这个函数会返回一个文件描述符,用于后续的 epoll 操作。

  1. 向 epoll 实例中添加文件描述符:
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

epfd 是 epoll 实例的文件描述符,op 是操作类型(如添加、修改或删除),fd 是要添加的文件描述符,event 是一个 epoll_event 结构体,用于指定事件类型和关联的数据

  1. 等待事件的发生:
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

epfd 是 epoll 实例的文件描述符,events 是一个数组,用于存储发生的事件,maxevents 是数组的大小,timeout 是等待的超时时间(-1 表示无限等待)。

  1. 处理发生的事件:
    遍历 epoll_wait 返回的事件数组,根据事件类型进行相应的处理。

  2. 关闭 epoll 实例

int close(int fd);

1.epoll_create

概念: epoll_create 函数用于创建一个 epoll 实例,并返回一个文件描述符,以便后续的 epoll 操作。
它的函数原型如下:

int epoll_create(int size);

参数说明:

  • size:指定 epoll 实例的大小,它是一个整数,通常可以设置为大于 0 的任意值。这个参数在较早的内核版本中被忽略,可以将其设置为 0。

返回值:

  • 如果成功,返回一个非负整数,表示 epoll 实例的文件描述符(epoll 文件描述符)。
  • 如果出错,返回 -1,并设置 errno。

2.epoll_event

epoll_event 是一个结构体类型,在 Linux 中使用 epoll 进行 I/O 多路复用时,用于表示事件的数据结构。
它的定义如下:

struct epoll_event {uint32_t events;  // 表示事件类型的位掩码epoll_data_t data;  // 与事件相关的用户数据
};
  • events:一个无符号 32 位整数,用于表示事件类型的位掩码。可以使用以下常量进行设置:

    • EPOLLIN:表示可读事件(有数据可读)。
    • EPOLLOUT:表示可写事件(可以写入数据)。
    • EPOLLRDHUP:表示对端关闭连接或关闭了写入一半的连接。
    • EPOLLERR:表示错误事件。
    • EPOLLHUP:表示挂起事件(连接被挂起)。
    • EPOLLET:使用边缘触发模式(Edge-Triggered)。
    • EPOLLONESHOT:一次性事件,只会触发一次。
    • 默认水平触发
  • data:一个 epoll_data_t 类型的联合体,用于存储与事件相关的用户数据。epoll_data_t 的定义如下:

typedef union epoll_data {void *ptr;  // 指针类型的用户数据int fd;     // 文件描述符类型的用户数据uint32_t u32;  // 32 位无符号整数类型的用户数据uint64_t u64;  // 64 位无符号整数类型的用户数据
} epoll_data_t;

3.epoll_ctl

epoll_ctl 是 Linux 中使用 epoll 进行事件注册和控制的系统调用。它用于向 epoll 实例中添加、修改或删除事件。
epoll_ctl 的原型如下:

#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

参数说明:

  • epfd:epoll 实例的文件描述符(即 epoll 文件描述符),通过 epoll_create 创建并返回。
  • op:操作类型,可以是以下值之一:
    • EPOLL_CTL_ADD:向 epoll 实例中添加事件。
    • EPOLL_CTL_MOD:修改 epoll 实例中的事件。
    • EPOLL_CTL_DEL:从 epoll 实例中删除事件。
  • fd:要操作的文件描述符,用于指定要添加、修改或删除的事件所属的文件描述符。
  • event:指向 struct epoll_event 结构体的指针,用于指定要添加、修改或删除的事件的详细信息。

返回值:

  • 如果成功,返回 0。
  • 如果出错,返回 -1,并设置 errno。

4.epoll_wait

epoll_wait 是 Linux 中使用 epoll 进行事件等待和处理的系统调用。它用于等待 epoll 实例中注册的事件发生,并返回就绪的事件信息。
epoll_wait 的原型如下:

#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

参数说明:

  • epfd:epoll 实例的文件描述符(即 epoll 文件描述符),通过 epoll_create 创建并返回。
  • events:指向 struct epoll_event 数组的指针,用于存储就绪的事件信息。
  • maxevents:events 数组的大小,即最多可以存储多少个事件信息。
  • timeout:等待的超时时间(以毫秒为单位),可以设置为以下值之一:
    • -1:无限等待,直到有事件发生。
    • 0:立即返回,不等待任何事件。
    • 大于 0:等待指定的毫秒数后返回,如果没有事件发生则超时。

返回值:

  • 如果成功,返回就绪的事件数量。
  • 如果超时,返回 0。
  • 如果出错,返回 -1,并设置 errno。

5.代码实例

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/epoll.h>int main()
{int sockfd = socket(AF_INET, SOCK_STREAM, 0);if(-1 == sockfd){perror("socket");exit(1);}struct sockaddr_in server_info;bzero(&server_info, sizeof(server_info));server_info.sin_family = AF_INET;server_info.sin_port = htons(7000);server_info.sin_addr.s_addr = inet_addr("192.168.175.129");if(bind(sockfd, (struct sockaddr *)&server_info, sizeof(server_info)) == -1){perror("bind");exit(2);}if(listen(sockfd, 10) == -1){perror("listen");exit(3);}printf("等待客户端连接...\n");//创建epoll对象(创建集合)int epfd = epoll_create(1);if(-1 == epfd){perror("epoll_create");exit(4);}//把sockfd封装成事件,添加到集合中struct epoll_event ev;ev.events = EPOLLIN | EPOLLET; //事件属性,边缘触发ev.data.fd = sockfd;if(epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev) == -1){perror("epoll_ctl");exit(5);}while(1){struct epoll_event events[10];int num = epoll_wait(epfd, events, 10, -1);if(-1 == num){perror("epoll_wait");break;}for(int i = 0; i < num; i++){if(events[i].data.fd == sockfd) //有客户端发起连接{struct sockaddr_in server_info;int length = sizeof(server_info);int fd = accept(sockfd, (struct sockaddr *)&server_info, &length);if(-1 == fd){perror("accept");break;}printf("客户端 %d 连接成功\n", fd);ev.events = EPOLLIN; //默认水平触发ev.data.fd = fd;if(epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev) == -1){perror("epoll_ctl");break;}}else{char buf[1024] = {0};ssize_t size;size = recv(events[i].data.fd, buf, sizeof(buf), 0);if(size == -1)perror("recv");else if(size == 0){if(epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, &events[i]) == -1){perror("epoll_ctl");exit(5);}close(events[i].data.fd);printf("客户端 %d 退出\n", events[i].data.fd);}elseprintf("%s\n", buf);}}}close(sockfd);return 0;
}

九、并发服务器的总结

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/255826.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

小游戏和GUI编程(3) | 基于 SFML 的字符阵

小游戏和GUI编程(3) | 基于 SFML 的字符阵 1. 简介 使用 EasyX 图形库时&#xff0c; 官方第一个例子是字符阵。 EasyX 不开源&#xff0c; 也不能跨平台&#xff0c; API 陈旧&#xff0c; API 是 C 而不是 C。 现在使用 SFML 来实现字符阵&#xff0c; 克服 EasyX 的这些问…

MySQL温故篇(一)SQL语句基础

一、SQL语句基础 数据库&#xff08;SQL&#xff09;思维导图_数据库设计思维导图-CSDN博客 1、SQL语言分类 DDL&#xff1a;数据定义语言 DCL&#xff1a;数据控制语言 DML&#xff1a;数据操作语言 DQL&#xff1a;数据的查询语言 2、数据类型 3、字符类型 char(11) &…

蓝桥杯Web应用开发-CSS3 新特性

CSS3 新特性 专栏持续更新中 在前面我们已经学习了元素选择器、id 选择器和类选择器&#xff0c;我们可以通过标签名、id 名、类名给指定元素设置样式。 现在我们继续选择器之旅&#xff0c;学习 CSS3 中新增的三类选择器&#xff0c;分别是&#xff1a; • 属性选择器 • 子…

《CSS 简易速速上手小册》第8章:CSS 性能优化和可访问性(2024 最新版)

文章目录 8.1 CSS 文件的组织和管理8.1.1 基础知识8.1.2 重点案例&#xff1a;项目样式表结构8.1.3 拓展案例 1&#xff1a;使用BEM命名规范8.1.4 拓展案例 2&#xff1a;利用 Sass 混入创建响应式工具类 8.2 提高网页加载速度的技巧8.2.1 基础知识8.2.2 重点案例&#xff1a;图…

C#,泰波拿契数(Tribonacci Number)的算法与源代码

1 泰波拿契数&#xff08;Tribonacci Number&#xff09; 泰波拿契数&#xff08;Tribonacci Number&#xff09;是斐波那契的拓展。 泰波拿契数 (Tribonacci Number) 即把费波拿契数 (Fibonacci Number) 的概念推广至三个数。 2 计算结果 3 源程序 using System; namespace…

Java安全 CC链1分析(Lazymap类)

Java安全 CC链1分析 前言CC链分析CC链1核心LazyMap类AnnotationInvocationHandler类 完整exp&#xff1a; 前言 在看这篇文章前&#xff0c;可以看下我的上一篇文章&#xff0c;了解下cc链1的核心与环境配置 Java安全 CC链1分析 前面我们已经讲过了CC链1的核心ChainedTransf…

[C/C++] -- Boost库、Muduo库编译安装使用

1.Muduo库 Muduo 是一个基于 C11 的高性能网络库&#xff0c;其核心是事件驱动、非阻塞 I/O、线程池等技术&#xff0c;以实现高并发、高性能的网络通信。Muduo 库主要由陈硕先生开发维护&#xff0c;已经成为 C 服务器程序员的常用工具之一。 Muduo 库的主要特点&#xff1a…

M1 Mac使用SquareLine-Studio进行LVGL开发

背景 使用Gui-Guider开发遇到一些问题&#xff0c;比如组件不全。使用LVGL官方的设计软件开发 延续上一篇使用的基本环境。 LVGL项目 新建项目 选择Arduino的项目&#xff0c;设定好分辨率及颜色。 设计UI 导出代码 Export -> Create Template Project 导出文件如图…

使用client-only 解决组件不兼容SSR问题

目录 前言 一、解决方案 1.基于Nuxt 框架的SSR应用 2.基于vue2框架的应用 3.基于vue3框架的应用 二、总结 往期回顾 前言 最近在我的单页面SSR应用上开发JSON编辑器功能&#xff0c;在引入组件后直接客户端跳转OK&#xff0c;但是在直接加载服务端渲染的时候一直报这…

【数据分析】Excel中的常用函数公式总结

目录 0 引用方式0.1 相对引用0.2 绝对引用0.3 混合引用0.4 3D引用0.5 命名引用 1 基础函数1.1 加法、减法、乘法和除法1.2 平均数1.3 求和1.4 最大值和最小值 2 文本函数2.1 合并单元格内容2.2 查找2.3 替换 3 逻辑函数3.1 IF函数3.2 AND和OR函数3.3 IFERROR函数 4 统计函数4.1…

Visual Studio使用Git忽略不想上传到远程仓库的文件

前言 作为一个.NET开发者而言&#xff0c;有着宇宙最强IDE&#xff1a;Visual Studio加持&#xff0c;让我们的开发效率得到了更好的提升。我们不需要担心环境变量的配置和其他代码管理工具&#xff0c;因为Visual Studio有着众多的拓展工具。废话不多说&#xff0c;直接进入正…

吉他学习:右手拨弦方法,右手拨弦训练 左手按弦方法

第六课 右手拨弦方法https://m.lizhiweike.com/lecture2/29362775 第七课 右手拨弦训练https://m.lizhiweike.com/lecture2/29362708

阿里云服务器“带宽计费模式”怎么选?有啥区别?

阿里云服务器带宽计费模式分为“按固定带宽”和“按使用流量”&#xff0c;有什么区别&#xff1f;按固定带宽是指直接购买多少M带宽&#xff0c;比如1M、5M、10M、100M等&#xff0c;阿里云直接分配用户所购买的带宽值&#xff0c;根据带宽大小先付费再使用&#xff1b;按使用…

qt-C++笔记之判断一个QLabel上有没有load图片

qt-C笔记之判断一个QLabel上有没有load图片 code review! 在Qt框架中&#xff0c;QLabel是用来显示文本或者图片的一个控件。如果你想判断一个QLabel控件上是否加载了图片&#xff0c;你可以检查它的pixmap属性。pixmap属性会返回一个QPixmap对象&#xff0c;如果没有图片被加…

【C语言初阶-结构体】关于结构体的声明定义、结构体传参详解

目录 1. 结构体的声明 1.1 结构的基础知识 1.2 结构的声明 1.3 结构成员的类型 1.4 结构体变量的定义和初始化 2. 结构体成员的访问 2.1(.)操作符 2.2&#xff08;->&#xff09;操作符 3.结构体传参 1. 结构体的声明 1.1 结构的基础知识 结构体是一些值的集合&…

S32 Design Studio的PE工具

S32 Design Studio软件是NXP公司专门为了方便用户开发S32K1系列芯片的IDE&#xff0c;跟Eclipse比较像。里面有个配套的图形工具Processor Expert&#xff0c;会产生一个后缀名为pe的文件&#xff0c;跟ST的cubemx作用类似。 双击pe文件即可打开pe界面&#xff0c;生成的文件将…

拓展边界:前端世界的跨域挑战

目录 什么是跨域 概念 同源策略及限制内容 常见跨域场景 如何解决跨域 CORS Nginx代理跨域 Node中间件代理跨域 WebSocket postMessage JSONP 其他 什么是跨域 概念 在此之前&#xff0c;我们了解一下一个域名地址的组成&#xff1a; 跨域指的是在网络安全中&…

2.6日学习打卡----初学RabbitMQ(一)

2.6日学习打卡 初识RabbitMQ、 一. MQ 消息队列 MQ全称Message Queue&#xff08;消息队列&#xff09;&#xff0c;是在消息的传输过程中保 存消息的容器。多用于系统之间的异步通信。 同步通信相当于两个人当面对话&#xff0c;你一言我一语。必须及时回复 异步通信相当于通…

数据库管理-第148期 最强Oracle监控EMCC深入使用-05(20240208)

数据库管理148期 2024-02-08 数据库管理-第148期 最强Oracle监控EMCC深入使用-05&#xff08;20240208&#xff09;1 性能主页2 ADDM Spotlight3 实时ADDM4 数据库的其他5 主机总结 数据库管理-第148期 最强Oracle监控EMCC深入使用-05&#xff08;20240208&#xff09; 作者&am…

【算法】{画决策树 + dfs + 递归 + 回溯 + 剪枝} 解决排列、子集问题(C++)

文章目录 1. 前言2. 算法例题 理解思路、代码46.全排列78.子集 3. 算法题练习1863.找出所有子集的异或总和再求和47.全排列II17.电话号码的字母组合 1. 前言 dfs问题 我们已经学过&#xff0c;对于排列、子集类的问题&#xff0c;一般可以想到暴力枚举&#xff0c;但此类问题用…