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

Linux socket网络编程实战(tcp)实现双方聊天

在上节已经系统介绍了大致的流程和相关的API,这节就开始写代码!

回顾上节的流程:

创建一个NET文件夹 来存放网络编程相关的代码:

tcp服务端代码初步实现--上

这部分先实现服务器的连接部分的代码并进行验证

server1.c:

#include <sys/types.h>     
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <linux/in.h>
#include <string.h>int main()
{int sockfd;int conn_sockfd;int ret;struct sockaddr_in my_addr;struct sockaddr_in client_addr;memset(&my_addr,0,sizeof(struct sockaddr_in));memset(&client_addr,0,sizeof(struct sockaddr_in));//socketsockfd = socket(AF_INET,SOCK_STREAM,0);if(sockfd == -1){perror("socket");return 1;}else{printf("socket success, sockfd = %d\n",sockfd);}//bindmy_addr.sin_family = AF_INET;my_addr.sin_port = htons(8888);//host to net (2 bytes)inet_aton("192.168.20.137",&my_addr.sin_addr); //char* format -> net formatret = bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr_in));if(ret == -1){perror("bind");return 1;}else{printf("bind success\n");}//listenret = listen(sockfd,10);if(ret == -1){perror("listen");return 1;}else{printf("listening...\n");}//acceptint len = sizeof(struct sockaddr_in);conn_sockfd = accept(sockfd,(struct sockaddr *)&client_addr,&len);if(conn_sockfd == -1){perror("accept");return 1;}else{printf("accept success, client IP = %s\n",inet_ntoa(client_addr.sin_addr));//将网络格式的IP地址再转回字符串}//read//write//read//closereturn 0;
}

代码验证:

先编译并运行这部分代码:

可见,此时没有客户端进行连接,程序会阻塞在监听的阶段

此时打开windows的cmd(windows系统和linux虚拟机的系统可以看作两台不同的终端

telnet指令使用的也是TCP协议

执行这条命令后,windows的cmd变成了这样:

再反观linux虚拟机:

使用windows的ipconfig可以验证IP地址 :

所以,连接部分的代码已经成功,只是因为没有接下来的数据传输所以退出了。 

tcp服务端代码初步实现--下

这部分实现服务器的连接成功后的读写并验证

server1.c:

#include <sys/types.h>     
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <linux/in.h>
#include <string.h>int main()
{int sockfd;int conn_sockfd;int ret;int n_read;int n_write;char readbuf[1024];struct sockaddr_in my_addr;struct sockaddr_in client_addr;memset(&my_addr,0,sizeof(struct sockaddr_in));memset(&client_addr,0,sizeof(struct sockaddr_in));//socketsockfd = socket(AF_INET,SOCK_STREAM,0);if(sockfd == -1){perror("socket");return 1;}else{printf("socket success, sockfd = %d\n",sockfd);}//bindmy_addr.sin_family = AF_INET;my_addr.sin_port = htons(8888);//host to net (2 bytes)inet_aton("192.168.20.137",&my_addr.sin_addr); //char* format -> net formatret = bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr_in));if(ret == -1){perror("bind");return 1;}else{printf("bind success\n");}//listenret = listen(sockfd,10);if(ret == -1){perror("listen");return 1;}else{printf("listening...\n");}//acceptint len = sizeof(struct sockaddr_in);conn_sockfd = accept(sockfd,(struct sockaddr *)&client_addr,&len);if(conn_sockfd == -1){perror("accept");return 1;}else{printf("accept success, client IP = %s\n",inet_ntoa(client_addr.sin_addr));}//readn_read = read(conn_sockfd,&readbuf,1024);if(n_read == -1){perror("read");return 1;}else{printf("%d bytes has been read, context = %s\n",n_read,readbuf);}//writechar *msg = "this is server, I have received your msg\n";n_write = write(conn_sockfd,msg,strlen(msg));if(n_write == -1){perror("write");return 1;}else{printf("%d bytes has been written\n",n_write);}//read//closereturn 0;
}

代码验证:

这部分如果用windows的telnet,打一个符号就会直接结束,所以用Linux另开一个cmd使用telnet来模拟服务器和客户端的对话:

还是先运行代码:

然后运行telnet:

反观服务端:

 

在连接成功后客户端输入一句话然后回车:

客户端显示:

服务器显示:

可见,数据的交互基本没什么问题。

至此,构建了一个大致的服务器代码框架,可以开始着手编写客户端的代码了。

客户端代码初步实现

client.c:

#include <sys/types.h>     
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <linux/in.h>
#include <string.h>int main()
{int sockfd;int ret;int n_read;int n_write;char readbuf[1024];struct sockaddr_in server_addr;memset(&server_addr,0,sizeof(struct sockaddr_in));//socketsockfd = socket(AF_INET,SOCK_STREAM,0);if(sockfd == -1){perror("socket");return 1;}else{printf("socket success, sockfd = %d\n",sockfd);}//connectserver_addr.sin_family = AF_INET;server_addr.sin_port = htons(8888);//host to net (2 bytes)inet_aton("192.168.20.137",&server_addr.sin_addr); ret = connect(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in));if(ret == -1){perror("connect");return 1;}else{printf("connect success!\n");}//writechar *msg = "client: hello\n";n_write = write(sockfd,msg,strlen(msg));if(n_write == -1){perror("write");return 1;}else{printf("%d bytes has been written\n",n_write);}//readn_read = read(sockfd,&readbuf,1024);if(n_read == -1){perror("read");return 1;}else{printf("%d bytes has been read, context = %s\n",n_read,readbuf);}//closereturn 0;
}

代码验证:

先编译并允许服务端:

再编译并运行客户端:

 回看服务端:

 

客户端 & 服务端 代码的最终实现

之前,已经大致编写出了客户端和服务端的代码,但是和本节开头的框图对比,发现还差一些,首先,数据的发送和读取应该持续进行,直到收到结束信号,另外在最后要编写关闭套接字,关闭连接的代码,且服务器应该可以接受多个客户端的数据,还有一些细节的优化

回顾之前关于fork函数的一节:进程的创建_mjmmm的博客-CSDN博客

 所以此处可以使用fork函数!

小知识点:自动对齐(gg=G)

(参考:linux代码对齐快捷键和man帮助文档的使用总结_陌上花开缓缓归以的博客-CSDN博客)

在命令模式下(即非“插入”等编辑模式),先输入gg,这时候光标会移动到第一行第一个字符,然后按 “=” 号之后切换成大写,再按一下G,这时候光标会移到最后一行的第一个字符,这时候就可以看到代码被排得整整齐齐了!
“gg"将光标移动到代码首部,”="表示对齐指令,"G"表示代码尾部,所以执行"gg=G"后,该文件的所有代码都将对齐!

实现思路

服务端:

在listen之后,进入一个while(1)循环,并调用accept阻塞,一旦连接到一个客户端,就fork一个子进程来处理数据,父进程则继续通过while(1)继续调用accept阻塞等待下一个客户端连接。

而子进程中再次调用fork,父进程写一个while(1)不断的写数据,子进程写一个while(1)不断的读数据。

客户端:

在connect之后进行fork,父进程写一个while(1)不断的写数据,子进程写一个while(1)不断的读数据。

如何退出:

我希望的退出方式是客户端输入“quit”就会退出,但是不管是客户端还是服务端,为了读和写不会相互阻塞,都在不同的进程中的while(1)里,当客户端输入“quit”之后,只有客户端的写端和服务器的读端知道,客户端的读端和服务器的写端并不知情,所以需要使用进程间的通讯,此处我使用了FIFO


在客户端中创建FIFO并在不断写数据的父进程不断检测是否输入了“quit”如果是就只写打开fifo,并阻塞等待....一旦等待到了有进程只读打开FIFO,就会往FIFO写入“quit”,然后关闭FIFO;关闭套接字;收集子进程退出状态;然后退出循环;正常退出。 同时在不断读数据的子进程不断非阻塞的只读打开fifo,并每次都将光标移到最开头,一旦从FIFO读取到了“quit”,就exit。


但是,不断读数据的子进程会阻塞读取服务器传来(写入)的数据,这就导致,当客户端输入“quit”之后无法立刻退出,而是要等到服务器再发来消息,才能进行下一轮的FIFO读取,才能使得子进程收到父进程通过FIFO发来的“quit”并退出。解决办法就是:在服务器端中一旦检测到客户端发来的消息是quit之后,就立刻给客户端发送一句话


此时,对于客户端来说输入了“quit”之后会立刻退出,但是服务端只有读端的while可以退出,写端的while无法退出,此时就有一个疑问“我在读端关闭了客户端套接字,照理说写端应该往这个套接字里写会报错,我直接在报错处理函数里退出写端不就行了”,但其实这是行不通的,因为文件描述符的作用域默认情况下只在进程内有效,而无法在进程之间进行传递。所以还是需要使用FIFO,且注意,FIFO是进程与进程间的,客户端和服务端本质也属于两个进程,所以服务端如果要使用FIFO应该在mkfifo函数中对于FIFO的名字修改,不要和服务端的FIFO重名


在服务器端创建另一个FIFO并在不断读数据的子进程不断判断是否从客户端收到了“quit”,如果收到了就立刻回复(写)一个“Bye”(也就是为了让客户端能立刻退出);只写打开FIFO,并阻塞等待....一旦等待到了有进程只读打开FIFO,就会往FIFO写入“quit”,然后exit。 同时在不断写数据的父进程不断非阻塞的只读打开fifo,并每次都将光标移到最开头,一旦从FIFO读取到了“quit”,就关闭FIFO;关闭客户端的套接字;收集子进程退出状态;然后退出循环;执行fork之后的exit。

程序框图:

server_final.c:

#include <sys/types.h>     
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <linux/in.h>
#include <string.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>int main(int argc, char **argv)
{int conn_num = 0;int flag = 0;int sockfd;int conn_sockfd;int ret;int n_read;int n_write;int len = sizeof(struct sockaddr_in);char readbuf[128];char msg[128];int fd; //fifochar fifo_readbuf[20] = {0};char *fifo_msg = "quit";pid_t fork_return;pid_t fork_return_1;struct sockaddr_in my_addr;struct sockaddr_in client_addr;memset(&my_addr,0,sizeof(struct sockaddr_in));memset(&client_addr,0,sizeof(struct sockaddr_in));if(argc != 3){printf("param error!\n");return 1;}//socketsockfd = socket(AF_INET,SOCK_STREAM,0);if(sockfd == -1){perror("socket");return 1;}else{printf("socket success, sockfd = %d\n",sockfd);}//bindmy_addr.sin_family = AF_INET;my_addr.sin_port = htons(atoi(argv[2]));//host to net (2 bytes)inet_aton(argv[1],&my_addr.sin_addr); //char* format -> net formatret = bind(sockfd, (struct sockaddr *)&my_addr, len);if(ret == -1){perror("bind");return 1;}else{printf("bind success\n");}//listenret = listen(sockfd,10);if(ret == -1){perror("listen");return 1;}else{printf("listening...\n");}//fifoif(mkfifo("./fifo1",S_IRWXU) == -1 && errno != EEXIST){perror("fifo");}while(1){//acceptconn_sockfd = accept(sockfd,(struct sockaddr *)&client_addr,&len);if(conn_sockfd == -1){perror("accept");return 1;}else{conn_num++;if(conn_num > 1){printf("there are more then one client, msg may not be sent accuratly!\n");}printf("accept success, no.%d client IP = %s\n",conn_num,inet_ntoa(client_addr.sin_addr));}fork_return = fork();if(fork_return > 0){//father keeps waiting for new request//wait(NULL); //cant wait,will block	}else if(fork_return < 0){perror("fork");return 1;}else{//son deals with requestfork_return_1 = fork();if(fork_return_1 > 0){//father keeps writing msgwhile(1){fd = open("./fifo1",O_RDONLY|O_NONBLOCK);lseek(fd, 0, SEEK_SET);read(fd,&fifo_readbuf,20);//printf("read from fifo:%s\n",fifo_readbuf);if(fifo_readbuf[0]=='q' && fifo_readbuf[1]=='u' && fifo_readbuf[2]=='i' && fifo_readbuf[3]=='t'){printf("sorry,the last msg sent fail,client has quit\n");close(fd);close(conn_sockfd);wait(NULL);break;}//writememset(&msg,0,sizeof(msg));//printf("\ntype msg:");scanf("%s",(char *)msg);n_write = write(conn_sockfd,&msg,strlen(msg));if(n_write == -1){perror("write");return 1;}else{printf("%d bytes msg sent\n",n_write);}}}else if(fork_return_1 < 0){perror("fork");return 1;}else{//son keeps reading msgwhile(1){//readmemset(&readbuf,0,sizeof(readbuf));n_read = read(conn_sockfd,&readbuf,128);if(readbuf[0]=='q' && readbuf[1]=='u' && readbuf[2]=='i' && readbuf[3]=='t'){printf("client quit\n");conn_num--;printf("%d client remain\n",conn_num);write(conn_sockfd,"BYE",3);fd = open("./fifo1",O_WRONLY);write(fd,fifo_msg,strlen(fifo_msg));exit(1);}if(n_read == -1){perror("read");return 1;}else{printf("\nclient: %s\n",readbuf);}}}exit(2);}}return 0;
}

client_final.c:

#include <sys/types.h>     
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <linux/in.h>
#include <string.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>int main(int argc, char **argv)
{int sockfd;int ret;int n_read;int n_write;char readbuf[128];char msg[128];int fd; //fifochar fifo_readbuf[20] = {0};char *fifo_msg = "quit";pid_t fork_return;if(argc != 3){printf("param error!\n");return 1;}struct sockaddr_in server_addr;memset(&server_addr,0,sizeof(struct sockaddr_in));//socketsockfd = socket(AF_INET,SOCK_STREAM,0);if(sockfd == -1){perror("socket");return 1;}else{printf("socket success, sockfd = %d\n",sockfd);}//connectserver_addr.sin_family = AF_INET;server_addr.sin_port = htons(atoi(argv[2]));//host to net (2 bytes)inet_aton(argv[1],&server_addr.sin_addr); ret = connect(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in));if(ret == -1){perror("connect");return 1;}else{printf("connect success!\n");}//fifoif(mkfifo("./fifo",S_IRWXU) == -1 && errno != EEXIST){perror("fifo");}//forkfork_return = fork();if(fork_return > 0){//father keeps writing msgwhile(1){//writememset(&msg,0,sizeof(msg));//printf("\ntype msg:");scanf("%s",(char *)msg);n_write = write(sockfd,&msg,strlen(msg));if(msg[0]=='q' && msg[1]=='u' && msg[2]=='i' && msg[3]=='t'){printf("quit detected!\n");fd = open("./fifo",O_WRONLY);write(fd,fifo_msg,strlen(fifo_msg));close(fd);close(sockfd);wait(NULL);break;}if(n_write == -1){perror("write");return 1;}else{printf("%d bytes msg sent\n",n_write);}}}else if(fork_return < 0){perror("fork");return 1;}else{//son keeps reading while(1){fd = open("./fifo",O_RDONLY|O_NONBLOCK);lseek(fd, 0, SEEK_SET);read(fd,&fifo_readbuf,20);//printf("read from fifo:%s\n",fifo_readbuf);if(fifo_readbuf[0]=='q' && fifo_readbuf[1]=='u' && fifo_readbuf[2]=='i' && fifo_readbuf[3]=='t'){exit(1);}//readmemset(&readbuf,0,sizeof(readbuf));n_read = read(sockfd,&readbuf,128);if(n_read == -1){perror("read");return 1;}else{printf("\nserver: %s\n",readbuf);}}}return 0;
}

实现效果:

编译并运行server端:

编译并运行client端:

此时回看server端:

 

此时,就可以实现双方聊天的功能了:(服务器左,客户端右)

为什么不打成hows it going而是hows/it/going,原因是scanf函数,如果占位符是“%s”即字符串时,空格和换行键都会被视为结束符号!

(详见:scanf与空格_scanf读空格_CSU迦叶的博客-CSDN博客

所以如果输入hows it going的话:

解决方法是使用fgets函数

(详见:如何读取带空格的字符串?

直到客户端打出“quit” :

客户端会立刻退出,服务器此时只有读端知道客户端退出了

必须再打一句话让服务器写端刷新,这样才能从FIFO读取到信息,让写端也知道客户端退出了:

此时,服务器的读写端也全部退出,再次进入阻塞状态,等待新连接...

下面模拟如果同时有两个或以上连接的时候:(左侧一个服务器,右侧两个客户端) 

 我代码执行的顺序是:连接第一个客户端(右上)--> 服务器发送“hi” --> 连接第二个客户端(右下)--> 服务器发送“hihi” --> 服务器发送“hihihi” --> 服务器发送“hihihihi” 

可见,连接第一个客户端的时候,服务器发送hi准确的到了右上的客户端1,但是当连接第二个客户端了之后,服务器分别发送了“hihi”,“hihihi” ,“hihihihi” ,从服务端看没有任何区别,但实际情况下“hihi”和“hihihihi”到了客户端1,“hihihi”到了客户端2,明显出现了混乱,所以我在代码中设置了提醒,检测到大于1个客户端接入时会提醒。

问题探讨 和 一些思路过程

  • 如刚刚所说,服务器用了两次fork,相当于有3个独立的进程,所以我的conn_num变量的设置实际上是相当残疾的,因为我把conn_num-- 放在了一个子进程里,而fork之后的变量空间都是独立的,所以我的conn_num变量只要有客户端推出就不准了,应该也使用进程间通信来通知,但是鉴于我的代码实现目的本来就是双人聊天,所以多客户端的连接部分就没有深入修改了
  • 在我目前的代码逻辑中,我本来觉得只设置了一个conn_sockfd变量来存放客户端的套接字不合理,因为当多个客户端接入的时候,套接字可能会被覆盖,导致读写异常,但是其实一个就够了,原因也是因为fork之后变量空间也会被复制,根据我的代码逻辑,每出现一个新连接,就会fork一个子进程来处理这个连接的读写,如果有多个客户端,就会有多个子进程,里面的套接字变量名都是conn_sockfd,但其实值是不一样的。
  • 并且,服务端的第一个fork,父进程不能调用wait,因为wait会阻塞直到子进程退出,但我希望父进程不被阻塞而一直while循环等待新连接,所以第一次fork生成的子进程在客户端退出之后会变成僵尸进程,且每有一个新的客户端退出就会多一个僵尸进程,在当前的逻辑下不可避免。

且,当多个客户端连接服务器的时候,服务器会针对每个客户端fork一个子进程来处理,而每一个子进程都会再fork一个读端不停的scanf检测输入

但是进程与进程之间是竞争的关系,所以在cmd中看来,光标一直在闪没有变化,但实际上,可能上一秒这是客户端1对应子进程的scanf,下一秒就变成了客户端2对应子进程的scanf

这就导致了如果此时对着光标输入消息并回车,无法确定收到消息的是客户端1还是客户端2。

  • 解决办法1:使用线程,实现真正的多方数据收发,但是难度很大,需要更多的新知识
  • 解决办法2:舍弃服务器的scanf功能,改为自动回复,这样可以实现多个客户端对服务器的自定义消息发送,但是服务器只能回复预设的内容
  • 解决办法3:(也就是现在实现的效果)舍弃多方发送,只用一个客户端,这样就可以实现客户端和服务器的自定义消息交互,但是此时不能增加更多的客户端

相关文章:

Linux socket网络编程实战(tcp)实现双方聊天

在上节已经系统介绍了大致的流程和相关的API&#xff0c;这节就开始写代码&#xff01; 回顾上节的流程&#xff1a; 创建一个NET文件夹 来存放网络编程相关的代码&#xff1a; tcp服务端代码初步实现--上 这部分先实现服务器的连接部分的代码并进行验证 server1.c&#xff…...

BuhoCleaner for mac:让你的Mac重获新生

你是否曾经因为电脑运行缓慢而感到困扰&#xff1f;是否曾经因为大量的垃圾文件和无效的临时文件而感到头疼&#xff1f;如果你有这样的烦恼&#xff0c;那么BuhoCleaner for mac就是你的救星&#xff01; BuhoCleaner for mac是一款专门为Mac用户设计的系统清理工具&#xff…...

陶氏公司将出席2023第二届中国汽车碳中和峰会

2023第二届中国汽车碳中和峰会将于10月19日-20日在上海举办。 本次峰会将为行业领导者、政策制定者和专家提供一个平台&#xff0c;讨论汽车行业减少碳排放的策略。专家们将从政策、供应链、ESG、替代能源解决方案、汽车材料创新、法律等不同领域分享碳中和与可持续策略。 通…...

【linux命令讲解大全】051.Linux Awk脚本语言中的字段定界符和流程控制

文章目录 设置字段定界符流程控制语句条件判断语句循环语句while语句for循环do循环 其他语句 数组应用数组的定义读取数组的值数组相关函数二维、多维数组使用 从零学 python 设置字段定界符 默认的字段定界符是空格&#xff0c;可以使用-F “定界符” 明确指定一个定界符&…...

Gradle下载安装教程

1、Gradle 入门 1.1、Gradle 简介 Gradle 是一款Google 推出的基于 JVM、通用灵活的项目构建工具&#xff0c;支持 Maven&#xff0c;JCenter 多种第三方仓库;支持传递性依赖管理、废弃了繁杂的xml 文件&#xff0c;转而使用简洁的、支持多种语言(例如&#xff1a;java、groo…...

Python 之 match 表达式

Python 从 3.10 版本开始增加了 match 语句&#xff0c;和其他语言常见的 switch 语句极其相似&#xff0c;但功能更加强大。 本文通过实例&#xff0c;了解下其用法。 基本的 match 语句 def http_code(status): match status: case 400 | 404 | 418: …...

.NET Framework 微软官方下载地址

微软官方下载地址&#xff1a; 下载 .NET Framework | 免费官方下载 (microsoft.com) 版本发布日期终止支持.NET Framework 4.8.12022年8月9日.NET Framework 4.82019年4月18日.NET Framework 4.7.22018年4月30日.NET Framework 4.7.12017年10月17日.NET Framework 4.72017年…...

OpenCV(十四):ROI区域截取

在OpenCV中&#xff0c;你可以使用Rect对象或cv::Range来截取图像的感兴趣区域&#xff08;Region of Interest&#xff0c;ROI&#xff09;。 方法一&#xff1a;使用Rect对象截取图像 Rect_(_Tp _x&#xff0c; _Tp _y&#xff0c; _Tp _width,_Tp _height) Tp:数据类型&…...

Java学习笔记之----I/O(输入/输出)一

在变量、数组和对象中存储的数据是暂时存在的&#xff0c;程序结束后它们就会丢失。想要永久地存储程序创建的数据&#xff0c;就需要将其保存在磁盘文件中(就是保存在电脑的C盘或D盘中&#xff09;&#xff0c;而只有数据存储起来才可以在其他程序中使用它们。Java的I/O技术可…...

介绍GitHub

GitHub 是一个基于互联网的源代码托管平台&#xff0c;可以帮助软件开发者存储和管理源代码&#xff0c;方便团队协作和版本控制。GitHub 的主要功能包括&#xff1a; 代码托管&#xff1a;开发者可以在 GitHub 上创建远程代码仓库&#xff0c;存储和管理他们的源代码。 版本控…...

js常用的循环遍历

1.while 循环 While语句包括一个循环条件和一段代码块&#xff0c;只要条件为真&#xff0c;就不断循环执行代码块。 while (条件) 语句; // 或者while (条件) 语句;//举例&#xff1a; var i 0; while (i < 100) { console.log(i 当前为&#xff1a; i); i i 1; }2.…...

存量时代的面经

spring-series,感谢这个项目的作者,还有springboot_learn 这个项目 Spring, 说来惭愧,接触spring 已经接近十年了 通过这个项目,我把spring重写的学习了一遍 1,早上先debug 的一个项目的源码是 springboot_security 在这里插入代码片/****: Creating filter chain:org…...

Python入门教程 | Python 流程控制语句

​ 程序流程控制的三种结构 1.顺序结构 计算机执行程序的步骤是从上到下依次执行 2.选择结构&#xff08;条件控制&#xff09; Python 条件语句是通过一条或多条语句的执行结果&#xff08;True 或者 False&#xff09;来决定执行的代码块。 可以通过下图来简单了解条件…...

力扣:83. 删除排序链表中的重复元素(Python3)

题目&#xff1a; 给定一个已排序的链表的头 head &#xff0c; 删除所有重复的元素&#xff0c;使每个元素只出现一次 。返回 已排序的链表 。 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚…...

iKeyPrime完美解4G信号,可以登录iCloud,有消息通知,支持最新iOS16.6。

iKeyPrime是一款绕过激活锁界面的解锁工具&#xff0c;可以激活所有iPhone苹果手机&#xff0c;二网/三网恢复信号&#xff0c;并且支持插卡接打电话、收发短信、4G流量上网&#xff0c;支持iCloud登录&#xff0c;有消息通知&#xff0c;支持iPhone5S~X的所有型号&#xff0c;…...

micro python 编译流程和方法,以及一部分问题解决

micro python官网 https://micropython.org/ 点击 点击对应的芯片&#xff0c;我这里是ESP32-S3 点击到git 到esp32目录下 按照指引下载安装ESP-IDF IDF版本查看连接如下&#xff1a; https://docs.espressif.com/projects/esp-idf/en/latest/esp32/versions.html 我这里选择…...

AI篇-chatgpt基本用法(文心一言也适用)

目录 &#xff08;1&#xff09;基本规则 &#xff08;2&#xff09;例子1-文章摘要 &#xff08;3&#xff09;例子2-代码生成 &#xff08;4&#xff09;文心一言链接 &#xff08;1&#xff09;基本规则 相比于搜索引擎&#xff0c;ChatGPT的优势在于其高效的想法关联和…...

Python小知识 - 使用Python进行数据分析

使用Python进行数据分析 数据分析简介 数据分析&#xff0c;又称为信息分析&#xff0c;是指对数据进行综合处理、归纳提炼、概括总结的过程&#xff0c;是数据处理的第一步。 数据分析的目的是了解数据的内在规律&#xff0c;为数据挖掘&#xff0c;并应用于商业决策、科学研究…...

Pytorch.tensor 相关用法

Torch.tensor.backward()方法的使用举例 理解optimizer.zero_grad(), loss.backward(), optimizer.step()的作用及原理 Autograd: 自动求导 import torchatorch.randn(2,2) # tensor默认requires_gradFalse a((a*3)/(a-1)) print(a.requires_grad) print(a.grad_fn) # leaf …...

信维通信投资者关系活动:揭示5G创新实践,展望未来发展

信维通信于近期举办了一场面向投资者的关系活动&#xff0c;旨在揭示公司在5G时代的创新实践和展望未来发展计划。这场活动吸引了众多投资者和证券分析师的关注&#xff0c;他们期待了解信维通信在5G市场的布局和竞争优势。 在活动当天&#xff0c;信维通信的投资者关系部门负…...

AOP进阶-通知顺序

通知顺序 当多个切面的切入点都匹配到目标方法&#xff0c;目标方法运行时&#xff0c;多个通知都会被执行 执行顺序 不同切面类中&#xff0c;默认按照切面类的类名称字母排序 目标方法前的通知方法&#xff1a;字母排名靠前的先执行目标方法后的通知方法&#xff1a;字母排…...

Sui流动性质押黑客松|本周Workshop预告

Sui流动性质押黑客松正在如火如荼的报名中&#xff0c;Sui基金会现诚邀全球开发者前来参与&#xff0c;助力资产再流通。了解黑客松详情&#xff1a;Sui流动性质押黑客松开启报名&#xff0c;赢取千万美金质押和奖励&#xff01; 黑客松官网&#xff1a;Sui Liquid Staking Ha…...

数学建模:线性与非线性优化算法

&#x1f506; 文章首发于我的个人博客&#xff1a;欢迎大佬们来逛逛 数学建模&#xff1a;线性与非线性优化算法 优化算法是指在满足一定条件下,在众多方案中或者参数中最优方案,或者参数值,以使得某个或者多个功能指标达到最优,或使得系统的某些性能指标达到最大值或者最小…...

数学建模--粒子群算法(PSO)的Python实现

目录 1.开篇提示 2.算法流程简介 3.算法核心代码 4.算法效果展示 1.开篇提示 """ 开篇提示: 这篇文章是一篇学习文章,思路和参考来自:https://blog.csdn.net/weixin_42051846/article/details/128673427?utm_mediumdistribute.pc_relevant.none-task-blog-…...

【C++】STL-函数对象-内建函数对象

0.前言 1.算术仿函数 #include <iostream> using namespace std;// STL-内建函数对象-算术仿函数 #include<functional> // 内建函数对象头文件//以下举例两个仿函数 -- negate and plus// negate 一元仿函数 取反仿函数 void test01() {negate<int>n;cout …...

Redis 教程 - Redis 基本操作

Redis 教程 - Redis 基本操作 Redis&#xff08;Remote Dictionary Server&#xff09;是一个开源的内存数据库&#xff0c;它提供了键值对存储和多种数据结构的支持&#xff0c;被广泛应用于缓存、消息队列、计数器等场景。本教程将介绍 Redis 的基本操作&#xff0c;包括连接…...

FreeRTOS中断与任务之间同步(Error:..\..\FreeRTOS\portable\RVDS\ARM_CM4F\port.c,422 )

前言&#xff1a; FreeRTOS中&#xff0c;中断需要注意几点&#xff1a; 何时使用中断&#xff1b;中断服务函数&#xff08;ISR&#xff09;要处理的数据量有多大&#xff0c;通常我们希望中断的切换越快越好&#xff0c;也就是说&#xff0c;ISR尽量采用耗时较少的处理方式…...

WebRTC清晰度和流畅度

WebRTC清晰度和流畅度 flyfish WebRTC提供了4种模式DISABLED&#xff0c;MAINTAIN_FRAMERATE&#xff0c;MAINTAIN_RESOLUTION&#xff0c;BALANCED // Based on the spec in // https://w3c.github.io/webrtc-pc/#idl-def-rtcdegradationpreference. // These options are …...

华为数通方向HCIP-DataCom H12-821题库(单选题:181-200)

第181题 某管理员需要创建AS Path过滤器(ip as-path-iter),允许AS_Path中包含65001的路由通过,那么以下哪一项配置是正确的? A、​​ip as-path-filter 1 permit 65001​​ B、​​ip as-path-filter 1 permit "65001​​ C、​​ip as-path-filter 1 permit *6500…...

海康威视二次开发适配安卓电视盒子

收到一个需求&#xff0c;需要在安卓电视盒子上适配海康威视摄像头视频&#xff1a; 1.类似电视家app界面&#xff0c;左边滑动菜单显示通道列表、设置按钮&#xff0c;遥控器呼出菜单。 2.遥控器操作&#xff1a;切换视频通道、云台上下左右控制、缩放等。 3.服务器域名、用…...