README_zh.md
1# Niobe407开发板TCP联网演示
2
3本案例程序将演示怎么在拓维Niobe407开发板上编写一个连接tcp服务器的业务程序,实现开发板联网上报数据到服务器。
4
5## 简述
6传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793 定义。
7
8TCP旨在适应支持多网络应用的分层协议层次结构。 连接到不同但互连的计算机通信网络的主计算机中的成对进程之间依靠TCP提供可靠的通信服务。TCP假设它可以从较低级别的协议获得简单的,可能不可靠的数据报服务。 原则上,TCP应该能够在从硬线连接到分组交换或电路交换网络的各种通信系统之上操作。
9
10## TCP编程步骤
11 1、创建一个socket,用函数socket();
12 2、设置socket属性,用函数setsockopt();* 可选
13 3、绑定IP地址、端口等信息到socket上,用函数bind();* 可选
14 4、设置要连接的对方的IP地址和端口等属性;
15 5、连接服务器,用函数connect();
16 6、收发数据,用函数send()和recv(),或者read()和write();
17 7、关闭网络连接;
18
19## 特点
20(1)基于流的方式;
21(2)面向连接;
22(3)可靠通信方式;
23(4)在网络状况不佳的时候尽量降低系统由于重传带来的带宽开销;
24(5)通信连接维护是面向通信的两个端点的,而不考虑中间网段和节点
25
26## 结构体详解
27
28```
29struct sockaddr_in {
30 sa_family_t sin_family;
31 in_port_t sin_port;
32 struct in_addr sin_addr;
33 uint8_t sin_zero[8];
34};
35```
36
37**描述:**
38
39地址和端口信息
40
41**参数:**
42
43| 名字 | 描述 |
44| ------ | -------- |
45|sin_family| 指代协议族,在socket编程中只能是AF_INET|
46|sin_port| 存储端口号(使用网络字节顺序)|
47|sin_addr| 存储IP地址,使用in_addr这个数据结构|
48|sin_zero| 为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节|
49
50
51## 函数详解
52
53### void tcp_client_example(void)
54
55 调用此函数初始化ETH接口,并注册eth_enable_state_callBack回调函数
56
57 函数原型:
58
59 void tcp_client_example(void)
60 {
61 EthLinkInfo info = {
62 .useStaticIp = 1,
63 .ipaddr.u8_addr = {192, 168, 8, 200},
64 .netmask.u8_addr = {255, 255, 255, 0},
65 .gw.u8_addr = {192, 168, 8, 1},
66
67 .useStaticMac = 1,
68 .macAddr = {0x02, 0x00, 0x00, 0x00, 0x00, 0x01}
69 };
70
71 set_ethernet_link_info(&info);
72 ethernet_enable(eth_enable_state_callBack); //有线网络使能
73 }
74
75 这里使用的是静态IP和静态MAC地址的模式。
76
77 当需要使用静态IP时, 将useStaticIp设置成1,补充相应的IP参数,并使用set_ethernet_link_info()接口函数将参数注册,调用ethernet_enable()即可。
78
79 当需要使用静态MAC时,将useStaticMac设置成1,补充MAC参数,并使用set_ethernet_link_info()接口函数将参数注册,调用ethernet_enable()即可。
80
81 当使用DHCP和随机MAC地址时,无需设置参数,直接调用ethernet_enable()即可。
82
83### static void eth_enable_state_callBack(EthLinkState state)
84
85 此函数为ETH状态回调函数,在ETH初始化的时候被注册。
86
87 state:当前ETH连接状态
88
89 函数原型:
90
91 static void eth_enable_state_callBack(EthLinkState state)
92 {
93 /* ETH连接断开*/
94 if(state == STATE_UPDATE_LINK_DOWN){
95 printf("ETH LINK STATE: DisConnected!\r\n");
96 osThreadTerminate(tcp_client_id);
97 tcp_client_id = NULL;
98 }
99 /* ETH连接成功*/
100 else if(state == STATE_UPDATE_LINK_UP){
101 printf("ETH LINK STATE: Connected!\r\n");
102
103 osThreadAttr_t attr;
104 attr.name = "tcp_client";
105 attr.attr_bits = 0U;
106 attr.cb_mem = NULL;
107 attr.cb_size = 0U;
108 attr.stack_mem = NULL;
109 attr.stack_size = 1024 * 4;
110 attr.priority = 25;
111 tcp_client_id = osThreadNew((osThreadFunc_t)tcp_client, NULL, &attr);
112 if (tcp_client_id == NULL)
113 {
114 printf("Failed to create tcp_client thread!\n");
115 }
116 }
117 }
118
119 函数实现功能: 当ETH连接状态发生改变时,回调此函数。
120
121 当state == STATE_UPDATE_LINK_UP时,表示连接成功,此时创建1个TCPClient线程
122
123 当state == STATE_UPDATE_LINK_DOWN时,表示连接断开,删除创建的TCPClient线程
124
125
126### void tcp_client(void *thread_param)
127
128 此函数为TCPClient初始化及数据收发任务函数
129
130 thread_param: 线程传入参数,这里未使用
131
132 实现功能: 实现TCP数据接收后,原数据返回的功能
133
134 函数原型:
135
136 void tcp_client(void *thread_param)
137 {
138 int sock = -1;
139 struct sockaddr_in client_addr;
140 char recv_data[512]={0};
141 int recv_data_len;
142
143 printf("start tcp_client test\r\n");
144 while(1)
145 {
146 /* 申请套接字,本质是netconn_new函数的封装 */
147 sock = socket(AF_INET, SOCK_STREAM, 0);
148 if (sock < 0)
149 {
150 printf("Socket error\n");
151 osDelay(100);
152 continue;
153 }
154
155 /* 清空sockaddr_in结构体内存空间 */
156 memset_s(&(client_addr), sizeof(client_addr), 0, sizeof(client_addr));
157
158 /* 为sockaddr_in结构体成员赋值,用于以下的connect绑定 */
159 client_addr.sin_family = AF_INET;
160 client_addr.sin_port = htons(SERVER_PORT);
161 client_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
162
163
164 printf("try connect to server "SERVER_IP":%d\n",SERVER_PORT);
165 /* 将远端server的ip地址与端口进行绑定 */
166 if (connect(sock, (struct sockaddr *)&client_addr, sizeof(struct sockaddr)) == -1)
167 {
168 closesocket(sock);
169 osDelay(1000);
170 continue;
171 }
172
173 printf("Connect to tcp server successful!\n");
174 while(1)
175 {
176 /* 成功接收到数据,返回接收的数据长度 */
177 recv_data_len = recv(sock, recv_data, 511, 0);
178 if (recv_data_len <= 0) break;
179 else
180 recv_data[recv_data_len] = '\0';
181
182 /* 串口打印接收的数据内容 */
183 printf("recv:%s\n",recv_data);
184 /* 发送数据内容 */
185 write(sock,recv_data,recv_data_len);
186 }
187 }
188 }
189
190 其中:
191
192 SERVER_IP 定义要连接的服务器IP
193
194 SERVER_PORT 定义要连接的服务器端口
195
196## 编译调试
197- 进入//kernel/liteos_m目录, 在menuconfig配置中进入如下选项:
198
199 `(Top) → Platform → Board Selection → select board niobe407 → use talkweb niobe407 application → niobe407 application choose`
200
201- 选择 `301_network_tcpclient`
202
203- 回到sdk根目录,执行`hb build -f`脚本进行编译。
204
205### 运行结果
206
207示例代码编译烧录代码后,按下开发板的RESET按键,连接电脑端TCP服务器。
208
209电脑端TCP服务器发送消息:
210 "hello,this is device tcp_client test!"
211
212设备端TCP客户端接收消息:
213
214 ETH Init Success!
215 ETH LINK STATE: Connected!
216 start tcp_client test
217 try connect to server 192.168.8.119:8080
218 Connect to tcp server successful!
219 recv:hello,this is device tcp_client test!
220
221 recv:hello,this is device tcp_client test!
222
223 recv:hello,this is device tcp_client test!
224
225 recv:hello,this is device tcp_client test!
226
227 recv:hello,this is device tcp_client test!
228
229 recv:hello,this is device tcp_client test!
230
231 recv:hello,this is device tcp_client test!
232
233 recv:hello,this is device tcp_client test!
234
235 recv:hello,this is device tcp_client test!