FreeRTOS+TCP TCP客户端实验

  1. TCP协议简介

TCP是Transmission Control Protocol的简称,中文名是传输控制协议。它是一种面向连接的、可靠的、基于IP的传输层协议。两个TCP应用之间在传输数据的之前必须建立一个TCP连接,TCP采用数据流的形式在网络中传输数据。TCP为了保证报文传输的可靠性,会对每一个包进行编号,同时序号也能保证接收端在接收数据的时候可以按序接收。接收端在接到数据后会返回一个相应的应答信号,如果发送端在合理的往返延时内未接收到应答信号,那么对应的数据将会重传。在数据确认无误后才会将数据传递给应用层。

  1. TCP的特性

连接机制:

TCP是一个面向连接的协议,无论哪一方向另一方发送数据之前,都必须先在双方之间建立一个连接,否则将无法发送数据,一个TCP连接必须有双方IP地址与端口号。

确认与重传:

一个完整的TCP传输必须有数据的交互,接收方在接收到数据之后必须正面进行确认,向发送方报告接收的结果,而发送方在发送数据之后必须等待接收方的确认,同时发送时候会启动一个定时器,在指定超时时间内没收到确认,发送方就会认为发送失败,然后进行重发操作,这就是重传报文。

TCP提供可靠的传输层,但它依赖的是IP层的服务,IP数据报的传输是无连接、不可靠的,因此它要通过确认来知道接收方确实已经收到数据了。但数据和确认都有可能会丢失,因此TCP通过在发送时设置一个超时机制(定时器)来解决这种问题,如果当超时时间到达的时候还没有收到对方的确认,它就重传该数据。

缓冲机制:

在发送方想要发送数据的时候,由于应用程序的数据大小、类型都是不可预估的,而TCP协议提供了缓冲机制来处理这些数据,如在数据量很小的时候,TCP会将数据存储在一个缓冲空间中,等到数据量足够大的时候在进行发送数据,这样子能提高传输的效率并且减少网络中的通信量,而且在数据发送出去的时候并不会立即删除数据,还是让数据保存在缓冲区中,因为发送出去的数据不一定能被接收方正确接收,它需要等待到接收方的确认再将数据删除。同样的,在接收方也需要有同样的缓冲机制,因为在网络中传输的数据报到达的时间是不一样的,而且TCP协议还需要把这些数据报组装成完整的数据,然后再递交到应用层中。

全双工通讯:

在TCP连接建立后,那么两个主机就是对等的,任何一个主机都可以向另一个主机发送数据,数据是双向流通的,所以TCP协议是一个全双工的协议,这种机制为TCP协议传输数据带来很大的方便,一般来说,TCP协议的确认是通过捎带的方式来实现,即接收方把确认信息放到反向传来的是数据报文中,不必单独为确认信息申请一个报文,捎带机制减少了网络中的通信流量。由于双方主机是对等的存在,那么容易一方都可以断开连接,此时这个方向上的数据流就断开了,但是另一个 方向上的数据仍是连通的状态,这种情况就称之为半双工。

流量控制:

TCP提供了流量控制服务(flow-control service)以消除发送方使接收方缓冲区溢出的可能性。流量控制是一个速度匹配服务,即发送方的发送速率与接收方应用程序的读取速率相匹配,TCP通过让发送方维护一个称为接收窗口(receive window)的变量来提供流量控制,是的,你没看错,是接收窗口(rwnd),它用于给发送方一个指示:接收方还能接收多少数据,接收方会将此窗口值放在TCP报文的首部中的窗口字段,然后传递给发送方,这个窗口的大小是在发送数据的时候动态调整的。当接收方主机的接收窗口为0时,发送方继续发送只有一个字节的报文段,这些报文段将被接收方接收,直到缓存清空,并在确认报文中包含一个非0的接收窗口值。

差错控制:

除了确认与重传之外,TCP协议也会采用校验和的方式来检验数据的有效性,主机在接收数据的时候,会将重复的报文丢弃,将乱序的报文重组,发现某段报文丢失了会请求发送方进行重发,因此在TCP往上层协议递交的数据是顺序的、无差错的完整数据。

拥塞控制:

当数据从一个大的管道(如一个快速局域网)向一个较小的管道(如一个较慢的广域网)发送时便会发生拥塞。当多个输入流到达一个路由器,而路由器的输出流小于这些输入流的总和时也会发生拥塞,这种是网络状况的原因。如果一个主机还是以很大的流量给另一个主机发送数据,但是其中间的路由器通道很小,无法承受这样大的数据流量的时候,就会导致拥塞的发生,这样子就导致了接收方无法在超时时间内完成接收(接收方此时完全有能力处理大量数据),而发送方又进行重传,这样子就导致了链路上的更加拥塞,延迟发送方必须实现一直自适应的机制,在网络中拥塞的情况下调整自身的发送速度,这种形式对发送方的控制被称为拥塞控制(congestion control)。

常见端口号:

常见的TCP协议端口号有21、53、80等等,更多端口描述具体见下图,其中80端口号是我们日常生活中最常见的一个端口号,它也是HTTP服务器默认开放的端口。

 

  1. TCP的报文结构

TCP报文段的封装:

如ICMP报文一样,TCP报文段依赖IP协议进行发送,因此TCP报文段与ICMP报文一样,都是封装在IP数据报中,IP数据报封装在以太网帧中,因此TCP报文段也是经过了两次的封装,然后发送出去,其封装具体见下图。

TCP报文段格式:

TCP报文段如APR报文、IP数据报一样,也是由首部+数据区域组成,TCP报文段的首部我们称之为TCP首部,其首部内推很丰富,各个字段都有不一样的含义,如果不计算选项字段,一般来说TCP首部只有20个字节,具体见下图。

 

每个TCP报文段都包含源主机和目标主机的端口号,用于寻找发送端和接收端应用线程,这两个值加上IP首部中的源IP地址和目标IP地址就能确定唯一一个TCP连接。序号字段用来标识从TCP发送端向TCP接收端发送的数据字节流,它的值表示在这个报文段中的第一个数据字节所处位置,根据接收到的数据区域长度,就能计算出报文最后一个数据所处的序号,因为TCP协议会对发送或者接收的数据进行编号(按字节的形式),那么使用序号对每个字节进行计数,就能很轻易管理这些数据。序号是32bit的无符号整数。

当建立一个新的连接时,TCP报文段首部的SYN标志变1,序号字段包含由这个主机随机选择的初始序号ISN(Initial Sequence Number)。该主机要发送数据的第一个字节序号为ISN+1,因为SYN标志会占用一个序号,既然TCP协议给每个传输的字节都了编号,那么确认序号就包含接收端所期望收到的下一个序号,因此,确认序号应当是上次已成功收到数据的最后一个字节序号加1。当然,只有ACK标志为1时确认序号字段才有效,TCP为应用层提供全双工服务,这意味数据能在两个方向上独立地进行传输,因此确认序号通常会与反向数据(即接收端传输给发送端的数据)封装在同一个报文中(即捎带),所以连接的每一端都必须保持每个方向上的传输数据序号准确性。

首部长度字段占据4bit空间,它指出了TCP报文段首部长度,以字节为单位,最大能记录15*4=60 字节的首部长度,因此,TCP报文段首部最大长度为60字节。在字段后接下来有6bit空间是保留未用的。

此外还有6bit空间,是TCP报文段首部的标志字段,用于标志一些信息:

URG:首部中的紧急指针字段标志,如果是 1 表示紧急指针字段有效。

ACK:首部中的确认序号字段标志,如果是 1 表示确认序号字段有效。

PSH:该字段置一表示接收方应该尽快将这个报文段交给应用层。

RST:重新建立 TCP 连接。

SYN:用同步序号发起连接。

FIN:中止连接。

TCP的流量控制由连接的每一端通过声明的窗口大小来提供,窗口大小为字节数,起始于确认序号字段指明的值,这个值是接收端正期望接收的数据序号,发送方根据窗口大小调整发送数据,以实现流量控制。窗口大小是一个占据16bit间的字段,因而窗口最大为65535字节,当接收方告诉发送方一个大小为0的窗口时,将完全阻止发送方的数据发送。

检验和覆盖了整个的TCP报文段:TCP首部和TCP数据区域,由发送端计算和填写,并由接收端进行验证。

只有当URG标志置1时紧急指针才有效,紧急指针是一个正的偏移量,和序号字段中的值相加表示紧急数据最后一个字节的序号。简单来说,本TCP报文段的紧急数据在报文段数据区域中,从序号字段开始,偏移紧急指针的值结束。

  1. TCP建立连接

TCP是一个面向连接的协议,无论哪一方向另一方发送数据之前,都必须先在双方之间建立一条连接,俗称“握手”。

建立连接:

首先建立连接的过程是由客户端发起,而服务器无时无刻都在等待着客户端的连接,其示意图具体见下图,TCP连接一般来说会经历以下过程:

第一步:客户端的TCP首先向服务器端的TCP发送一个特殊的TCP报文段。该报文段中不包含应用层数据,但是在报文段的首部中的SYN标志位会被置为1。因此,这个特殊报文段被称为SYN报文段(我们暂且称之为握手请求报文)。另外,客户会随机地选择一个初始序号(ISN,假设为A),并将此序号放置于该SYN报文段的序号字段中;但SYN报文段中的ACK 标志位 0,此时它的确认序号段是无效的。该报文段会被封装在一个IP数据报中, 然后发送给目标服务器。

第二步:一旦服务器收到了客户端发出的SYN报文段,知道客户端要请求握手了,服务器便会从SYN报文段中提取对应的信息,为该TCP连接分配TCP缓存和变量,并向该客户TCP发送允许连接的报文段(握手应答报文)。这个报文段同样也不包含任何应用层数据,但是,在报文段的首部却包含3个重要的信息。

  1. SYN与ACK标志都被置为 1。
  2. 将TCP报文段首部的确认序号字段设置为A+1(这个A(ISN)是从握手请求报文中得到)。
  3. 服务器随机选择自己的初始序号(ISN,注意此ISN是服务器端的SN,假设为B),并将其放置到TCP报文段首部的序号字段中。

这个允许连接的报文段实际上表明了:“我收到了你发起建立连接的请求,初始序号为A,我同意建立该TCP连接,我自己的初始序号是B。”该允许连接的报文段有时被称为SYN ACK报文段(SYN ACK segment),同时由于ACK标志位1,所以TCP报文段首部的窗口大小字段是有效的。

第三步:当客户端收到服务器的握手应答报文后,会将ACK标志置位,此时客户端的TCP报文段的ACK标志被设置为1,而对于SYN标志,因为连接已经建立了,所以该标志会被置为0,同时客户端也要给该TCP连接分配缓存和变量,并且客户端还需要返回一个应答报文段,这个报文对服务器的应答报文段作出应答,将TCP报文段首部的确认序号字段设置为B+1,同时也会告知服务器的窗口大小。在三次握手的第三个阶段可以在报文段数据区域中携带客户到服务器的数据。

在完成握手后,客户端与服务器就建立了连接,同时双方都得到了彼此的窗口大小,序列号等信息,在传输TCP报文段的时候,每个TCP报文段首部的SYN标志都会被置0,因为它只用于发起连接,同步序号。

 

  1. TCP终止连接

建立一个连接需要三次握手,而终止一个连接要经过四次挥手(有一些书上也会称为“四次握手”),这由TCP的特性造成的,因为TCP连接是全双工连接的服务,因此每个方向上的连接必须单独关闭。当一端完成它的数据发送任务后就能发送一个FIN报文段(可以称之为终止连接请求,其实就是FIN标志位被设置为1)来终止这个方向上的连接。另一端收到一个FIN报文段,它必须通知应用层对方几经终止了那个方向的连接,发送FIN报文段通常是应用层进行关闭的结果。客户端发送一个FIN报文段只意味着在这一方向上没有数据流动,一个TCP连接在发送一个FIN后仍能接收数据,但是在实际应用中只有很少的TCP应用程序这样做。“四次挥手”终止连接示意图具体见下图,其具体过程如下:

第一步:客户端发出一个FIN报文段主动进行关闭连接,此时报文段的FIN标志位为1,假设序号为C,一般来说ACK标志也会被置一,但确认序号字段是无效的。

第二步:当服务器收到这个FIN报文段,它发回一个ACK报文段(此报文段是终止连接应答),确认序号为收到的序号加1(C+1),和SYN一样,一个FIN将占用一个序号,此时断开客户端->服务器的方向连接。

第三步:服务器会向应用程序请求关闭与这个客户端的连接,接着服务器就会发送一个FIN报文段(这个报文段是服务器向客户端发出,请求终止连接),此时假设序号为D,ACK标志虽然也为1,但是确认序号字段是无效的。

第四步:客户端返回一个ACK报文段来确认终止连接的请求,ACK标志置1,并将确认序号设置为收到序号加1(D+1),此时断开服务器->客户端的方向连接。

  1. FreeRTOS的远程连接函数

函数原型:

BaseType_t FreeRTOS_connect( Socket_t xClientSocket, struct freertos_sockaddr *pxAddress, socklen_t xAddressLength );

连接套接字的连接端口。该套接字必须由FreeRTOS_socket()创建。

参数描述:

xClientSocket:目标套接字,该套接字必须由FreeRTOS_socket()创建。

*pxAddress:指向FreeRTOS_sockaddr结构的指针,该结构包含目标端口的详细信息。

xAddressLength:该参数为使用。

返回值:

如果连接操作成功,则返回0。如果xSocket不是有效的TCP套接字,则返回-pdFREERTOS_ERRNO_EBADF。如果xSocket在调用FreeRTOS_CONNECT()之前已经连接,则返回-pdFREERTOS_ERRNO_EISCONN。如果xSocket不处于允许连接操作的状态,则返回-pdFREERTOS_ERRNO_EINPROGRESS或-pdFREERTOS_ERRNO_EAGAIN。如果套接字的读取块时间为零,并且连接操作不能立即成功,则返回-pdFREERTOS_ERRNO_EWOULDBLOCK。如果连接尝试超时,则返回-pdFREERTOS_ERRNO_ETIMEDOUT。

  1. FreeRTOS的关闭连接函数

函数原型:

BaseType_t FreeRTOS_shutdown( Socket_t xSocket, BaseType_t xHow );

关闭连接并禁用连接的TCP套接字上的读写。

参数描述:

xSocket:目标套接字,该套接字必须由FreeRTOS_socket()创建。

xHow:关闭的方式,必须设置为FreeRTOS_CLOST_RDWR。因为FreeRTOS+TCP同时关闭读和写。

返回值:

如果关闭请求成功,则返回0。关闭完成的指示为FreeRTOS_recv()对套接字的调用导致返回FreeRTOS_EINVAL。如果xSocket不是有效的TCP套接字,则返回pdFREERTOS_ERRNO_EOPNOTSUPP。如果xSocket是有效的TCP套接字,但套接字没有连接到远程套接字,则返回pdFREERTOS_ERRNO_EOPNOTSUPP。

  1. FreeRTOS的关闭套接字函数

函数原型:

BaseType_t FreeRTOS_closesocket( Socket_t xSocket );

关闭套接字。

参数描述:

xSocket:目标套接字,该套接字必须由FreeRTOS_socket()创建。

返回值:

总是返回0。

  1. FreeRTOS的发送数据函数

函数原型:

BaseType_t FreeRTOS_send( Socket_t xSocket, const void *pvBuffer, size_t xDataLength, BaseType_t xFlags);

将数据发送到套接字。调用此函数之前应确保远程连接成功。

参数描述:

xSocket:目标套接字,该套接字必须由FreeRTOS_socket()创建。

*pvBuffer:指向正在传输的数据源。

xDataLength:发送数据的字节数。

xFlags:该参数未使用。

返回值:

如果发送成功,则返回排队等待发送的字节数(注意,这可能比xTotalDataLength参数请求的字节数少)。如果由于套接字关闭或连接关闭而无法发送数据则返回pdFREERTOS_ERRNO_ENOTCONN。如果由于内存不足而无法发送数据则返回pdFREERTOS_ERRNO_ENOMEM。如果由于xSocket不是有效的TCP套接字而无法发送数据则返回pdFREERTOS_ERRNO_EINVAL。如果在发送任何数据之前发生超时则返回pdFREERTOS_ERRNO_ENOSPC。

  1. FreeRTOS的接收数据函数

函数原型:

BaseType_t FreeRTOS_recv( Socket_t xSocket, void *pvBuffer, size_t xBufferLength, BaseType_t xFlags );

从套接字接收数据。调用此函数之前应确保远程连接成功。

参数描述:

xSocket:目标套接字,该套接字必须由FreeRTOS_socket()创建。

*pvBuffer:指向存放数据的缓冲区。

xDataLength:接收数据的字节数,也是读取的最大字节数。

xFlags:该参数未使用。

返回值:

总是返回0。

部分历程,详情请见源码。

#include "TCP_Client.h"

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include "NetInfoConfig.h"

// 定义端口
#define echoECHO_PORT	( 8088 )

struct freertos_sockaddr xEchoServerAddress;
static const TickType_t xReceiveTimeOut = pdMS_TO_TICKS( 2000 );
static const TickType_t xSendTimeOut = pdMS_TO_TICKS( 1000 );
Socket_t xSocket;
Recivebuff recivebuffes;
TaskHandle_t TCPTest_TaskHandle;

// 连接函数
uint8_t prvEchoConnect(void)
{
	// 每次连接之前逻辑清空接收数组
	recivebuffes.length = 0;
	
	// 填充目标端口和IP地址
	xEchoServerAddress.sin_port = FreeRTOS_htons( echoECHO_PORT );
	xEchoServerAddress.sin_addr = FreeRTOS_inet_addr_quick( configECHO_SERVER_ADDR0,configECHO_SERVER_ADDR1,configECHO_SERVER_ADDR2,configECHO_SERVER_ADDR3 );
	 
	// 创建套接字
	xSocket = FreeRTOS_socket( FREERTOS_AF_INET, FREERTOS_SOCK_STREAM, FREERTOS_IPPROTO_TCP );
	configASSERT( xSocket != FREERTOS_INVALID_SOCKET );

	// 设置超时
	FreeRTOS_setsockopt( xSocket, 0, FREERTOS_SO_RCVTIMEO, &xReceiveTimeOut, sizeof( xReceiveTimeOut ) );
	FreeRTOS_setsockopt( xSocket, 0, FREERTOS_SO_SNDTIMEO, &xSendTimeOut, sizeof( xSendTimeOut ) );
	
	// 连接
	if( FreeRTOS_connect( xSocket, &xEchoServerAddress, sizeof( xEchoServerAddress ) ) == 0 )		return 1;
	else	return 0;
}

// 断开连接
void prvEchoDisconnect(void)
{
	TickType_t xTimeOnEntering;
	char  *pcReceivedString;
	BaseType_t xReturned;
	
	// 关闭连接
	FreeRTOS_shutdown( xSocket, FREERTOS_SHUT_RDWR );
	
	// 检查连接是否断开
	xTimeOnEntering = xTaskGetTickCount();
	do
	{
		xReturned = FreeRTOS_recv( xSocket,&( pcReceivedString[ 0 ] ),echoBUFFER_SIZES,0 );
		if( xReturned < 0 )
		{
			break;
		}
	} 
	while( ( xTaskGetTickCount() - xTimeOnEntering ) < xReceiveTimeOut );
	
	// 关闭套接字
	FreeRTOS_closesocket( xSocket );
}

// 发送函数
uint8_t prvEchoSend(char *pSendBuff,uint32_t lBuffLen)
{	
	BaseType_t lTransmitted;
	
	// 发送数据
	lTransmitted = FreeRTOS_send(	xSocket,pSendBuff,lBuffLen,0 );
	vTaskDelay(150 / portTICK_PERIOD_MS);
	
	if(lTransmitted < 0)
	{
		return 0;
	}
	else
	{
		return 1;
	}
}

// 接收函数
void prvEchoRecive(void)
{
	BaseType_t lrecive;
	
	// 接收数据
	lrecive = FreeRTOS_recv(xSocket,recivebuffes.cRxBuffers,echoBUFFER_SIZES,0);
	if(lrecive <= 0 )
		recivebuffes.length = 0;
	else
	{
		recivebuffes.length = lrecive;
	}
}

// 测试任务 将接到的数据转发
void Task_TCPTest(void *pvParameters)
{
	
	while(1)
	{
		prvEchoRecive();
		if(recivebuffes.length > 0)
		{
			prvEchoSend(recivebuffes.cRxBuffers,recivebuffes.length);
			recivebuffes.length = 0;
		}
		vTaskDelay(5 / portTICK_RATE_MS );
	}
}

 

  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
STM32F407是一款32位ARM Cortex-M4内核的微控制器,拥有丰富的周边设备和高性能。FreeRTOS是一款用于嵌入式系统的实时操作系统,提供了任务调度、内存管理、定时器等功能,使得开发者能够创建复杂的多任务应用程序。TCP客户端是指在TCP/IP协议栈的应用层,通过TCP协议与服务器进行通信的客户端程序。 将STM32F407与FreeRTOSTCP客户端结合起来,可以创建一个具有实时性能的TCP客户端应用。首先,我们需要将FreeRTOS移植到STM32F407上,这可以通过使用STM32CubeMX和FreeRTOS内核文件进行配置和生成来实现。接下来,我们需要编写TCP客户端代码,使用TCP/IP协议栈的相关API进行连接服务器、发送和接收数据等操作。在编写TCP客户端代码时,我们需要创建一个或多个任务,用于处理与服务器的TCP连接和数据通信。这些任务可以使用FreeRTOS提供的任务调度器进行管理和调度。 在使用STM32F407、FreeRTOSTCP客户端时,还需要注意一些重要的方面。首先,需要根据应用需求进行系统资源的合理配置,包括内存大小、任务优先级等。其次,需要注意任务之间的同步和通信,以避免竞争条件和数据一致性的问题。还需要考虑网络连接的稳定性,处理网络异常和错误情况的方法。最后,还需要进行性能测试和调优,以确保系统在给定约束条件下的稳定工作。 综上所述,将STM32F407、FreeRTOSTCP客户端结合起来,可以实现一个具有实时性能的嵌入式TCP客户端应用程序。这种应用可以广泛应用于物联网、智能家居、远程监测等领域。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值