一文带你读懂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.rhkb.cn/news/385979.html

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

相关文章

VScode使用Github Copilot插件时出现read ECONNREST问题的解决方法

文章目录 read ECONNREST查看是否仍是 Copilot 会员查看控制台输出网络连接问题浏览器设置问题笔者的话 read ECONNREST 最近使用 Copilot 时一直出现 read ECONNREST 问题&#xff0c;这个表示连接被对方重置了&#xff0c;就是说在读取数据时连接被关闭。 我首先怀疑是不是…

springboo 整合 redis

springBoot 整合 redis starter启动依赖。—包含自动装配类—完成相应的装配功能。 引入依赖 <!--引入了redis整合springboot 的依赖--> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis&…

PostgreSQL的pg-collector工具

PostgreSQL的pg-collector工具 pg-collector 是一个用于 PostgreSQL 数据库的监控和数据收集工具。它主要用于收集 PostgreSQL 实例的性能指标、查询统计和日志信息&#xff0c;以便进行数据库性能分析和故障排查。通过收集这些数据&#xff0c;管理员可以更好地了解数据库的运…

盘点2024年网上很火的4个语音识别转文字工具。

语音识别转文字是一项非常实用的技术&#xff0c;可以帮助我们在会议记录中省去手动记录&#xff0c;在采访中迅速得到文字稿&#xff0c;在学习中快速生成课堂笔...运用十分广泛。但是很多人不知道要怎么转换&#xff0c;在这里我便给大家介绍几款效率非常高的语音转文字的工具…

python 裁剪图片

情况&#xff1a; 有时候看视频&#xff0c;看到一个漂亮的妹子&#xff0c;按下 Alt PrintScreen 进行截图之后&#xff0c;会把整个屏幕都截图。 需要适当剪裁一下。 每次打开 PS &#xff0c; 也太慢了。 所以写个代码&#xff0c; 快速处理。 效果对比&#xff1a; 原始…

轨道式智能巡检机器人,助力综合管廊安全运维

1 引言 当前城市综合管廊建设已经成为世界范围内的发展趋势&#xff0c;2017年5月住建部、发改委联合发布《全国城市市政基础设施建设“十三五”规划》&#xff0c;截至2017年4月底国内地下综合管廊试点项目已开工建设687 km&#xff0c;建成廊体260 km&#xff0c;完成投资40…

MSSQL注入前置知识

简述 Microsoft SQL server也叫SQL server / MSSQL&#xff0c;由微软推出的关系型数据库&#xff0c;默认端口1433 常见搭配C# / .net IISmssql mssql的数据库文件 数据文件&#xff08;.mdf&#xff09;&#xff1a;主要的数据文件&#xff0c;包含数据表中的数据和对象信息…

使用update-alternatives管理GCC版本

使用update-alternatives管理GCC版本 简介操作过程 简介 当操作系统中存在多个版本的GCC时&#xff0c;可以使用使用update-alternatives管理默认使用的编译器版本。 本文使用gcc-9和gcc-11做演示&#xff0c;操作系统为ubuntu-20.04 操作过程 ①使用以下命令确认gcc已正确…

Ubuntu22.04重装系统+基础配置

重装系统 note&#xff1a;备份数据&#xff0c;重装系统后home下的文件会丢失&#xff0c;所以先备份一下home的数据到其他的盘/mnt/下里。记住之前系统的DNS&#xff0c;IP和掩码。 先在Ubuntu官网下载22.04桌面版&#xff08;种子链接要用迅雷下载&#xff09;。但是版本还…

橙单前端项目下载编译遇到的问题与解决

今天下载orange-admin前端项目&#xff0c;不过下载下来运行也出现一些问题。 1、运行出现下面一堆错误&#xff0c;如下&#xff1a; 2、对于下面这个错误 error Expected linebreaks to be LF but found CRLF linebreak-style 这就是eslint的报错了&#xff0c;可能是原作者…

隆尧县“隆品佳尧”区域公用品牌发布推介会暨地标之都七月选品会成功举办

在国家乡村振兴战略与农业现代化建设的大背景下&#xff0c;隆尧县凭借其得天独厚的地理优势和丰富的自然资源&#xff0c;正在成为区域经济与品牌建设的一颗新星。为了进一步推动隆尧县的农业发展和乡村建设&#xff0c;由隆尧县商务局指导、隆尧县电子商务公共服务中心主办的…

【leetcode 详解】生成特殊数字的最少操作【中等】(C++思路精析)

题目见下&#xff1a; 测试数据: 解题思路笔记&#xff1a; 最初拿到这道题是很蒙的&#xff0c;联想不到什么数据结构的模型&#xff08;肯定是笔者积累太少了&#xff09;&#xff0c;甚至惯性地想怎么实现“删除数字”的操作&#xff1a;在原字符串中抽出一个字符然后将剩…

南非云手机:助力企业在南非的商业活动

中国企业在南非的商业活动涵盖了多个领域&#xff0c;包括基础设施建设、采矿业、制造业、能源、电信、金融服务等。随着中国企业在南非的不断扩展&#xff0c;如何高效管理业务和保护数据安全成为了重要课题&#xff0c;而南非云手机为企业提供了强大的技术支持和便利的管理工…

神经网络与注意力机制的权重学习对比:公式探索

神经网络与注意力机制的权重学习对比&#xff1a;公式探索 注意力机制与神经网络权重学习的核心差异 在探讨神经网络与注意力机制的权重学习时&#xff0c;一个核心差异在于它们如何处理输入数据的权重。神经网络通常通过反向传播算法学习权重&#xff0c;而注意力机制则通过学…

使用flutter做圆形进度条 (桌面端)

前言 最近收到一个需求&#xff0c;需要使用flutter 来做一个圆形进度条&#xff0c;这可难倒我了&#xff0c;毕竟我是做前端的&#xff0c;flutter 之前接触的也少&#xff0c;但没办法&#xff0c;既然需求有了&#xff0c;也得硬着头皮上了&#xff0c;先来看看做的效果。…

C语言百分号打印器

目录 开头程序程序的流程图程序输入与输出的效果例1输入输出 例2输入输出 例3输入输出 结尾 开头 大家好&#xff0c;我叫这是我58。今天&#xff0c;我们来看一下我用C语言编译的百分号打印器和与之相关的一些东西。 程序 #define _CRT_SECURE_NO_WARNINGS 1 #include <…

05 循环神经网络

目录 1. 基本概念 2. 简单循环网络 2.1 简单循环网络 2.2 长程依赖问题 3. 循环神经网络的模式与参数学习 3.1 循环神经网络的模式 3.2 参数学习 4. 基于门控的循环神经网络 4.1 长短期记忆网络 4.2 LSTM网络的变体网络 4.3 门控循环单元网络 5. 深层循环神经网络…

怀旧必玩!重返童年,扫雷游戏再度登场!

Python提供了一个标准的GUI&#xff08;图形用户界面&#xff09;工具包&#xff1a;Tkinter。它可以用来创建各种窗口、按钮、标签、文本框等图形界面组件。 而且Tkinter 是 Python 自带的库&#xff0c;无需额外安装。 Now&#xff0c;让我们一起来回味一下扫雷小游戏吧 扫…

BUUCTF [MRCTF2020]Ezpop

这道题对于刚接触到pop链的我直接把我整懵了&#xff0c;一边看着魔术方法一边分析 魔术方法可以看这里PHP 魔术方法 - 简介 - PHP 魔术方法 - 简单教程&#xff0c;简单编程 (twle.cn) 代码解析 经过以上的分析我们可以理一下解题思路&#xff1a;接收参数反序列化之前先触发…

Go语言教程(一看就会)

全篇文章 7000 字左右&#xff0c; 建议阅读时长 1h 以上。 Go语言是一门开源的编程语言&#xff0c;目的在于降低构建简单、可靠、高效软件的门槛。Go平衡了底层系统语言的能力&#xff0c;以及在现代语言中所见到的高级特性。它是快速的、静态类型编译语言。 第一个GO程序…