TCP 套接字--服务器相关

1.创建 TCP 套接字

int server_sockfd = socket(AF_INET,SOCK_STREAM, 0);

函数原型:

#include <sys/socket.h>

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

domain协议族(地址族)AF_INET(IPv4)
type套接字类型SOCK_STREAM(TCP)
protocol协议类型0(自动选择)
(1) domain(协议族)
  • AF_INET:IPv4 地址族(最常用)。
  • AF_INET6:IPv6 地址族。
  • AF_UNIX(或 AF_LOCAL):本地进程间通信(UNIX 域套接字)。
(2) type(套接字类型)
  • SOCK_STREAM
    • 面向连接的 TCP 套接字
    • 提供可靠、双向、基于字节流的通信。
  • SOCK_DGRAM
    • 无连接的 UDP 套接字
    • 提供不可靠、无边界的数据报服务。
  • SOCK_RAW
    • 原始套接字,用于自定义协议(如 ICMP)。
(3) protocol(协议类型)
  • 0
    • 让系统自动选择与 domain 和 type 匹配的协议。
    • 对于 AF_INET + SOCK_STREAM,默认选择 TCP
  • 其他常见值:
    • IPPROTO_TCP(显式指定 TCP,但通常用 0 即可)。
    • IPPROTO_UDP(用于 SOCK_DGRAM)。

 

返回值

  • 成功:返回一个 非负整数,即 套接字描述符server_sockfd)。
  • 失败:返回 -1,并设置 errno(如 EMFILEENFILEEACCES 等)。

2.设置套接字选项 SO_REUSEADDR 的标准用法,用于控制套接字的行为

setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) 

  • 作用
    • 允许套接字(sockfd)绑定到 处于 TIME_WAIT 状态的地址(例如服务器重启时)。
    • 避免 bind() 失败(EADDRINUSE 错误)。
  • 参数说明
    • sockfd:目标套接字描述符。
    • SOL_SOCKET:表示操作套接字层选项(通用选项)。
    • SO_REUSEADDR:选项名称,允许地址重用。
    • &reuse:指向选项值的指针(int 类型,1 启用,0 禁用)。
    • sizeof(reuse):选项值的大小。

3.TCP 服务器绑定(bind())操作 

struct sockaddr_in server_sockaddr;server_sockaddr.sin_family = AF_INET; // IPv4 协议族server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 绑定到所有本地接口(0.0.0.0) server_sockaddr.sin_port = htons(voice_SysParameter.port); // 绑定到指定端口(网络字节序)// 绑定套接字 
if (bind(server_sockfd, (struct sockaddr *)&server_sockaddr, sizeof(server_sockaddr)) == -1) 
{perror("bind"); // 输出错误信息 goto voice_tcp_Thread_TaskProcError; // 错误处理}
  • 关键点
    • AF_INET:使用 IPv4 协议。
    • INADDR_ANY:绑定到所有本地网络接口(即 0.0.0.0),允许通过任何本地 IP 访问服务。
    • htons() 和 htonl():将主机字节序(小端或大端)转换为网络字节序(大端)。
    • bind() 返回值:成功返回 0,失败返回 -1 并设置 errno

4.TCP 连接超时检测与清理  

for (int i = 0; i < voice_MaxConnectNum; i++) {// 检查条件:// 1. ConnfdCurTime[i] > 0(记录过心跳时间)// 2. 当前时间 - 最后一次心跳时间 > 允许的心跳超时时间(voice_SysParameter.heartBeat)// 3. Connfd[i] > 0(连接有效)if ((ConnfdCurTime[i] > 0) && (ConnfdCurTime[i] + voice_SysParameter.heartBeat < HeartBeatCnt) && (Connfd[i] > 0)) {struct sockaddr_in sa = {0};int len = sizeof(sa);// 获取客户端的 IP 地址和端口信息getpeername(Connfd[i], (struct sockaddr *)&sa, &len);// 关闭超时连接,并打印日志(连接描述符 + 客户端 IP)close(Connfd[i]);printf("Connfd Tcp__closed=%d %s\n", Connfd[i], inet_ntoa(sa.sin_addr));// 重置连接状态Connfd[i] = 0;ConnfdCurTime[i] = 0;}
}

5.检查所有已记录的子设备(SubDev)状态

for (int j = 0; j < voice_SysParameter.connectNo; j++) {// 如果子设备的 IP 地址为空,跳过处理if (strcmp(voice_SysParameter.SubDevState.SubDevState[j].ip, "") == 0) continue;int i = 0;// 遍历所有 TCP 连接,检查是否有连接匹配当前子设备的 IPfor (i = 0; i < voice_MaxConnectNum; i++) {if (Connfd[i] == 0) continue; // 跳过无效连接(Connfd[i]=0 表示空闲)struct sockaddr_in sa = {0};int len = sizeof(sa);// 获取当前连接的客户端 IP 地址getpeername(Connfd[i], (struct sockaddr *)&sa, &len);// 如果当前连接的 IP 匹配子设备的 IP,跳出循环if (strcmp(voice_SysParameter.SubDevState.SubDevState[j].ip, inet_ntoa(sa.sin_addr)) == 0) break;}// 如果遍历完所有连接都没找到匹配的 IP(i == voice_MaxConnectNum)if (i == voice_MaxConnectNum) {// 清除子设备状态(标记为未连接)voice_SysParameter.SubDevState.SubDevState[j].slaveNo = (char)-1;strcpy(voice_SysParameter.SubDevState.SubDevState[j].ip, "");voice_SysParameter.SubDevState.SubDevState[j].status = 0;}
}

 6.使用 select() 监听多个文件描述符(file descriptors)的可读事件

// 清空文件描述符集合
FD_ZERO(&rset);
FD_SET(server_sockfd, &rset);  // 将服务器套接字加入监听集合
maxfd = server_sockfd;         // 初始化最大文件描述符为服务器套接字// 遍历所有客户端连接,更新监听集合和最大文件描述符
for (int i = 0; i < voice_MaxConnectNum; i++) {maxfd = maxfd > Connfd[i] ? maxfd : Connfd[i]; // 更新最大文件描述符if (Connfd[i] > 0)                            // 只监听有效的连接FD_SET(Connfd[i], &rset);                 // 加入监听集合
}// 调用 select() 监听 I/O 事件
nready = select(maxfd + 1, &rset, NULL, NULL, &timeout);// 超时处理
if (nready == 0) {continue; // select 超时,继续循环
}
  • 核心功能:使用 select() 监听服务器套接字和客户端连接的可读事件,实现 I/O 多路复用。
  • 关键点
    • FD_ZERO() + FD_SET() 初始化监听集合。
    • select(maxfd + 1, &rset, ...) 阻塞等待 I/O 事件。
    • FD_ISSET() 检查哪个文件描述符就绪。
  • 改进方向
    • 改用 epoll()/kqueue() 提高性能。
    • 增加错误处理和连接管理。
    • 动态调整超时时间。

7.处理 select() 返回的就绪文件描述符 

for (int i = 0; i < voice_MaxConnectNum; i++) {if (FD_ISSET(Connfd[i], &rset)) {  // 检查 Connfd[i] 是否就绪(可读)ConnfdCurTime[i] = HeartBeatCnt; // 更新心跳时间戳if (voice_ConTask(Connfd[i]) == -1) { // 处理客户端数据,返回 -1 表示错误struct sockaddr_in sa = {0};int len = sizeof(sa);// 1. 获取客户端 IP 地址(用于日志或调试)getpeername(Connfd[i], (struct sockaddr *)&sa, &len);// 2. 关闭连接并清理资源close(Connfd[i]);printf("Connfd Tcp__closed=%d %s\n", Connfd[i], inet_ntoa(sa.sin_addr));Connfd[i] = 0;          // 标记连接为无效ConnfdCurTime[i] = 0;   // 清空心跳时间戳}}
}

8.处理单个客户端连接的数据接收、解析和响应

int voice_ConTask(int sockfd) {char buffer[2048];          // 接收数据的缓冲区memset(buffer, 0, sizeof(buffer)); // 清空缓冲区// 1. 接收客户端数据int len = recv(sockfd, buffer, sizeof(buffer), 0);// 2. 检查是否收到 "exit" 命令(客户端主动关闭)if (strcmp(buffer, "exit") == 0) {printf("sockfd: %d exited.1\n", sockfd);return -1; // 返回 -1 表示连接需要关闭}// 3. 检查连接是否已关闭(len=0 表示客户端断开)else if (len == 0) {printf("sockfd: %d exited.2\n", sockfd);return -1; // 返回 -1 表示连接需要关闭}// 4. 处理有效数据else {// 调用 voice_TaskCommand 处理数据,并返回响应长度len = voice_TaskCommand(buffer, len);if (len > 0) {// 发送响应数据给客户端send(sockfd, buffer, len, 0);}}return 1; // 返回 1 表示处理成功,继续保持连接
}

9.TCP 服务器 处理 新客户端连接 的逻辑

// 1. 检查服务器 socket 是否可读(即是否有新连接到达)
if (FD_ISSET(server_sockfd, &rset)) {struct sockaddr_in client_addr;socklen_t length = sizeof(client_addr);// 2. 接受新连接int conn = accept(server_sockfd, (struct sockaddr*)&client_addr, &length);if (conn < 0) {perror("New client connect Error");continue; // 接受失败,跳过本次循环} else {printf("new client accepted.\n");}// 3. 将新连接存入连接池(Connfd 数组)for (int i = 0; i < voice_MaxConnectNum; i++) {if (Connfd[i] == 0) { // 找到空闲位置Connfd[i] = conn; // 存储新连接的文件描述符printf("Connfd[%d]=%d\n\r", i, conn);printf("IP:%s Connected...\n\r", inet_ntoa(client_addr.sin_addr));break; // 存入后跳出循环}// 4. 处理连接数超限if (i == voice_MaxConnectNum - 1) { // 遍历完所有位置printf("New Conn Num limit to %d \n\r", voice_MaxConnectNum);close(conn); // 关闭新连接(因为无法存储)}}
}

完整代码

/*** @brief TCP 服务器线程处理函数* @param p 线程参数(结构体指针,包含线程启动标志)* @return void* 线程返回值(未使用)*/
void* voice_tcp_Thread_TaskProc(void* p) {// 1. 解析线程参数struct voice_Thread_PARA_S *pstPara = (struct voice_Thread_PARA_S*)p;// 2. 创建服务器 socket(IPv4 + TCP)int server_sockfd = socket(AF_INET, SOCK_STREAM, 0);if (server_sockfd < 0) {perror("socket creation failed");goto voice_tcp_Thread_TaskProcError;}// 3. 初始化连接池和心跳时间记录数组int Connfd[voice_MaxConnectNum] = {0};          // 存储客户端连接的文件描述符int ConnfdCurTime[voice_MaxConnectNum] = {0};    // 记录最后一次心跳时间(秒)// 4. 设置 SO_REUSEADDR 选项(避免端口占用)int reuse = 1;if (setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) {perror("Setting SO_REUSEADDR failed");goto voice_tcp_Thread_TaskProcError;}// 5. 绑定服务器地址和端口struct sockaddr_in server_sockaddr;memset(&server_sockaddr, 0, sizeof(server_sockaddr));server_sockaddr.sin_family = AF_INET;server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听所有网卡server_sockaddr.sin_port = htons(voice_SysParameter.port); // 转换端口为网络字节序if (bind(server_sockfd, (struct sockaddr*)&server_sockaddr, sizeof(server_sockaddr)) == -1) {perror("bind failed");goto voice_tcp_Thread_TaskProcError;}printf("bind success.\n");// 6. 开始监听(待处理连接队列长度为 20)if (listen(server_sockfd, 20) == -1) {perror("listen failed");goto voice_tcp_Thread_TaskProcError;}printf("listen success.\n");printf("Tcp Server: %s:%d\n", voice_SysParameter.ip, voice_SysParameter.port);// 7. 主循环(处理客户端连接和数据)while (pstPara->bThreadStart == 1) {// 7.1 获取当前时间(用于心跳检测)struct timeval tv;gettimeofday(&tv, NULL);int current_time = tv.tv_sec;// 7.2 心跳检测:关闭超时未活动的连接for (int i = 0; i < voice_MaxConnectNum; i++) {if (Connfd[i] > 0 && ConnfdCurTime[i] > 0 && (current_time - ConnfdCurTime[i] > voice_SysParameter.heartBeat)) {struct sockaddr_in client_addr;socklen_t len = sizeof(client_addr);getpeername(Connfd[i], (struct sockaddr*)&client_addr, &len); // 获取客户端 IPprintf("Heartbeat timeout, close connection: fd=%d, IP=%s\n", Connfd[i], inet_ntoa(client_addr.sin_addr));close(Connfd[i]); // 关闭连接Connfd[i] = 0;    // 清空连接池位置ConnfdCurTime[i] = 0;}}// 7.3 检查设备状态(如果客户端断开,更新设备状态)for (int j = 0; j < voice_SysParameter.connectNo; j++) {if (strcmp(voice_SysParameter.SubDevState.SubDevState[j].ip, "") == 0) continue;int i;for (i = 0; i < voice_MaxConnectNum; i++) {if (Connfd[i] <= 0) continue; // 跳过无效连接struct sockaddr_in client_addr;socklen_t len = sizeof(client_addr);getpeername(Connfd[i], (struct sockaddr*)&client_addr, &len);if (strcmp(voice_SysParameter.SubDevState.SubDevState[j].ip, inet_ntoa(client_addr.sin_addr)) == 0) {break; // 找到匹配的连接}}// 如果未找到匹配连接,清空设备状态if (i == voice_MaxConnectNum) {voice_SysParameter.SubDevState.SubDevState[j].slaveNo = -1;strcpy(voice_SysParameter.SubDevState.SubDevState[j].ip, "");voice_SysParameter.SubDevState.SubDevState[j].status = 0;}}// 7.4 使用 select 监听 I/O 事件fd_set rset;FD_ZERO(&rset);FD_SET(server_sockfd, &rset); // 监听服务器 socket(接受新连接)int maxfd = server_sockfd;for (int i = 0; i < voice_MaxConnectNum; i++) {if (Connfd[i] > 0) {FD_SET(Connfd[i], &rset); // 监听所有客户端连接maxfd = (Connfd[i] > maxfd) ? Connfd[i] : maxfd; // 更新最大文件描述符}}// 设置 select 超时时间(1 秒)struct timeval timeout;timeout.tv_sec = 1;timeout.tv_usec = 0;int nready = select(maxfd + 1, &rset, NULL, NULL, &timeout);if (nready <= 0) continue; // 超时或错误,继续循环// 7.5 处理客户端数据for (int i = 0; i < voice_MaxConnectNum; i++) {if (Connfd[i] > 0 && FD_ISSET(Connfd[i], &rset)) {ConnfdCurTime[i] = current_time; // 更新心跳时间// 调用业务逻辑处理函数(如解析协议、处理请求)if (voice_ConTask(Connfd[i]) == -1) {struct sockaddr_in client_addr;socklen_t len = sizeof(client_addr);getpeername(Connfd[i], (struct sockaddr*)&client_addr, &len);printf("Client disconnected or error, close connection: fd=%d, IP=%s\n", Connfd[i], inet_ntoa(client_addr.sin_addr));close(Connfd[i]);Connfd[i] = 0;ConnfdCurTime[i] = 0;}}}// 7.6 处理新连接if (FD_ISSET(server_sockfd, &rset)) {struct sockaddr_in client_addr;socklen_t length = sizeof(client_addr);int conn = accept(server_sockfd, (struct sockaddr*)&client_addr, &length);if (conn < 0) {perror("accept failed");continue;}printf("New client accepted: fd=%d, IP=%s\n", conn, inet_ntoa(client_addr.sin_addr));// 将新连接存入连接池for (int i = 0; i < voice_MaxConnectNum; i++) {if (Connfd[i] == 0) {Connfd[i] = conn;ConnfdCurTime[i] = current_time; // 初始化心跳时间printf("Connfd[%d]=%d, IP=%s Connected\n", i, conn, inet_ntoa(client_addr.sin_addr));break;}// 连接数超限if (i == voice_MaxConnectNum - 1) {printf("Connection limit reached (%d), reject new client\n", voice_MaxConnectNum);close(conn); // 关闭新连接}}}}// 8. 清理资源(线程退出时)for (int i = 0; i < voice_MaxConnectNum; i++) {if (Connfd[i] > 0) {close(Connfd[i]);Connfd[i] = 0;}}close(server_sockfd);return NULL;voice_tcp_Thread_TaskProcError:if (server_sockfd >= 0) close(server_sockfd);return NULL;
}

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

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

相关文章

六、搭建springCloudAlibaba2021.1版本分布式微服务-admin监控中心

前言Spring Boot Actuator 是 spring-boot 自带监控功能 &#xff0c;可以帮助实现对程序内部运行情况监控&#xff0c;比如监控状况、Bean 加载情况、环境变量、日志信息、线程信息等。 Spring Boot Admin是一个针对 spring-boot 的 actuator 接口进行 UI 美化封装的监控工具。…

轻量级远程开发利器:Code Server与cpolar协同实现安全云端编码

前言&#xff1a;作为一款专为Web环境设计的VS Code托管方案&#xff0c;Code Server通过精简架构重新定义了远程开发体验。其核心优势在于将完整的编辑器功能封装于轻量容器中——仅需不到200MB内存即可运行基础服务&#xff0c;并支持在树莓派等低性能设备上流畅操作。系统采…

图论:最小生成树

今天要介绍两中最小生成树的算法&#xff0c;分别是prim算法和kruskal算法。 最小生成树是所有节点的最小连通子图&#xff0c;即&#xff1a;以最小的成本&#xff08;边的权值&#xff09;将图中所有节点链接到一起。 图中有n个节点&#xff0c;那么一定可以用n-1条边将所有节…

haproxy七层代理

1、负载均衡Load Balance(LB) 概念 负载均衡&#xff1a;是一种服务或基于硬件设备等实现的高可用反向代理技术&#xff0c;负载均衡将特定的业务(web服务、网络流量等)分担给指定的一个或多个后端特定的服务器或设备&#xff0c;从而提高了 公司业务的并发处理能力、保证了业务…

【NLP舆情分析】基于python微博舆情分析可视化系统(flask+pandas+echarts) 视频教程 - 微博文章数据可视化分析-点赞区间实现

大家好&#xff0c;我是java1234_小锋老师&#xff0c;最近写了一套【NLP舆情分析】基于python微博舆情分析可视化系统(flaskpandasecharts)视频教程&#xff0c;持续更新中&#xff0c;计划月底更新完&#xff0c;感谢支持。今天讲解微博文章数据可视化分析-点赞区间实现 视频…

Redis实战(3)-- 高级数据结构zset

有序集合&#xff08;ZSET&#xff09;&#xff1a;可以用作相关有序集合相对于哈希、列表、集合来说会有一点点陌生,但既然叫有序集合,那么它和集合必然有着联系,它保留了集合不能有重复成员的特性,但不同的是,有序集合中的元素可以排序。但是它和列表使用索引下标作为排序依据…

Mistral AI开源 Magistral-Small-2507

宣布Magistral——Mistral AI推出的首款推理模型&#xff0c;专精于垂直领域、具备透明化特性与多语言推理能力。 最优秀的人类思维并非线性——它穿梭于逻辑、洞见、不确定性与发现之间。推理型语言模型让我们得以将复杂思考和深度理解交由AI增强或代劳&#xff0c;提升了人类…

【Kotlin】如何实现静态方法?(单例类、伴生对象、@JvmStatic)

静态方法 静态方法&#xff08;类方法&#xff09;&#xff1a;不需要创建实例就可以调用&#xff08;直接通过类名调用&#xff09;的方法 Java 中的静态方法&#xff08;static&#xff09; public class Util {public static void doAction() {//...} }调用&#xff1a;Util…

SQL Schema 和Pandas Schema什么意思

在数据处理和分析领域&#xff0c;SQL Schema 和 Pandas Schema 分别指的是在不同数据处理环境中数据的结构定义&#xff0c;以下为你详细介绍&#xff1a;SQL Schema含义SQL Schema&#xff08;模式&#xff09;是数据库对象的一个逻辑容器&#xff0c;它定义了数据库中表、视…

机器学习(一)KNN,K近邻算法(K-Nearest Neighbors)

&#x1f4a1; 建议初学者掌握KNN作为理解其他复杂算法&#xff08;如SVM、决策树、神经网络&#xff09;的基石。K近邻算法&#xff08;K-Nearest Neighbors, KNN&#xff09;详解&#xff1a;原理、实践与优化K近邻算法&#xff08;K-Nearest NeighboKrs&#xff0c;简称KNN&…

Qt 多线程数据库操作优化

在多线程应用中&#xff0c;数据库操作往往是性能瓶颈与稳定性风险的高发区。当多个线程同时读写数据库时&#xff0c;若处理不当&#xff0c;轻则出现查询卡顿、事务冲突&#xff0c;重则导致数据错乱、连接泄漏甚至程序崩溃。Qt作为跨平台框架&#xff0c;提供了QSql系列类支…

Qt 状态机框架:复杂交互逻辑的处理

Qt状态机框架&#xff08;Qt State Machine Framework&#xff09;是一个强大的工具&#xff0c;用于简化复杂的交互逻辑和状态管理。它基于UML状态图概念&#xff0c;提供了声明式的方式来定义对象行为&#xff0c;特别适合处理具有多种状态和转换的场景&#xff08;如GUI交互…

【docker】DM8达梦数据库的docker-compose以及一些启动踩坑

摘要&#xff1a;本文介绍了通过docker-compose配置启动达梦数据库(DM8)的方法。配置包括容器镜像、端口映射、数据卷挂载以及关键环境变量设置&#xff08;如字符集、大小写敏感等&#xff09;。也说明了启动过程可能遇到的一些问题。通过docker-compose启动达梦数据库可以按照…

服务器中的防火墙设置需要打开吗

服务器中的防火墙属于是一种保护计算机网络不会受到未经授权的网络设备所访问的技术手段&#xff0c;防火墙还有着将内部网络和外部网络进行隔离的功能&#xff0c;在网络之间创建安全屏障&#xff0c;以此来保护网络中数据的安全。防火墙是一种网络安全系统&#xff0c;可以帮…

Java项目:基于SSM框架实现的社区团购管理系统【ssm+B/S架构+源码+数据库+毕业论文+答辩PPT+远程部署】

摘 要 使用旧方法对社区团购信息进行系统化管理已经不再让人们信赖了&#xff0c;把现在的网络信息技术运用在社区团购信息的管理上面可以解决许多信息管理上面的难题&#xff0c;比如处理数据时间很长&#xff0c;数据存在错误不能及时纠正等问题。 这次开发的社区团购系统有…

介绍一下static关键字

在Java中&#xff0c;被static修饰的成员称为静态成员&#xff0c;static关键字可以用来修饰方法或者成员变量&#xff0c;且被static修饰的方法或者成员变量属于类方法或者类属性&#xff0c;也就是说被static修饰的方法或者成员变量不是单独存储在某一个对象的空间&#xff0…

【Java学习|黑马笔记|Day23】网络编程、反射、动态代理

【DAY23】 文章目录【DAY23】一.网络编程1&#xff09;三要素1.1&#xff09;IPInetAddress类的使用1.2&#xff09;端口号1.3&#xff09;协议2.1&#xff09;UDP协议发送数据2.2&#xff09;UDP协议接收数据2.3&#xff09;UDP的三种通信方式3.1&#xff09;TCP协议的发送和接…

【Zephyr】Window下的Zephyr编译和使用

工具下载 参考文档&#xff08;Getting Started Guide — Zephyr Project Documentation&#xff09;中介绍&#xff0c;可以直接通过winget下载&#xff1a; winget download Kitware.CMake Ninja-build.Ninja oss-winget.gperf python Git.Git oss-winget.dtc wget 7zip.7z…

图论(BFS)构造邻接表(运用队列实现搜索)

码蹄集OJ-夏日漫步 #include<bits/stdc.h> using namespace std; int n; int a[200010],dis[200010],qaq[1000010]; vector<int>son[200010]; int que[200010]; int main( ) {memset(qaq,-1,sizeof(qaq));memset(dis,-1,sizeof(dis));cin>>n;for(int i1;i…

vue怎么实现导入excel表功能

<el-uploadref"upload":action"aaa":on-change"importProject"name"excelFile"multiple:auto-upload"false":show-file-list"false"><el-button type"warning">导入</el-button><…