有线网络通信实验3之TCP客户端

描述

26.1 TCP协议概述

TCP是一种面向连接的,可靠地,基于IP的传输层协议,面向连接就意味着两个实用TCP的应用在进行数据交换的时候必须先建立一个TCP连接,当应用层向TCP层发送用于传输的,用8位字节表示的数据流,TCP先把数据流分割成适当长度的报文段,最大传输段的长度MSS通常受计算机连接的网络的数据链路层的最大传输单元MTU控制,之后TCP才把数据包传给IP层,通过它来将数据传送给接收端的TCP层。为了保证报文传输的可靠性,会给每个包一个序号,同时序号也保证了传送到接收端的数据报文能被按照顺序接收,然后接收端对成功接收的报文发回一个响应的确认ACK,如果传送端在合理的时间RTT内没有收到确认,那么对应的数据就会被重传TCP在数据正确性和合法性上采用一个校验和函数来测定数据是否有错误,在发送和接收时都必须计算校验和,在保证可靠性上,对于窗口内未经确认的分组需要重传报文,在拥塞控制上,采用TCP拥塞控制算法。

TCP数据被封装在一个IP数据报文中,IP数据报文结构如下图所示。

TCP

TCP报文数据格式在没有选项的情况下,通常是20个字节,数据结构如下图所示

TCP

(1)源端口号和目的端口号用于寻找发送端和接收端的应用进程,这个与UDP报文相同,这两个值加上IP首部中的源IP地址和目的IP地址唯一确定了一个TCP连接。

(2)序列号字段用来标识从TCP发送端向TCP接收端发送的数据字节流,用于表示在这个报文段中的第一个数据字节,当建立一个新的连接时,握手报文中的SYN标志置1,这个握手报文中的序号字段为随机选择的初始序号ISN(Initial Sequence Number),当连接建立好以后发送方要发送的第一个字节序号为ISN+1。

(3)确认号字段只有在ACK为1的时候才有用,确认号中包含发送确认的一方所期望接收到的下一个序号,确认号是在上一次成功接收到的数据字节序列号上加1,例如上次接收成功接收到对方发过来的数据序号为X,那么返回的确认号就应该为X+1

(4)头部长度又称为首部长度,首部长度中给出了首部的长度,以4个字节为单位,这个字段有4bit,因此TCP最多有60字节的首部,如果没有任何的选项字段,正常的首部长度是20字节,TCP首部中还有6个标志位,这6个标志位如下表所示。

标志位 说明
URG 置1时表示紧急指针有效
ACK 置1时表示确认序号字段有效
PSH 置1表示接收方应该尽快将这个报文段交给应用层
RST 置1表示重建连接
SYN 用于发起连接
FIN 发送端完成发送任务,终止连接

(5)窗口尺寸也就是窗口大小,其中填写相应的值以通知对方期望接收的字节数,窗口大小字段是TCP流量控制的关键字段,窗口大小是一个2个字节的字段,因此窗口大小最大为65535个字节。

(6)16位校验和和UDP的校验和计算原理相同,这是一个强制性的字段,校验和覆盖整个TCP报文段。

(7)紧急指针只有在URG置1时有效,是一个正偏移量,和序号字段中的值相加表示紧急数据最后一个字节的序号。

tcp.c,tcp.h,tcp_in.c和tcp_out.c是LWIP中关于TCP协议的文件,TCP层中函数的关系如下图所示。

TCP

常用的TCP协议的API函数如下表所示。

函数类型 API函数 功能
建立TCP连接 tcp_new() 创建一个TCP的PCB控制块
tcp_bind() 为TCP的PCB控制块绑定一个本地IP地址和端口号
tcp_listen() 开始TCP的PCB监听
tcp_accept() 控制块accept字段注册的回调函数,侦听到连接时被调用
tcp_accepted() 通知LWIP协议栈一个TCP连接被接受了
tcp_conect() 连接远端主机
TCP数据发送 tcp_write() 构造一个报文并放在控制块的发送队列缓冲中
tcp_sent() 控制块sent字段注册的回调函数,数据发送成功后被回调
tcp_output() 将发送缓冲队列中的数据发送出去
TCP数据接收 tcp_recv() 控制块recv字段注册的回调函数,当接收到新数据时被调用
tcp_recved() 当程序处理完数据后一定要调用这个函数,通知内核更新接收窗口
数据轮询 tcp_poll() 控制块poll字段注册的回调函数,该函数周期性调用
关闭和终止连接 tcp_close() 关闭TCP连接
tcp_err() 控制块err字段注册的回调函数,遇到错误时被调用
tcp_abort() 中断TCP连接

26.2 应用编写

26.2.1 tcp_client.c代码编写

#include "tcp_client.h"
#include "delay.h"
#include "usart1.h"
#include "lcd.h"
#include "malloc.h"
#include "string.h"
#include "comm.h"
#include "lwip/debug.h"
#include "lwip/stats.h"
#include "lwip/memp.h"
#include "lwip/mem.h"
//TCP Client 测试全局状态标记变量
//bit7:0,没有数据要发送;1,有数据要发送
//bit6:0,没有收到数据;1,收到数据了
//bit5:0,没有连接上服务器;1,连接上服务器了
//bit4~0:保留
u8 tcp_client_flag;   
//设置远端IP地址
void tcp_client_set_remoteip()
{
  u8 *tbuf;
  tbuf=mymalloc( SRAMIN, 100 ) ;                      //申请内存
  if( tbuf==NULL )
    return ;
  //前三个IP保持和DHCP得到的IP一致
  lwipdev.remoteip[ 0 ] = lwipdev.ip[ 0 ] ;
  lwipdev.remoteip[ 1 ] = lwipdev.ip[ 1 ] ;
  lwipdev.remoteip[ 2 ] = lwipdev.ip[ 2 ] ;
  lwipdev.remoteip[ 3 ] = 113 ;
  sprintf( ( char* )tbuf, "Remote IP:%d.%d.%d.", lwipdev.remoteip[0], lwipdev.remoteip[1], lwipdev.remoteip[2] ) ;
  LCD_ShowString( 30, 150, tbuf ) ;                    //远端IP
  myfree( SRAMIN, tbuf ) ;
}
//关闭与服务器的连接
void tcp_client_connection_close( struct tcp_pcb *tpcb, struct tcp_client_struct *es )
{
  tcp_abort( tpcb ) ;                            //终止连接,删除pcb控制块
  tcp_arg( tpcb, NULL ) ;
  tcp_recv( tpcb, NULL ) ;
  tcp_sent( tpcb, NULL ) ;
  tcp_err( tpcb, NULL ) ;
  tcp_poll( tpcb, NULL, 0 );
  if( es )
    mem_free( es ) ;
  tcp_client_flag &= ~( 1<<5 ) ;                      //标记连接断开了
}
// tcp_recv函数的回调函数
u8 tcp_client_recvbuf[ TCP_CLIENT_RX_BUFSIZE ] ;                //接收数据缓冲区
err_t tcp_client_recv( void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err )
{
  u32 data_len=0 ;
  struct pbuf *q ;
  struct tcp_client_struct *es ;
  err_t ret_err ;
  LWIP_ASSERT( "arg != NULL", arg!=NULL ) ;
  es = ( struct tcp_client_struct* )arg ;
  //如果从服务器接收到空的数据帧就关闭连接
  if( p==NULL )
  {
    es->state = ES_TCPCLIENT_CLOSING ;                //需要关闭TCP连接了
     es->p = p ;
    ret_err = ERR_OK ;
  }
  //当接收到一个非空的数据帧,但是err!=ERR_OK
  else if( err!=ERR_OK )
  { 
    if( p )
      pbuf_free( p ) ;                        //释放接收pbuf
    ret_err = err ;
  }
  //当处于连接状态时
  else if( es->state==ES_TCPCLIENT_CONNECTED )
  {
    //当处于连接状态并且接收到的数据不为空时
    if( p!=NULL )
    {
      memset( tcp_client_recvbuf, 0, TCP_CLIENT_RX_BUFSIZE ) ;      //数据接收缓冲区清零
      //遍历完整个pbuf链表
      for( q=p; q!=NULL; q=q->next )
      {
        if( q->len>( TCP_CLIENT_RX_BUFSIZE-data_len ) )
          memcpy( tcp_client_recvbuf+data_len, q->payload, TCP_CLIENT_RX_BUFSIZE-data_len ) ; 
        else
          memcpy( tcp_client_recvbuf+data_len, q->payload, q->len ) ;
        data_len += q->len ;
        //超出TCP客户端接收数组,跳出
        if( data_len>TCP_CLIENT_RX_BUFSIZE )
          break ;
      }
      tcp_client_flag |= 1<<6 ;                    //标记接收到数据了
       tcp_recved( tpcb,p->tot_len );                  //用于获取接收数据
      pbuf_free( p ) ;                        //释放内存
      ret_err = ERR_OK ;
    }
  }
  //接收到数据但是连接已经关闭
  else
  { 
    tcp_recved( tpcb, p->tot_len ) ;                    //用于获取接收数据
    es->p = NULL ;
    pbuf_free( p ) ;                          //释放内存
    ret_err = ERR_OK ;
  }
  return ret_err ;
}
// tcp_err函数的回调函数
void tcp_client_error( void *arg, err_t err )
{


}
//发送数据
void tcp_client_senddata( struct tcp_pcb *tpcb, struct tcp_client_struct *es )
{
  struct pbuf *ptr ; 
   err_t wr_err = ERR_OK ;
  while( ( wr_err==ERR_OK )&&( es->p )&&( es->p->len<=tcp_sndbuf( tpcb ) ) )
  {
    ptr = es->p ;
    wr_err = tcp_write( tpcb, ptr->payload, ptr->len, 1 ) ;          //数据加入到发送缓冲队列中
    if( wr_err==ERR_OK )
    {
      es->p = ptr->next ;                      //指向下一个pbuf
      //pbuf的ref加一
      if( es->p )
        pbuf_ref( es->p );
      pbuf_free( ptr ) ;                        //释放ptr
    }
    else if( wr_err==ERR_MEM )
      es->p = ptr ;
    tcp_output( tpcb ) ;                        //发送缓冲队列中的数据发送
  }
}
// tcp_sent的回调函数(从远端接收到ACK后发送数据)
err_t tcp_client_sent( void *arg, struct tcp_pcb *tpcb, u16_t len )
{
  struct tcp_client_struct *es ;
  LWIP_UNUSED_ARG( len ) ;
  es = ( struct tcp_client_struct* )arg ;
  if( es->p )
    tcp_client_senddata( tpcb, es ) ;                    //发送数据
  return ERR_OK ;
}
// tcp_poll的回调函数
const u8 *tcp_client_sendbuf = "STM32F103 TCP Client send data\\r\\n" ;      //TCP服务器发送数据内容
err_t tcp_client_poll( void *arg, struct tcp_pcb *tpcb )
{
  err_t ret_err ;
  struct tcp_client_struct *es ; 
  es = ( struct tcp_client_struct* )arg ;
  //连接处于空闲可以发送数据
  if( es!=NULL )
  {
    //判断是否有数据要发送
    if( tcp_client_flag&( 1<<7 ) )
    {
      es->p = pbuf_alloc( PBUF_TRANSPORT, strlen( ( char* )tcp_client_sendbuf ), PBUF_POOL ) ; 
      pbuf_take( es->p, ( char* )tcp_client_sendbuf, strlen( ( char* )tcp_client_sendbuf ) ) ; 
      tcp_client_senddata( tpcb, es ) ;                  //将数据发送出去
      tcp_client_flag &= ~( 1<<7 ) ;                  //清除数据发送标志
      //释放内存
      if( es->p )
        pbuf_free( es->p ) ;
    }
    else if( es->state==ES_TCPCLIENT_CLOSING )
       tcp_client_connection_close( tpcb, es ) ;              //关闭TCP连接
    ret_err = ERR_OK ;
  }
  else
  { 
    tcp_abort( tpcb ) ;                          //终止连接,删除pcb控制块
    ret_err = ERR_ABRT ;
  }
  return ret_err ;
}
//连接建立后调用回调函数
err_t tcp_client_connected(void *arg, struct tcp_pcb *tpcb, err_t err)
{
  struct tcp_client_struct *es=NULL;  
  if(err==ERR_OK)   
  {
    es = ( struct tcp_client_struct* )mem_malloc( sizeof( struct tcp_client_struct ) ) ; //申请内存
    //内存申请成功
    if( es )
    {
       es->state = ES_TCPCLIENT_CONNECTED ;            //状态为连接成功
      es->pcb = tpcb ;
      es->p = NULL ;
      tcp_arg( tpcb, es ) ;                      //更新tpcb的callback_arg
      tcp_recv( tpcb, tcp_client_recv ) ;                  //初始化tcp_recv回调功能
      tcp_err( tpcb, tcp_client_error ) ;                  //初始化tcp_err()回调函数
      tcp_sent( tpcb, tcp_client_sent ) ;                  //初始化tcp_sent回调功能
      tcp_poll( tpcb, tcp_client_poll, 1 ) ;                //初始化tcp_poll回调功能
       tcp_client_flag |= 1<<5 ;                    //标记连接到服务器了
      err = ERR_OK ;
    }
    else
    {
      tcp_client_connection_close( tpcb, es ) ;              //关闭连接
      err = ERR_MEM ;                        //返回内存分配错误
    }
  }
  else
    tcp_client_connection_close( tpcb, 0 ) ;                //关闭连接
  return err ;
}
//客户机测试
void tcp_client_test()
{
   struct tcp_pcb *tcppcb ;                        //定义一个TCP服务器控制块
  struct ip_addr rmtipaddr ;                        //远端ip地址
  u8 *tbuf ;
  u8 res=0 ;    
  u8 t=0 ; 
  u8 connflag=0 ;                            //连接标记
  tcp_client_set_remoteip() ;                        //先选择IP
  tbuf = mymalloc( SRAMIN, 200 ) ;                    //申请内存
  //内存申请失败了,直接退出
  if( tbuf==NULL )
    return ;
  sprintf( ( char* )tbuf, "Local IP:%d.%d.%d.%d", lwipdev.ip[0], lwipdev.ip[1], lwipdev.ip[2], lwipdev.ip[3] ) ;
  LCD_ShowString( 30, 130, tbuf ) ;                    //服务器IP
  sprintf( ( char* )tbuf, "Remote IP:%d.%d.%d.%d", lwipdev.remoteip[0], lwipdev.remoteip[1], lwipdev.remoteip[2], lwipdev.remoteip[3] ) ;
  LCD_ShowString( 30, 150, tbuf ) ;                    //远端IP
  sprintf( ( char* )tbuf, "Remote Port:%d", TCP_CLIENT_PORT ) ;          //客户端端口号
  LCD_ShowString( 30, 170, tbuf ) ;
  LCD_ShowString( 30, 190, "STATUS:Disconnected" ) ;
  tcppcb = tcp_new() ;                          //创建一个新的pcb
  //创建成功
  if( tcppcb )
  {
  IP4_ADDR( &rmtipaddr, lwipdev.remoteip[0], lwipdev.remoteip[1], lwipdev.remoteip[2], lwipdev.remoteip[3] ) ;
    tcp_connect( tcppcb, &rmtipaddr, TCP_CLIENT_PORT, tcp_client_connected ) ; 
   }
  else
    res = 1 ;
  while( res==0 )
  {
    //是否收到数据
    if( tcp_client_flag&1<<6 )
    {
      LCD_ShowString( 30, 230, tcp_client_recvbuf ) ;            //显示接收到的数据
      tcp_client_flag |= 1<<7 ;                    //标记要发送数据
      tcp_client_flag &= ~( 1<<6 ) ;                  //标记数据已经被处理了
    }
    //是否连接上
    if( tcp_client_flag&1<<5 )
    {
      if( connflag==0 )
      { 
        LCD_ShowString( 30, 190, "STATUS:Connected   " ) ;
        LCD_ShowString( 30, 210, "Receive Data:" ) ;
        connflag = 1 ;                        //标记连接了
      }
    }
    else if( connflag )
    {
       LCD_ShowString( 30, 190, "STATUS:Disconnected" ) ;
      connflag = 0 ;                          //标记连接断开了
    } 
    lwip_periodic_handle() ;
    lwip_pkt_handle() ;
    delay_ms( 2 ) ;
    t ++ ;
    if( t==200 )
    {
      //未连接上,则尝试重连
      if( ( connflag==0 )&&( ( tcp_client_flag&1<<5 )==0 ) )
      { 
        tcp_client_connection_close( tcppcb, 0 ) ;            //关闭连接
        tcppcb = tcp_new() ;                    //创建一个新的pcb
        //创建成功
        if( tcppcb )
          tcp_connect( tcppcb, &rmtipaddr, TCP_CLIENT_PORT, tcp_client_connected ) ; 
      }
      t = 0 ;
    }    
  }
  tcp_client_connection_close( tcppcb, 0 ) ;                  //关闭TCP Client连接
  myfree( SRAMIN, tbuf ) ;
}

26.2.2 tcp_client.h代码编写

#ifndef _TCP_CLIENT_H_
#define _TCP_CLIENT_H_
#include "sys.h"
#include "lwip/tcp.h"
#include "lwip/pbuf.h"
#define TCP_CLIENT_RX_BUFSIZE  1500            //最大接收数据长度
#define TCP_CLIENT_TX_BUFSIZE  200              //最大发送数据长度
#define LWIP_SEND_DATA      0x80            //有数据发送
#define  TCP_CLIENT_PORT    8087            //远端端口
//tcp服务器连接状态
enum tcp_client_states
{
  ES_TCPCLIENT_NONE = 0,    //没有连接
  ES_TCPCLIENT_CONNECTED,  //连接到服务器了 
  ES_TCPCLIENT_CLOSING,    //关闭连接
};
//LWIP回调函数使用的结构体
struct tcp_client_struct
{
  u8 state;            //当前连接状
  struct tcp_pcb *pcb;      //指向当前的pcb
  struct pbuf *p;        //指向接收/或传输的pbuf
};  
void tcp_client_test( void ) ;                    //TCP Client测试函数
#endif

26.2.3 主函数代码编写

#include "sys.h"
#include "delay.h"
#include "usart1.h"
#include "tim.h"
#include "lcd.h"
#include "malloc.h"
#include "dm9000.h"
#include "lwip/netif.h"
#include "comm.h"
#include "lwipopts.h"
#include "tcp_client.h"
int main()
{
  u8 buf[ 30 ];
   STM32_Clock_Init( 9 ) ;                        //系统时钟设置
  SysTick_Init( 72 ) ;                          //延时初始化
  USART1_Init( 72, 115200 ) ;                      //串口初始化为115200
  LCD_Init() ;                            //初始化LCD
  TIM3_Init( 1000, 719 ) ;                        //定时器3频率为100hz
  my_mem_init( SRAMIN ) ;                      //初始化内部内存池
  while( lwip_comm_init() ) ;                      //lwip初始化
  //等待DHCP获取成功/超时溢出
  while( ( lwipdev.dhcpstatus!=2 )&&( lwipdev.dhcpstatus!=0xFF ) )
  {
    lwip_periodic_handle() ;                    //LWIP内核需要定时处理的函数
    lwip_pkt_handle() ;
  }
  POINT_COLOR=RED;
  LCD_ShowString( 30, 110, "LWIP Init Successed" ) ;
  //打印动态IP地址
  if( lwipdev.dhcpstatus==2 )
    sprintf( ( char* )buf, "DHCP IP:%d.%d.%d.%d", lwipdev.ip[0], lwipdev.ip[1], lwipdev.ip[2], lwipdev.ip[3] ) ;
  //打印静态IP地址
  else
    sprintf( ( char* )buf, "Static IP:%d.%d.%d.%d", lwipdev.ip[0], lwipdev.ip[1], lwipdev.ip[2], lwipdev.ip[3] ) ;
  LCD_ShowString( 30, 130, buf ) ; 
  //得到网速
  if( ( DM9000_Get_SpeedAndDuplex()&0x02 )==0x02 )
    LCD_ShowString( 30, 150, "Ethernet Speed:10M" ) ;
  else
    LCD_ShowString( 30, 150, "Ethernet Speed:100M" ) ;
   while( 1 )
  {
    tcp_client_test() ;
    lwip_periodic_handle() ;
    lwip_pkt_handle() ;
    delay_ms( 2 ) ;
  }
}

26.3 实验结果

TCP

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分