12 嵌入式操作系统 | UDP 服务器编程
发布时间:2026/9/26 2:16:27来源:尧图网络
嵌入式操作系统 | UDP 服务器编程模块四 · 2 学时 · 环境VMware Ubuntu 20.04一句话概述用 C 写一个 UDP 服务器socket / bind / recvfrom / sendto 回包讲清服务器为什么必须 bind、INADDR_ANY 是什么意思并用 Python 写的测试客户端发包验证文档给出启动顺序、命令与运行结果配套课堂练习与课后作业。本节目标能说出服务器和客户端的区别解释服务器为什么必须 bind会用 socket / bind / recvfrom / sendto 写出一个收包并回包的 UDP 服务器会先把 C 服务器跑起来再用 Python 工具发包测试并会用ss确认端口在监听一、讲解内容1.1 知识讲解服务器与客户端的区别。客户端是主动去发的一方服务器是一直等着收的一方。上节课的客户端不写 bind系统自动给临时端口而服务器必须 bind 到固定的端口因为别人只有知道端口才能找到它——就像快递必须有个固定地址。服务器四步步骤函数在干嘛1socket(AF_INET, SOCK_DGRAM, 0)创建 UDP 套接字2bind(...)绑定自己的 IP 和端口之后别人才能发数据给你3recvfrom(...)收数据它会把是谁发的对方的 IP 和端口一起带回来4sendto(...)用第 3 步带回来的对方地址把结果回过去INADDR_ANY是什么。bind 时填htonl(INADDR_ANY)等于 0.0.0.0意思是本机所有网卡都收不管是 127.0.0.1 还是虚拟机网的 192.168.x.x只要有包发到这个端口服务器都收得到。如果只绑127.0.0.1那只有本机自己能连上。为什么可以一直循环收。服务器写成while (1) { recvfrom ... sendto ... }没有包时recvfrom会睡着不占 CPU来一个包就处理一个。这就是最朴素的网络服务模型下节课的 TCP 服务器也用这个思路只是多了连接管理。为什么服务器也要能回包。实际项目里客户端发完数据往往要等一个收到的确认这样才知道数据没丢。本课让服务器回一句服务器已收到 N 字节xxx方便你在客户端那侧直接看到效果。1.2 怎么运行两个终端服务器先开、测试工具后开终端 AC 服务器 终端 BPython 测试客户端 ① 第 1 个启动打印已启动 ──────► ② 看到启动信息后再发包 ③ 打印收到的每个包 ③ 打印已发送/收到的回复 ④ 按 CtrlC 停止 ④ 发完自动退出第 1 步在终端 A 编译并启动 C 服务器先做这一步gcc-Wall-Wextra-gudp_server.c-oudp_server# 编译无输出成功./udp_server9999# 启动监听 9999 端口看到这一行才算启动成功UDP 服务器已启动监听 9999 端口按 CtrlC 退出第 2 步在终端 B 确认端口在监听不是必须但很有用出问题时靠它定位ss-ulnp|grep9999正常会看到类似udp UNCONN 0 0 0.0.0.0:9999 0.0.0.0:*的输出说明端口已经打开。第 3 步在终端 B 用 Python 工具发包服务器开着的时候才做python3 udp_sender.py127.0.0.199993# 发 3 条并等回复第 4 步对照两个终端的输出本机真实运行结果。终端 BPython 测试客户端已发送: sensor-1: 温度 21.5 C 收到回复: 服务器已收到 23 字节sensor-1: 温度 21.5 C 已发送: sensor-2: 温度 22.5 C 收到回复: 服务器已收到 23 字节sensor-2: 温度 22.5 C 已发送: sensor-3: 温度 23.5 C 收到回复: 服务器已收到 23 字节sensor-3: 温度 23.5 C 测试结束终端 AC 服务器UDP 服务器已启动监听 9999 端口按 CtrlC 退出 收到 127.0.0.1:37085 的 23 字节sensor-1: 温度 21.5 C 收到 127.0.0.1:37085 的 23 字节sensor-2: 温度 22.5 C 收到 127.0.0.1:37085 的 23 字节sensor-3: 温度 23.5 C一边发、一边收两边的字节数一样就说明服务器工作正常。第 5 步再玩两个花样。① 用客户端来测如果课时 11 的 C 客户端还在./udp_client 127.0.0.1 9999 你好也能收到本课服务器的回包② 故意先把服务器 CtrlC 停掉再用 Python 发包会看到每条都打印2 秒内没有收到回复——这就是服务器没开的现象。第 6 步收工。回到终端 A 按CtrlC停服务器。顺序口诀服务器先开、测试工具后开服务器最后关。1.3 常见错误与排查现象原因解决bind端口被占用...: Address already in use上一次的服务器没关端口被占着CtrlC停掉旧的或换个端口如 88882 秒内没有收到回复服务器没启动或端口不一致先看终端 A 有没有打印已启动再核对端口号本机能通别的机器发不进来绑到了127.0.0.1用htonl(INADDR_ANY)绑所有网卡permission denied用了 1024 以下的端口如 80换 1024 以上的端口本课用 9999服务器收到乱码客户端发的不是 UTF-8统一用 UTF-8 编码发送和显示二、学生练习本节命令速查表命令作用示例gcc 源文件 -o 程序编译 C 服务器gcc -Wall -Wextra -g udp_server.c -o udp_server./udp_server 端口启动服务器终端 A先开./udp_server 9999ss -ulnp | grep 端口查看 UDP 端口监听情况ss -ulnp | grep 9999python3 udp_sender.py IP 端口 条数用 Python 工具发包终端 Bpython3 udp_sender.py 127.0.0.1 9999 3CtrlC停止服务器在终端 A 按编号任务提示预期结果L12.1按文档跑通一次先服务器、后发包两个终端注意顺序服务器打印 3 个包客户端收到 3 条回复L12.2换端口 8888 再来一次服务器参数和 Python 参数都要改依然收发成功L12.3停掉服务器再发包先按 CtrlC再运行 Python每条都提示2 秒内没有收到回复L12.4用ss看监听状态启动服务器前后各执行一次启动后能看到 9999 端口L12.5服务器改成回包内容不同例如回OK改snprintf那一行重新编译客户端收到的回复变成新的内容L12.6和课时 11 的 C 客户端联调服务器本课写的客户端用课时 11 的两边输出互相对应三、作业必做完成 L12.1、L12.4提交两个终端截图服务器输出 Python 客户端输出与ss -ulnp | grep 9999的截图并回答① 服务器为什么必须 bind客户端为什么不用②INADDR_ANY是什么意思选做把服务器改成收到什么就原样回什么回显服务器再和课时 11 的客户端互测提交截图 文字回答命名课时12-作业截图.png四、本节小结与下节预告本节写出了 UDP 服务器socket → bind → 循环 recvfrom → sendto 回包并学会用ss检查端口、用 Python 工具当客户端做测试再次确认了服务先开、测试后开的顺序。下节进入 TCP同样是客户端但先要connect完成三次握手可靠但不丢数据。附录怎么运行 完整代码一个课件就够用先看附录 A 的运行顺序服务端先开、客户端后开再把附录 B/C 的代码按文件名新建到同一个目录里。行尾请用 LF。附录 A怎么编译运行注意先后顺序# 编译 C 服务器gcc-Wall-Wextra-gudp_server.c-oudp_server# 第 1 步终端 A 先启动服务器必须先做不能颠倒./udp_server9999# 看到 UDP 服务器已启动监听 9999 端口 才算成功ss-ulnp|grep9999# 可选确认端口在监听# 第 2 步终端 B 用 Python 工具发包并等回复python3 udp_sender.py127.0.0.199993# 第 3 步对照两个终端的输出# 收工回到终端 A 按 CtrlC 停止服务器附录 B文件udp_server.c/* * udp_server.c —— 课时 12UDP 服务器收包 → 打印 → 回包 * * 编译gcc -Wall -Wextra -g udp_server.c -o udp_server * 运行./udp_server # 默认监听 9999 端口 * ./udp_server 8888 # 也可以指定端口 */#includestdio.h#includestdlib.h#includestring.h#includestdint.h#includeunistd.h#includearpa/inet.h#includesys/socket.h#defineBUF_SIZE512intmain(intargc,char*argv[]){intsock;intport9999;charbuf[BUF_SIZE];charreply[BUF_SIZE32];structsockaddr_inaddr;structsockaddr_inpeer;socklen_tpeer_lensizeof(peer);ssize_tn;if(argc2){portatoi(argv[1]);}/* 1. 创建 UDP 套接字 */socksocket(AF_INET,SOCK_DGRAM,0);if(sock0){perror(socket);return1;}/* 2. 绑定自己的地址和端口服务器必须 bind客户端不用 * INADDR_ANY 表示本机所有网卡0.0.0.0都收包 */memset(addr,0,sizeof(addr));addr.sin_familyAF_INET;addr.sin_addr.s_addrhtonl(INADDR_ANY);addr.sin_porthtons((uint16_t)port);if(bind(sock,(structsockaddr*)addr,sizeof(addr))0){perror(bind端口被占用换个端口试试);close(sock);return1;}printf(UDP 服务器已启动监听 %d 端口按 CtrlC 退出\n,port);fflush(stdout);/* 输出重定向到文件时是块缓冲不刷新就看不到 *//* 3. 循环收包recvfrom 会告诉我们是哪一个客户端发来的 */while(1){memset(buf,0,sizeof(buf));nrecvfrom(sock,buf,sizeof(buf)-1,0,(structsockaddr*)peer,peer_len);if(n0){perror(recvfrom);continue;}printf(收到 %s:%d 的 %zd 字节%s\n,inet_ntoa(peer.sin_addr),ntohs(peer.sin_port),n,buf);fflush(stdout);/* 实时看到收到的包 *//* 4. 回一句话给客户端地址用 recvfrom 带回来的 peer */snprintf(reply,sizeof(reply),服务器已收到 %zd 字节%s,n,buf);if(sendto(sock,reply,strlen(reply),0,(structsockaddr*)peer,peer_len)0){perror(sendto);}}close(sock);return0;}附录 C文件udp_sender.py#!/usr/bin/env python3课时 12 测试工具UDP 测试客户端给 C 服务器发包并等回复 用法python3 udp_sender.py [IP] [端口] [条数] 例python3 udp_sender.py 127.0.0.1 9999 3 importsocketimportsysimporttime ipsys.argv[1]iflen(sys.argv)1else127.0.0.1portint(sys.argv[2])iflen(sys.argv)2else9999countint(sys.argv[3])iflen(sys.argv)3else3ssocket.socket(socket.AF_INET,socket.SOCK_DGRAM)s.settimeout(2)foriinrange(1,count1):msgfsensor-{i}: 温度{20i}.5 Cs.sendto(msg.encode(),(ip,port))print(f已发送:{msg})try:data,addrs.recvfrom(1024)print(f收到回复:{data.decode(utf-8,replace)})exceptsocket.timeout:print(2 秒内没有收到回复)time.sleep(0.5)s.close()print(测试结束)
网站建设高端定制企业官网