TCP并发服务器
端口号快速复用函数
通过
getsockopt
和setsockopt
函数,管理套接字的端口号复用设置。具体操作如下:
getsockopt
函数
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
功能:获取套接字的某些选项的属性。
参数:
sockfd
: 套接字描述符。level
: 获取的层级(例如SOL_SOCKET
)。optname
: 要获取的操作名称(如SO_REUSEADDR
)。optval
: 获取的值(0表示禁用,非0表示启用)。optlen
: 参数4的大小。
返回值: 成功返回0,失败返回-1。
setsockopt
函数
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
功能:设置套接字的某些选项的属性。
参数:
sockfd
: 套接字描述符。level
: 设置的层级(例如SOL_SOCKET
)。optname
: 要设置的操作名称(如SO_REUSEADDR
)。optval
: 设置的值(0表示禁用,非0表示启用)。optlen
: 参数4的大小。
返回值: 成功返回0,失败返回-1。
示例代码:端口号快速复用
// 获取当前端口号是否能快速复用 int n; int len = sizeof(n); getsockopt(oldfd, SOL_SOCKET, SO_REUSEADDR, &n, &len); if (n == 0) {printf("端口号快速复用未启动\n"); } else {printf("端口号快速复用已经启动\n"); }// 设置当前套接字端口号快速复用 n = 999; setsockopt(oldfd, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n));
1. 循环服务器模型
描述
循环服务器模型是一次只处理一个客户端请求的传统方式,处理完一个客户端请求后,才会继续处理下一个客户端请求。由于其效率较低,每次只能执行单个任务,因此在高并发场景下表现不佳。
在循环服务器模型中,服务器端采用了一个循环来不断接收客户端请求。每当一个客户端连接成功后,服务器创建一个新的套接字用于通信,并处理客户端的请求。每处理完一个客户端请求后,服务器继续等待下一个客户端的连接。
主要步骤:
- 创建套接字:使用
socket()
函数创建套接字。- 绑定:将套接字与服务器的 IP 地址和端口号绑定。
- 监听:开始监听客户端连接请求。
- 循环接收客户端请求:
- 每次
accept()
成功后,创建一个新的套接字用于通信。- 循环收发信息,直到客户端断开连接。
- 关闭连接:每次客户端断开连接后,关闭相应的套接字。
关闭监听套接字
循环服务器代码实现
代码示例
#include <myhead.h>
#define IP "192.168.60.45"
#define PORT 6666
#define BACKLOG 20int main(int argc, const char *argv[]) {// 1. 创建套接字int oldfd = socket(AF_INET, SOCK_STREAM, 0); // IPV4, TCP协议if (oldfd == -1) {perror("socket");return -1;}// 2. 获取端口号属性,查看是否启用端口号快速复用int n;int len = sizeof(n);if (getsockopt(oldfd, SOL_SOCKET, SO_REUSEADDR, &n, &len) == -1) {perror("getsockopt");return -1;}if (n) {printf("端口号快速复用已经启动\n");} else {printf("端口号快速复用未启动\n");}// 设置端口号快速复用n = 999;if (setsockopt(oldfd, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n)) == -1) {perror("setsockopt");return -1;}printf("端口号快速复用成功\n");// 3. 绑定IP和端口号struct sockaddr_in server = {.sin_family = AF_INET, // IPV4.sin_port = htons(PORT), // 端口号转换为网络字节序.sin_addr.s_addr = inet_addr(IP), // IP地址};if (bind(oldfd, (struct sockaddr *)&server, sizeof(server)) == -1) {perror("bind");return -1;}// 4. 监听if (listen(oldfd, BACKLOG) == -1) {perror("listen");return -1;}// 5. 接受客户端连接请求并创建新的描述符用于通信struct sockaddr_in client;socklen_t client_len = sizeof(client);int newfd;while (1) {newfd = accept(oldfd, (struct sockaddr *)&client, &client_len);if (newfd == -1) {perror("accept");return -1;}printf("%s发来连接请求\n", inet_ntoa(client.sin_addr));// 6. 循环收发信息char buff[1024];while (1) {memset(buff, 0, sizeof(buff));int len = recv(newfd, buff, sizeof(buff), 0); // 接收客户端信息if (len == 0) { // 客户端断开连接printf("客户端下线\n");break;}printf("%s\n", buff);strcat(buff, "5201314"); // 添加回复信息send(newfd, buff, sizeof(buff), 0); // 发送信息}}// 关闭套接字close(oldfd);close(newfd);return 0;
}
代码流程:
- 创建套接字:
socket()
函数创建一个 TCP 套接字。 - 设置端口号复用:通过
setsockopt()
设置端口复用,允许在同一端口上建立多个连接。 - 绑定:使用
bind()
将套接字与 IP 地址和端口绑定。 - 监听:调用
listen()
使套接字进入监听状态,准备接受客户端连接。 - 接受连接:通过
accept()
接受客户端连接,返回一个新的套接字描述符用于与客户端通信。 - 收发消息:在循环内通过
recv()
接收客户端消息,然后用send()
发送回应消息。直到客户端断开连接。 - 关闭套接字:处理完成后关闭套接字。
缺点:
- 同步阻塞:当前的设计是同步阻塞的,服务器在处理一个客户端请求时不能同时处理其他客户端的请求。因此,如果有多个客户端连接,必须等待前一个客户端的请求处理完成后才能继续。
- 客户端阻塞:新客户端要通信时,必须等到旧客户端退出。服务器只能在当前连接断开后接受新的客户端请求。
- 新客户端要通信时,必须等待旧客户端退出。
改进:
为了解决这个问题,可以采用 多线程 或 多进程 模型来并发处理客户端请求,从而避免客户端之间的阻塞。
2. 基于TCP的并发服务器
2.1 多进程并发服务器
多进程并发服务器的模型通常由一个父进程负责监听客户端的连接请求,每当接收到一个连接请求时,父进程创建一个新的子进程来处理与客户端的通信。父进程和子进程之间通过文件描述符传递数据。父进程负责监听和接收客户端连接,子进程则处理数据收发。
多进程服务器模型的关键问题:
1. 子进程在哪创建: 父进程在接收到客户端的连接请求时会创建一个子进程来处理该请求。
示例代码:
pid_t pid = fork(); if (pid > 0) {// 父进程:继续监听新的连接请求,关闭新文件描述符close(newfd); } else if (pid == 0) {// 子进程:关闭旧的监听套接字,并处理客户端通信close(server_fd);// 与客户端通信...close(newfd); // 处理完成后关闭与客户端的连接exit(0); // 子进程处理完请求后退出 } else {perror("fork");return -1; }
2. 子进程怎么回收:子进程处理完任务后会退出,因此需要确保父进程能够回收已经退出的子进程,防止僵尸进程的产生。
常见的方式:
- 阻塞回收:父进程使用
wait()
或waitpid()
函数在子进程退出时阻塞等待并回收它们。- 非阻塞回收:如果不想让父进程被
wait()
阻塞,可以使用信号处理机制,通过捕捉SIGCHLD
信号并在信号处理函数中回收子进程。示例代码(非阻塞回收):
// 信号处理函数,用于回收僵尸进程 void handle_sigchld(int sig) {while (waitpid(-1, NULL, WNOHANG) > 0); // 回收已退出的子进程 }// 在主程序中捕捉 SIGCHLD 信号 signal(SIGCHLD, handle_sigchld);
3. 文件描述符: 由于系统对打开的文件描述符有限制,因此需要确保在父子进程中正确管理文件描述符。
在操作系统中,文件描述符是有限的,每个进程可以打开的文件(包括套接字)的数量是有限的。如果服务器并发连接数较多,且每个连接都创建一个新的子进程,就可能遇到文件描述符耗尽的问题。可以通过以下方式解决或避免这一问题:
解决方案:
- 增加文件描述符限制:可以通过
ulimit -n
命令临时增加系统的文件描述符限制,或者在/etc/security/limits.conf
文件中永久增加限制。- 使用线程池或连接池:而不是为每个客户端创建一个新的子进程,可以通过线程池或连接池来管理客户端连接。多线程或线程池模型可以减少系统开销,因为线程的创建和销毁比进程更加轻量。
- 复用文件描述符:通过一些技术手段(例如
select()
、poll()
或epoll()
)来管理多个连接,不需要为每个客户端创建一个独立的进程或线程,从而避免耗尽文件描述符。
示例:修改文件描述符限制
- 临时修改文件描述符的限制:
ulimit -n 65535
- 永久修改文件描述符的限制: 在
/etc/security/limits.conf
文件中添加:* soft nofile 65535 * hard nofile 65535
总结
- 子进程创建:父进程在接收到客户端连接请求后,使用
fork()
创建子进程来处理该客户端的通信。- 子进程回收:父进程可以通过
wait()
或waitpid()
回收子进程,也可以通过信号处理函数来非阻塞回收已退出的子进程。- 文件描述符限制:多进程模型可能会受到系统文件描述符限制的影响,解决方法包括增加文件描述符限制、使用线程池或连接池等技术来减少文件描述符的占用。
多进程并发服务器执行模型:
定义信号处理函数,非阻塞回收僵尸进程。
绑定子进程退出时的信号。
- 创建套接字
- 绑定
- 监听
- 循环接收客户端连接
- 让父进程接收客户端请求并关闭新文件描述符,子进程关闭旧的描述符只负责数据收发。
多进程服务器代码:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <signal.h> #include <wait.h>#define IP "192.168.60.45" #define PORT 6666 #define BACKLOG 100// 信号处理函数,用于回收僵尸进程 void fun(int sss) {if (sss == SIGCHLD){while (waitpid(-1, NULL, 0) > 0); // 循环回收子进程} }int main(int argc, const char *argv[]) {// 1. 捕获子进程退出时的信号if (signal(SIGCHLD, fun) == SIG_ERR){perror("signal");return -1;}// 2. 创建TCP类型的套接字int oldfd = socket(AF_INET, SOCK_STREAM, 0);if (oldfd == -1){perror("socket");return -1;}// 3. 设置端口号快速复用int n = 1;if (setsockopt(oldfd, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n)) == -1){perror("setsockopt");return -1;}printf("端口号快速复用成功\n");// 4. 绑定struct sockaddr_in server = {.sin_family = AF_INET,.sin_port = htons(PORT),.sin_addr.s_addr = inet_addr(IP),};if (bind(oldfd, (struct sockaddr *)&server, sizeof(server)) == -1){perror("bind");return -1;}// 5. 监听if (listen(oldfd, BACKLOG) == -1){perror("listen");return -1;}struct sockaddr_in client;socklen_t client_len = sizeof(client);char buff[1024];// 6. 主循环接收客户端连接while (1){int newfd = accept(oldfd, (struct sockaddr *)&client, &client_len); // 接收客户端连接printf("%s发来连接请求\n", inet_ntoa(client.sin_addr));pid_t pid = fork(); // 创建子进程if (pid > 0) // 父进程监听,并关闭新的描述符{close(newfd);}else if (pid == 0) // 子进程处理数据收发{close(oldfd); // 子进程关闭旧的描述符while (1){int len = recv(newfd, buff, sizeof(buff), 0);if (len == 0){printf("客户端退出\n");break;}printf("客户端%s发来消息:%s\n", inet_ntoa(client.sin_addr), buff);strcat(buff, inet_ntoa(client.sin_addr)); // 回去时加上客户端IPsend(newfd, buff, sizeof(buff), 0);}close(newfd);exit(0); // 子进程退出}else{perror("fork");return -1;}}return 0; }
客户端代码:
客户端通过连接到服务器来发送数据并接收服务器的响应。该程序创建一个TCP套接字,连接到指定的服务器IP和端口,并与服务器进行数据交互。
#include <myhead.h>#define IP "192.168.60.45" #define PORT 6666int main(int argc, const char *argv[]) {// 1. 创建套接字int oldfd = socket(AF_INET, SOCK_STREAM, 0);if (oldfd == -1){perror("socket");return -1;}// 2. 连接服务器struct sockaddr_in server = {.sin_family = AF_INET,.sin_port = htons(PORT),.sin_addr.s_addr = inet_addr(IP),};if (connect(oldfd, (struct sockaddr *)&server, sizeof(server)) == -1){perror("connect");return -1;}// 3. 数据收发char buff[1024];while (1){fgets(buff, sizeof(buff), stdin); // 键盘输入字符串buff[strlen(buff) - 1] = '\0'; // 去除换行符// 发送数据到服务器send(oldfd, buff, sizeof(buff), 0);// 接收服务器数据int len = recv(oldfd, buff, sizeof(buff), 0);if (len == 0){printf("服务器意外退出\n");break;}printf("接收服务器消息:%s\n", buff);}// 关闭套接字close(oldfd);return 0; }
总结
- 多进程并发模型: 通过父进程监听和创建子进程来处理每个客户端的请求,使用
fork
创建子进程,在子进程中负责数据收发,父进程关闭新文件描述符,子进程关闭旧文件描述符。通过信号处理函数回收子进程,避免僵尸进程。- TCP客户端: 连接到服务器后,通过套接字发送数据并接收响应,支持实时数据交互。
这种模型适用于客户端和服务器之间的实时通信,特别是在需要处理多个客户端请求的场景中。
2.2 多线程并发服务器
1. 多线程的优势
- 资源开销小:线程的资源开销比进程小,创建和销毁线程的速度比进程快。
- 响应速度快:如果客户端连接较多,可以通过线程池提前创建线程来分配给客户端,减少响应延迟。
- 线程销毁开销小:线程占用资源较少,销毁线程的资源开销也较小。
2. 服务器模型
- 主线程:负责监听客户端连接。
- 子线程:负责接收和发送数据,与客户端通信。
3. 服务器的工作流程
- 创建原始套接字:使用
socket()
创建一个 TCP 类型的套接字。- 绑定 IP 地址和端口号:使用
bind()
绑定服务器的 IP 地址和端口。- 监听客户端连接:使用
listen()
开始监听客户端的连接请求。- 接收客户端连接:使用
accept()
接收客户端的连接请求,并为每个客户端创建一个新线程。- 线程处理客户端请求:每个线程接收到客户端消息后进行处理并发送响应。线程执行完后退出。
4. 线程池
- 为了处理大量并发请求,线程池的机制可以预先创建一定数量的线程,接收到客户端请求时,从线程池中取出一个线程来处理该请求。
5. 代码实现
#include <myhead.h>// 定义服务器的 IP 地址、端口号和最大连接数 #define IP "192.168.60.45" #define PORT 9999 #define BACKLOG 10// 定义结构体,用于传递客户端信息和新文件描述符 typedef struct {struct sockaddr_in client; // 客户端信息int newfd; // 新的文件描述符 } ZYJ;// 线程体函数,处理与客户端的通信 void *fun(void *sss) {// 获取新文件描述符和客户端信息int newfd = ((ZYJ *)sss)->newfd;struct sockaddr_in client = ((ZYJ *)sss)->client;// 打印客户端的 IP 地址printf("%s发来信息\n", inet_ntoa(client.sin_addr));char buff[1024];while (1) {// 接收客户端发来的消息int len = recv(newfd, buff, sizeof(buff), 0);if (len == 0) { // 客户端退出printf("客户端退出\n");break;}// 打印收到的消息printf("收到消息:%s\n", buff);// 在消息末尾加上 "1973"strcat(buff, "1973");// 将处理后的消息发送回客户端send(newfd, buff, sizeof(buff), 0);}// 线程结束时退出pthread_exit(NULL); }int main(int argc, const char *argv[]) {// 1. 创建套接字int oldfd = socket(AF_INET, SOCK_STREAM, 0);if (oldfd == -1) {perror("socket");return -1;}// 2. 绑定 IP 地址和端口号struct sockaddr_in server = {.sin_family = AF_INET,.sin_port = htons(PORT),.sin_addr.s_addr = inet_addr(IP),};if (bind(oldfd, (struct sockaddr *)&server, sizeof(server)) == -1) {perror("bind");return -1;}// 3. 监听客户端的连接请求if (listen(oldfd, BACKLOG) == -1) {perror("listen");return -1;}struct sockaddr_in client;socklen_t client_len = sizeof(client);// 4. 主线程循环接收客户端连接while (1) {int newfd = accept(oldfd, (struct sockaddr *)&client, &client_len); // 接收客户端连接请求ZYJ sb;sb.newfd = newfd; // 保存新的文件描述符sb.client = client; // 保存客户端信息pthread_t tid;// 5. 创建子线程处理客户端请求tid = pthread_create(&tid, NULL, fun, &sb);if (tid == -1) {perror("pthread_create");return -1;}// 6. 将线程设为分离状态,系统会自动回收线程资源pthread_detach(tid);}return 0; }
代码功能
- 创建套接字:通过
socket()
创建一个 TCP 套接字。- 绑定 IP 地址和端口:通过
bind()
将套接字绑定到指定的 IP 地址和端口号。- 监听连接:调用
listen()
开始监听来自客户端的连接请求,最多允许BACKLOG
个连接排队。- 接受连接:主线程通过
accept()
接收客户端的连接请求,并返回一个新的文件描述符newfd
用于与客户端进行通信。- 创建子线程:每当接受到新的连接,主线程会通过
pthread_create()
创建一个子线程处理客户端的消息传递和接收。每个子线程的处理逻辑通过fun()
函数完成。- 线程回收:通过
pthread_detach()
将线程设置为分离状态,子线程在完成后会自动回收资源,避免主线程等待子线程退出。
关键函数
socket()
:创建一个套接字。bind()
:将套接字与本地的 IP 地址和端口号绑定。listen()
:让套接字进入监听状态,准备接受客户端连接。accept()
:阻塞等待客户端连接,并返回一个新的套接字用于与客户端通信。pthread_create()
:创建新的线程来处理客户端请求。pthread_detach()
:将线程设为分离状态,线程结束后自动回收资源。recv()
:接收客户端发送的消息。send()
:向客户端发送消息。
适用场景
这个多线程模型适用于客户端连接量较大或较为频繁的场景。每个客户端连接都会分配一个线程进行处理,因此可以在短时间内处理多个客户端的请求。
线程池优化
虽然该程序为每个连接创建了一个独立的线程,但在实际应用中,为了提高资源利用率和性能,可以引入线程池。线程池事先创建好一组线程,客户端请求到来时从线程池中取出一个线程来处理,而不是每次都创建一个新的线程。这样可以减少线程的创建和销毁开销。
注意事项
- 多线程同步问题:虽然本例中没有涉及多线程之间的共享资源,但在复杂应用中可能需要考虑线程同步机制(如互斥锁、条件变量等)。
- 线程回收:线程被设为分离状态(
pthread_detach()
),这样可以在线程结束后由系统自动回收资源,而不需要调用pthread_join()
。
总结
该程序展示了一个典型的多线程并发服务器模型,使用线程处理每个客户端请求,减少了主线程的负担,提高了处理效率。通过
pthread_detach()
来自动回收资源,避免了线程泄露问题。
6. 客户端代码示例
#include <myhead.h>#define IP "192.168.60.45" #define PORT 9999int main(int argc, const char *argv[]) {// 1、创建套接字int oldfd = socket(AF_INET, SOCK_STREAM, 0);if (oldfd == -1){perror("socket");return -1;}// 2、连接服务器struct sockaddr_in server = {.sin_family = AF_INET,.sin_port = htons(PORT),.sin_addr.s_addr = inet_addr(IP),};if (connect(oldfd, (struct sockaddr *)&server, sizeof(server)) == -1){perror("connect");return -1;}// 3、数据收发char buff[1024];while (1){fgets(buff, sizeof(buff), stdin); // 键盘输入字符串buff[strlen(buff) - 1] = '\0'; // 去除换行符// 发送数据到服务器send(oldfd, buff, sizeof(buff), 0);// 接收服务器数据int len = recv(oldfd, buff, sizeof(buff), 0);if (len == 0){printf("服务器意外退出\n");break;}printf("接收服务器消息:%s\n", buff);}// 关闭套接字close(oldfd);return 0; }
相关文章:

TCP并发服务器
端口号快速复用函数 通过getsockopt和setsockopt函数,管理套接字的端口号复用设置。具体操作如下: getsockopt函数 int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);功能:获取套接字的某些选项的属性。…...

Debug-031-近期功能实现小结
由于时间原因,没办法对每个小的功能点进行比较细致的总结,这里统一去记录一下最近的实现了的功能,算是存档备份,为今后开发带来便利和参考。 一、ACEeditor ACEeditor使用手册(一)_ace editor-CSDN博客 AC…...

Consumer Group
不,kafka-consumer-groups.sh 脚本本身并不用于创建 Consumer Group。它主要用于管理和查看 Consumer Group 的状态和详情,比如列出所有的 Consumer Group、查看特定 Consumer Group 的详情、删除 Consumer Group 等。 Consumer Group 是由 Kafka 消费者…...

.NET架构师学习大纲
目录 微服务 Consul Ocelot Polly Skywalking Exceptionless Apollo Jenkins Docker Kubernetes DDD领域驱动设计 DevOps CDN Nginx 应用服务器集群 数据库高可用 异步化架构 Azure前沿技术 工具排查 O/RM-EFCore IOC&AOP Core WebApi WebServer 数…...

【代码随想录】贪心
455. 分发饼干 题目 随想录 本质: 对于每个孩子,使用可以满足该孩子的最小的饼干。所以对孩子胃口和饼干进行sort排序,依次将大的饼干满足给孩子。 贪心策略: 想一下局部最优,想一下全局最优,如果局部最优…...

Harmony鸿蒙类似与Android中broadcast广播的api使用及释义
EventHub模块提供了事件中心,提供订阅、取消订阅、触发事件的能力。 这里需要注意,该模块接口仅可在Stage模型下使用。且Api>9 EventHub.on on(event: string, callback: Function): void; 订阅指定事件。(接收广播) 参…...

openGauss 6.0.0主备部署(企业版)
openGauss 6.0.0主备部署(企业版) 文章目录 openGauss 6.0.0主备部署(企业版)一、环境准备1.操作系统环境2.修改主机名3.设置字符集编码4.修改openEuler默认yum源5.安装所需工具6.同步网络时间7.关闭防火墙 二、安装openGauss数据…...

【机器学习】聚类算法原理详解
聚类算法 性能度量: 外部指标 jaccard系数(简称JC)FM指数(简称FMI)Rand指数(简称RI) 内部指标 DB指数(简称DBI)Dunn指数(简称DI) 距离计算&am…...

Ubuntu20.04从零安装IsaacSim/IsaacLab
Ubuntu20.04从零安装IsaacSim/IsaacLab 电脑硬件配置:安装Isaac sim方案一:pip安装方案二:预构建二进制文件安装1、安装ominiverse2、在ominiverse中安装isaac sim,下载最新的4.2版本 安装Isaac Lab1、IsaacLab环境克隆2、创建con…...

基于Java Springboot大学校园旧物捐赠网站
一、作品包含 源码数据库设计文档万字PPT全套环境和工具资源部署教程 二、项目技术 前端技术:Html、Css、Js、Vue、Element-ui 数据库:MySQL 后端技术:Java、Spring Boot、MyBatis 三、运行环境 开发工具:IDEA/eclipse 数据…...

【Java 集合】Collections 空列表细节处理
问题 如下代码,虽然定义为非空 NonNull,但依然会返回空对象,导致调用侧被检测为空引用。 实际上不是Collections的问题是三目运算符返回了null对象。 import java.util.Collections;NonNullprivate List<String> getInfo() {IccReco…...

大数据实验4-HBase
一、实验目的 阐述HBase在Hadoop体系结构中的角色;能够掌握HBase的安装和配置方法熟练使用HBase操作常用的Shell命令; 二、实验要求 学习HBase的安装步骤,并掌握HBase的基本操作命令的使用; 三、实验平台 操作系统࿱…...

deepin系统下载pnpm cnpm等报错
deepin系统下载pnpm cnpm等报错 npm ERR! request to https://registry.npm.taobao.org/pnpm failed, reason: certificate has expired 报错提示证书过期,执行以下命令 npm config set registry https://registry.npmmirror.com下载pnpm npm install pnpm -g查…...

#Js篇:JSON.stringify 和 JSON.parse用法和传参
JSON.stringify 和 JSON.parse 1. JSON.stringify JSON.stringify 方法将一个 JavaScript 对象或数组转换为 JSON 字符串。 基本用法 const obj { name: "Alice", age: 25 }; const jsonString JSON.stringify(obj); console.log(jsonString); // 输出: {"…...

c#通过网上AI大模型实现对话功能
目录 基础使用给大模型额外提供函数能力用Microsoft.Extensions.AI库实现用json格式回答 基础使用 https://siliconflow.cn/网站有些免费的大模型可以使用,去注册个账户,拿到apikey 引用 nuget Microsoft.Extensions.AI.OpenAI using Microsoft.Extensi…...

pymysql模块
1.pymysql基本使用 打开数据库连接,使用cursor()方法获取操作游标执行SQL语句 获取命令执行的查询结果 1.1 打开数据库连接 # 打开数据库连接 db pymysql.connect(host127.0.0.1,userroot,port3306,password"123",databasedb5) 1.2 使用cursor()方法获取操作游…...

WPF-模板和样式
在 WPF(Windows Presentation Foundation)中,模板是一种强大的机制,用于定义控件的外观。它允许你将控件的逻辑(功能)和外观(UI)分离开来。例如,一个按钮控件,…...

网络编程 day1.2~day2——TCP和UDP的通信基础(TCP)
笔记脑图 作业: 1、将虚拟机调整到桥接模式联网。 2、TCP客户端服务器实现一遍。 服务器 #include <stdio.h> #include <string.h> #include <myhead.h> #define IP "192.168.60.44" #define PORT 6666 #define BACKLOG 20 int mai…...

element ui table 每行不同状态
table 每行定义值 tableData: [ { name: ,type:,location:, ziduan:,createtype:,ziduanvalue:,checkAll:true,checkedCities: [空, null, str随机, int随机],isIndeterminate: true,table_id:single,downloaddisabled:true,deldisabled:true} ], table c…...

力扣--LRC 142.训练计划IV
题目 给定两个以 有序链表 形式记录的训练计划 l1、l2,分别记录了两套核心肌群训练项目编号,请合并这两个训练计划,按训练项目编号 升序 记录于链表并返回。 注意:新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&am…...

windows下,用CMake编译qt项目,出现错误By not providing “FindQt5.cmake“...
开发环境:windows10 qt5.14, 编译器msvc2017x64,CMake3.30; 现象: CMakeList文件里,如有find_package(Qt5 COMPONENTS Widgets REQUIRED) target_link_libraries(dis_lib PRIVATE Qt5::Widgets) 用CMak…...

【element-tiptap】Tiptap编辑器核心概念----结构篇
core-concepts 前言:这篇文章来介绍一下 Tiptap 编辑器的一些核心概念 (一)结构 1、 Schemas 定义文档组成方式。一个文档就是标题、段落以及其他的节点组成的一棵树。 每一个 ProseMirror 的文档都有一个与之相关联的 schema,…...

半导体工艺与制造篇3 离子注入
离子注入工艺 一般掺杂的杂质类别,包括:提供载流子的施主杂质和受主杂质;产生复合中心的重金属杂质 离子注入往往需要生成井well,其中井的定义:晶圆与杂质之间形成的扩散层或杂质与杂质之间形成的扩散层 离子注入的目的:用掺杂改…...

利用开源的低代码表单设计器FcDesigner高效管理和渲染复杂表单结构
FcDesigner 是一个强大的开源低代码表单设计器组件,支持快速拖拽生成表单。提供丰富的自定义及扩展功能,FcDesigner支持多语言环境,并允许开发者进行二次开发。通过将表单设计输出为JSON格式,再通过渲染器进行加载,实现…...

淘宝 NPM 镜像源
npm i vant/weapp -S --production npm config set registry https://registry.npmmirror.com 要在淘宝 NPM 镜像站下载项目或依赖,你可以按照以下步骤操作: 1. 设置淘宝 NPM 镜像源 首先,你需要设置淘宝 NPM 镜像源以加速下载。可以通过…...

i春秋-GetFlag(md5加密,字符串比较绕过)
练习平台地址 竞赛中心 题目描述 题目内容 你好,单身狗,这是一个迷你文件管理器,你可以登录和下载文件,甚至得到旗帜 点击登录 发现capture需要满足条件substr(md5(captcha), 0, 6)xxxxxx 编写python脚本破解验证码 import has…...

SpringBoot中设置超时30分钟自动删除元素的List和Map
简介 在 Spring Boot 中,你可以使用多种方法来实现自动删除超时元素的 List 或 Map。以下是两种常见的方式: 如果你需要简单的功能并且不介意引入外部依赖,可以选择 Guava Cache。如果你想要更灵活的控制,使用 Spring 的调度功能…...

入门车载以太网(6) -- XCP on Ethernet
目录 1.寻址方式 2.数据帧格式 3.特殊指令 4.使用实例 了解了SOME/IP之后,继续来看看车载以太网在汽车标定领域的应用。 在汽车标定领域XCP是非常重要的协议,咱们先来回顾下基础概念。 XCP全称Universal Measurement and Calibration Protocol&a…...

DAY4 网络编程(广播和多线程并发)
作业1: 1、将广播发送和接收端实现一遍,完成一个发送端发送信息,对应多个接收端接收信息实验。 send.c代码: #include <myhead.h> #define IP "192.168.61.255"//广播IP #define PORT 7777 int main(int argc, …...

C++个人复习(4)
C中为什么要引入make_shared,它有什么优点 1. 减少内存分配次数 使用 make_shared 时,内存分配只发生一次,它同时分配了对象和控制块(用于管理引用计数等信息)。而如果直接使用 new 创建对象并传递给 shared_ptr,则会…...