跳到主要内容Linux 下 UDP 网络编程套接字详解 | 极客日志C++
Linux 下 UDP 网络编程套接字详解
Linux 环境下基于 UDP 协议的 socket 网络编程。内容涵盖源/目的 IP 地址与端口号的概念,TCP 与 UDP 的区别,网络字节序与主机字节序的转换方法(htons/htonl 等)。详细讲解了 socket 创建、绑定、发送与接收的核心 API,并提供了 C++ 实现的 UDP 服务器与客户端代码示例,包括多线程处理及命令行参数解析。文章还涉及了数据类型选择(uint16_t vs int)、地址转换函数(inet_pton/ntop)以及常见调试技巧。
技术博主3 浏览 Linux 下 UDP 网络编程套接字详解
源 IP 地址和目的 IP 地址
在 IP 数据包头部中,有两个 IP 地址,分别叫做源 IP 地址和目的 IP 地址。仅拥有 IP 地址不足以完成通信,还需要一个标识来区分数据应发送给哪个程序进行解析。
认识端口号
端口号用于标识主机上的特定进程。一个进程可以绑定多个端口号,但一个端口号不能被多个进程同时绑定。
理解 "端口号" 和 "进程 ID"
进程 ID (PID) 唯一表示一个进程,端口号同样用于唯一表示一个进程。两者关系密切,但在网络通信中,端口号是跨主机通信的关键标识。
理解源端口号和目的端口号
传输层协议 (TCP 和 UDP) 的数据段中有两个端口号,分别叫做源端口号和目的端口号。这描述了'数据是谁发的'以及'要发给谁'。
IP:Port
IP 地址为 4 字节,端口号为 2 字节。在公网上,IP 地址能表示唯一的一台主机,端口号用来标识该主机上的唯一的一个进程。IP:Port=标识全网唯一的一个进程。
认识 TCP 协议与 UDP 协议
- TCP 协议:面向连接、可靠传输。
- UDP 协议:无连接、不可靠传输、速度快。
网络字节序
内存中的多字节数据相对于内存地址有大端和小端之分,磁盘文件和网络数据流同样存在大端小端之分。TCP/IP 协议规定,网络数据流应采用大端字节序(Big Endian),即低地址高字节。无论发送主机是大端机还是小端机,都会按照这个 TCP/IP 规定的网络字节序来发送/接收数据;如果当前发送主机是小端,就需要先将数据转成大端。
为使网络程序具有可移植性,使同样的 C/C++ 代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。
网络字节序和主机字节序的转换函数
htons():将主机字节序转换为网络字节序,适用于 16 位数据(short)。
htonl():将主机字节序转换为网络字节序,适用于 32 位数据(long)。
ntohs():将网络字节序转换为主机字节序,适用于 16 位数据(short)。
ntohl():将网络字节序转换为主机字节序,适用于 32 位数据(long)。
示例代码
#include <stdio.h>
#include <arpa/inet.h>
int main() {
uint16_t host_short = 0x1234;
uint32_t host_long = 0x12345678;
uint16_t network_short = htons(host_short);
uint32_t network_long = htonl(host_long);
uint16_t converted_short = ntohs(network_short);
uint32_t converted_long = ntohl(network_long);
printf("Host short: 0x%04X, Network short: 0x%04X, Converted back: 0x%04X\n", host_short, network_short, converted_short);
printf("Host long: 0x%08X, Network long: 0x%08X, Converted back: 0x%08X\n", host_long, network_long, converted_long);
return 0;
}
适用平台
这些字节序转换函数通常用于 Linux 和 UNIX 系统中,通过包含 arpa/inet.h 头文件来访问。对于 Windows 系统,提供了类似的转换函数,但位于 winsock2.h 中,使用方法相同。
地址转换函数(字符串传 4 字节 IP)
本节只介绍基于 IPv4 的 socket 网络编程,sockaddr_in 中的成员 struct in_addr sin_addr 表示 32 位的 IP 地址。但是我们通常用点分十进制的字符串表示 IP 地址,以下函数可以在字符串表示和 in_addr 表示之间转换。
- 字符串转
in_addr 的函数:inet_pton()
in_addr 转字符串的函数:inet_ntop()
其中 inet_pton 和 inet_ntop 不仅可以转换 IPv4 的 in_addr,还可以转换 IPv6 的 in6_addr,因此函数接口是 void *addrptr。
关于 inet_ntoa:该函数返回了一个 char*,内部申请了一块静态存储区保存结果,不需要手动释放。但如果多次调用,第二次调用的结果会覆盖上一次的结果。在多线程环境下,推荐使用 inet_ntop,由调用者提供一个缓冲区保存结果,可以规避线程安全问题。
套接字编程的种类
UDP 代码示例
Socket API
int socket(int domain, int type, int protocol);
int bind(int socket, const struct sockaddr *address, socklen_t address_len);
int listen(int socket, int backlog);
int accept(int socket, struct sockaddr* address, socklen_t* address_len);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- 第一个参数:创建一个套接字的域,如
AF_INET (IPv4)。
- 第二个参数:socket 对应的类型,UDP 是
SOCK_DGRAM,TCP 是 SOCK_STREAM。
- 第三个参数:不用填,协议类型设为 0。
C++ 类封装示例
#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
using namespace std;
class UdpServer {
public:
UdpServer() {}
void Init() {
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
}
void Run() {}
~UdpServer() {}
private:
int sockfd;
};
绑定 Bind
绑定端口号时,需要填充 sockaddr_in 结构体。
sockaddr 结构
IPv4 和 IPv6 的地址格式定义在 netinet/in.h 中。IPv4 地址用 sockaddr_in 结构体表示,包括 16 位地址类型,16 位端口号和 32 位 IP 地址。
虽然 socket API 的接口是 sockaddr,但是我们真正在基于 IPv4 编程时,使用的数据结构是 sockaddr_in。这个结构里主要有三部分信息:地址类型,端口号,IP 地址。
清空结构体
可以使用 bzero 或 memset 把上面的结构体清空。
常用宏
INET:相关宏定义。
AF_INET:IPv4 协议族。
数据类型选择
在网络中为什么用 uint16_t,不用 int?
- 一致性和标准化:
uint16_t 是一个无符号 16 位整数,大小和范围是固定的,明确且可移植。无论在哪个平台或操作系统上,其大小都是 16 位。而 int 的大小依赖于平台。
- 避免负数:端口号不能为负数,使用无符号类型更符合逻辑。
核心流程
- 创建套接字:
socket(AF_INET, SOCK_DGRAM, 0)。
- 绑定端口号:
bind(sockfd, ...)。
- 发送消息:
sendto(sockfd, buf, len, flags, addr, addrlen)。
- 接收消息:
recvfrom(sockfd, buf, len, flags, addr, addrlen)。
命令行参数处理
可以将程序写成命令行形式,例如 ./udpServer + port 或 ./udpClient + ip + port。
测试与调试
- 查看端口号和 IP 地址:
netstat -naup。
- 云服务器最好直接不写 IP 地址,使用默认值 0。
- 0-1023 端口不让绑定,最好绑定 1024 以上的端口。
客户端实现
客户端也需要创建套接字,记得 close 网络文件。客户端不需要 bind,因为操作系统会自动分配临时端口。服务器的端口号要确定,因为客户端是要访问服务端的,需要知道确定的端口号和 IP。
多线程处理
登录 QQ 时,不发送信息也能收到信息,目前的简单客户端没法做到。客户端可以改成多线程,一个线程输入,一个线程显示。UDP 的 sockfd 是可以同时被读写的,是线程安全的。
聊天室思路
制作简单的聊天室类似群聊,需要注意覆盖问题。如果发来的是命令呢?需要在服务器外面给接收到的数据做处理。本地环回地址为 127.0.0.1。
在服务器查看发信的客户端的端口号和 IP,可以通过网络转主机拿到端口号和 IP,改变一下处理数据方式,打印出 IP 和 port。
总结
本文介绍了 Linux 环境下基于 UDP 协议的 socket 网络编程,涵盖了 IP、端口、字节序转换及核心 API 的使用,并提供了 C++ 实现的 UDP 服务器与客户端代码示例。
微信扫一扫,关注极客日志
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
- JSON 压缩
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
- JSON美化和格式化
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online