当前位置: 首页 > 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…...

CSP-201912-1-报数

CSP-201912-1-报数 知识点总结 整数转化为字符串#include <string> string str_num to_string(num);字符串中查找是否包含字符‘7’&#xff1a;str_num.find(7) 未找到返回-1找到返回返回该字符在字符串中的位置&#xff08;即第一次出现的索引位置&#xff09; #i…...

前后端分离好处多多,怕就怕分工不分人,哈哈

前后端分离倡导多年了&#xff0c;现在基本成为了开发的主流模式了&#xff0c;贝格前端工场承接的前端项目只要不考虑seo的&#xff0c;都采用前后端分离模式&#xff0c;这篇文章就来介绍一下前后端分离模式。 一、什么是前后端分离开发模式 前后端分离是一种软件开发的架构…...

机器学习:Softmax介绍及代码实现

Softmax原理 Softmax函数用于将分类结果归一化&#xff0c;形成一个概率分布。作用类似于二分类中的Sigmoid函数。 对于一个k维向量z&#xff0c;我们想把这个结果转换为一个k个类别的概率分布p(z)。softmax可以用于实现上述结果&#xff0c;具体计算公式为&#xff1a; 对于…...

python基于flask的网上订餐系统769b9-django+vue

课题主要分为两大模块&#xff1a;即管理员模块和用户模块&#xff0c;主要功能包括个人中心、用户管理、菜品类型管理、菜品信息管理、留言反馈、在线交流、系统管理、订单管理等&#xff1b; 如果用户想要交换信息&#xff0c;他们需要满足双方交换信息的需要。由于时间有限…...

jenkins 发布远程服务器并部署项目

安装参考另一个文章 配置maven 和 jdk 和 git 注意jdk的安装目录&#xff0c;是jenkins 安装所在服务器的jdk目录 注意maven的目录 是jenkins 安装所在服务器的maven目录 注意git的目录 是jenkins 安装所在服务器的 git 目录 安装 Publish Over SSH 插件 配置远程服务器 创…...

【数学建模】【2024年】【第40届】【MCM/ICM】【D题 五大湖的水位控制问题】【解题思路】

一、题目 &#xff08;一&#xff09; 赛题原文 2024 ICM Problem D: Great Lakes Water Problem Background The Great Lakes of the United States and Canada are the largest group of freshwater lakes in the world. The five lakes and connecting waterways const…...

【开源】JAVA+Vue+SpringBoot实现公司货物订单管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 客户管理模块2.2 商品维护模块2.3 供应商管理模块2.4 订单管理模块 三、系统展示四、核心代码4.1 查询供应商信息4.2 新增商品信息4.3 查询客户信息4.4 新增订单信息4.5 添加跟进子订单 五、免责说明 一、摘要 1.1 项目…...

###C语言程序设计-----C语言学习(12)#进制间转换,十进制,二进制,八进制,十六进制

前言&#xff1a;感谢您的关注哦&#xff0c;我会持续更新编程相关知识&#xff0c;愿您在这里有所收获。如果有任何问题&#xff0c;欢迎沟通交流&#xff01;期待与您在学习编程的道路上共同进步。 计算机处理的所有信息都以二进制形式表示&#xff0c;即数据的存储和计算都采…...

锐捷设备常用命令

一、命令模式 命令行主要有用户模式、特权模式、全局模式、VLAN模式、接口模式、线程模式 switch> "用户模式"switch# "特权模式"switch(config) "全局模式"switch(conf…...

python:lxml 读目录.txt文件,用 xmltodict 转换为json数据,生成jstree所需的文件

请参阅&#xff1a;java : pdfbox 读取 PDF文件内书签 请注意&#xff1a;书的目录.txt 编码&#xff1a;UTF-8&#xff0c;推荐用 Notepad 转换编码。 pip install lxml ; lxml-5.1.0-cp310-cp310-win_amd64.whl (3.9 MB) pip install xmltodict ; lxml 读目录.txt文件&…...

【Spring】Spring 对 Ioc 的实现

一、Ioc 控制反转 控制反转是一种思想 控制反转是为了降低程序耦合度&#xff0c;提高程序扩展力&#xff0c;达到 OCP 原则&#xff0c;达到 DIP 原则 控制反转&#xff0c;反转的是什么&#xff1f; 将对象的创建权利交出去&#xff0c;交给第三方容器负责 将对象和对象之…...

QT学习文件操作类 QFile

&#xff08;一&#xff09;QFile QFile 是 Qt 框架中用于文件处理的一个类。它提供了读取和写入文件的功能&#xff0c;支持文本和二进制文件。QFile 继承自 QIODevice &#xff0c;因此它可以像其他 IO 设备一样使用。 &#xff08;1&#xff09;主要功能 1. 文件读写…...

VOL_常用记录!!

目录 前端1.js如何获取当前时间(yy-MM-dd HH:MM:SS)2.http请求3.grid扩展js常用 后端1.待补充 前端 1.js如何获取当前时间(yy-MM-dd HH:MM:SS) getCurrentTime() {const now new Date();return ${now.getFullYear()}-${(now.getMonth() 1).toString().padStart(2, "0&…...

解决Typora导出HTML不显示图片

解决Typora导出HTML不显示图片 产生原因 Typora导出HTML不显示图片&#xff0c;可能时图片存放在我们的硬盘中。 我们可以将markdown中的图片转化为base64格式&#xff0c;嵌入到html中。 解决步骤 首先&#xff0c;下载 TyporaToBase64.jar 密码:45jq 其次&#xff0c;将…...

React Native开发iOS实战录

文章目录 背景环境准备主要工具xcode安装安装CocoaPods 基本步骤采用Expo go运行iOS模拟器运行安装在真机上测试发布到苹果商店 常见问题ruby3在macOS上编译失败import of module ‘glog.glog.log_severity’ appears within namespace ‘google’yarn网络问题pod安装失败unabl…...

C++局部变量与全局变量

在C中&#xff0c;可以为函数的参数指定默认值。这样做的好处是在调用函数时&#xff0c;如果没有提供对应的参数&#xff0c;那么将会使用默认值。 下面是一个求2个或3个数中最大数的函数的示例&#xff0c;其中使用了默认参数&#xff1a; #include <iostream> using…...

深入理解ES的倒排索引

目录 数据写入过程 词项字典 term dictionary 倒排表 posting list FOR算法 RBM算法 ArrayContainer BitMapContainer 词项索引 term index 在Elasticsearch中&#xff0c;倒排索引的设计无疑是惊为天人的&#xff0c;下面看下倒排索引的结构。 倒排索引分为词项索引【…...

HTML世界之第一重天

一、HTML 元素 注&#xff1a;HTML 文档由 HTML 元素定义。 1.HTML 元素 开始标签 * 元素内容 结束标签 * <p> 这是一个段落 </p> <a href"default.htm"> 这是一个链接 </a> <br> 换行 开始标签常被称为起始标签&…...

docker run报 docker: Error response from daemon: no command specified.

docker run报 docker: Error response from daemon: no command specified. 1. export出mysql的container为tar, 拷贝到另一台虚拟机, import该tar为image, docker run该image时报 docker: Error response from daemon: no command specified. 时间240211 export出mysql的con…...

vue3 之 商城项目—详情页

整体认识 路由配置 准备组件模版 <script setup></script><template><div class"xtx-goods-page"><div class"container"><div class"bread-container"><el-breadcrumb separator">">&…...