# issue 8 TCP内部原理和UDP编程

news2025/1/15 7:37:58

TCP 通信三大步骤:
1 三次握手建立连接;
2 开始通信,进行数据交换;
3 四次挥手断开连接;

一、TCP内部原理--三次握手

【第一次握手】套接字A∶"你好,套接字B。我这儿有数据要传给你,建立连接吧。"
【第二次握手】套接字B∶"好的,我这边已就绪。"
【第三次握手】套接字A∶"谢谢你受理我的请求。"

首先,请求连接的主机A 向主机B 传递如下信息∶
[SYN] SEQ:1000, ACK: -
该消息中SEQ 为1000,ACK 为空,而SEQ 为1000 的含义如下∶
"现传递的数据包序号为1000,如果接收无误,请通知我向您传递1001 号数据包。"这是首
次请求连接时使用的消息,又称SYN。SYN 是Synchronization 的简写,表示收发数据前传输
的同步消息。
接下来主机B 向A 传递如下消息∶
[SYN+ACK]SEQ:2000, ACK:1001
此时SEQ 为2000,ACK 为1001,而SEQ 为2000 的含义如下∶
"现传递的数据包序号为2000 如果接收无误,请通知我向您传递2001 号数据包。"
而ACK1001 的含义如下∶
"刚才传输的SEQ 为1000 的数据包接收无误,现在请传递SEQ 为1001 的数据包。"
对主机A 首次传输的数据包的确认消息(ACK1001)和为主机B 传输数据做准备的同步消息
(SEQ2000)拥绑发送,因此,此种类型的消息又称SYN+ACK
收发数据前向数据包分配序号,并向对方通报此序号,这都是为防止数据丢失所做的准备。
通过向数据包分配序号并确认,可以在数据丢失时马上查看并重传丢失的数据包。因此,TCP
可以保证可靠的数据传输。最后观察主机A 向主机B 传输的消息∶
[ACK]SEQ:1001, ACK:2001
TCP 连接过程中发送数据包时需分配序号。
在之前的序号1000 的基础上加1,也就是分配1001。此时该数据包传递如下消息∶
"已正确收到传输的SEQ 为2000 的数据包,现在可以传输SEQ 为2001 的数据包。"
这样就传输了添加ACK2001 的ACK 消息。至此,主机A 和主机B 确认了彼此均就绪。

特么的,文字太复杂了,来个通俗易懂的。
TCP 三次握手好比在一个夜高风黑的夜晚,你一个人在小区里散步,不远处看见小区里的
一位漂亮妹子迎面而来,但是因为路灯有点暗等原因不能100%确认,所以要通过招手的方
式来确定对方是否认识自己。

你首先向妹子招手(syn),妹子看到你向自己招手后,向你点了点头挤出了一个微笑
(ack)
。你看到妹子微笑后确认了妹子成功辨认出了自己(进入established 状态)
但是妹子有点不好意思,向四周看了一看,有没有可能你是在看别人呢,她也需要确认一下。
妹子也向你招了招手(syn),你看到妹子向自己招手后知道对方是在寻求自己的确认,于是
也点了点头挤出了微笑(ack),妹子看到对方的微笑后确认了你就是在向自己打招呼(进入
established 状态)。

二、TCP内部原理--四次挥手

三、UDP编程--UDP基本原理

        在4 层TCP/IP 模型中,第二层传输(Transport)层分为TCP 和UDP 这2 种。数据交换
过程可以分为通过TCP 套接字完成的TCP 方式和通过UDP 套接字完成的UDP 方式。
UDP 套接字的特点:
        我们可以通过信件说明UDP 的工作原理,这是讲解UDP 时使用的传统示例,它与UDP
特性完全相符。寄信前应先在信封上填好寄信人和收信人的地址,之后贴上邮票放进邮筒
即可
。当然,信件的特点使我们无法确认对方是否收到。另外,邮寄过程中也可能发生信件
丢失的情况。也就是说,信件是一种不可靠的传输方式。与之类似,UDP 提供的同样是不可
靠的数据传输服务。
"既然如此,TCP 应该是更优质的协议吧?"
        如果只考虑可靠性,TCP 的确比UDP 好。但UDP 在结构上比TCP 更简洁。UDP 不会发
送类似ACK 的应答消息,也不会像SEQ 那样给数据包分配序号。因此,UDP 的性能有时比
TCP 高出很多
。编程中实现UDP 也比TCP 简单。另外,UDP 的可靠性虽比不上TCP,但也不
会像想象中那么频繁地发生数据损毁。因此,在更重视性能而非可靠性的情况下,UDP 是一
种很好的选择。
        既然如此,UDP 的作用到底是什么呢?为了提供可靠的数据传输服务,TCP 在不可靠的
IP 层进行流控制,而UDP 就缺少这种流控制机制。
        流控制是区分UDP 和TCP 的最重要的标志。但若从TCP 中除去流控制,所剩内容也屈
指可数。也就是说,TCP 的生命在于流控制。
        如果把TCP 比喻为电话,把UDP 比喻为信件。但这只是形容协议工作方式,并没有包
含数据交换速率。请不要误认为"电话的速度比信件快,因此TCP 的数据收发速率也比UDP
快"。实际上正好相反。TCP 的速度无法超过UDP,但在收发某些类型的数据时有可能接近
UDP。例如,每次交换的数据量越大,TCP 的传输速率就越接近UDP 的传输速率。

        从上图可以看出,IP 的作用就是让离开主机B 的UDP 数据包准确传递到主机A。但把
UDP 包最终交给主机A 的某一UDP 套接字的过程则是由UDP 完成的。UDP 最重要的作用就
是根据端口号将传到主机的数据包交付给最终的UDP 套接字
        其实在实际的应用场景中,UDP 也具有一定的可靠性。网络传输特性导致信息丢失频发,
可若要传递压缩文件(发送1 万个数据包时,只要丢失1 个就会产生问题),则必须使用
TCP
,因为压缩文件只要丢失一部分就很难解压。但 通过网络实时传输视频或音频时的情况
有所不同。对于多媒体数据而言,丢失一部分也没有太大问题,这只会引起短暂的画面抖动,
或出现细微的杂音。但因为需要提供实时服务,速度就成为非常重要的因素,此时需要考虑
使用UDP。但UDP 并非每次都快于TCP,TCP 比UDP 慢的原因通常有以下两点。
1 收发数据前后进行的连接设置及清除过程
2 收发数据过程中为保证可靠性而添加的流控制。
尤其是收发的数据量小但需要频繁连接时,UDP 比TCP 更高效。 

四、UDP服务端(上)

UDP 中的服务器端和客户端没有连接
        UDP 服务器端/客户端不像TCP 那样在连接状态下交换数据,因此与TCP 不同,无需经
过连接过程。也就是说,不必调用TCP 连接过程中调用的listen 函数和accept 函数。UDP 中
只有创建套接字的过程和数据交换过程
UDP 服务器端和客户端均只需1 个套接字
        TCP 中,套接字之间应该是一对一的关系。若要向10 个客户端提供服务,则除了守门
的服务器套接字外,还需要10 个服务器端套接字。但在UDP 中,不管是服务器端还是客户
端都只需要1 个套接字
。之前解释UDP 原理时举了信件的例子,收发信件时使用的邮筒
以比喻为UDP 套接字。只要附近有1 个邮筒,就可以通过它向任意地址寄出信件。同样,
只需1 个UDP 套接字就可以向任意主机传输数据

        上图展示了1 个UDP 套接字与2 个不同主机交换数据的过程。也就是说,只需1 个UDP
套接字就能和多台主机通信。

        创建好TCP 套接字后,传输数据时无需再添加地址信息。因为TCP 套接字将保持与对方
套接字的连接。换言之,TCP 套接字知道目标地址信息。但UDP 套接字不会保持连接状态(UDP
套接字只有简单的邮筒功能),因此每次传输数据都要添加目标地址信息。这相当于寄信前
在信件中填写地址
。以下为:填写地址并传输数据时调用的UDP 相关函数。 

发送:

#include<sys/socket.h>
ssize_t sendto(int sock,void*buff,size_t nbytes,int flags,struct sockaddr *to, socklen_t addrlen);

→成功时返回传输的字节数,失败时返回-1。
sock
用于传输数据的UDP 套接字文件描述符。
buff
保存待传输数据的缓冲地址值。
nbytes
待传输的数据长度,以字节为单位。
flags
可选项参数,若没有则传递0。
to
存有目标地址信息的sockaddr 结构体变量的地址值。
addrlen
传递给参数to 的地址值结构体变量长度。

        上述函数与之前的TCP 输出函数最大的区别在于,此函数需要向它传递目标地址信息。接
下来介绍接收UDP 数据的函数。UDP 数据的发送端并不固定,因此该函数定义为可接收发
送端信息的形式,也就是将同时返回UDP 数据包中的发送端信息。

接收:

#include<sys/socket.h>
ssize_t recvfrom(int sock, void *buff,size_t nbytes, int flags,struct sockaddr*from, socklen_t*addrlen);

→成功时返回接收的字节数,失败时返回-1。
sock 用于接收数据的UDP 套接字文件描述符。
buff 保存接收数据的缓存地址值
nbytes 可接收的最大字节数,故无法超过参数buf 所指的缓冲大小。
flags 可选项参数,若没有则传入0。
from 存有发送端地址信息的sockaddr 结构体变量的地址值。
addrlen 保存参数from 的结构体变量长度的变量地址值。
编写UDP 程序时最核心的部分就在于上述两个函数,这也说明二者在UDP 数据传输中
的地位。

注意:UDP是DDOS攻击的一个最主要的形式,因为他是无法拒收的。

五、UDP服务端(下)

int lession73(int argc, char* argv[])
{
	printf("server start");
	system("echo 'server start' > /output.txt");
	int ser_sock = -1;
	char message[1024] = "";
	struct sockaddr_in servaddr, clientaddr;
	socklen_t clientlen = 0;

	if (argc != 2) {
		printf("usage:% <port>\n", argv[0]);
		handle_error("argement is error:");
	}


	ser_sock = socket(PF_INET, SOCK_DGRAM, 0);           //UDP
	if (ser_sock == -1) {
		handle_error("create socket failed:");
	}


	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);        //127.0.0.1
	servaddr.sin_port = htons((short)atoi(argv[1]));


	if (bind(ser_sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1) {
		handle_error("bind failed:");
	}


	for (int i = 0; i < 50; i++) {
		system("echo 'server start 0' > /output.txt");
		clientlen = sizeof(clientaddr);
		ssize_t len = recvfrom(ser_sock, message, sizeof(message), 0, (struct sockaddr*)&clientaddr, &clientlen);
		system("echo 'server start 2' > /output.txt");
		sendto(ser_sock, message, len, 0, (struct sockaddr*)&clientaddr, clientlen);
	}

	printf("server ok");
	close(ser_sock);


	return 0;
}

六、UDP客户端

int lession74(int argc, char* argv[]) {
	int client_sock;

	struct sockaddr_in serv_addr;
	socklen_t serv_len = sizeof(serv_addr);
	char massege[1024] = "";


	//校验参数
	if (argc != 3) {
		printf("usge:%s ip port\n", argv[0]);
		handle_error("argement error!");

		
		
	}

	client_sock = socket(AF_INET, SOCK_DGRAM, 0);
	if (client_sock == -1) {
		handle_error("soket create failed!");
	}



	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((short)atoi(argv[2]));




	while (1) {
		printf("input massege(Q to quit):");
		scanf("%s", massege);
		if ((strcmp(massege, "Q") == 0) || (strcmp(massege, "q") == 0)) {
			break;
		}
		printf("Sending message: '%s' with length: %zu\n", massege, strlen(massege));
		
		//printf("Sending message: '%s' with length: %zu\n", massege, strlen(massege));
		
		
		printf("debug:%s", massege);
		
		ssize_t len=sendto(client_sock, massege, strlen(massege), 0, (sockaddr*)&serv_addr, serv_len);
		memset(massege, 0, (unsigned int)len);
		recvfrom(client_sock, massege, sizeof(massege), 0, (sockaddr*)&serv_addr, &serv_len);
		printf("recv:%s\n", massege);
	}

	close(client_sock);
	return 0;

}

七、UDP的传输特性和调用  

        前面讲解了UDP 服务器端/客户端的实现方法。但如果仔细观察UDP 客户端会发现,它
缺少把IP 和端口分配给套接字的过程。TCP 客户端调用connect 函数自动完成此过程,而
UDP 中连能承担相同功能的函数调用语句都没有。究竟在何时分配IP 和端口号呢?
UDP 程序中,调用sendto 函数传输数据前应完成对套接字的地址分配工作,因此调用bind
函数。当然,bind 函数在TCP 程序中出现过,但bind 函数不区分TCP 和UDP,也就是说,
在UDP 程序中同样可以调用。另外,如果调用sendto 函数时发现尚未分配地址信息,则在
首次调用sendto 函数时给相应套接字自动分配IP 和端口。而且此时分配的地址一直保留到
程序结束为止,因此也可用来与其他UDP 套接字进行数据交换。当然,IP 用主机IP,端口
号选尚未使用的任意端口号。
综上所述,调用sendto 函数时自动分配IP 和端口号,因此,UDP 客户端中通常无需额
外的地址分配过程。

八、SO_REUSEADDR

void client78() {
	int client = socket(PF_INET, SOCK_STREAM, 0);
	struct sockaddr_in servaddr;
	memset(&servaddr, 0, sizeof(servaddr)); //清零 防止意外
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");

	servaddr.sin_port = htons(33005);
	int ret = connect(client, (struct sockaddr*)&servaddr, sizeof(servaddr));
	while (ret == 0) {
		printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);
		char buffer[256] = "";

		fputs("input message(Q to quit):", stdout);//输出提示符
		fgets(buffer, sizeof(buffer), stdin/*标准输入流*/);//读入一行
		if ((strcmp(buffer, "Q\n") == 0) || (strcmp(buffer, "q\n") == 0)) {
			break;
		}

		size_t len = strlen(buffer);
		size_t send_len = 0;
		while (send_len < len) {
			ssize_t ret = write(client, buffer + send_len, len - send_len);//发给服务器
			if (ret <= 0)
			{
				fputs("write failed!\n", stdout);
				//close(client);
				std::cout << "client done!" << std::endl;
				return;
			}
			send_len += (size_t)ret;
		}
		memset(buffer, 0, sizeof(buffer));
		size_t read_len = 0;
		while (read_len < len)
		{
			ssize_t ret = read(client, buffer + read_len, len - read_len);
			if (ret <= 0)
			{
				fputs("read failed!\n", stdout);
				//close(client);
				std::cout << "client done!" << std::endl;
				return;
			}
			send_len += (size_t)ret;
		}

		std::cout << "from server:" << buffer;
	}
	//close(client);
	std::cout << "client done!" << std::endl;
}
void server78() {
	printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);
	int sock,client,optval=0;
	struct sockaddr_in addr,cli;
	socklen_t addrlen=sizeof(addr);
	char message[256] = "";

	sock = socket(PF_INET, SOCK_STREAM, 0);
	getsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &optval, &addrlen);
	printf("SO_REUSEADDR=%d\n", optval);
	optval = 1;
	setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
	getsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &optval, &addrlen);
	printf("SO_REUSEADDR=%d\n", optval);


	memset(&addr, 0, sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = inet_addr("127.0.0.1");
	addr.sin_port = htons(33005);

	
	
	addrlen = sizeof(addr);
	if (bind(sock, (struct sockaddr*)&addr, addrlen) == -1) {
		handle_error("bind failed!");
	}

	listen(sock, 3);
	client = accept(sock, (struct sockaddr*)&cli, &addrlen);
	read(client, message, sizeof(message));
	close(client);
	close(sock);
	return;
}
void lession78(char*option){
	if (strcmp(option, "1") == 0) {
		//服务器
		server78();
		server78();
		server78();
		server78();
		server78();
		server78();
		server78();
	}
	else {
		//客户端
		client78();
	}







}

九、TCP_NODELAY

"什么是Nagle 算法?使用该算法能够获得哪些数据通信特性?"
Nagle 算法是以他的发明人John Nagle 的名字命名的,它用于自动连接许多的小缓冲
器消息;这一过程(称为nagling)通过减少必须发送包的个数来增加网络软件系统的效率。

从上图中可以得到如下结论:
        "只有收到前一数据的ACK 消息时,Nagle 算法才发送下一数据。"
        TCP 套接字默认使用Nagle 算法交换数据,因此最大限度地进行缓冲,直到收到ACK。
上图左侧正是这种情况。为了发送字符串"Nagle",将其传递到输出缓冲。这时头字符"N"之
前没有其他数据(没有需接收的ACK),因此立即传输。之后开始等待字符"N"的ACK 消息,
等待过程中,剩下的"agle"填入输出缓冲。接下来,收到字符"N"的ACK 消息后,将输出缓冲
的"agle"装入一个数据包发送。也就是说,共需传递4 个数据包以传输1 个字符串。
        接下来分析未使用Nagle 算法时发送字符串"Nagle"的过程。假设字符"N"到"e"依序传到
输出缓冲。此时的发送过程与ACK 接收与否无关,因此数据到达输出缓冲后将立即被发送
出去。从上图右侧可以看到,发送字符串"Nagle"时共需10 个数据包。由此可知,不使用Nagle
算法将对网络流量产生负面影响。即使只传输1 个字节的数据,其头信息都有可能是几十个
字节。因此,为了提高网络传输效率,必须使用Nagle 算法。
        在程序中将字符串传给输出缓冲时并不是逐字传递的,故发送字符串"Nagle"的实际情
况并非如上图所示。但如果隔一段时间再把构成字符串的字符传到输出缓冲(如果存在此
类数据传递)的话,则有可能产生类似上图的情况。上图中就是隔一段时间向输出缓冲传递
待发送数据的。
        但Nagle 算法并不是什么时候都适用。根据传输数据的特性,网络流量未受太大影响时,
不使用Nagle 算法要比使用它时传输速度快。最典型的是"传输大文件数据"。将文件数据传
入输出缓冲不会花太多时间,因此,即便不使用Nagle 算法,也会在装满输出缓冲时传输数
据包。
这不仅不会增加数据包的数量,反而会在无需等待ACK 的前提下连续传输,因此可
以大大提高传输速度。

        一般情况下,不适用Nagle 算法可以提高传输速度。但如果无条件放弃使用Nagle 算法,
就会增加过多的网络流量,
反而会影响传输。因此,未准确判断数据特性时不应禁用Nagle
算法。
        刚才说过的"大文件数据"应禁用Nagle 算法。换言之,如果有必要,就应禁用Nagle 算
法。"Nagle 算法使用与否在网络流量上差别不大,使用Nagle 算法的传输速度更慢"禁用方
法非常简单。

十、IO缓存大小

        我们进行套接字编程时往往只关注数据通信,而忽略了套接字具有的不同特性。但是,理解
这些特性并根据实际需要进行更改也十分重要。

        从上表可以看出,套接字可选项是分层的。IPPROTOIP 层可选项是IP 协议相关事项,
IPPROTO_TCP 层可选项是TCP 协议相关的事项,SOL_SOCKET 层是套接字相关的通用可选项。
        也许有人看到表格会产生畏惧感,但我们真的无需全部背下来或理解,因此不必有负担。实
际能够设置的可选项数量是上表的好几倍,也无需一下子理解所有可选项,实际开发中逐一

掌握即可。接触的可选项多了,自然会掌握大部分重要的。
getsockopt & setsockopt
        我们几乎可以针对上表中的所有可选项进行读取(Get)和设置(Set)(当然,有些可选项
只能进行一种操作)。可选项的读取和设置通过如下2 个函数完成。
#include<sys/socket.h>
int getsockopt(int sock, int level,int optname, void *optval, socklen_t *optlen);

→成功时返回0,失败时返回-1。
sock :用于查看选项套接字文件描述符。
level 要查看的可选项的协议层。
optname 要查看的可选项名。
optval 保存查看结果的缓冲地址值。
optlen 向第四个参数optval 传递的缓冲大小。调用函数后,该变量中保存通过第四个参数
返回的可选项信息的字节数。
#include<sys/socket.h>
int setsockopt(int sock, int level, int optname, const void*optval, socklen_t optlen);

→成功时返回0,失败时返回-1。
sock 用于更改可选项的套接字文件描述符。
level 要更改的可选项协议层。
optname 要更改的可选项名。
optval 保存要更改的选项信息的缓冲地址值。
optlen 向第四个参数optval 传递的可选项信息的字节数。

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

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

相关文章

Dubbo应用篇

文章目录 一、Dubbo简介二、SSM项目整合Dubbo1.生产者方配置2.消费者方配置 三、Spring Boot 项目整合Dubbo1.生产者方配置2.消费者方配置 四、应用案例五、Dubbo配置的优先级别1. 方法级配置&#xff08;Highest Priority&#xff09;2. 接口级配置3. 消费者/提供者级配置4. 全…

Vulnhub---kioptirx5 超详细wp

个人博客 WuTongSec 欢迎大佬指点 打点 nmap 192.168.128.0/24 -sP 找ip nmap 192.168.128.137 --min-rate 10000 -p- 简单全端口扫描 nmap 192.168.128.137 -sC -sV -O -sT 详细 脚本 版本 系统 扫描 dirsearch -u http://192.168.128.137 目录扫描 PORT S…

JAVA八股文-运行篇-创建项目运行(1)

前置环境搭建:jdk、maven、idea、linux环境 一、创建一个java项目 File->New->Project 二、填写基本信息 三、完成&#xff0c;写了一段代码 四、打包 五、本地运行&#xff0c;运行和debug二选一 六、上传至linux环境 七、linux环境下命令执行 7.1 指定Main方法类 …

【C++】异常之道,行者无疆:解锁 C++ 的异常捕获哲学

文章目录 C语言处理错误C异常异常的抛出与捕获基本语法catch 的匹配原则函数调用链中的匹配原则异常的重新抛出 异常安全异常规范C标准库异常 C语言处理错误 终止程序&#xff1a;利用 assert() 断言去终止程序&#xff0c;当 ()的表达结果为 false 时会终止程序。返回错误码&…

[SWPUCTF 2022 新生赛]Ez_upload 详细题解

知识点: 文件上传 MIME绕过 script版本一句话木马 .htaccess配置文件 蚁剑虚拟终端的使用 打开题目可以文件上传 传入php文件, 提示 后缀不能是ph 所以也不能输入 phtml php3 pht等可以解析为php文件的后缀 bp抓包把传入的php木马文件后缀手动修改为jpg图片格式 提示 你上…

V20变频器设置电机电流超过设定的值,变频器报警停止运转

之前使用的台达变频器是有相关参数的设置的&#xff0c;比如设置额定电流的限制比例未1.5A时&#xff0c;超过1.5A时&#xff0c;变频器会输出报警信号&#xff0c;并停机报警。换到V20变频时&#xff0c;翻遍了说明书&#xff0c;并各种参数测试组合&#xff0c;未找到明确的相…

网络安全-态势感知

0x00 定义&#xff1a; 态势感知&#xff08;Situation Awareness&#xff0c;SA&#xff09;能够检测出超过20大类的云上安全风险&#xff0c;包括DDoS攻击、暴力破解、Web攻击、后门木马、僵尸主机、异常行为、漏洞攻击、命令与控制等。利用大数据分析技术&#xff0c;态势感…

未完成_RFdiffusion应用案例_从头设计pMHC的结合剂

目录 1. 论文导读1&#xff09;摘要2&#xff09;设计流程3&#xff09;设计流程的验证 2. 实战 1. 论文导读 Liu, Bingxu, et al. “Design of high specificity binders for peptide-MHC-I complexes.” bioRxiv (2024): 2024-11. 1&#xff09;摘要 MHC-I 将胞内抗原肽递呈…

Vant UI Axure移动端元件库:提升移动端原型设计效率

UI框架的选择对于提升开发效率和用户体验至关重要。Vant UI&#xff0c;作为一款基于Vue.js的轻量、可靠的移动端组件库&#xff0c;自2017年开源以来&#xff0c;凭借其丰富的组件库、良好的性能以及广泛的兼容性&#xff0c;在移动端开发领域崭露头角&#xff0c;赢得了众多开…

stm32中的常用函数

目录 一、定义声明类 1.1 预定义 1.2 条件编译 1.3 extern 声明 1.3 typedef 类型别名 1.4 结构体 二、基础函数 2.1 delay类函数 2.2 printf函数 三、GPIO 3.1 硬件 3.2 通用外设驱动模型 3.3 例程 四、中断 4.1. 什么是中断 4.2. NVIC 4.3. EXTI 4.4. EXTI和…

静态路由与交换机配置实验

1.建立网络拓扑 添加2台计算机&#xff0c;标签名为PC0、PC1&#xff1b;添加2台二层交换机2960&#xff0c;标签名为S0、S1&#xff1b;添加2台路由器2811&#xff0c;标签名为R0、R1&#xff1b;交换机划分的VLAN及端口根据如下拓扑图&#xff0c;使用直通线、DCE串口线连接…

【Appium】AttributeError: ‘NoneType‘ object has no attribute ‘to_capabilities‘

目录 1、报错内容 2、解决方案 &#xff08;1&#xff09;检查 &#xff08;2&#xff09;报错原因 &#xff08;3&#xff09;解决步骤 3、解决结果 1、报错内容 在PyCharm编写好脚本后&#xff0c;模拟器和appium也是连接成功的&#xff0c;但是运行脚本时报错&…

【汇编语言】标志寄存器(三) —— 条件跳转,精准决策:汇编语言的比较与转移

文章目录 前言1. 检测比较结果的条件转移指令1.1 什么是条件转移指令&#xff1f;1.2 两类条件转移指令 2. 根据无符号比较的条件转移指令2.1 如何记忆&#xff1f;2.2 如何实现比较转移的功能&#xff1f;2.3 举例说明2.3.1 例12.3.2 例2 3. 总结4. 例题巩固4.1 问题一4.1.1 问…

【Unity高级】如何获取着色器(Shader)的关键词

在动态设置Shader时&#xff0c;会需要通过EnableKeyword, DisableKeyword来完成。但一个Shader有哪些关键词呢&#xff1f;Unity的文档中并没有列出来&#xff0c;但我们可以通过遍历Shader的KeywordSpace来查看。 1. 代码如下 using UnityEngine;public class KeywordExamp…

针对边缘计算优化LoRa的TinyML信道跳变管道

论文标题&#xff1a;Optimizing LoRa for Edge Computing with TinyML Pipeline for Channel Hopping&#xff08;针对边缘计算优化LoRa的TinyML信道跳变管道&#xff09; 作者信息&#xff1a;Marla Grunewald, Mounir Bensalem 和 Admela Jukan&#xff0c;来自德国布伦瑞克…

使用AI工具Screenshot to Code将UI设计图翻译成代码

一、获取openAI apikey。 一般有两种方式&#xff0c;一种是到openAI官网注册账号&#xff0c;付费申请GPT4的apikey。另一种是某宝买代理。我这里采用第二种。 二、安装Screenshot to Code 1.到github下载源码。 2.启动&#xff0c;两种方式&#xff1a;源码启动和docker启动…

python学opencv|读取图像(三)放大和缩小图像

【1】引言 前序已经学习了常规的图像读取操作和图像保存技巧&#xff0c;相关文章链接为&#xff1a; python学opencv|读取图像-CSDN博客 python学opencv|读取图像&#xff08;二&#xff09;保存彩色图像-CSDN博客 今天我们更近一步&#xff0c;学习放大和缩小图像的技巧&…

【vue2自定义指令】v-disabled 实现el-switch,el-button等elementUI禁用(disabled)效果

如果你搜过类似的功能&#xff0c;肯定看到过千篇一律的 // 实现按钮禁用el.disabled true// 增加 elementUI 的禁用样式类el.classList.add(is-disabled)但是这个方案明显对el-switch&#xff0c;不起作用&#xff0c;所以我这边直接把方案贴出来&#xff0c;不想了解具体原理…

烟草行业通过Profinet转EthernetIP网关打通数据壁垒

在工业自动化领域&#xff0c;Profinet转Ethernet/IP是两种广泛应用的工业以太网协议。它们各自具有独特的特点和优势&#xff0c;而在实际应用中&#xff0c;经常需要实现这两种协议之间的互通&#xff0c;这时就需要使用到开疆智能Profinet转Ethernet/IP网关KJ-EIP-108。同时…