linux-并发通信

news2025/1/13 15:43:41

一.linux-tcp通信框架

1.基础框架

1.1 tcp 服务器框架

1.套接字
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
 

返回的文件描述符可以指向当前的socket,后续通过对文件描述符的访问就可以配置这个socket 

成功时返回文件描述符,失败时返回-1。

●domain 套接字中使用的协议族(ProtocolFamily)信息。

●type 套接字数据传输类型信息。((SOCK_STREAM)---TCP,(SOCKDGRAM)---UDP)

●protocol 计算机间通信中使用的协议信息。  

2.bind 函数

如果把套接字比喻为电话,那么创建套接字只安装了电话机。 接着就要给电话机分配号码的方法,即给套接字分配 IP地址和端口号。就是用的bind函数。 

#include<sys/socket.h>
int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);

参数一:套接字描述符 sockfd 要分配地址信息(IP地址和端口号)的套接字文件描述符。

参数二:myaddr 存有地址信息的结构体变量地址值 

struct sockaddr   
{  
    unsigned short sa_family;    //2 
    char sa_data[14];     //14
};  
struct in_addr
{
    In_addr_t s_addr; //32 位 IPv4 地址
};

struct sockaddr_in
 {
    sa_family//地址族(Address Family)
    sa_family_t sin_family;// //地址族(Address Family)
    uint16_t sin_port; // 16 位 TCP/UDP 端口号
    struct in_addr sin_addr; //32 位 IP 地址
    char sin_zero[8]; //不使用
}

这里我们使用scokaddr_in来配置端口和ip(参数填写更方便),然后转换为socketadrr就行,两个结构体可以互相转换的

参数三:第二个结构体变量的长度

示例配置如下:

struct sockaddr_in addr;
char* serv_ip="211.217.168.13";
 //声明 IP地址字符串
char * serv_port="9198"; //声明端口号字符串
memset(&addr,0,sizeof(addr);//结构体变量 addr 的所有成员初始化为 0
 //指定地址族
addr.sin_family =AF_INET;
 //基于字符串的IP地址初始化
//inet用于将点分十进制的IP地址字符串转换成网络字节顺序(big-endian)的整数表示形式。
addr.sin_addr.s_addr=inet_addr(serv_ip);
 //基于字符串的端口号初始化
//atoi字符型转换为整型
addr.sin_port=htons(atoi(serv_port));
3.listen 函数:
#include <sys/socket.h>
int listen(int sock,int backlog);

sock 希望进入等待连接请求状态的套接字文件描述符

  • 传递的描述符套接字参数成为服务器端 套接字(监听套接字)。
  • backlog 连接请求等待队列(Queue)的长度,若为5,则队列长度为5,表示最多使5个连 接请求进入队列。
 4.accept 函数:
#include<sys/socket.h>
int accept(int sock,struct sockaddr * addr, socklen_t*addrlen);

成功时返回创建的套接字文件描述符,失败时返回-1。

  • 参数sock:服务器套接字的文件描述符。
  • 参数addr:保存发起连接请求的客户端地址信息的变量地址值,调用函数后向传递来的地址变量参数填充客户端地址信息。
  • 参数addrlen:第二个参数结构体的长度,但是存有长度的变量地址。函数调用完成后,该变量即被填客户端地址长度

 accept 函数受理连接请求等待队列中待处理的客户端连接请求。函数调用成功时,accept函数内部将产生用于数据I/O的套接字,并返回其文件描述符。套接字是自动创建的,并自 动与发起连接请求的客户端建立连接。上图展示了accept函数调用过程。

5. tcp 服务端框架
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

int main(){
    int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;  
/*
在网络编程中,一个服务器可能有多个网络接口(即多个IP地址)。如果你指定了一个具体的
IP地址来绑socket,那么服务器程序只能接受发送到这个特定IP地址的连接请求。相反,
使用INADDR_ANY可以让服务接受到达服务器上任何网络接口的连接请求,
这样就不需要针对每个可能的IP地址分别设置监听了。
*/
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);  
    serv_addr.sin_port = htons(9190);  

    bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
    listen(serv_sock, 20);
//用于生成客户端套接字,中间需要使用一个addr结构体,我们创建一个空结构体传入
accept辅助完成socket创建
    struct sockaddr_in clnt_addr;
    socklen_t clnt_addr_size = sizeof(clnt_addr);
//accept后会创建一个套接字,
    int clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);

    char message[] = "Hello, World!";
    write(clnt_sock, message, sizeof(message));

    close(clnt_sock);  
    close(serv_sock);  

    return 0;
}

1.2 tcp 客户端框架

1.connect
#include<sys/socket.h>
int connect(int sock,struct sockaddr*servaddr, socklen_t addrlen);

成功时返回0,失败时返回-1。

  • sock 客户端套接字文件描述符。
  • servaddr 保存目标服务器端地址信息的变量地址值。
  • addrlen 以字节为单位传递给第二个结构体参数servaddr的地址变量长度
2.客户端框架 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

int main(){
    int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));  
    serv_addr.sin_family = AF_INET;  
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  
    serv_addr.sin_port = htons(9190);  
//
    connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

    char buffer[40];
    read(sock, buffer, sizeof(buffer)-1);

    printf("Message from server: %s\n", buffer);

    close(sock);
    return 0;
}

1.3 线程基础

1.线程创建
#include<pthread.h>
int pthread_create(
     pthread_t*restrict thread,
     const pthread_attr_t * restrict attr,
     void *(* start_routine)(void *),
     void*restrict arg
);

成功时返回 0,失败时返回其他值。

●thread:保存新创建线程ID的变量地址值。线程与进程相同,也需要用于区分不同线程的ID。

attr:用于传递线程属性的参数,传递NULL时,创建默认属性的线程。

start_routine:相当于线程main函数的、在单独执行流中执行的函数地址值(函数指针)。

arg:通过第三个参数传递调用函数时包含传递参数信息的变量地址值。

#include<pthread.h>
int pthread_join(pthread_t thread, void ** status);

 调用pthread_join 函数的进程(或线程)将进入等待状态,直到第一个参数为ID的线程终 止为止。而且可以得到线程的main函数返回值,所以该函数比较有用。下面通过示例了解 该函数的功能。

成功时返回e,失败时返回其他值。

  • thread 该参数值ID的线程终止后才会从该函数返回。
  • status保存线程的main函数返回值的 指针变量地址值。
2.互斥锁
#include<pthread.h>
int pthread_mutex_init(pthread mutex_t*mutex, const pthread_mutexattr_t* attr);
int pthread_mutex_destroy(pthread_mutex_t * mutex);
  • mutex 创建互斥量时传递保存互斥量的变量地址值,销毁时传递需要销毁的互斥量地址值。
  • attr传递即将创建的互斥量属性,没有特别需要指定的属性时传递NULL。
#include<pthread.h>
int pthread_mutex_lock(pthread_mutex_t* mutex);
int pthread_mutex_unlock(pthread mutex_t* mutex);

成功时返回0,失败时返回其他值。

使用方法:

pthread_mutex_lock(&mutex);
 //临界区的开始
//.....
 // 临界区的结束
pthreadmutex_unlock(&mutex);

线程使用实例:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

// 全局变量作为共享资源
int shared_resource = 0;
// 互斥量对象
pthread_mutex_t mutex;

// 线程函数,递增共享资源
void* thread_func(void* arg) {
    for(int i = 0; i < 10000; ++i) {
        // 锁定互斥量
        pthread_mutex_lock(&mutex);
        // 访问并修改共享资源
        shared_resource++;
        // 解锁互斥量
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}

int main() {
    pthread_t thread1, thread2;

    // 初始化互斥量
    pthread_mutex_init(&mutex, NULL);

    // 创建两个线程
    pthread_create(&thread1, NULL, thread_func, NULL);
    pthread_create(&thread2, NULL, thread_func, NULL);

    // 等待线程结束
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    // 打印共享资源的最终值
    printf("Final value of shared resource: %d\n", shared_resource);

    // 销毁互斥量
    pthread_mutex_destroy(&mutex);

    return 0;
}

二、并发服务器

2.1 多线程服务器实现

1.服务器端:

每次accept都阻塞,来一个连接,就创建一个线程进行处理,多线程互不干扰 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <pthread.h>
#define BUF_SIZE 100
#define MAX_CLNT 256
void* handle_clnt(void* arg);
void send_msg(char* msg, int len);
void error_handling(char* msg);
int clnt_cnt = 0;
int clnt_socks[MAX_CLNT];
pthread_mutex_t mutx;
int main(int argc, char* argv[])
{
	int serv_sock, clnt_sock;
	struct sockaddr_in serv_adr, clnt_adr;
	int clnt_adr_sz;
	pthread_t t_id;
	if (argc != 2) {
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}
	pthread_mutex_init(&mutx, NULL);
	serv_sock = socket(PF_INET, SOCK_STREAM, 0);
	memset(&serv_adr, 0, sizeof(serv_adr));
	serv_adr.sin_family = AF_INET;
	serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
	serv_adr.sin_port = htons(atoi(argv[1]));
	if (bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)
		error_handling("bind() error");
	if (listen(serv_sock, 5) == -1)
		error_handling("listen() error");
	while (1)
	{
		clnt_adr_sz = sizeof(clnt_adr);
		clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
		pthread_mutex_lock(&mutx);
		clnt_socks[clnt_cnt++] = clnt_sock;
		pthread_mutex_unlock(&mutx);
		pthread_create(&t_id, NULL, handle_clnt, (void*)&clnt_sock);
		pthread_detach(t_id);
		printf("Connected client IP: %s \n", inet_ntoa(clnt_adr.sin_addr));
	}
	close(serv_sock);
	return 0;
}
void* handle_clnt(void* arg)
{
	int clnt_sock = *((int*)arg);
	int str_len = 0, i;
		char msg[BUF_SIZE];
	while ((str_len = read(clnt_sock, msg, sizeof(msg))) != 0)
		send_msg(msg, str_len);
	pthread_mutex_lock(&mutx);
	for (i = 0; i < clnt_cnt; i++)
		// remove disconnected client
	{
	}
	if (clnt_sock == clnt_socks[i])
	{
		while (i++ < clnt_cnt - 1)
			clnt_socks[i] = clnt_socks[i + 1];
		break;
	}
	clnt_cnt--;
	pthread_mutex_unlock(&mutx);
	close(clnt_sock);
	return NULL;
}
void send_msg(char* msg, int len)
{
	int i;
	// send to all
	pthread_mutex_lock(&mutx);
	for (i = 0; i < clnt_cnt; i++)
		write(clnt_socks[i], msg, len);
	pthread_mutex_unlock(&mutx);
}
void error_handling(char* msg)
{
	fputs(msg, stderr);
	fputc('\n', stderr);
	exit(1);
}

2.客户端:

一个主线程即可

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <pthread.h>
#define BUF_SIZE 100
#define NAME_SIZE 20
void* send_msg(void* arg);
void* recv_msg(void* arg);
void error_handling(char* msg);
char name[NAME_SIZE] = "[DEFAULT]";
char msg[BUF_SIZE];
int main(int argc, char* argv[])
{
	int sock;
	struct sockaddr_in serv_addr;
	pthread_t snd_thread, rcv_thread;
	void* thread_return;
	if (argc != 4) {
		printf("Usage : %s <IP> <port> <name>\n", argv[0]);
		exit(1);
	}
	sprintf(name, "[%s]", argv[3]);
	sock = socket(PF_INET, SOCK_STREAM, 0);
	memset(&serv_addr, 0, sizeof(serv_addr));
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
	serv_addr.sin_port = htons(atoi(argv[2]));
	if (connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)
		error_handling("connect() error");
	pthread_create(&snd_thread, NULL, send_msg, (void*)&sock);
	pthread_create(&rcv_thread, NULL, recv_msg, (void*)&sock);
	pthread_join(snd_thread, &thread_return);
	pthread_join(rcv_thread, &thread_return);
	close(sock);
	return 0;
}
void* send_msg(void* arg)
{
	int sock = *((int*)arg);
	// send thread main
	char name_msg[NAME_SIZE + BUF_SIZE];
	while (1)
	{
	}
	fgets(msg, BUF_SIZE, stdin);
	if (!strcmp(msg, "q\n") || !strcmp(msg, "Q\n"))
	{
		close(sock);
		exit(0);
	}
	sprintf(name_msg, "%s %s", name, msg);
	write(sock, name_msg, strlen(name_msg));
	return NULL;
}
void* recv_msg(void* arg)
{
	int sock = *((int*)arg);
	// read thread main
	char name_msg[NAME_SIZE + BUF_SIZE];
	int str_len;
	while (1)
	{
		str_len = read(sock, name_msg, NAME_SIZE + BUF_SIZE - 1);
		if (str_len == -1)
			return (void*)-1;
		name_msg[str_len] = 0;
		fputs(name_msg, stdout);
	}
	return NULL;
}
void error_handling(char* msg)
{
	fputs(msg, stderr);
	fputc('\n', stderr);
	exit(1);
}

2.2 select 多路io复用实现

1. 原理及步骤

 文件描述符:

一开始默认是空的,当我们创建socket或者文件时,就会从3开始分配文件描述符给相应的socket或者文件

2.select 函数 与fd_set

select: 

#include<sys/select.h>#include <sys/time.h>
int select(int maxfd, fd_set*readset, fd_set* writeset, fd_set*exceptset, const struct
timeval * timeout);

成功时返回大于0的值,失败时返回-1。

  • maxtfd :监视对象文件描述符数量
  • readset:用于检查可读性
  • writeset:用于检查可写性
  • exceptset:用于检查带外数据
  • timeout:一个指向timeval 结构的指针,用于决定select等待I/O的最长时间。如果为空将 一直等待。

fd_set结构体:

作用:用于表示一组文件描述符的集合,不要和文件描述符结构混淆

  • FD_ZERO(fdset*fdset)∶将 fdset 变量的所有位初始化为0。
  • FD_SET(int fd,fd set*fdset)∶在参数 fdset 指向的变量中注册文件描述符fd的信息。
  • FD_CLR(int fd,fdset*fdset)∶从参数 fdset 指向的变量中清除文件描述符fd的信息。
  • FD_ISSET(int fd,fd_set*fdset)∶若参数 fdset 指向的变量中包含文件描述符fd的信息,则 返回"真"。

3.select实现并发服务器 

主要步骤: 

1.由于serv_sock是最开始创建的,那么它一定是最大的文件描述符,且只创建了 serv_sock,我们将这个文件描述符添加到fd_map中(具有能够被select监听的资格,但这个监听和listen不是一个东西,如果相应文件描述符表示的sock有动作,那么select就会结束阻塞,开始后面的工作):

	FD_ZERO(&reads);
	FD_SET(serv_sock, &reads);
	fd_max = serv_sock;

2. 进入循环,我们将当前的所有得到的文件描述符都放入进行监听

if ((fd_num = select(fd_max + 1, &cpy_reads, 0, 0, &timeout)) == -1)
			break;
if (fd_num == 0)
			continue;

 3.遍历到最大文件描述符,通过FD_SET来判断哪些文件描述符是被激活的(导致select不阻塞的),同时记得处理时,要把这个文件描述符clear掉

for (i = 0; i < fd_max + 1; i++)
		{
			if (FD_ISSET(i, &cpy_reads))
			{
				if (i == serv_sock)
				{
					// connection request!
					adr_sz = sizeof(clnt_adr);
					clnt_sock =
						accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
					FD_SET(clnt_sock, &reads);
					if (fd_max < clnt_sock)
						fd_max = clnt_sock;
					printf("connected client: %d \n", clnt_sock);
				}
				else
				{
					// read message!
					str_len = read(i, buf, BUF_SIZE);
					if (str_len == 0)
						// close request!
					{
						FD_CLR(i, &reads);
						close(i);
						printf("closed client: %d \n", i);
					}
					else
					{
					}
				}
				write(i, buf, str_len);
			}
		}

完整代码如下: 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/select.h>
#define BUF_SIZE 100
void error_handling(char* buf);
int main(int argc, char* argv[])
{
	int serv_sock, clnt_sock;
	struct sockaddr_in serv_adr, clnt_adr;
	struct timeval timeout;
	fd_set reads, cpy_reads;
	socklen_t adr_sz;
	int fd_max, str_len, fd_num, i;
	char buf[BUF_SIZE];
	if (argc != 2) {
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}
	serv_sock = socket(PF_INET, SOCK_STREAM, 0);
	memset(&serv_adr, 0, sizeof(serv_adr));
	serv_adr.sin_family = AF_INET;
	serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
	serv_adr.sin_port = htons(atoi(argv[1]));
	if (bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)
		error_handling("bind() error");
	if (listen(serv_sock, 5) == -1)
		error_handling("listen() error");
	FD_ZERO(&reads);
	FD_SET(serv_sock, &reads);
	fd_max = serv_sock;
	while (1){
		cpy_reads = reads;
		timeout.tv_sec = 5;
		timeout.tv_usec = 5000;
		if ((fd_num = select(fd_max + 1, &cpy_reads, 0, 0, &timeout)) == -1)
			break;
		if (fd_num == 0)
			continue;
		for (i = 0; i < fd_max + 1; i++){
			if (FD_ISSET(i, &cpy_reads)){
				if (i == serv_sock){
					// connection request!
					adr_sz = sizeof(clnt_adr);
					clnt_sock =
						accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
					FD_SET(clnt_sock, &reads);
					if (fd_max < clnt_sock)
						fd_max = clnt_sock;
					printf("connected client: %d \n", clnt_sock);
				}
				else{
					// read message!
					str_len = read(i, buf, BUF_SIZE);
					// close request!
					if (str_len == 0){
						FD_CLR(i, &reads);
						close(i);
						printf("closed client: %d \n", i);
					}
					else{
						write(i, buf, str_len);
					}
				}
				
			}
		}
	}
	close(serv_sock);
	return 0;
}
void error_handling(char* buf)
{
	fputs(buf, stderr);
	fputc('\n', stderr);
	exit(1);
}

4.客户端

客户端代码和一般客户端无异: 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 1024
void error_handling(char* message);
int main(int argc, char* argv[])
{
	int sock;
	char message[BUF_SIZE];
	int str_len;
	struct sockaddr_in serv_adr;
	if (argc != 3) {
		printf("Usage : %s <IP> <port>\n", argv[0]);
		exit(1);
	}
		sock = socket(PF_INET, SOCK_STREAM, 0);
	if (sock == -1)
		error_handling("socket() error");
	memset(&serv_adr, 0, sizeof(serv_adr));
	serv_adr.sin_family = AF_INET;
	serv_adr.sin_addr.s_addr = inet_addr(argv[1]);
	serv_adr.sin_port = htons(atoi(argv[2]));
		if (connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)
			error_handling("connect() error!");
		else
			puts("Connected...........");
	while (1)
	{
		fputs("Input message(Q to quit): ", stdout);
		fgets(message, BUF_SIZE, stdin);
		if (!strcmp(message, "q\n") || !strcmp(message, "Q\n"))
			break;
		write(sock, message, strlen(message));
		str_len = read(sock, message, BUF_SIZE - 1);
		message[str_len] = 0;
		printf("Message from server: %s", message);
	}
	close(sock);
	return 0;
}
void error_handling(char* message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

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

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

相关文章

云原生应用测试:挑战与方法

&#x1f60f;作者简介&#xff1a;博主是一位测试管理者&#xff0c;同时也是一名对外企业兼职讲师。 &#x1f4e1;主页地址&#xff1a;【Austin_zhai】 &#x1f646;目的与景愿&#xff1a;旨在于能帮助更多的测试行业人员提升软硬技能&#xff0c;分享行业相关最新信息。…

Linux进程 ----- 信号处理

前言 从信号产生到信号保存&#xff0c;中间经历了很多&#xff0c;当操作系统准备对信号进行处理时&#xff0c;还需要判断时机是否 “合适”&#xff0c;在绝大多数情况下&#xff0c;只有在 “合适” 的时机才能处理信号&#xff0c;即调用信号的执行动作。 一、信号的处理…

万界星空科技MES系统,实现数字化智能工厂

万界星空科技帮助制造型企业解决生产过程中遇到的生产过程不透明&#xff0c;防错成本高&#xff0c;追溯困难&#xff0c;品质不可控&#xff0c;人工效率低下&#xff0c;库存积压&#xff0c;交期延误等问题&#xff0c;从而达到“降本增效”的目标。打通各个信息孤岛&#…

Python性能测试框架Locust实战教程

01、认识Locust Locust是一个比较容易上手的分布式用户负载测试工具。它旨在对网站&#xff08;或其他系统&#xff09;进行负载测试&#xff0c;并确定系统可以处理多少个并发用户&#xff0c;Locust 在英文中是 蝗虫 的意思&#xff1a;作者的想法是在测试期间&#xff0c;放…

推荐一个 Obsidian 的 ChatGPT 插件

源码地址&#xff1a;https://github.com/nhaouari/obsidian-textgenerator-plugin Text Generator 是目前我使用过的最好的 Obsidian 中的 ChatGPT 功能插件。它旨在智能生成内容&#xff0c;以便轻松记笔记。它不仅可以在 Obsidian 中直接使用 ChatGPT&#xff0c;还提供了优…

Vue+SpringBoot打造衣物搭配系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、研究内容2.1 衣物档案模块2.2 衣物搭配模块2.3 衣物收藏模块 三、系统设计3.1 用例设计3.2 E-R图设计3.3 数据库设计3.3.1 衣物档案表3.3.2 衣物搭配表3.3.3 衣物收藏表 四、系统实现4.1 登录页4.2 衣物档案模块4.3 衣物搭配模块4.4…

力扣用例题:2的幂

此题的解题方法在于根据用例调整代码 bool isPowerOfTwo(int n) {if(n1){return true;}if(n<0){return false;}while(n>2){if(n%21){return false;}nn/2; }if(n1){return false;}return true;}

RDMA内核态函数ib_post_send()源码分析

最近调用linux内核下RDMA的Verb API ib_post_send()出现了问题&#xff0c;因此从源码分析一下这个函数的调用过程。 我使用的内核版本为5.15.0-94 这是函数ib_post_send的头文件定义&#xff0c;这个函数的意义是向发送队列提交发送请求&#xff0c;他会调用qp对应设备的post_…

C# EF Core迁移数据库

现象&#xff1a; 在CodeFirst时&#xff0c;先写字段与表&#xff0c;创建数据库后&#xff0c;再添加内容 但字段与表会变更&#xff0c;比如改名删除增加等 需求&#xff1a; 当表字段变更时&#xff0c;同时变更数据库&#xff0c;执行数据库迁移 核心命令 Add-Migrat…

一种基于道路分类特性的超快速车道检测算法

摘要&#xff1a; 本文介绍了一种新颖、简单但有效的车道检测公式。 车道检测是自动驾驶和高级驾驶员辅助系统 (ADAS) 的基本组成部分&#xff0c;在实际高阶驾驶辅助应用中&#xff0c;考虑车道保持、转向、限速等相关的控制问题&#xff0c;这种方式通常是通过受限的车辆计算…

java——多线程基础

目录 线程的概述多线程的创建方式一&#xff1a;继承Thread类方式二&#xff1a;实现Runnable接口方式三&#xff1a;利用Callable接口、FutureTask类来实现。Thread常用的方法 线程安全问题线程安全问题概述线程安全问题案例取钱案例描述模拟代码如下&#xff1a;执行结果 线程…

2024-02-25 Unity 编辑器开发之编辑器拓展7 —— Inspector 窗口拓展

文章目录 1 SerializedObject 和 SerializedProperty2 自定义显示步骤3 数组、List 自定义显示3.1 基础方式3.2 自定义方式 4 自定义属性自定义显示4.1 基础方式4.2 自定义方式 5 字典自定义显示5.1 SerizlizeField5.2 ISerializationCallbackReceiver5.3 代码示例 1 Serialize…

【Activiti7系列】Activi7简介和基于Spring Boot整合Activiti7(流程设计器)

本文将介绍Activiti7基础概念及基于Spring Boot整合Activiti7(流程设计器)的具体步骤。 作者&#xff1a;后端小肥肠 1. 前言 在企业级应用中&#xff0c;业务流程的管理和执行是至关重要的一环。Activiti7是一个强大的开源工作流引擎&#xff0c;它提供了灵活的流程定义、任务…

linux---安使用nginx

目录 一、编译安装Nginx 1、关闭防火墙&#xff0c;将安装nginx所需要软件包传到/opt目录下 ​编辑2、安装依赖包 3、创建运行用户、组 4、编译安装nginx 5、创建软链接后直接nginx启动 ​编辑 6、创建nginx自启动文件 ​编辑6.1 重新加载配置、设置开机自启并开启服务…

Kafka之Producer源码

Producer源码解读 在 Kafka 中, 我们把产生消息的一方称为 Producer 即 生产者, 它是 Kafka 的核心组件之一, 也是消息的来源所在。它的主要功能是将客户端的请求打包封装发送到 kafka 集群的某个 Topic 的某个分区上。那么这些生产者产生的消息是怎么传到 Kafka 服务端的呢&a…

unity发布webGL压缩方式的gzip,使用nginx作为web服务器时的配置文件

unity发布webGL压缩方式的gzip&#xff0c;使用nginx作为web服务器时的配置文件 Unity版本是&#xff1a;2021.3 nginx的版本是&#xff1a;nginx-1.25.4 Unity发布webgl时的测试 设置压缩方式是gzip nginx配置文件 worker_processes 1;events {worker_connections 102…

SpringBoot实现热插拔AOP

热插拔AOP执行核心逻辑 Advice&#xff1a;“通知”&#xff0c;表示 Aspect 在特定的 Join point 采取的操作。包括 “around”, “before” and “after 等 Advice&#xff0c;大体上分为了三类&#xff1a;BeforeAdvice、MethodInterceptor、AfterAdviceAdvisor&#xff1a…

STM32存储左右互搏 QSPI总线FATS文件读写FLASH W25QXX

STM32存储左右互搏 QSPI总线FATS文件读写FLASH W25QXX FLASH是常用的一种非易失存储单元&#xff0c;W25QXX系列Flash有不同容量的型号&#xff0c;如W25Q64的容量为64Mbit&#xff0c;也就是8MByte。这里介绍STM32CUBEIDE开发平台HAL库Quad SPI总线实现FATS文件操作W25Q各型号…

智能SQL生成:后端技术与LLM的完美结合

文章目录 引言一、什么是大模型二、为什么选择LLM三、开发技术说明四、系统架构说明五、编码实战1. Maven2. 讯飞大模型配置类3. LLM相关的封装4. 编写LLM的service5. 编写controller6. 运行测试 六、总结 引言 本篇文章主要是关于实现一个类似Chat2DB的根据自然语言生成SQL的…

SpringMVC 学习(四)之获取请求参数

目录 1 通过 HttpServletRequest 获取请求参数 2 通过控制器方法的形参获取请求参数 3 通过 POJO 获取请求参数&#xff08;重点&#xff09; 1 通过 HttpServletRequest 获取请求参数 public String handler1(HttpServletRequest request) <form action"${pageCont…