嵌牛6

姓名 李泽浩 学号 21181214372 学院 广州研究院

转载自 https://blog.csdn.net/FPGADesigner/article/details/88775286

【嵌牛导读】本文是TCP发送Hello World(client模式)使用详解

【嵌牛鼻子】TCP发送Hello World(client模式)

【嵌牛提问】TCP发送Hello World(client模式)

【嵌牛正文】

TCP的工作机制比UDP要复杂的多。本文介绍用TCP发送“Hello World”的实例,讨论程序设计中几个关键的问题。本文撰写思路假设您已经阅读了本系列前几篇与lwIP、UDP相关的文章,重复性语言不过多描述。本文TCP工作在client模式。TCP内容较多,后面的文章会逐步深入介绍。

SDK程序设计

按照前文方法,新建工程后启用lwIP 1.4.1库,其余配置都保持默认即可(使用RAW API)。使用lwIP需要启动中断系统,sys_intr.h和sys_intr.c文件代码与UDP实例中的相同。

此外lwIP要求每250ms调用一次tcp_tmr()函数(具体原因后面小节分析)因此我们要添加定时器资源。定时器使用方法参考本系列第10篇,这里不再讲述。

timer_intr.h文件代码如下:

#include

#include "xadcps.h"

#include "xil_types.h"

#include "Xscugic.h"

#include "Xil_exception.h"

#include "xscutimer.h"

extern volatile int TcpTmrFlag;

#define TIMER_DEVICE_ID    XPAR_XSCUTIMER_0_DEVICE_ID

#define TIMER_IRPT_INTR    XPAR_SCUTIMER_INTR

void Timer_start(XScuTimer *TimerPtr);

void Timer_Setup_Intr_System(XScuGic *GicInstancePtr,XScuTimer *TimerInstancePtr, u16 TimerIntrId);

int Timer_init(XScuTimer *TimerPtr,u32 Load_Value,u32 DeviceId);

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

timer_intr.c文件的代码如下:

#include "timer_intr.h"

volatile int TcpTmrFlag;

//---------------------------------------------------------

//                  定时器中断处理函数

//---------------------------------------------------------

static void TimerIntrHandler(void *CallBackRef)

{

    XScuTimer *TimerInstancePtr = (XScuTimer *) CallBackRef;

    XScuTimer_ClearInterruptStatus(TimerInstancePtr);

TcpTmrFlag = 1;

}

//---------------------------------------------------------

//                    启动定时器函数

//---------------------------------------------------------

void Timer_start(XScuTimer *TimerPtr)

{

XScuTimer_Start(TimerPtr);

}

//---------------------------------------------------------

//                  定时器中断设置函数

//---------------------------------------------------------

void Timer_Setup_Intr_System(XScuGic *GicInstancePtr,XScuTimer *TimerInstancePtr, u16 TimerIntrId)

{

XScuGic_Connect(GicInstancePtr, TimerIntrId,

        (Xil_ExceptionHandler)TimerIntrHandler, (void *)TimerInstancePtr);

XScuGic_Enable(GicInstancePtr, TimerIntrId);

XScuTimer_EnableInterrupt(TimerInstancePtr);

}

//---------------------------------------------------------

//                    定时器初始化函数

//---------------------------------------------------------

int Timer_init(XScuTimer *TimerPtr,u32 Load_Value,u32 DeviceId)

{

    XScuTimer_Config *TMRConfigPtr;

    TMRConfigPtr = XScuTimer_LookupConfig(DeviceId);

    XScuTimer_CfgInitialize(TimerPtr,TMRConfigPtr,TMRConfigPtr->BaseAddr);

    XScuTimer_LoadTimer(TimerPtr, Load_Value);

    XScuTimer_EnableAutoReload(TimerPtr);

    return 1;

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

先看mian.c文件中的代码,看下RAW模式下使用TCP的架构与使用UDP有何不同:

//--------------------------------------------------

//          blog.csdn.net/FPGADesigner

//          copyright by CUIT Qi Liu

//      Zynq Lwip TCP Communication Test Program

//--------------------------------------------------

#include "timer_intr.h"

#include "sys_intr.h"

#include "user_tcp.h"

#include "sleep.h"

#define TIMER_LOAD_VALUE    XPAR_CPU_CORTEXA9_0_CPU_CLK_FREQ_HZ / 8 //0.25S

static  XScuGic Intc; //GIC

static  XScuTimer Timer;//timer

extern volatile unsigned tcp_client_connected;

extern int tcp_trans_cnt;

//--------------------------------------------------

//                中断与定时器初始化

//--------------------------------------------------

void System_Init()

{

Timer_init(&Timer,TIMER_LOAD_VALUE,TIMER_DEVICE_ID);

Init_Intr_System(&Intc); // initial DMA interrupt system

Setup_Intr_Exception(&Intc);

Timer_Setup_Intr_System(&Intc,&Timer,TIMER_IRPT_INTR);

Timer_start(&Timer);

TcpTmrFlag = 0;

}

//--------------------------------------------------

//                    主程序

//--------------------------------------------------

int main(void)

{

struct netif *netif, server_netif;  //用于lwIP网络接口的通用数据结构

struct ip_addr ipaddr, netmask, gw;  //unsigned int

//开发板的MAC地址

unsigned char mac_ethernet_address[] = {0x00,0x0a,0x35,0x00,0x01,0x02};

System_Init();

netif = &server_netif;

//将4byte结构的IP地址转换为unsigned int

IP4_ADDR(&ipaddr,  192, 168,  1, 10);  //IP地址(开发板)

IP4_ADDR(&netmask, 255, 255, 255,  0);  //网络掩码

IP4_ADDR(&gw,      192, 168,  1,  1);  //网关

lwip_init();    //初始化lwIP

//将网络接口添加到netif_list中

if (!xemac_add(netif, &ipaddr, &netmask, &gw, mac_ethernet_address, XPAR_XEMACPS_0_BASEADDR)) {

xil_printf("Error adding N/W interface\r\n");

return -1;

}

netif_set_default(netif);  //设置默认网络接口

netif_set_up(netif);      //启动网络接口

tcp_send_init();          //初始化TCP PCB

while(1) {

/* call tcp timer every 250ms */

if(TcpTmrFlag)

{

tcp_tmr();

TcpTmrFlag = 0;

}

xemacif_input(netif);    //将MAC队列中的packets传输到lwIP栈中

if (tcp_client_connected) {  //连接成功则发送数据

sleep(1);

send_data();

xil_printf("tran_cnt:%d\n\r", tcp_trans_cnt);

}

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

与UDP相同的lwIP配置流程代码中给出了详细注释。最大区别在于下面这部分:

while(1) {

/* call tcp timer every 250ms */

if(TcpTmrFlag)

{

tcp_tmr();

TcpTmrFlag = 0;

}

user_function();  //用户功能

}

1

2

3

4

5

6

7

8

9

别小瞧这个函数,tcp_tmr对TCP的稳定使用至关重要,可查看本文后面小节的测试。

user_tcp.h文件代码如下(为了避免混淆,尽量不要取名为lwIP库中已用过的udp.h/c和tcp.h/c):

#include

#include

#include "lwip/err.h"

#include "lwip/tcp.h"

#include "lwipopts.h"

#include "netif/xadapter.h"

#include "lwip/init.h"

#include "lwip/tcp_impl.h"

#include "xil_printf.h"

extern volatile unsigned tcp_client_connected;

extern int tcp_trans_cnt;

int tcp_send_init();

void send_data(void);

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

user_tcp.c文件代码如下:

#include "user_tcp.h"

#define SEND_SIZE 12

static struct tcp_pcb *connected_pcb = NULL;

volatile unsigned tcp_client_connected = 0;

int tcp_trans_cnt = 0;

char sendBuffer[12]="Hello World!";

//--------------------------------------------------

//            TCP数据发送成功的回调函数

//--------------------------------------------------

static err_t tcp_sent_callback(void *arg, struct tcp_pcb *tpcb, u16_t len)

{

tcp_trans_cnt++;    //统计发送数据的次数

xil_printf("send int");

return ERR_OK;

}

//--------------------------------------------------

//            TCP连接成功的回调函数

//--------------------------------------------------

static err_t tcp_connected_callback(void *arg, struct tcp_pcb *tpcb, err_t err)

{

xil_printf("txperf: Connected to iperf server\r\n");

connected_pcb = tpcb;  //存储连接的TCP状态

tcp_nagle_disable(connected_pcb);

tcp_arg(tpcb, NULL);    //指定应该传递回调函数的参数

//设置当TCP数据成功传递到远程主机时调用回调函数tcp_sent_callback

tcp_sent(tpcb, tcp_sent_callback);

tcp_client_connected = 1;  //置1表示连接已建立

xil_printf("Connect Success.\r\n");

return ERR_OK;

}

//--------------------------------------------------

//              TCP PCB初始化函数

//--------------------------------------------------

int tcp_send_init()

{

struct tcp_pcb *pcb;

struct ip_addr ipaddr;

err_t err;

u16_t port;

pcb = tcp_new(); //创建新的TCP PCB

if (!pcb) {

xil_printf("txperf: Error creating PCB. Out of Memory\r\n");

return -1;

}

IP4_ADDR(&ipaddr, 192, 168, 1, 100);  //服务器的IP地址

port = 7;           //服务器的默认端口

    tcp_client_connected = 0;

    //连接主机,连接建立后调用回调函数tcp_connected_callback

err = tcp_connect(pcb, &ipaddr, port, tcp_connected_callback);

if (err != ERR_OK) {

xil_printf("txperf: tcp_connect returned error: %d\r\n", err);

return err;

}

xil_printf("%d\r\n",err);

return 0;

}

//--------------------------------------------------

//                TCP数据发送函数

//--------------------------------------------------

void send_data(void)

{

err_t err;

struct tcp_pcb *tpcb = connected_pcb;

if (!connected_pcb)

return;

err = tcp_write(tpcb, sendBuffer, SEND_SIZE, 3);

if (err != ERR_OK) {

xil_printf("txperf: Error on tcp_write: %d\r\n", err);

connected_pcb = NULL;

return;

}

err = tcp_output(tpcb);

if (err != ERR_OK) {

xil_printf("txperf: Error on tcp_output: %d\r\n",err);

return;

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

本设计的TCP工作在客户端模式,将远程主机当作服务器,主动请求连接。TCP client和TCP server在lwIP中的连接流程和区别可参考本系列前面与lwIP相关的文章。

可以看到仅仅是TCP发送,就用到了两个回调函数。TCP的回调机制比UDP要复杂的多。下面我们分小节探讨下上面程序中的各种问题。

回调机制

在初始化TCP连接时,我们使用tcp_connect函数请求建立于服务器的连接,绑定了第一个回调函数tcp_connected_callback。这个回调函数会在服务器给出应答,也就是连接的确建立起来后才会调用。在这个回调函数中我们可以设置“xil_printf(“Connect Success.\r\n”);”这句代码,通过串口打印确认回调函数有没有被调用。

tcp_connect函数中又使用tcp_sent函数绑定了另一个函数tcp_sent_callback。我们要知道,这个回调函数并不是在TCP发送后就会马上被调用。它需要等主机返回应答信号,确认数据成功的发送到主机后才会调用。我们可以在tcp_sent_callback中计数,并通过“xil_printf(“send int”);”来查看是否进入了回调。main.c中每次发送时打印计数值。

测试结果如上图,注意并不是每次发送数据都进入了tcp_sent_callback回调函数。正确理解回调函数的调用机制,我们才能灵活使用。

TCP客户端连接

回顾下本系列第11篇中介绍过的TCP主动连接方法:

调用pcb_new创建一个pcb。

(可选)调用tcp_arg将应用程序中特定的值于PCB关联在一起。

(可选)调用tcp_bind函数指定本地IP地址和端口

调用tcp_connect函数。

对照本实例中的tcp_send_init函数,你会发现我们2、3步都没有设置,读者可以自行添加代码查看效果。如果没有使用tcp_bind绑定本地端口,那么与主机连接后会自动分配一个端口;本地IP地址使用main.c中设置了的地址。

在网络调试助手中,可以查看与TCP服务器连接的客户端IP地址和端口号。

tcp_tmr函数为何重要

如果各位实际使用时,把while循环中的tcp_tmr函数注释掉,或者不启动定时器,发现好像tcp也可以正常使用,确实连接成功也发送出去了呀?其实不然。我举几个例子,不知道您是否遇到过下列情况:

连接到网络助手后,一开始工作正常,但一段时间后失常;

已经连接到网络调试助手,再次下载程序(即再次连接),发现tcp_connect返回的也是ERR_OK,但就是没有进入连接回调函数;

发送过程中偶尔出现tcp_write函数返回ERR_CONN(对应-13,表示未连接)。

这就是tcp工作不稳定的表现。lwIP的TCP内部也有定时器,维持其正常运作。我们只有周期性地(通常是250ms)调用tcp_tmr函数,才能调度TCP内部的定时器。

神奇的nagle算法

本实例中我们在连接回调函数中使用“tcp_nagle_disable(connected_pcb);”这条代码关闭了nagle算法。如果把这句代码注释掉,会出现如下情况:

一下子接收到一大片Hello World,且数量不一,这便是nagle算法的作用。如果我们要发送字节较短的数据:TCP/IP要为其加包,加包的字节比数据本身还多,还要进行校验和的计算;主机接收到包后再解包,检查校验和……这无疑是个效率低下的过程。

Nagle算法会自动连接许多小的消息,减少发送包的个数来增加网络效率。TCP/IP协议中无论发送多少数据,总需要在数据前面加上协议头;对方接收到数据也需要发送应答。Nagle算法尽可能发送大块数据,避免小数据块,从而充分利用网络带宽。

然而本例中我们不追求效率,只希望实现确确实实的是每秒发送一个Hello World,那我们就有必要关掉nagle算法。效果如下:

TCP内容较多,后面的文章会逐步深入介绍。

————————————————

版权声明:本文为CSDN博主「FPGADesigner」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/FPGADesigner/article/details/88775286

你可能感兴趣的:(嵌牛6)