一文带你读懂TCP

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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值