当前位置: 首页 > news >正文

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;
}

九、并发服务器的总结

在这里插入图片描述

相关文章:

Linux下的socket操作

一、TCP服务端 创建一个TCP服务器的基本操作&#xff1a; 创建一个套接字&#xff08;socket&#xff09;&#xff1a;使用socket函数绑定套接字&#xff08;socket&#xff09;:将套接字绑定到一个特定的IP地址和端口号上&#xff0c;这些信息要用结构体sockaddr_in来保存监…...

爬虫练习——动态网页的爬取(股票和百度翻译)

动态网页也是字面意思&#xff1a;实时更新的那种 还有就是你在股票这个网站上&#xff0c;翻页。他的地址是不变的 是动态的加载&#xff0c;真正我不太清楚&#xff0c;只知道他是不变的。如果用静态网页的方法就不可行了。 静态网页的翻页&#xff0c;是网址是有规律的。 …...

Name or service not known问题解决和分析过程解析

目 录 一、问题描述 二、问题查处过程 &#xff08;一&#xff09;为何不能识别到bogon &#xff08;二&#xff09;为何会出现bogon &#xff08;三&#xff09;能不能更改bogon &#xff08;四&#xff09;能识别其他host的名字 三、问题分析 四、问题解决 …...

emmet语法

一.html $排序 直接.dem或#two是默认div 内容可写{}里 二.css 直接写首字母 三.格式化 一次&#xff08;右键格式化&#xff09; 永久...

【PTA主观题】8-1 文件操作

题目要求 编写函数int input(FILE * fp)&#xff0c;录入学生的信息&#xff0c;自定义录入结束方式&#xff0c;但至少包括学号、姓名、班级、分数和登录密码&#xff0c;并按照学号排序后以二进制方式存入stus.dat&#xff0c;函数返回学生数量&#xff1b;定义函数void enc…...

机器学习算法决策树

决策树的介绍 决策树是一种常见的分类模型&#xff0c;在金融风控、医疗辅助诊断等诸多行业具有较为广泛的应用。决策树的核心思想是基于树结构对数据进行划分&#xff0c;这种思想是人类处理问题时的本能方法。例如在婚恋市场中&#xff0c;女方通常会先询问男方是否有房产&a…...

ssh和sftp服务分离

目录 一、增加sftp的deamon二、增加sftp的service三、其他配套文件四、修改配置文件五、分别重启两个服务&#xff1a; 由于安全需要&#xff0c;客户这边想把sftp使用的端口与ssh使用的端口分开。 我们知道sftp没有自己的服务器守护进程&#xff0c;它需要依赖sshd守护进程来…...

Bootstrap学习三

Bootstrap学习三 文章目录 前言四、Bootstrap插件4.1. 插件概览4.1.1. data属性4.1.2. 编程方式的API4.1.3. 避免命名空间冲突4.1.4. 事件 4.2. 模态框4.2.1. 引入4.2.2. 基本结构4.2.3. 基本使用4.2.4. 触发模态框的方法 4.3. 下拉菜单和滚动监听4.3.1. 下拉菜单4.3.2. 滚动监…...

第77讲用户管理功能实现

用户管理功能实现 前端&#xff1a; views/user/index.vue <template><el-card><el-row :gutter"20" class"header"><el-col :span"7"><el-input placeholder"请输入用户昵称..." clearable v-model"…...

锐捷(十九)锐捷设备的接入安全

1、PC1的IP地址和mac地址做全局静态ARP绑定; 全局下&#xff1a;address-bind 192.168.1.1 mac&#xff08;pc1&#xff09; G0/2:ip verify source port-securityarp-check 2、PC2的IP地址和MAC地址做全局IPMAC绑定&#xff1a; Address-bind 192.168.1.2 0050.7966.6807Ad…...

【MySQL题】——基础概念论述(二)

&#x1f383;个人专栏&#xff1a; &#x1f42c; 算法设计与分析&#xff1a;算法设计与分析_IT闫的博客-CSDN博客 &#x1f433;Java基础&#xff1a;Java基础_IT闫的博客-CSDN博客 &#x1f40b;c语言&#xff1a;c语言_IT闫的博客-CSDN博客 &#x1f41f;MySQL&#xff1a…...

Spring Boot + flowable 快速实现工作流

背景 使用flowable自带的flowable-ui制作流程图 使用springboot开发流程使用的接口完成流程的业务功能 文章来源&#xff1a;https://blog.csdn.net/zhan107876/article/details/120815560 一、flowable-ui部署运行 flowable-6.6.0 运行 官方demo 参考文档&#xff1a; htt…...

(已解决)LaTeX Error: File `svproc.cls‘ not found. (用Springer LNCS 会议Proceedings模板)

会议要求使用LNCS模板&#xff0c;并给了获取模板链接&#xff1a;https://www.springer.com/gp/authors-editors/conference-proceedings/conference-proceedings-guidelines。我在里面下载了latex模板之后&#xff0c;编译那个author.tex发现抱错&#xff1a; 解决办法&#…...

Spring Boot 自定义指标

Spring Boot 自定义指标 阅读本文需要对一些前置技术有所了解,下面列出的一些前置技术是必须要了解的。 Prometheus:这是一个时序数据库,我们的指标数据一般保存在这个数据库中。Grafana:借助Grafana可以将Prometheus中的数据以图表的方式展示出来。Micrometer:是一个用于…...

安全的接口访问策略

渗透测试 一、Token与签名 一般客户端和服务端的设计过程中&#xff0c;大部分分为有状态和无状态接口。 一般用户登录状态下&#xff0c;判断用户是否有权限或者能否请求接口&#xff0c;都是根据用户登录成功后&#xff0c;服务端授予的token进行控制的。 但并不是说有了tok…...

最佳视频转换器软件:2024年视频格式转换的选择

我们生活在一个充满数字视频的世界&#xff0c;但提供的内容远不止您最喜欢的流媒体服务目录。虽然我们深受喜爱的设备在播放各种自制和下载的视频文件方面变得越来越好&#xff0c;但在很多情况下您都需要从一种格式转换为另一种格式。 经过大量测试&#xff0c; 我们尝试过…...

深入理解 Nginx 插件及功能优化指南

深入理解 Nginx 插件及功能优化指南 深入理解 Nginx 插件及功能优化指南1. Nginx 插件介绍1.1 HTTP 模块插件ngx_http_rewrite_modulengx_http_access_module 1.2 过滤器插件ngx_http_gzip_modulengx_http_ssl_module 1.3 负载均衡插件ngx_http_upstream_modulengx_http_upstre…...

鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之Blank组件

鸿蒙&#xff08;HarmonyOS&#xff09;项目方舟框架&#xff08;ArkUI&#xff09;之Blank组件 一、操作环境 操作系统: Windows 10 专业版、IDE:DevEco Studio 3.1、SDK:HarmonyOS 3.1 二、Blank组件 空白填充组件&#xff0c;在容器主轴方向上&#xff0c;空白填充组件具…...

InternLM大模型实战-4.XTuner大模型低成本微调实战

文章目录 前言笔记正文XTuner支持模型和数据集 微调原理跟随文档学习快速上手自定义微调准备数据准备配置文件 MS-Agent微调 前言 本文是对于InternLM全链路开源体系系列课程的学习笔记。【XTuner 大模型单卡低成本微调实战】 https://www.bilibili.com/video/BV1yK4y1B75J/?…...

【SpringBoot篇】解决Redis分布式锁的 误删问题 和 原子性问题

文章目录 &#x1f354;Redis的分布式锁&#x1f6f8;误删问题&#x1f388;解决方法&#x1f50e;代码实现 &#x1f6f8;原子性问题&#x1f339;Lua脚本 ⭐利用Java代码调用Lua脚本改造分布式锁&#x1f50e;代码实现 &#x1f354;Redis的分布式锁 Redis的分布式锁是通过利…...

蓝桥杯Web应用开发-CSS3 新特性【练习三:文本阴影】

文本阴影 text-shadow 属性 给文本内容添加阴影的效果。 文本阴影的语法格式如下&#xff1a; text-shadow: x-offset y-offset blur color;• x-offset 是沿 x 轴方向的偏移距离&#xff0c;允许负值&#xff0c;必须参数。 • y-offset 是沿 y 轴方向的偏移距离&#xff0c…...

LRU缓存

有人从网络读数据&#xff0c;有人从磁盘读数据&#xff0c;机智的人懂得合理利用缓存加速数据的读取效率&#xff0c;提升程序的性能&#xff0c;搏得上司的赏识&#xff0c;赢得白富美的青睐&#xff0c;进一步走向人生巅峰~ LRU假说 LRU缓存&#xff08;Least Recently Used…...

ncc匹配提速总结

我们ncc最原始的匹配方法是&#xff1a;学习模板w*h个像素都要带入ncc公式计算 第一种提速&#xff0c;学习模板是w*h&#xff0c;而我们支取其中的w/2*h/2,匹配窗口同理&#xff0c;计算量只有1/4。 另外一种因为ncc是线性匹配&#xff0c;我们在这上面也做了文章&#xff0…...

人力资源智能化管理项目(day06:员工管理)

学习源码可以看我的个人前端学习笔记 (github.com):qdxzw/humanResourceIntelligentManagementProject 页面结构 <template><div class"container"><div class"app-container"><div class"left"><el-input style&qu…...

Java实现数据可视化的智慧河南大屏 JAVA+Vue+SpringBoot+MySQL

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、系统展示四、核心代码4.1 数据模块 A4.2 数据模块 B4.3 数据模块 C4.4 数据模块 D4.5 数据模块 E 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVueSpringBootMySQL的数据可视化的智慧河南大屏&#xff0c;包含了GDP、…...

【Flink】FlinkSQL的DataGen连接器(测试利器)

简介 我们在实际开发过程中可以使用FlinkSQL的DataGen连接器实现FlinkSQL的批或者流模拟数据生成,DataGen 连接器允许按数据生成规则进行读取,但注意:DataGen连接器不支持复杂类型: Array,Map,Row。 请用计算列构造这些类型 创建有界DataGen表 CREATE TABLE test ( a…...

5G NR 频率计算

5G中引入了频率栅格的概念&#xff0c;也就是小区中心频点和SSB的频域位置不能随意配置&#xff0c;必须满足一定规律&#xff0c;主要目的是为了UE能快速的搜索小区&#xff1b;其中三个最重要的概念是Channel raster 、synchronization raster和pointA。 1、Channel raster …...

关于物理机ping不通虚拟机问题

方法一 设置虚拟机处于桥接状态即可&#xff1a;&#xff08;虚拟机->设置->网络适配器&#xff09;&#xff0c;选择完确定&#xff0c;重启虚拟机即可。 方法二 如果以上配置还是无法ping通&#xff1a;&#xff08;编辑->虚拟网络编辑器&#xff09; 首先查看主机网…...

深度学习在知识图谱问答中的革新与挑战

目录 前言1 背景知识2 基于深度学习改进问句解析模型2.1 谓词匹配2.2 问句解析2.3 逐步生成查询图 3 基于深度学习的端到端模型3.1 端到端框架3.2 简单嵌入技术 4 优势4.1 深入的问题表示4.2 实体关系表示深挖4.3 候选答案排序效果好 5 挑战5.1 依赖大量训练语料5.2 推理类问句…...

JAVA设计模式之职责链模式详解

职责链模式 1 职责链模式介绍 职责链模式(chain of responsibility pattern) 定义: 避免将一个请求的发送者与接收者耦合在一起,让多个对象都有机会处理请求.将接收请求的对象连接成一条链,并且沿着这条链传递请求,直到有一个对象能够处理它为止. 在职责链模式中&#xff0c…...