一文带你读懂TCP

news2024/11/17 23:29:29

文章目录

  • 1 TCP协议
    • 1.1 TCP 基础
      • 1.1.1 TCP 特性
      • 1.2.2 TCP连接数
    • 1.2 TCP 头
      • 1.2.1 TCP 头格式
      • 1.2.2 MTU,MSS,分片传输
    • 1.3 TCP 连接三路握手
    • 1.4 TCP 断开四次挥手
    • 1.5 SYN攻击和防范
    • 1.6 重传机制
      • 1.6.1 超时重传
      • 1.6.2 快速重传
      • 1.6.3 SACK
    • 1.7 滑动窗口
    • 1.8 流量控制
    • 1.9 拥塞控制
    • 1.10 TCP Socket编程

1 TCP协议

1.1 TCP 基础

1.1.1 TCP 特性

​ TCP 是一种面向连接的,可靠的,基于字节流传输层通信协议,TCP 能确保接收端接收的网络包无损坏,无间隔,非冗余(即同一数据包只接收一次),按序的

  • 面向连接:首先,面向连接是一对一的,面向连接在通信前,需要先建立链接才能传输数据;
  • 可靠性:无论网络链路中出现什么变化, TCP 都可以保证报文到达接收端;
  • 字节流:数据以字节流形式进行传输,因此数据可以无限拓展(不超过MTU最大传输单元);

​ 确定一个 TCP 连接,需要 TCP 四元组:源地址 : 源端口 + 目的地址 : 目的端口。还有如下基础概念:

  • Socket套接字:套接字由 IP 地址和 Port 端口号组成;
  • IP 地址:(IPv4 32位)主机之间通过 IP 地址识别报文应属于哪一个主机;
  • Port 端口:(16位)端口用来识别报文属于一台主机上的哪一个进程;
  • 序列号:用来解决乱序问题;
  • 窗口大小:用来做流量控制;

​ 由于 TCP 是面向连接,能保证数据是一定被交付的,因此常用于:

  • FTP 文件传输

  • HTTP / HTTPS连接

1.2.2 TCP连接数

​ TCP 的连接数量永远到达不了 IP x Port 数的理论上限,主要受限于以下两点:

  1. 内存限制,每个TCP都会占用一定的内存(2~4KB),但操作系统的内存是有限的。

  2. 文件描述符限制(一般是1024),因为 Socket 也是文件描述符的一种。解除文件描述符有如下方法:

    # 这是临时修改文件描述符上限的方法
    # (1)查看文件描述符的限制
    ulimit -n
    # (2)修改上限,但系统重启后就不再生效
    ulimit -n 100000
    
    # 这是永久修改文件描述符上限的方法
    # (1)用nano或vim打开limits.conf配置文件
    sudo nano /etc/security/limits.conf
    sudo vim /etc/security/limits.conf
    # (2)添加代码,其中 * 代表所有用户,可以替换成用户名来单独取消某一用户的文件描述符上限。
    * soft nofile 100000
    * hard nofile 100000
    # (3)重启系统
    

1.2 TCP 头

1.2.1 TCP 头格式

在这里插入图片描述

TCP 头 20~60 字节长

字段长度含义
源端口16bit源端口,标识是哪个应用程序发送的
目的端口16bit目的端口,标识哪个应用程序接收
序列号(client_isn和server_isn)32bit序号字段,用来标识发送顺序,因为接收端不一定是按发送顺序接收到报文的。
首部长度4bit首部长度指出TCP报文段的数据起始处距离TCP报文段的起始处有多远,以32比特(4字节)为计算单位。最多有60字节的首部,若无选项字段,正常为20字节。
保留位6bit必须填0
URG1bit紧急指针有效标志位
ACK1bit应答位
PSH1bit紧急位
RST1bit该位置为1的时候,表示 TCP 出现了异常,必须断开连接
SYN1bit发起请求的信号
FIN1bit表示希望断开连接
窗口大小16bit窗口的大小
校验和16bitCRC校验和
紧急指针16bit用紧急指针来表明紧急数据在数据流的哪个位置
选项
数据

TCP 数据长度 = IP 总长度 - IP首部长度 - TCP 首部长度

1.2.2 MTU,MSS,分片传输

在这里插入图片描述

  • MTU:一个网络包的最大长度,以太网中一般默认设置为 1500 字节。
  • MSS:出去 IP 和 TCP 头,一个网络包中 TCP 数据的最大长度。

为什么我们有了 IP 分片之后,还需要 TCP 分片呢?

当 IP 层有一个超过 MTU 长度的报文需要发送的时候,如果一个 IP 分片丢失,那么所有的 IP 分片都需要重新传送,而有了 MSS 之后,当发现数据长度超过了 MSS 之后,就先进行分片,这样就能避免这个问题了。

1.3 TCP 连接三路握手

在这里插入图片描述

  1. 客户端和服务端同时处于 CLOSE 关闭状态。
  2. 服务端将 Socket 套接字设置为被动 LISTEN 状态。准备接收客户端发来的 connect() 连接。
  3. 客户端通过 connect() 发起请求,此时客户端会随机初始化序列号(client_isn)。同时把 SYN 位设置为1。发送报文,随后客户端进入 SYN_SENT状态。
  4. 服务端接收到报文后,会初始化自己的(server_isn)序列号,同时将收到的(client_isn)+1 然后填入到确认应答号中,之后把 SYN 和 ACK 位设置为1。发送报文,随后服务端进入 SYN_RCVD 状态。
  5. 客户端接收到报文后,同样的将收到的(server_isn)+1 填入到确认应答号中,把 SYN 位设置为1。发送报文,随后客户端进入 ESTABLISHED 状态。
  6. 服务端接收到报文后,进入 ESTABLISHED 状态。

完成三次握手后,客户端服务端都处于 ESTABLISHED 状态,双方就可以相互发送数据了。值得一提的是,第一次和第二次握手是不能携带数据的,但第三次握手是可以携带数据的

Linux中我们可以使用netstat -napt命令查看 TCP 状态:

在这里插入图片描述

需要三次握手,而不是两次,四次的原因:

因为三次握手才能保证双方具有接收和发送的能力。

例如 A 和 B 相约好去打篮球:

  1. A 向 B 发消息:“下午五点咱去打篮球”。此时 B 收到了消息,但 A 并不知道 B 是否收到了消息;
  2. B 向 A 发消息:“好的没问题,记得带球”。此时 A 知道了 B 收到了自己的消息,但是 B 并不知道 A 是否收到了自己的回复;
  3. 所以此时还需要 A 给 B 发消息确认:“OK”。至此, A 和 B 才能确认彼此都收到了自己的消息。

即:

  • 三次握手才可以阻止历史重复连接的初始化(主要原因)
  • 三次握手才可以同步双方的初始序列号
  • 三次握手才可以避免资源浪费

1.4 TCP 断开四次挥手

在这里插入图片描述

1.5 SYN攻击和防范

​ 我们都知道 TCP 连接建立是需要三次握手,若发送大量不同 IP 地址的 SYN 报文到同一个服务器,服务端每接收到一个 SYN 报文,就进入SYN_RCVD 状态,但服务端发送出去的 ACK + SYN 报文,无法得到未知 IP 主机的 ACK 应答,久而久之就会占满服务端的 SYN 接收队列(未连接队列),使得服务器不能为正常用户服务。

避免方式:

(1)修改 Linux 内核参数

# 当网卡接收数据包的速度大于内核处理的速度时,会有一个队列保存这些数据包。控制该队列的最大值如下参数:
net.core.netdev_max_backlog

# SYN_RCVD 状态连接的最大个数:
net.ipv4.tcp_max_syn_backlog

# 超出处理能时,对新的 SYN 直接回 RST,丢弃连接:
net.ipv4.tcp_abort_on_overflow

(2)启动 cookie

正常流程:

  • 当服务端接收到客户端的 SYN 报文时,会将其加入到内核的「 SYN 队列」;
  • 接着发送 SYN + ACK 给客户端,等待客户端回应 ACK 报文;
  • 服务端接收到 ACK 报文后,从「 SYN 队列」移除放入到「 Accept 队列」;
  • 应用通过调用 accpet() socket 接口,从「 Accept 队列」取出的连接。

在这里插入图片描述

而启动cookie后,服务端接收到客户端的应答报文时,服务器会检查这个 ACK 包的合法性。如果合法,直接放入到 Accept 队列。不合法则丢弃。

1.6 重传机制

​ TCP 通过序列号与确认应答实现可靠传输,TCP 中,发送端的数据到达接收主机时,接收端主机会返回一个 ACK 确认应答消息。

在这里插入图片描述

这是数据正常传输的情况,但若TCP数据包丢失时,TCP 会怎么办呢?没错,就是重传机制。

1.6.1 超时重传

​ 发送数据时,发送端会设定一个定时器,当定时器超时,发送端仍未收到对方的 ACK 报文时,发送端就会重新发送该数据。

基于这个原理,下面这两种情况会导致超时重传:

  • 数据包丢失
  • 确认信号 ACK 丢失

那么定时器的时间,超时时间 (RTO) 我们应该如何设置呢?他与 RTO(数据从一端到达另一端的时间) 有关。

超时时间 RTO 应该略大于我们的 RTT ,过大或过小都不合适,Linux 计算 RTO 有个公式大概原理是:

  • 需要 TCP 通过采样 RTT 的时间,然后进行加权平均,算出一个平滑 RTT 的值,而且这个值还是要不断变化的,因为网络状况不断地变化。
  • 除了采样 RTT,还要采样 RTT 的波动范围,这样就避免如果 RTT 有一个大的波动的话,很难被发现的情况。

如果超时重发的数据,再次超时的时候,又需要重传的时候,TCP 的策略是超时间隔加倍。

也就是**每当遇到一次超时重传的时候,都会将下一次超时时间间隔设为先前值的两倍。两次超时,就说明网络环境差,不宜频繁反复发送。**这样就避免了大量重复发送的同一报文使网络变得更加拥堵。

1.6.2 快速重传

​ TCP 还有一种快速重传的机制,它不以时间为判断依据,是以数据为判断依据。

在这里插入图片描述

  1. 当发送端重复收到了消息 seq1 的ACK2 信号,那么就证明 seq2 没有被接收。
  2. 发送端接收到三个同样的ACK信号后,就知道了seq2并没有被收到。
  3. 于是就会在定时器超时前,重传seq2,但是因为3,4,5都被收到了,于是此时ACK会回复6。

对于上面的例子来说,假如我们现在有Seq2、Seq3、Seq4、Seq5、Seq6、Seq7、Seq8、Seq9、这么多消息,当发送端接收了三次ACK信号时,我们并不知道,这三次ACK 代表的是Seq2、Seq7、Seq9、收到触发的ACK信号,还是Seq3、Seq5、Seq6收到触发的ACK信号,因此我们并不清除这连续的三个 ACK2 代表的是谁被接收了,我们就不知道之后的这几条消息里,我们应该重传那些 TCP 报文,于是我们就有了 SACK 方法。

1.6.3 SACK

这种方式需要在 TCP 头部「选项」字段里加一个 SACK 的东西,它可以将缓存的地图发送给发送方,这样发送方就可以知道哪些数据收到了,哪些数据没收到,知道了这些信息,就可以只重传丢失的数据

如下图,发送方收到了三次同样的 ACK 确认报文,于是就会触发快速重发机制,通过 SACK 信息发现只有 200~299 这段数据丢失,则重发时,就只选择了这个 TCP 段进行重复。

在这里插入图片描述

若要支持 SACK。在 Linux 下,需要通过 net.ipv4.tcp_sack 参数打开这个功能(Linux 默认打开)。

还有一种技术叫做 Duplicate SACK。Duplicate SACK 又叫 D-SACK,其主要使用了 SACK 来告诉「发送方」有哪些数据被重复接收了。

使用 D-SACK 有如下好处 :

  1. 可以让发送方知道,是发出去的包丢了,还是接收方回应的 ACK 包丢了;
  2. 可以知道是不是发送方的数据包被网络延迟了;
  3. 可以知道网络中是不是把发送方的数据包给复制了;

若要支持 DSACK。在 Linux 下,需要通过 net.ipv4.tcp_dsack 参数打开这个功能(Linux 默认打开)。

1.7 滑动窗口

​ 若 TCP 每发送一个数据,就要进行一次确认的应答,这样的模式效率低下。那么如何解决这个问题呢?使用滑动窗口:

窗口的大小就是一次可以无需等待确认应答,可以继续发送数据的最大值。窗口的实现,实际上是操作系统开辟了一个缓存空间,发送方主机在等到确认应答返回之前,必须在缓冲区中保留已发送的数据。如果按期收到确认应答,此时数据就可以从缓存区清除。

在这里插入图片描述

​ ACK 600 确认应答报文丢失,也没关系,操作系统会在下一个确认应答进行确认,只要发送方收到了 ACK 700 确认应答,就意味着 700 之前的所有数据接收方都收到了。这个模式就叫累计确认累计应答。通常窗口的大小是由接收方的决定的。

发送窗口构成:

在这里插入图片描述

接受窗口构成:

在这里插入图片描述

1.8 流量控制

​ TCP 提供一种机制可以让 发送方 根据 接收方 的实际接收能力控制发送的数据量,这就是流量控制。

其实流量控制就是通过之前的滑动窗口来实现的,例如如下例子:

在这里插入图片描述

​ 这就是流量控制,但是滑动窗口的大小不代表我们系统可以接收的缓存的大小,那么他们之间是什么关系呢?实际上,窗口中存放的字节数,都是放在操作系统内存缓冲区中的,而操作系统的缓冲区,会被操作系统调整,此时,窗口也会被调整。当应用进程没办法及时读取缓冲区的内容时,也会对我们的缓冲区造成影响。

窗口糊涂综合征

​ 想象一下,若接收端需要接受很多的数据,接收窗口越来越小,每一次只能腾出几个字节的接收窗口,但是我们的发送端也会义无反顾的发送报文,为了几个字节,我们需要多发 TCP + IP 头40个字节的数据,这会影响我们的通信速率,同时还会影响我们的系统的开销,不断地进行数据的拷贝。那么我们如何解决呢?

解决方法就是接收方不通知小窗口给发送方,发送方也不发送小数据给接收方,我们可以通过 Nagle 延时算法来避免这个问题:

  • 等到窗口大小 >= MSS 或是 数据大小 >= MSS后再发送
  • 收到之前发送数据的 ack 再发送

只要没满足上面条件中的一条,发送方一直在囤积数据,直到满足上面的发送条件。

Nagle 算法默认是打开的,如果对于一些需要小数据包交互的场景的程序,我们需要关闭这个算法:可以在 Socket 设置 TCP_NODELAY 选项来关闭这个算法。

1.9 拥塞控制

拥塞控制和流量控制是不同的,想象从水龙头用一根水管往水桶内接水,以此来模拟 TCP 数据传输的过程,我们上面提到的流量控制是因为桶太小,所以我们需要控制水龙头出水的流速,以此来保证桶中的水不会因为水龙头流速太快导致水溢出,放到 TCP 中去理解,就是发送端主动减少发送流量,以此来避免接收端接收数据的能力不够,导致接收出错或者效率减慢。

​ 而拥塞控制是水管中已经有了很多很多的水,即网络中已经含有很多数据了,这时候水龙头的流速过大反而会使得水管中的水流更加拥挤,所以我们主动调节水龙头的水流量,放到 TCP 中去理解,就是发送端主动控制发送流量,以此来避免造成网络信道的拥挤。

拥塞控制主要靠三个算法来实现:

  • 慢启动
  • 拥塞避免
  • 快速恢复

​ 为了实现拥塞控制,我们定义了一个叫做拥塞窗口 (swnd) 的概念,拥塞窗口是发送方的一个窗口,他会根据网络的拥塞程度进行动态变化,可以简单理解为是网络拥塞时 TCP 发送窗口的数量。同时还有一个叫做慢启动门限(ssthresh)的概念,他的作用是判定什么时候使用慢启动算法,什么时候是使用拥塞避免算法:

  • cwnd < ssthresh 时,使用慢启动算法。
  • cwnd >= ssthresh 时,使用拥塞避免算法。

拥塞控制过程

在这里插入图片描述

  1. 一开始采用慢启动过程,这个过程是 cwnd 指数级增长的:1、2、4、8…。
  2. 达到 ssthresh 门限后,会进入拥塞避免算法,这个过程拥塞窗口 cwnd 是线性增加的。
  3. 若遇到超时情况,会重新开始慢启动过程,同时将 ssthresh 设置为之前的 1/2 。
  4. 进入拥塞避免算法后,若遇到三次 ACK 应答,即快速重传的情况,我们会使用快速恢复算法。
  5. 此时不会进入慢启动过程从零开始,而是从上一次拥塞避免的 ssthresh 的数值开始线性增长。

拥塞处理的过程:

在这里插入图片描述

1.10 TCP Socket编程

//服务端代码
/*********************************************************************************
                实现socket_server服务器,并且每隔接收一次温度
 ********************************************************************************/

#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <dirent.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>    
#include <sys/stat.h>       
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <getopt.h>
#include <arpa/inet.h>

#define MAX_LISTEN 13
#define LISTEN_PORT 8999

int help_printf( char *program );//打印使用信息
int socket_init(int *listen_fd,int listen_port);//进行socket初始化

int main( int argc,char *argv[])
{
	int 							listen_fd = -1;
	int 							client_fd = -1;
	int 							choo = -1;
	int								daemon_var = 0;//用来决定是否后台运行
	int                             listen_port = LISTEN_PORT;
	int 							rv =-1;
	char							buf[1024];
	struct option			 		opts[] = 
	{
		{"help",no_argument,NULL,'h'},
		{"port",required_argument,NULL,'p'},
		{"daemon",required_argument,NULL,'d'},
        {0,0,0,0}
	};
 
	//选项系统
	while((choo = getopt_long(argc,argv,"hp:d:",opts,NULL)) != -1)
	{
		switch(choo)
		{
			case 'h':
				help_printf(argv[0]);
				return 0;
				
			case 'p':
				listen_port = atoi(optarg);//optarg 不是值,而是指向选项后参数的指针
				break;

			case 'd':
				daemon_var = atoi(optarg);
				break;
		}
	}
	
	if (listen_port == LISTEN_PORT) 
	{
		printf("server will listen default port:%d\n",listen_port);
	}
	else
	{
		printf("server will listen port:%d\n",listen_port);
	}

    //是否转移到后台运行
	if ( daemon_var )
	{
		daemon(0,0);
	}
    
	if (socket_init(&listen_fd,listen_port) < 0)
	{
		printf("socket_init error:[%s]\n",strerror(errno));
		return -1;
	}

	printf("listen_fd:[%d]\n",listen_fd);

   	while(1)
	{
	//开始accept过程
		if ((client_fd = accept(listen_fd,NULL,NULL)) < 0)
		{
			printf("accept error:[%s]\n",strerror(errno));
			close(listen_fd);
			return -2;
		}
		printf("client_fd[%d]\n",client_fd);
    
		while(1)
		{
			rv = read(client_fd,buf,sizeof(buf));
			if(rv<0)
			{
				printf("error or disconnect[%s]\n",strerror(errno));
		    	close(client_fd);
				return 0;
			}
			if(rv == 0)
			{
				printf("client disconnect and waiting new clientfd connet\n");
        	    close(client_fd);
				break;
			}
			printf("client message:[%dbytes]%s\n",rv,buf);

			if (write(client_fd,"Receive success\n",sizeof("Receive success\n")) < 0)
			{
				printf("error:[%s]\n",strerror(errno));
				continue;
			}
		}

		continue;//若客户端断开,rv=0后break,重新到accept环节去监听socketfd
	}

	return 0;
}



//打印使用信息函数
int help_printf( char *program )
{
	if (NULL == program )
	{
		printf("help_printf arguments error[%s]\n",strerror(errno));
		return -1;
	}
	printf("%s usage:\n",program);
	printf("--help (-h) : get help menu\n");
	printf("--port (-p) : listen port \n");
    printf("--daemon(-d): [0]un_daemon use ,[1]daemon use\n");
	return 0;
}

//socket_init初始化
int socket_init(int *listen_fd,int listen_port)
{
	struct sockaddr_in 					servaddr_in;
	int 								opt = 1; 
    printf("start init socket...\n");
	if ((*listen_fd = socket(AF_INET,SOCK_STREAM,0)) < 0)
	{
		printf("socket failture:%s\n",strerror(errno));
		return -1;
	}
	//printf("listen_fd[%d]\n",*listen_fd);
    setsockopt(*listen_fd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));

	memset(&servaddr_in,0,sizeof(servaddr_in));
	servaddr_in.sin_family       = AF_INET;
	servaddr_in.sin_port		 = htons(listen_port);
    servaddr_in.sin_addr.s_addr  = htonl(INADDR_ANY);
	if (bind(*listen_fd,(struct sockaddr *)&servaddr_in,sizeof(servaddr_in)) < 0 )
	{
		printf("bind error[%s]\n",strerror(errno));
		close(*listen_fd);
		return -2;
	}
	printf("bind success!\n");

    if (listen(*listen_fd,MAX_LISTEN) < 0)
	{
		printf("listen error[%s]\n");
        close(*listen_fd);
		return -3;
	}
	printf("listen_fd[%d]\n",*listen_fd);
    printf("init_socket finish...\n");
	
	return 0;
}



//客户端代码
/*********************************************************************************
                每十秒上报一次温湿度的客户端
 ********************************************************************************/
#include <stdio.h>
#include <sys/types.h>
#include <string.h>
#include <dirent.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <getopt.h>
#include <sys/types.h>         
#include <sys/socket.h>
#include <arpa/inet.h>

int ds18b20(float *temp);
void help_printf(char *program);
int socket_client(int *sock_fd,char *listen_ip,int *listen_port);

int main(int argc, char *argv[])
{
	int							sock_fd = -1;
	int 						listen_port = -1;
	char 						*listen_ip = NULL;
	int 						choo = -1;
	float 						temp = -1;
   	char						buf[1024];
	int 						rv = -1;
	struct option				opts[]=
	{
		{"help",no_argument,NULL,'h'},
		{"port",required_argument,NULL,'p'},
		{"ip",required_argument,NULL,'i'},
		{0,0,0,0}
	};

	while( (choo = getopt_long(argc,argv,"hp:i:",opts,NULL)) != -1 ) 
	{
		switch(choo)
		{
			case 'h':
				help_printf(argv[0]);
				return 0;
			case 'p':
				listen_port = atoi(optarg);
				break;
			case 'i':
				listen_ip = optarg;
				break;
		}
	}

	//printf("will connect ip:%s port:%d\n",listen_ip,listen_port);
    if ( (listen_port == 0) || (listen_ip == NULL) )
	{
		help_printf(argv[0]);
		return -1;
	}
	printf("will connect ip:%s port:%d\n",listen_ip,listen_port);

	//ds18b20(&temp);
	//printf("real-time temperature:%.2f'c\n",temp);
    //sock过程开始

	if (socket_client(&sock_fd,listen_ip,&listen_port) < 0)
	{
		printf("error:%s\n",strerror(errno));
		return -2;
	}

	while(1)
	{   
		ds18b20(&temp);
		printf("real-time temperature:%.2f'c\n",temp);

		memset(buf,0,sizeof(buf));
		snprintf(buf,sizeof(buf),"temp:%.2f",temp);
        if ((rv = write(sock_fd,buf,strlen(buf))) < 0)
		{
			printf("write error\n");
			close(sock_fd);
			return -3;
		}

		if ((rv = read(sock_fd,buf,sizeof(buf))) <= 0)
		{
			printf("error:%s\n",strerror(errno));
			close(sock_fd);
			return -4;
		}

		printf("server message:%s\n",buf);

		sleep(10);
	}

	return 0;
}

int socket_client(int *sock_fd,char *listen_ip,int *listen_port)
{
	int 						client_fd = -1;
	struct sockaddr_in 			clieaddr_in;

	if ((*sock_fd = socket(AF_INET,SOCK_STREAM,0)) < 0)
	{
		printf("socket error:%s\n",strerror(errno));
		return -1;
	}
	printf("client_fd %d\n",*sock_fd);

	memset(&clieaddr_in,0,sizeof(clieaddr_in));
	clieaddr_in.sin_family    	= AF_INET;
	clieaddr_in.sin_port 		= htons(*listen_port);
	inet_aton(listen_ip,&clieaddr_in.sin_addr);
	printf("listen_port:%d\n",*listen_port);
	printf("listen_ip:%s\n",listen_ip);

	if (connect(*sock_fd,(struct sockaddr*)&clieaddr_in,sizeof(clieaddr_in)))
	{
		printf("connect error:%s\n",strerror(errno));
		close(*sock_fd);
		return -3;
	}
	printf("connect success!\n");
	return 0;

}

void help_printf(char* program)
{
	printf(" %s usage:\n",program);
	printf("--help(-h):help menu\n");
	printf("--port(-p):port\n");
	printf("--ip(-i);ip address\n");

	return ;

}

int ds18b20(float *temp)
{
	int 						fd = -1;
	int 						found_var = -1;
	char 						*w1_path = "/sys/bus/w1/devices/";
    char						chip_path[128];
	char						ds_path[256];
	DIR  						*dirp = NULL;
	struct dirent 				*direntp = NULL;
	char 						buf[128];
    char						*ptr = NULL;

	if (NULL == temp)
	{
		printf("arguments error:%s\n",strerror(errno));
		return -1;
	}

    if ( (dirp = opendir(w1_path)) == NULL )
	{
		printf("dirp nofound error:%s\n",strerror(errno));
		return -2;
	}
	printf("open dir %s success,DIR address:%p\n",w1_path,dirp);

	while ( (direntp = readdir(dirp)) != NULL )
	{
		if( strstr(direntp->d_name,"28-") )
		{
			strncpy(chip_path,direntp->d_name,sizeof(chip_path));  //chip_path = *direntp->d_name;不行,为什么编译会报错
			found_var = 1;
		}
	}
	
	if (found_var < 0)
	{
		printf("nofound dir 28-...\n");
		closedir(dirp);
		return -3;
	}
	printf("success found [%s]\n",chip_path);

	closedir(dirp);

	snprintf(ds_path,sizeof(ds_path),"%s%s/w1_slave",w1_path,chip_path);
	printf("ds18b20 devices route:%s\n",ds_path);
	
	if ((fd = open(ds_path,O_RDONLY)) < 0)
	{
		printf("open error:%s\n",strerror(errno));
		return -4;
	}

	memset(buf,0,sizeof(buf));
	if (read(fd,buf,sizeof(buf)) <= 0)
	{
		printf("read error:%s\n",strerror(errno));
		return -5;
	}

	close(fd);

	if ((ptr = strstr(buf,"t=")) == NULL)
	{
		printf("ptr error:%s\n",strerror(errno));
		close(fd);
		return -6;
	}
	
	ptr +=2;
    
	*temp = atof(ptr)/1000;

	return 0;
}

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

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

相关文章

【Vulnhub系列】Vulnhub_DC-1靶场渗透(原创)

【Vulnhub系列靶场】Vulnhub_DC-1靶场渗透 原文转载已经过授权 原文链接&#xff1a;Lusen的小窝 - 学无止尽&#xff0c;不进则退 (lusensec.github.io) 一、环境准备 1、在百度网盘中下载DC-1靶场。DC-1靶场受virtual box 的影响&#xff0c;在VM中直接打开是扫描不到IP 的…

基于Java的微博传播分析系统的设计与实现

1 项目介绍 1.1 摘要 本文致力于展示一项创新的微博传播分析系统设计与应用研究&#xff0c;该系统基于Java技术&#xff0c;巧妙利用大数据环境下的社交媒体——微博的庞大用户群及高度活跃特性&#xff0c;旨在深度探索信息传播的内在逻辑与社会影响机制。研究开篇明确定了…

【网络安全】文件上传黑白名单及数组绕过技巧

不安全的文件上传&#xff08;Unsafe FileUpload&#xff09; 不安全的文件上传是指Web应用程序在处理用户上传的文件时&#xff0c;没有采取足够的安全措施&#xff0c;导致攻击者可能利用这些漏洞上传恶意文件&#xff0c;进而对服务器或用户造成危害。 目录 一、文件上传…

20240729 每日AI必读资讯

Meta科学家最新采访&#xff0c;揭秘Llama 3.1是如何炼成的 - Llama 3.1都使用了哪些数据&#xff1f;其中有多少合成数据&#xff1f;为什么不使用MoE架构&#xff1f;后训练与RLHF流程是如何进行的&#xff1f;模型评估是如何进行的&#xff1f; - 受访者Thomas Scialom现任…

在Android上实现汉字笔顺动画效果——HanZiWriter

序&#xff0c;万般皆是命&#xff0c;半点不由人。 Hanzi Writer 是 javascript 免费开源库&#xff0c;根据汉字书写时按照笔画顺序的特征&#xff0c;可以播放正确笔画顺序的描边动画和练习测试。支持简体字和繁体字。可以让全球用户能够通过手绘模仿的方式来学习和练习书写…

复杂系统的动态演化与自相似性探究——揭示系统内部的结构与行为模式

复杂系统的动态演化与自相似性探究——揭示系统内部的结构与行为模式 动态演化与自相似性的核心思想 想象一下&#xff0c;你正在观察一棵树的生长。随着时间的推移&#xff0c;树会不断长高&#xff0c;长出新的叶子和枝条。这就是动态演化。同时&#xff0c;你会发现树的每一…

甄选范文“论企业集成架构设计及应用”软考高级论文,系统架构设计师论文

论文真题 论企业集成架构设计及应用企业集成架构(Enterprise Integration Arhitecture,EIA) 是企业集成平台的核心,也是解决企业信息孤岛问题的关键。企业集成架构设计包括了企业信息、业务过程、应用系统集成架构的设计。实现企业集成的技术多种多样,早期的集成方式是通过…

LexLIP——图片搜索中的多模态稀疏化召回方法

LexLIP——图片搜索中的多模态稀疏化召回方法 FesianXu 20240728 at WeChat Search Team 前言 最近笔者在回顾&笔记一些老论文&#xff0c;准备整理下之前看的一篇论文LexLIP&#xff0c;其很适合在真实的图片搜索业务场景中落地&#xff0c;希望笔记能给读者带来启发。如…

业务记录:处理动态表头的CSV/EXCEL文件

业务描述&#xff1a;传入一个动态表头的CSV文件&#xff0c;解析CSV&#xff0c;并保存入库。 CSV文件的表头是不确定的&#xff0c;即顺序和字段个数不确定&#xff0c;以及表头是中文字段。 例如&#xff1a; 为了顺利解析CSV文件&#xff0c;前端需要传入对应的字段名和顺…

Qwen-VL全文翻译(from GPT-4o)

目录 Abstract1 Introduction2 Methodology2.1 Model Architecture2.2 Inputs and Outputs 3 Training3.1 Pre-training3.2 Multi-task Pre-training3.3 Supervised Fine-tuning 4 Evaluation4.1 Image Caption and General Visual Question Answering4.2 Text-oriented Visual…

01 Go Web基础_20240728 课程笔记

概述 如果您没有Golang的基础&#xff0c;应该学习如下前置课程。 基础不好的同学每节课的代码最好配合视频进行阅读和学习&#xff0c;如果基础比较扎实&#xff0c;则阅读本教程巩固一下相关知识点即可&#xff0c;遇到不会的知识点再看视频。 视频课程 最近发现越来越多…

【算法专题】双指针算法之18. 四数之和(力扣)

欢迎来到 CILMY23的博客 &#x1f3c6;本篇主题为&#xff1a;双指针算法之18. 四数之和&#xff08;力扣&#xff09; &#x1f3c6;个人主页&#xff1a;CILMY23-CSDN博客 &#x1f3c6;系列专栏&#xff1a;Python | C | C语言 | 数据结构与算法 | 贪心算法 | Linux | 算…

网络安全威胁情报是什么,它对代工生产(OEM)意味着什么?

随着汽车数字环境的不断变化&#xff0c;网络安全基础设施及其面临的威胁也日趋复杂。 为了更好地识别、理解并最终预防这些风险&#xff0c;网络安全威胁情报&#xff08;CTI&#xff09;的管理应是一个综合多方面的过程。 以下是CTI对OEM的意义&#xff0c;以及如何利用网络…

代码随想录算法训练营第40天|LeetCode 198.打家劫舍、213.打家劫舍II、337.打家劫舍III

1. LeetCode 198.打家劫舍 题目链接&#xff1a;https://leetcode.cn/problems/house-robber/ 文章链接&#xff1a;https://programmercarl.com/0198.打家劫舍.html#算法公开课 视频链接&#xff1a;https://www.bilibili.com/video/BV1Te411N7SX 思路&#xff1a; 递推公式&a…

Profinet从站转TCP/IP协议转化网关(功能与配置)

如何将Profinet和TCP/IP网络连接通讯起来呢?近来几天有几个朋友问到这个问题&#xff0c;那么作者在这里统一说明一下。其实有一个不错的设备产品可以很轻易地解决这个问题&#xff0c;名为JM-DNT-PN。接下来作者就从该设备的功能及配置详细说明一下。 一&#xff0c;设备主要…

相机镜头移动特效视频转场模板Pr工程文件

Pr转场模板&#xff0c;相机镜头移动特效视频转场Pr工程文件 逼真的相机移动&#xff1a;具有一系列动态相机移动功能&#xff0c;包括平移、倾斜、缩放和旋转&#xff0c;为您的过渡添加逼真和引人入胜的视觉元素。 无缝集成&#xff1a;以.prproj文件形式提供&#xff0c;便…

操作系统——进程深度理解

目录 一、操作系统 1、概念 2、操作系统的结构 3、操作系统的理解 二、进程 一、操作系统 1、概念 操作系统是一个软件&#xff0c;一款进行软硬件资源管理的软件 电脑开机的时间&#xff0c;就是把操作系统加载到内存并运行的过程。 对操作系统广义的认识&#xff1a;…

PowerBI 度量值不被切片器筛选

我们有这样一张表&#xff1a; 我们用一个切片器绑定奖金表[奖金]&#xff0c;就可以用表格来联动显示数据: 现在用户有个新的需求&#xff0c;当单选某个奖金时&#xff0c;需要统计小于这个奖金数的人数。 我用了一个度量值来统计&#xff1a; 度量值 VAR selected_bonus…

全国区块链职业技能大赛样题第9套后端源码

后端源码地址:https://blog.csdn.net/Qhx20040819/article/details/140746050 前端源码地址:https://blog.csdn.net/Qhx20040819/article/details/140746216 智能合约+数据库表设计:https://blog.csdn.net/Qhx20040819/article/details/140746646 项目预览 登录 用户管理

vue3 快速入门 (五) : Flex布局

1. 如何变成Flex布局 变成Flex容器&#xff0c;只需在容器布局的节点的CSS中&#xff0c;增加display : flex .mylayout {/* 省略了其他代码 */display: flex; }2. flex direction : 方向 row : 以行排列 row-reverse &#xff1a; 以行反向排列 column &#xff1a;以列排列…