- 学习目标
- 学习内容
- include
- include
- include
- include “cmsis_os2.h”
- include “ohos_init.h”
- include “wifi_sta.h”
- include “lwip/sockets.h”
- define SSID “xq”
- define PASSWORD “qwer1234”
- define HOSTNAME “itcast”
- define SERVER_IP “192.168.137.1”
- define SERVER_PORT 8080
- 服务端开发
- include
- include
- include
- include “cmsis_os2.h”
- include “ohos_init.h”
- include “wifi_sta.h”
- include “lwip/sockets.h”
- define SSID “xq”
- define PASSWORD “qwer1234”
- define HOSTNAME “itcast”
- define SERVER_PORT 8080
- UDP编程
- include
- include
- include
- include “cmsis_os2.h”
- include “ohos_init.h”
- include “wifi_sta.h”
- include “lwip/sockets.h”
- define SSID “xq”
- define PASSWORD “qwer1234”
- define HOSTNAME “itcast”
- define SERVER_IP “192.168.137.1”
- define SERVER_PORT 8080
- 服务端开发
- include
- include
- include
- include “cmsis_os2.h”
- include “ohos_init.h”
- include “wifi_sta.h”
- include “lwip/sockets.h”
- define SSID “xq”
- define PASSWORD “qwer1234”
- define HOSTNAME “itcast”
- define SERVER_PORT 8080
- 广播开发
- include
- include
- include
- include “cmsis_os2.h”
- include “ohos_init.h”
- include “wifi_sta.h”
- include “lwip/sockets.h”
- define SSID “xq”
- define PASSWORD “qwer1234”
- define HOSTNAME “itcast”
- define SERVER_PORT 8080
- 练习题
学习目标
- 理解TCP通讯协议
- 理解UDP通讯协议
- 能够编写TCP客户端
- 能够编写TCP服务端
- 能够编写UDP的发送端
- 能够编写UDP的接收端
- 能够使用UDP广播
学习内容
Socket通讯
socket(简称 套接字) ,是支持TCP/IP的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。
它能实现不同主机间的进程间通信,我们网络上各种各样的服务大多都是基于 Socket 来完成通信的
例如我们每天浏览网页、QQ 聊天、收发 email 等等
TCP协议
TCP协议,传输控制协议(英语:Transmission Control Protocol,缩写为 TCP) 是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793定义。
TCP通信需要经过创建连接、数据传送、终止连接三个步骤。
TCP通信模型中,在通信开始之前,一定要先建立相关的链接,才能发送数据,类似于生活中,”打电话””
TCP的特点:
- 面向连接。
- 通信双方必须先建立连接才能进行数据的传输,双方都必须为该连接分配必要的系统内核资源,以管理连接的状态和连接上的传输。
- 双方间的数据传输都可以通过这一个连接进行。
- 完成数据交换后,双方必须断开此连接,以释放系统资源。
- 传输可靠
- TCP采用发送应答机制。
- 超时重传。发送端发出一个报文段之后就启动定时器,如果在定时时间内没有收到应答就重新发送这个报文段。TCP为了保证不发生丢包,就给每个包一个序号,同时序号也保证了传送到接收端实体的包的按序接收。然后接收端实体对已成功收到的包发回一个相应的确认(ACK);如果发送端实体在合理的往返时延(RTT)内未收到确认,那么对应的数据包就被假设为已丢失将会被进行重传。
- 错误校验。TCP用一个校验和函数来检验数据是否有错误;在发送和接收时都要计算校验和。
- 流量控制和阻塞管理。流量控制用来避免主机发送得过快而使接收方来不及完全收下。
UDP协议
UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。
由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输例如视频会议都使用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议。
UDP传输的每个数据包被限制在64K以内(数据包大小由一个16位的无符号整数记录)。
UDP (User Datagram Protocol )不提供复杂的控制机制, 如果传输过程中出现丢包, UDP 也不负责重发. 甚至当出现包到达顺序乱掉时候也没有纠正的功能. 由于 UDP 面向无连接, 它可以随时发送数据. 再加上 UDP 本身的处理既简单又高效, 因此常用于以下几个方面:
- 包总量较少的通信(DNS).
- 视频、音频等多媒体通信(即时通信).
- 限定于 LAN 等特定网络中的应用通信.
- 广播通信(广播、多播)
UDP的特点:
- 需要资源少
- 不保证接收
- 无连接
- 快
UDP和TCP的区别
| UDP | TCP | | —- | —- | | 面向无连接 | 面向有连接 | | 支持一对一、一对多、多对一、和多对多的通信 | 只能有两个端点,实现一对一的通信 | | 不保证数据传输的可靠性 | 传输数据无差错,不丢失,不重复,且按时序到达 | | 占用资源较少 | 占用资源较多 |
TCP编程
开发流程图
客户端开发
需求说明
- 确保设备能够连接网络
- 当前开发的是客户端,需要有一个服务端配合,实现两者通讯
- 确保服务端和客户端在同一个网络环境中(在同一个局域网)
-
开发流程
来到应用开发的根目录。
我们编写代码的根目录为device/board/itcast/genkipi/app
。 根目录下新建
tcp_client
文件夹,此为项目目录。- 此项目目录下新建
main.c
文件 - 此项目目录下新建
BUILD.gn
文件 - 修改根目录下的
BUILD.gn
文件代码部分
```cinclude
include
include
include “cmsis_os2.h”
include “ohos_init.h”
include “wifi_sta.h”
include “lwip/sockets.h”
define SSID “xq”
define PASSWORD “qwer1234”
define HOSTNAME “itcast”
define SERVER_IP “192.168.137.1”
define SERVER_PORT 8080
static void start_tcp_client(void) { int sock_fd; int ret; sock_fd = socket(AF_INET, SOCK_STREAM, 0); if (sock_fd < 0) { perror(“[actuator_service]sock_fd create error\r\n”); return; }
// receive addr config
struct sockaddr_in server_addr;
socklen_t server_addr_len = sizeof(server_addr);
memset((void *) &server_addr, 0, server_addr_len);
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
server_addr.sin_port = htons(SERVER_PORT);
ret = connect(sock_fd, (struct sockaddr *) &server_addr, server_addr_len);
if (ret == -1) {
perror("connect error\r\n");
return;
}
char msg[1024];
int cnt = 0;
while (1) {
sprintf(msg, "hello %d\r\n", cnt++);
ret = send(sock_fd, msg, strlen(msg), 0);
if (ret == -1) {
perror("send error\r\n");
break;
}
usleep(1 * 1000 * 1000);
}
}
static void startSta(void) { wifi_sta_connect(SSID, PASSWORD, HOSTNAME); start_tcp_client(); }
static void main(void) { osThreadAttr_t attr;
attr.name = "tcp_client";
attr.attr_bits = 0U;
attr.cb_mem = NULL;
attr.cb_size = 0U;
attr.stack_mem = NULL;
attr.stack_size = 1024 * 4;
attr.priority = 25;
// Create the Thread1 task
if (osThreadNew((osThreadFunc_t)startSta, NULL, &attr) == NULL) {
printf("Failed to create say hello Thread!\n");
}
}
APP_FEATURE_INIT(main);
```c
static_library("tcp_client") {
sources = [ "main.c" ]
include_dirs = [
"//base/iothardware/peripheral/interfaces/inner_api",
"../../services/wifi/include",
]
deps = [
"../../services:genkipi_services",
]
}
import("//build/lite/config/component/lite_component.gni")
lite_component("app") {
features = [
"tcp_client"
]
}
校验方式
打开网络调试助手,当前编写的代码为tcp 客户端,那么我们需要将调试助手配置为tcp 服务端。配置如图:
观察输出结果:
服务端开发
需求说明
- 确保设备能够连接网络
- 当前开发的是服务端,需要有一个客户端配合,实现两者通讯
- 确保服务端和客户端在同一个网络环境中(在同一个局域网)
-
开发流程
来到应用开发的根目录。
我们编写代码的根目录为device/board/itcast/genkipi/app
。 根目录下新建
tcp_server
文件夹,此为项目目录。- 此项目目录下新建
main.c
文件 - 此项目目录下新建
BUILD.gn
文件 - 修改根目录下的
BUILD.gn
文件代码部分
```cinclude
include
include
include “cmsis_os2.h”
include “ohos_init.h”
include “wifi_sta.h”
include “lwip/sockets.h”
define SSID “xq”
define PASSWORD “qwer1234”
define HOSTNAME “itcast”
define SERVER_PORT 8080
static void start_tcp_server(void) { int sock_fd; int ret; sock_fd = socket(AF_INET, SOCK_STREAM, 0); if (sock_fd < 0) { perror(“[actuator_service]sock_fd create error\r\n”); return; } // config receive addr struct sockaddr_in recvfrom_addr; socklen_t recvfrom_addr_len = sizeof(recvfrom_addr);
memset((void *) &recvfrom_addr, 0, recvfrom_addr_len);
recvfrom_addr.sin_family = AF_INET;
recvfrom_addr.sin_addr.s_addr = htonl(INADDR_ANY);
recvfrom_addr.sin_port = htons(SERVER_PORT);
// bind
ret = bind(sock_fd, (struct sockaddr *) &recvfrom_addr, recvfrom_addr_len);
if (ret == -1) {
perror("bind error\r\n");
return;
}
// listen, just listen 1 connect
ret = listen(sock_fd, 1);
if (ret == -1) {
perror("[actuator_service]listen error\r\n");
return;
}
while (1) {
int client_fd;
struct sockaddr_in client_addr;
socklen_t client_addr_len = sizeof(client_addr);
client_fd = accept(sock_fd, (struct sockaddr *) &client_addr, &client_addr_len);
if (client_fd < 0) {
perror("accept client error\r\n");
continue;
}
printf("client connect: %s\r\n", inet_ntoa(((struct sockaddr_in*)(&client_addr))->sin_addr));
char recv_buf[1024];
int recv_len;
while (1) {
recv_len = recv(client_fd, recv_buf, sizeof(recv_buf), 0);
if (recv_len <= 0) {
break;
}
char recv_data[recv_len];
memcpy(recv_data, recv_buf, recv_len);
recv_data[recv_len] = '\0';
printf("len: %d data: %s\r\n", recv_len, recv_data);
}
}
}
static void startSta(void) { wifi_sta_connect(SSID, PASSWORD, HOSTNAME); start_tcp_server(); }
static void main(void) { osThreadAttr_t attr;
attr.name = "tcp_server";
attr.attr_bits = 0U;
attr.cb_mem = NULL;
attr.cb_size = 0U;
attr.stack_mem = NULL;
attr.stack_size = 1024 * 4;
attr.priority = 25;
// Create the Thread1 task
if (osThreadNew((osThreadFunc_t)startSta, NULL, &attr) == NULL) {
printf("Failed to create say hello Thread!\n");
}
}
APP_FEATURE_INIT(main);
```c
static_library("tcp_server") {
sources = [ "main.c" ]
include_dirs = [
"//base/iothardware/peripheral/interfaces/inner_api",
"../../services/wifi/include",
]
deps = [
"../../services:genkipi_services",
]
}
import("//build/lite/config/component/lite_component.gni")
lite_component("app") {
features = [
"tcp_server"
]
}
校验方式
打开网络调试助手,当前编写的代码为tcp 服务端,那么我们需要将调试助手配置为tcp 客户端。配置如图:
服务器IP的获取方式需要通过路由器获得,我使用的是PC机作为热点,可以通过热点中进行设备ip的查询:
点击连接网络,查看串口打印:
客户端已经连接到自己编写的服务器上了。
在网络调试工具中输入要发送的内容,点击发送,查看服务端的打印结果:
UDP编程
客户端开发
需求说明
- 确保设备能够连接网络
- 当前开发的是客户端,需要有一个服务端配合,实现两者通讯
- 确保服务端和客户端在同一个网络环境中(在同一个局域网)
-
开发流程
来到应用开发的根目录。
我们编写代码的根目录为device/board/itcast/genkipi/app
。 根目录下新建
udp_client
文件夹,此为项目目录。- 此项目目录下新建
main.c
文件 - 此项目目录下新建
BUILD.gn
文件 - 修改根目录下的
BUILD.gn
文件代码部分
```cinclude
include
include
include “cmsis_os2.h”
include “ohos_init.h”
include “wifi_sta.h”
include “lwip/sockets.h”
define SSID “xq”
define PASSWORD “qwer1234”
define HOSTNAME “itcast”
define SERVER_IP “192.168.137.1”
define SERVER_PORT 8080
static void start_udp_client(void) { // 创建udp int sock_fd; int ret; sock_fd = socket(AF_INET, SOCK_DGRAM, 0); if (sock_fd < 0) { perror(“sock_fd create error\r\n”); return; }
// config send addr
struct sockaddr_in send_addr;
socklen_t send_addr_len = sizeof(send_addr);
// 初始化发送地址
memset((void *) &send_addr, 0, send_addr_len);
send_addr.sin_family = AF_INET;
//这个位置是否是inet_addr_t
send_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
send_addr.sin_port = htons(SERVER_PORT);
char msg[1024];
int cnt = 0;
while (1) {
sprintf(msg, "hello %d\r\n", cnt++);
ret = sendto(sock_fd, msg, strlen(msg), 0, (struct sockaddr *) &send_addr, send_addr_len);
usleep(1 * 1000 * 1000);
}
}
static void startSta(void) { wifi_sta_connect(SSID, PASSWORD, HOSTNAME); start_udp_client(); }
static void main(void) { osThreadAttr_t attr;
attr.name = "udp_client";
attr.attr_bits = 0U;
attr.cb_mem = NULL;
attr.cb_size = 0U;
attr.stack_mem = NULL;
attr.stack_size = 1024 * 4;
attr.priority = 25;
// Create the Thread1 task
if (osThreadNew((osThreadFunc_t)startSta, NULL, &attr) == NULL) {
printf("Failed to create say hello Thread!\n");
}
}
APP_FEATURE_INIT(main);
```c
static_library("udp_client") {
sources = [ "main.c" ]
include_dirs = [
"//base/iothardware/peripheral/interfaces/inner_api",
"../../services/wifi/include",
]
deps = [
"../../services:genkipi_services",
]
}
import("//build/lite/config/component/lite_component.gni")
lite_component("app") {
features = [
"udp_client"
]
}
校验方式
打开网络调试助手,当前编写的代码为udp客户端,那么我们需要将调试助手配置为UDP服务端。配置如图:
服务器IP也是本机IP地址,我们可以通过命令行查询本机的IP:
ipconfig
结果如图:
我查看的是无线局域网适配器,因为我的PC作为热点和我们的开发板进行连接的。查询结果有两个,两个都可以使用。
服务端开发
需求说明
- 确保设备能够连接网络
- 当前开发的是服务端,需要有一个客户端配合,实现两者通讯
- 确保服务端和客户端在同一个网络环境中(在同一个局域网)
-
开发流程
来到应用开发的根目录。
我们编写代码的根目录为device/board/itcast/genkipi/app
。 根目录下新建
udp_server
文件夹,此为项目目录。- 此项目目录下新建
main.c
文件 - 此项目目录下新建
BUILD.gn
文件 - 修改根目录下的
BUILD.gn
文件代码部分
```cinclude
include
include
include “cmsis_os2.h”
include “ohos_init.h”
include “wifi_sta.h”
include “lwip/sockets.h”
define SSID “xq”
define PASSWORD “qwer1234”
define HOSTNAME “itcast”
define SERVER_PORT 8080
static void start_udp_server(void) { // udp create int sock_fd; int ret; sock_fd = socket(AF_INET, SOCK_DGRAM, 0); if (sock_fd < 0) { perror(“sock_fd create error\r\n”); return; }
// config receive addr
struct sockaddr_in recvfrom_addr;
socklen_t recvfrom_addr_len = sizeof(recvfrom_addr);
memset((void *) &recvfrom_addr, 0, recvfrom_addr_len);
recvfrom_addr.sin_family = AF_INET;
recvfrom_addr.sin_addr.s_addr = htonl(INADDR_ANY);
recvfrom_addr.sin_port = htons(SERVER_PORT);
// bind receive addr
// bind
ret = bind(sock_fd, (struct sockaddr *) &recvfrom_addr, recvfrom_addr_len);
if (ret == -1) {
perror("bind error\r\n");
return;
}
char recv_buf[1024];
int recv_len;
while (1) {
struct sockaddr_in sender_addr;
int sender_addr_len;
recv_len = recvfrom(sock_fd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *) &sender_addr,
sender_addr_len);
if (recv_len <= 0) {
continue;
}
char recv_data[recv_len];
memcpy(recv_data, recv_buf, recv_len);
printf("len: %d data: %s\r\n", recv_len, recv_data);
}
}
static void startSta(void) { wifi_sta_connect(SSID, PASSWORD, HOSTNAME); start_udp_server(); }
static void main(void) { osThreadAttr_t attr;
attr.name = "udp_server";
attr.attr_bits = 0U;
attr.cb_mem = NULL;
attr.cb_size = 0U;
attr.stack_mem = NULL;
attr.stack_size = 1024 * 4;
attr.priority = 25;
// Create the Thread1 task
if (osThreadNew((osThreadFunc_t)startSta, NULL, &attr) == NULL) {
printf("Failed to create say hello Thread!\n");
}
}
APP_FEATURE_INIT(main);
```c
static_library("tcp_server") {
sources = [ "main.c" ]
include_dirs = [
"//base/iothardware/peripheral/interfaces/inner_api",
"../../services/wifi/include",
]
deps = [
"../../services:genkipi_services",
]
}
import("//build/lite/config/component/lite_component.gni")
lite_component("app") {
features = [
"tcp_server"
]
}
校验方式
打开网络调试助手,当前编写的代码为tcp 服务端,那么我们需要将调试助手配置为tcp 客户端。配置如图:
服务器IP的获取方式需要通过路由器获得,我使用的是PC机作为热点,可以通过热点中进行设备ip的查询:
点击连接网络,查看串口打印:
广播开发
需求说明
- 确保设备能够连接网络
- 当前开发的是广播端,需要多个接收端配合,实现一对多通讯
- 确保服务端和客户端在同一个网络环境中(在同一个局域网)
-
开发流程
来到应用开发的根目录。
我们编写代码的根目录为device/board/itcast/genkipi/app
。 根目录下新建
udp_broadcast
文件夹,此为项目目录。- 此项目目录下新建
main.c
文件 - 此项目目录下新建
BUILD.gn
文件 - 修改根目录下的
BUILD.gn
文件代码部分
```cinclude
include
include
include “cmsis_os2.h”
include “ohos_init.h”
include “wifi_sta.h”
include “lwip/sockets.h”
define SSID “xq”
define PASSWORD “qwer1234”
define HOSTNAME “itcast”
define SERVER_PORT 8080
static void start_udp_broadcast(void) { // udp create int sock_fd; int ret; sock_fd = socket(AF_INET, SOCK_DGRAM, 0); if (sock_fd < 0) { perror(“sock_fd create error\r\n”); return; }
// set broadcast mode
int yes = 1;
ret = setsockopt(sock_fd, SOL_SOCKET, SO_BROADCAST, (char *) &yes, sizeof(yes));
if (ret == -1) {
perror("setsockopt error\r\n");
return;
}
// config broadcast addr
struct sockaddr_in broadcast_addr;
socklen_t broadcast_addr_len = sizeof(broadcast_addr);
memset((void *) &broadcast_addr, 0, broadcast_addr_len);
broadcast_addr.sin_family = AF_INET;
broadcast_addr.sin_addr.s_addr = htonl(INADDR_BROADCAST);
broadcast_addr.sin_port = htons(SERVER_PORT);
char msg[1024];
int cnt = 0;
while (1) {
sprintf(msg, "hello %d\r\n", cnt++);
ret = sendto(sock_fd, msg, strlen(msg), 0, (struct sockaddr *) &broadcast_addr, broadcast_addr_len);
usleep(1 * 1000 * 1000);
}
}
static void startSta(void) { wifi_sta_connect(SSID, PASSWORD, HOSTNAME);
start_udp_broadcast();
}
static void main(void) { osThreadAttr_t attr;
attr.name = "tcp_client";
attr.attr_bits = 0U;
attr.cb_mem = NULL;
attr.cb_size = 0U;
attr.stack_mem = NULL;
attr.stack_size = 1024 * 4;
attr.priority = 25;
// Create the Thread1 task
if (osThreadNew((osThreadFunc_t)startSta, NULL, &attr) == NULL) {
printf("Failed to create say hello Thread!\n");
}
}
APP_FEATURE_INIT(main);
```c
static_library("udp_broadcast") {
sources = [ "main.c" ]
include_dirs = [
"//base/iothardware/peripheral/interfaces/inner_api",
"../../services/wifi/include",
]
deps = [
"../../services:genkipi_services",
]
}
import("//build/lite/config/component/lite_component.gni")
lite_component("app") {
features = [
"udp_broadcast"
]
}
校验方式
打开网络调试助手,当前编写的代码为UDP广播端,那么我们需要将调试助手配置为UDP接收端,而且不止一个UDP接收端,这个时候我们需要多台电脑,每台电脑都打开网络调试助手。网络调试助手配置如图:
点击连接网络,观察打印输出结果。
在这里需要特别强调的是,多台接收电脑和广播端必须在同一个局域网内。
练习题
- 实现TCP 客户端的编写,进行调试。
- 实现TCP 服务端的编写,进行调试。
- 实现UDP 客户端的编写,进行调试。
- 实现UDP 服务端的编写,进行调试。
- 实现UDP 广播端的编写,进行调试。