- #define NUM_SOCKETS MEMP_NUM_NETCONN
- /** 包含用于套接字的所有内部指针和状态 */
- struct lwip_sock {
- /** 套接字当前是基于 netconn 构建的,每个套接字都有一个 netconn */
- struct netconn *conn;
- /** 上一次读取时留下的数据 */
- union lwip_sock_lastdata lastdata;
- #if LWIP_SOCKET_SELECT || LWIP_SOCKET_POLL
- /** 接收到数据的次数,由 event_callback() 设置,由接收和选择函数测试 */
- s16_t rcvevent;
- /** 数据被确认的次数(释放发送缓冲区),由 event_callback() 设置,由选择测试 */
- u16_t sendevent;
- /** 此套接字发生的错误,由 event_callback() 设置,经过选择测试 */
- u16_t errevent;
- /** 使用选择等待此套接字的线程数量计数器 */
- SELWAIT_T select_waiting;
- #endif /* LWIP_SOCKET_SELECT || LWIP_SOCKET_POLL */
- #if LWIP_NETCONN_FULLDUPLEX
- /* 使用 struct lwip_sock 的线程数量计数器(不是 'int') */
- u8_t fd_used;
- /* 待处理关闭/删除操作的状态 */
- u8_t fd_free_pending;
- #define LWIP_SOCK_FD_FREE_TCP 1
- #define LWIP_SOCK_FD_FREE_FREE 2
- #endif
- };
Socket 通信流程,可参考以下流程图。
3 Socket API
下面是Socket通信常用的API介绍。
3.1 socket()
- int socket(int domain, int type, int protocol);
- 功能:创建一个新的套接字。
- 参数:
- domain:协议簇(如 AF_INET)。
- type:套接字类型(如 SOCK_STREAM、SOCK_DGRAM)。
- protocol:协议(通常为0,表示自动选择)。
- 返回值:成功时返回套接字描述符,失败时返回 -1。
3.2 bind()
- int bind(int sockfd, const struct sockaddr *addr, socklen_taddrlen);
- 功能:将套接字绑定到一个地址(IP和端口)。
- 参数:
- sockfd:套接字描述符。
- addr:指向 sockaddr 结构的指针,包含要绑定的地址。
- addrlen:地址结构的长度。
- 返回值:成功时返回 0,失败时返回 -1。
3.3 listen()
- int listen(int sockfd, int backlog);
- 功能:将套接字设置为被动监听状态,等待连接请求。
- 参数:
- sockfd:套接字描述符。
- backlog:等待连接的最大数量。
- 返回值:成功时返回 0,失败时返回 -1。
3.4 accept()
- int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
- 功能:接受一个连接请求,返回新的套接字用于与客户端通信。
- 参数:
- sockfd:监听套接字描述符。
- addr:指向 sockaddr 结构的指针,用于存储客户端地址。
- addrlen:指向地址长度的指针。
- 返回值:成功时返回新的套接字描述符,失败时返回 -1。
3.5 connect()
- int connect(int sockfd, const struct sockaddr *addr, socklen_taddrlen);
- 功能:连接到指定的服务器。
- 参数:
- sockfd:套接字描述符。
- addr:指向 sockaddr 结构的指针,包含服务器地址。
- addrlen:地址结构的长度。
- 返回值:成功时返回 0,失败时返回 -1。
3.6 send()
- ssize_tsend(int sockfd, const void *buf, size_tlen, int flags);
- 功能:向连接的套接字发送数据。
- 参数:
- sockfd:套接字描述符。
- buf:指向要发送数据的缓冲区。
- len:要发送的数据长度。
- flags:发送选项(通常为0)。
- 返回值:成功时返回发送的字节数,失败时返回 -1。
3.7 recv()
- ssize_trecv(int sockfd, void *buf, size_tlen, int flags);
- 功能:从连接的套接字接收数据。
- 参数:
- sockfd:套接字描述符。
- buf:指向接收数据的缓冲区。
- len:缓冲区的大小。
- flags:接收选项(通常为0)。
- 返回值:成功时返回接收到的字节数,失败时返回 -1。
3.8 close()
- 功能:关闭套接字,释放资源。
- 参数:
- sockfd:套接字描述符。
- 返回值:成功时返回 0,失败时返回 -1。
3.9 setsockopt()
- int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_toptlen);
- 功能:设置套接字选项。
- 参数:
- sockfd:套接字描述符。
- level:选项级别(如 SOL_SOCKET)。
- optname:选项名称(如 SO_REUSEADDR)。
- optval:指向选项值的指针。
- optlen:选项值的长度。
- 返回值:成功时返回 0,失败时返回 -1。
3.10 getsockopt()
- int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
- 功能:获取套接字选项。
- 参数:
- sockfd:套接字描述符。
- level:选项级别(如 SOL_SOCKET)。
- optname:选项名称。
- optval:指向存储选项值的缓冲区。
- optlen:指向选项值长度的指针。
- 返回值:成功时返回 0,失败时返回 -1。
3.11 总结
LWIP的Socket API提供了创建、绑定、监听、连接、发送和接收数据的基本功能,适用于嵌入式系统的网络编程。通过这些API,我们可以实现网络通信的各种需求。
4 Socket 编程实例
这次依旧实现一个简单的UDP的回显功能。由于我们已经实现了LWIP NETCONN UDP编程实例,所以我们直接沿用这个例程。
4.1 开启LWIP Socket编程
4.2 修改udp_ehco.c。
- #include "FreeRTOS.h"
- #include "lwip/opt.h"
- #include "task.h"
- #include "lwip/sockets.h"
- #include "lwip/api.h"
- #include "lwip/sys.h"
- #include <stdbool.h>
- #define UDPECHO_THREAD_PRIO ( tskIDLE_PRIORITY + 5 ) // 定义UDP回显线程的优先级
- #define PORT 6000 // 定义UDP服务器监听的端口号
- #define RECV_DATA (1024) // 定义接收数据的缓冲区大小
- /*-----------------------------------------------------------------------------------*/
- static void udpecho_thread(void *arg) // UDP回显线程函数
- {
- int sock = -1; // 套接字描述符
- char *recv_data = NULL; // 接收数据的缓冲区
- struct sockaddr_in udp_addr, seraddr; // UDP地址结构
- int recv_data_len; // 接收数据的长度
- socklen_t addrlen = sizeof(seraddr); // 初始化地址长度
- bool success = true; // 变量用于跟踪成功状态
- // 分配接收数据缓冲区
- recv_data = (char *)pvPortMalloc(RECV_DATA);
- if (recv_data == NULL) // 检查内存分配是否成功
- {
- printf("No memory\r\n"); // 打印内存不足信息
- success = false; // 设置成功状态为false
- }
- // 创建UDP套接字
- if (success)
- {
- sock = socket(AF_INET, SOCK_DGRAM, 0); // 创建UDP套接字
- if (sock < 0) // 检查套接字创建是否成功
- {
- printf("Socket error\r\n"); // 打印套接字错误信息
- success = false; // 设置成功状态为false
- }
- }
- // 绑定套接字
- if (success)
- {
- udp_addr.sin_family = AF_INET; // 设置地址族为IPv4
- udp_addr.sin_addr.s_addr = INADDR_ANY; // 绑定到所有可用接口
- udp_addr.sin_port = htons(PORT); // 设置端口号
- memset(&(udp_addr.sin_zero), 0, sizeof(udp_addr.sin_zero)); // 清零结构体的剩余部分
- if (bind(sock, (struct sockaddr *)&udp_addr, sizeof(struct sockaddr)) == -1) // 绑定套接字
- {
- printf("Unable to bind\r\n"); // 打印绑定失败信息
- success = false; // 设置成功状态为false
- }
- }
- // 主循环
- while (success)
- {
- recv_data_len = recvfrom(sock, recv_data, RECV_DATA, 0, (struct sockaddr*)&seraddr, &addrlen); // 接收数据
- if (recv_data_len < 0) // 检查接收是否成功
- {
- printf("Receive error\r\n"); // 打印接收错误信息
- break; // 退出循环
- }
- /* 显示发送者的IP地址 */
- printf("receive from %s\r\n", inet_ntoa(seraddr.sin_addr)); // 打印发送者IP地址
- /* 显示发送者发送的字符串 */
- printf("receive data: %s\r\n\r\n", recv_data); // 打印接收到的数据
- /* 将字符串返回给发送者 */
- sendto(sock, recv_data, recv_data_len, 0, (struct sockaddr*)&seraddr, addrlen); // 发送回显数据
- }
- // 清理资源
- if (sock >= 0) // 检查套接字是否有效
- closesocket(sock); // 关闭套接字
- if (recv_data) // 检查接收数据缓冲区是否有效
- free(recv_data); // 释放接收数据缓冲区
- }
- /*-----------------------------------------------------------------------------------*/
- void udpecho_init(void) // 初始化UDP回显功能
- {
- sys_thread_new("udpecho_thread", udpecho_thread, NULL, DEFAULT_THREAD_STACKSIZE, UDPECHO_THREAD_PRIO ); // 创建UDP回显线程
- }
以下是对代码功能的详细分析:
主要功能
- 1. 创建UDP套接字:创建一个UDP套接字,用于接收和发送数据。
- 2. 绑定套接字:将套接字绑定到指定的端口(6000),以便接收来自该端口的数据。
- 3. 接收数据:在主循环中,服务器不断接收来自客户端的数据。
- 4. 回显数据:接收到数据后,服务器将其打印到控制台,并将相同的数据返回给发送者(回显功能)。
- 5. 资源管理:在退出时,清理分配的资源,包括关闭套接字和释放内存。
4.3 实验现象
打开网络调试助手和串口调试助手,使用网口线连接,现象如下。
5.总结
以上只实现了UDP的一个简单实例,TCP的实现也不是太过于复杂,有兴趣的小伙伴可以尝试尝试。以下是我针对Socket编程的TCP和UDP配置流程做了一个简单的梳理表格。
步骤
| TCP编程流程描述
| UDP编程流程描述
|
1. 初始化LWIP
| 初始化LWIP协议栈,设置内存池和网络接口,确保LWIP能够正常工作。
| 同样初始化LWIP协议栈,设置内存池和网络接口,准备进行UDP通信。
|
2. 创建Socket
| 创建一个TCP Socket,指定协议族(如IPv4)和Socket类型(SOCK_STREAM)
| 创建一个UDP Socket,指定协议族(如IPv4)和Socket类型(SOCK_DGRAM)
|
3. 绑定Socket
| 将Socket绑定到特定的IP地址和端口号,以便接收来自该地址和端口的连接请求。
| 将Socket绑定到特定的IP地址和端口号,以便接收来自该地址和端口的数据报。
|
4. 监听连接
| 设置Socket为监听状态,准备接受客户端的连接请求。
| 不需要监听,因为UDP是无连接的协议,直接发送和接收数据报。
|
5. 接受连接
| 使用接受函数来接受客户端的连接请求,创建一个新的Socket用于与客户端通信。
| 不需要接受连接,直接使用绑定的Socket进行数据的发送和接收。
|
6. 发送和接收数据
| 通过已建立的连接进行数据的发送和接收,双方可以进行多次数据交互。
| 直接使用Socket发送和接收数据报,数据报是独立的,不需要建立连接。
|
7. 关闭Socket
| 在完成通信后,关闭Socket,释放资源,并进行四次挥手以正常终止连接。
| 在完成数据传输后,关闭Socket,释放资源。UDP不需要进行连接终止的过程。
|
8. 清理资源
| 清理LWIP使用的资源,包括释放内存和关闭网络接口,确保系统稳定。
| 同样清理LWIP使用的资源,确保没有内存泄漏和其他潜在问题。
|
附件是相关Demo, LWIP Socket UDP Demo。
UDP_SOCKET.zip
(6.71 MB, 下载次数: 28)