一文带你读懂TCP

文章目录

  • 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 8999int 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.mzph.cn/bicheng/51465.shtml

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

相关文章

Linux基础复习(二)

前言 本文介绍了一下Linux命令行基本操作及网络配置 一、 命令行提示含义 [当前用户主机名 工作目录]$ 若当前用户是root&#xff0c;则最后一个字符为# 否则&#xff0c;最后一个字符为$ 二、常用Linux命令及其解释 修改主机名 一般在创建一台主机后会使用hostname相关命…

在生信分析中大家需要特别注意的事情​

在生信分析中大家需要特别注意的事情 标准的软件使用和数据分析流程 1. 先看我的b站教学视频 2. 先从我的百度网盘把演示数据集下载下来&#xff0c;先把要运行的模块的演示数据集先运行一遍 3. 前两步都做完了&#xff0c;演示数据集也运行成功了&#xff0c;并且知道了软件…

ajax请求成功但不执行success-function回调函数

目录 一、问题分析 二、处理问题 一、问题分析 在测试员工管理系统的登录和注册代码时&#xff0c;登录一切正常&#xff0c;就是注册成功后没有跳转页面&#xff0c;后面发现是success-function回调函数没有正常执行。原因主要是前端和后端交流的数据格式不一致&#xff0c;…

【C++BFS算法 二分查找】2812. 找出最安全路径

本文涉及知识点 CBFS算法 C二分查找 LeetCode2812. 找出最安全路径 给你一个下标从 0 开始、大小为 n x n 的二维矩阵 grid &#xff0c;其中 (r, c) 表示&#xff1a; 如果 grid[r][c] 1 &#xff0c;则表示一个存在小偷的单元格 如果 grid[r][c] 0 &#xff0c;则表示一…

Windows配置AirSim过程(学习过程)

一、概述 因为需要在虚幻引擎当中使用AirSim&#xff0c;在Windows系统上进行操作&#xff0c;根据官方网站的操作过程&#xff0c;进行了配置&#xff0c;这里作为自己配置过程的记录。 二、具体过程 &#xff08;一&#xff09;系统版本 操作系统是Windows11&#xff0c;Ai…

昇思MindSpore 应用学习-RNN实现情感分类-CSDN

RNN实现情感分类 AI代码解析 概述 情感分类是自然语言处理中的经典任务&#xff0c;是典型的分类问题。本节使用MindSpore实现一个基于RNN网络的情感分类模型&#xff0c;实现如下的效果&#xff1a; 输入: This film is terrible 正确标签: Negative 预测标签: Negative输入…

HALCON如何创建本地函数

HALCON中有本地函数(.hdev)、外部函数(HDevelop函数文件.hdvp)和库函数(.hdpl) 本地函数(.hdev)&#xff1a;创建后仅在当前程序文件中使用&#xff1b; 外部函数(HDevelop函数文件.hdvp)&#xff1a;创建后可以在其他程序文件中复用&#xff0c;默认保存在…/ procedures/下…

vue实现滚动条下滑时隐藏导航栏,上滑时显示导航栏

效果展示 思路 监听滚动事件&#xff0c;记录上次的滚动距离&#xff0c;与最新滚动距离做对比&#xff0c;如果为正&#xff0c;说明滚动距离距顶值scrollTop变大&#xff0c;用户正在向下滚动页面&#xff0c;此时隐藏&#xff0c;反之则反&#xff0c;隐藏就是top值给他负导…

【C++语言】C++11新特性(1)

一、统一的列表初始化 1.1 {} 初始化 在C98中&#xff0c;标准允许使用花括号{}对数组或者结构体元素进行统一的列表初始值设定。比如&#xff1a; struct Point {int _x;int _y; };int main() {int array1[] { 1, 2, 3, 4, 5 };int array2[5] { 0 };Point p { 1, 2 };ret…

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 项目排期安排(200分) - 三语言AC题解(Python/Java/Cpp)

🍭 大家好这里是清隆学长 ,一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 💻 ACM银牌🥈| 多次AK大厂笔试 | 编程一对一辅导 👏 感谢大家的订阅➕ 和 喜欢💗 🍿 最新华为OD机试D卷目录,全、新、准,题目覆盖率达 95% 以上,支持题目在线…

C++使用MD5对字符串加密,获取文件的MD5值(附完整源码)

目录 1、为什么要使用MD5? 2、开源MD5类 3、获取字符串MD5值的MD5String接口封装 4、获取文件MD5值的MD5File接口封装 5、最后 C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/125529931C…

OSPF动态路由协议实验

首先地址划分 一个骨干网段分成三个&#xff0c;r1&#xff0c;r2&#xff0c;r5三个环回网段 &#xff0c;总共要四个网段 192.168.1.0/24 192.168.1.0/26---骨干网段 192.168.1.0/28 192.168.1.16/28 192.168.1.32/28 备用 192.168.1.64/28 192.168.1.64/26---r1环回 192.1…

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

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

【Vulnhub系列】Vulnhub_SecureCode1靶场渗透(原创)

【Vulnhub系列靶场】Vulnhub_SecureCode1靶场渗透 原文转载已经过授权 原文链接&#xff1a;Lusen的小窝 - 学无止尽&#xff0c;不进则退 (lusensec.github.io) 一、环境配置 1、从百度网盘下载对应靶机的.ova镜像 2、在VM中选择【打开】该.ova 3、选择存储路径&#xff0…

Mathtype7.8中文版远程安装并嵌入word(包成功)

Mathtype7.8是一款专业的数学公式编辑工具&#xff0c;能够帮助用户在各种文档中插入复杂的数学公式和符号。数学公式编辑器工具可以轻松输入各种复杂的公式和符号&#xff0c;与Office文档完美结合&#xff0c;显示效果超好&#xff0c;比Office自带的公式编辑器要强大很多。M…

obsidian 首页制作 辅助笔记总结回顾

记笔记最重要的是回顾与总结&#xff0c;有这么一款插件可以让我们自己搭建一个美观的首页&#xff0c;它包括热力图、文稿统计、文稿回顾等等功能&#xff0c;你是否愿意尝试呢&#xff1f; 今天就介绍一款插件&#xff0c;能快速制作笔记首页&#xff0c;辅助总结、回顾。 …

sql注入详解【从数据库架构分析】

简介 SQL注入是一种常见的Web应用程序安全漏洞&#xff0c;它允许攻击者在Web应用程序中插入恶意SQL语句&#xff0c;从而操纵数据库执行非授权的操作。这种攻击利用了应用程序在处理用户输入时的不足&#xff0c;特别是当应用程序直接将用户输入作为SQL语句的一部分使用&…

聊聊基于Alink库的特征工程方法

独热编码 OneHotEncoder 是用于将类别型特征转换为独热编码的类。独热编码是一种常用的特征编码方式&#xff0c;特别适用于处理类别型特征&#xff0c;将其转换为数值型特征。 对于每个类别型特征&#xff0c;OneHotEncoder 将其编码成一个长度为类别数量的向量。 每个类别对…

数据库实验:SQL Server基本表单表查询

一、实验目的&#xff1a; 1、掌握使用SQL语法实现单表查询 二、实验内容&#xff1a; 1. 查询订购日期为2001年5月22日的订单情况。&#xff08;Orders&#xff09;&#xff08;时间日期的表达方式为 dOrderDate ‘2001-5-22’&#xff0c;类似字符串&#xff0c;使用单引号…