Socket编程UDP
一.接口补充
1. socket(系统调用)
作用:创建一个套接字的一端
函数原型
cpp
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
- 参数
- domain(协议族/地址族)
AF_INET
: IPv4(常用)AF_INET6
: IPv6AF_UNIX
: 本地进程间通信(Unix 域)
- type(套接字类型)
SOCK_DGRAM
: 数据报(UDP)SOCK_STREAM
: 字节流(TCP)
- protocol(协议编号)
- 填
0
就行,让系统按domain+type自动选择合适的协议.
- 填
- domain(协议族/地址族)
- 返回值
- 成功:返回一个套接字的文件描述符
- 失败:返回-1,并设置 errno
2. bind(系统调用)
作用:将创建好的套接字与本地的 IP 地址和端口号绑定在一起。(告诉操作系统:我这个 socket 要监听哪个本地地址和端口。)
函数原型
cpp
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
-
参数
-
sockfd:套接字的文件描述符;
-
addr(地址信息结构体):
-
用于指定要绑定的本地 IP 和端口号。
-
对 IPv4 通常是
struct sockaddr_in
类型(需强转为struct sockaddr*
)。 -
关键字段:
cppstruct sockaddr_in { sa_family_t sin_family; // 地址族,网络通信选AF_INET/AF_INET6,进程通信选AF_UNIX in_port_t sin_port; // 端口号(网络字节序) struct in_addr sin_addr; // IP地址(网络字节序) };
struct sockaddr_in需要的头文件:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
-
-
addrlen:地址结构体的大小:
sizeof(struct sockaddr_in)
-
-
返回值
- 成功: 0
- 失败: -1,并设置 errno
3. bzero(库函数)
作用: 将指定内存区域的前 n 个字节全部清零(置为 0); 常用于初始化结构体、数组或缓冲区。
函数原型
cpp
#include <strings.h>
void bzero(void *s, size_t n);
- 参数
- *s:要清零的内存起始地址(void 类型);**
- n:要清零的字节数.
4. inet_addr(库函数)
作用: 将一个点分十进制的 IPv4 地址字符串(如 "192.168.1.1")转换为网络字节序的32 位整数(in_addr_t(uint32_t)类型)。 常用于为 sockaddr_in.sin_addr.s_addr
赋值。
函数原型
cpp
#include <arpa/inet.h>
in_addr_t inet_addr(const char *cp);
-
参数
- cp
- C 风格字符串形式的 IPv4 地址,例如
"127.0.0.1"
或"192.168.10.5"
; - 要求是 点分十进制格式(四个 0--255 的数字,用点分隔).
- C 风格字符串形式的 IPv4 地址,例如
- cp
-
返回值
- 成功: 返回对应的 网络字节序 IP 地址(
in_addr_t
类型,实质为uint32_t
); - 失败: 返回常量 INADDR_NONE(即 0xFFFFFFFF).
- 成功: 返回对应的 网络字节序 IP 地址(
5. inet_ntoa(库函数)
作用: 将一个网络字节序的 IPv4 地址(struct in_addr
类型)转换为"点分十进制字符串。
函数原型
cpp
#include <arpa/inet.h>
char *inet_ntoa(struct in_addr in);
-
参数:in
struct in_addr
类型,表示一个 IPv4 地址;- 通常从
sockaddr_in.sin_addr
成员获得;
-
返回值
-
成功: 返回一个 指向静态字符串 的指针,内容为点分十进制形式的 IPv4 地址;
-
失败: 无特别错误返回,但注意:
该字符串存放在静态内存区中,每次调用
inet_ntoa()
都会覆盖上一次的结果,非线程安全。
-
6. inet_pton(库函数)
作用:将"点分十进制"或"冒号分隔"的IP地址字符串(IPv4/IPv6)转换为网络字节序的二进制格式,是inet_addr()
的现代、安全版本。
函数原型
cpp
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
- 参数
- af(地址族):指定地址类型
AF_INET
AF_INET6
- src(源字符串):C 字符串形式的 IP 地址;
- dst(目标内存地址):转换后的结果将存放到此结构体地址中;
- IPv4:
&sockaddr_in.sin_addr
- IPv6:
&sockaddr_in6.sin6_addr
- IPv4:
- af(地址族):指定地址类型
- 返回值
- 成功: 1;
- **失败: **
- IP地址非法:0
- 出错:-1, 并设置 errno
7. inet_ntop(库函数)
作用:将一个网络字节序的二进制 IP 地址(IPv4 或 IPv6)转换为字符串形式(点分/冒号分隔)。是inet_ntoa()
的可重入(线程安全)版本。
函数原型
cpp
#include <arpa/inet.h>
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
- 参数
- af(地址族):指定地址类型
AF_INET
AF_INET6
- src(源地址):指向存放网络字节序IP地址的内存(结构体);
- IPv4:
&sockaddr_in.sin_addr
- IPv6:
&sockaddr_in6.sin6_addr
- IPv4:
- dst(目标缓冲区):用于保存转换后的字符串;
- 一般定义为
char buf[INET_ADDRSTRLEN]
(IPv4)或char buf[INET6_ADDRSTRLEN]
(IPv6)。
- 一般定义为
- size(缓冲区大小)
- af(地址族):指定地址类型
-
返回值
-
成功: 返回指向
dst
的指针(即转换后的字符串); -
**失败:errno置为EAFNOSUPPORT 或 ENOSPC) **
-
8. recvfrom(系统调用)
作用: 从一个 UDP 套接字中接收数据,同时获取发送方(客户端)的地址信息。(常用于 UDP 服务器端接收消息)
函数原型
cpp
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
- 参数
- sockfd:套接字文件描述符;
- buf:用来接收数据的缓冲区首地址;
- len :缓冲区的大小(单位:字节);
- flags:一般填 0 (阻塞IO,如果对方不发数据,该函数(进程)就会一直阻塞,等同与scanf; MSG_DONTWAIT 表示非阻塞接收);
- src_addr:
- 一个
struct sockaddr_in
类型的指针,用于存储发送方的地址信息(IP + 端口); - 如果不关心发送方,可以填 NULL;
- 一个
- addrlen:
- 指向一个socklen_t类型的变量,socklen_t实际上是一个整数类型(可跨平台);
- addrlen既是输入参数, 又是输出参数: 初始时应设置为 sizeof(struct sockaddr_in); 函数返回时,会被填入实际的地址长度.
- 返回值
- 成功: 返回接收到的字节数(ssize_t是有符号整数long int);
- 失败: 返回 -1,并设置 errno.
9. sendto(系统调用)
作用: 向指定的目标地址发送一块数据(UDP 无连接发送)。(常用于 UDP 客户端发送,或服务器向收到的源地址回发数据)
函数原型
cpp
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
- 参数
- sockfd:套接字文件描述符;
- buf:待发送数据的起始地址;
- len:待发送数据的字节数;
- flags:发送标志 (一般填 0 阻塞发送; 常见可选:
MSG_DONTWAIT
非阻塞发送,MSG_NOSIGNAL
发送失败不触发SIGPIPE
等); - dest_addr:目标地址结构体指针
- 服务器回包可直接用 recvfrom 得到的
src_addr
作为dest_addr
。
- 服务器回包可直接用 recvfrom 得到的
- addrlen:地址结构体(dest_addr)大小
- 返回值
- 成功: 返回实际发送的字节数;
- 失败: 返回 -1,并设置 errno.
二.命令补充
1. netstat(系统命令)
作用: 显示网络连接、路由表、接口状态、端口监听情况等网络统计信息。
命令格式
shell
netstat [选项]
-
常用选项组合
-a
:显示所有连接和监听端口(all
)-l
:仅显示处于监听(listening)状态的端口-n
:以数字形式显示 IP 地址和端口号(不解析主机名)-u
:仅显示 UDP 协议连接-t
:仅显示 TCP 协议连接-p
:显示对应的进程号与程序名(需要 root 权限)
-
常见用法举例
shellnetstat -anup
作用:
显示系统中所有 UDP 套接字(包括监听与已连接的),并显示使用这些端口的程序信息。
-
输出示例
shellProto Recv-Q Send-Q Local Address Foreign Address State PID/Program name udp 0 0 0.0.0.0:8080 0.0.0.0:* 2314/udpserver udp 0 0 127.0.0.1:53 0.0.0.0:* 924/dnsmasq udp6 0 0 :::68 :::* 652/systemd-network
-
字段说明
字段 含义 Proto
协议类型(TCP/UDP) Recv-Q
接收队列中尚未被应用读取的字节数 Send-Q
发送队列中尚未发送的字节数 Local Address
本地 IP + 端口号 ( 0.0.0.0
表示监听所有网卡)Foreign Address
远端 IP + 端口号(UDP 无连接时显示为 *
)State
套接字状态(UDP 无状态,通常为空) PID/Program name
占用该端口的进程号及程序名 -
常用变体
命令 作用 netstat -anup
查看所有 UDP 端口与进程信息 netstat -antp
查看所有 TCP 连接与进程信息
三.知识补充
1. 客户端bind问题
问题:client要不要bind?需要bind.
client要不要显式的bind?不要!首次发送消息,OS会自动给client进行bind,OS知道IP,端口号采用随机端口号的方式
为什么?一个端口号,只能被一个进程bind,为了避免client端口冲突
client端的端口号是几,不重要,只要是唯一的就行!
2.本地环回
本地环回:127.0.0.1, 要求客户端和服务端必须在一台服务器上, 表明我们是本地通信, 客户端发送的数据不会被推送到网络,而是在OS内部转一圈直接交给对应的服务器端。经常用来进行网络代码的测试
3. 三个现象
(1).bind公网iP -- 不能
公网IP其实没有配置到你的IP上。公网IP无法被直接bind!
(2).bind 127.0.0.1||内网IP -- ok
(3).server bind内网IP,但是用127.0.0.1访问---访问不 (反过来也不行)
如果我们显式的进行地址bind,client未来访问的时候,就必须使用server端bind的地址信息!
-
如何跨网络访问?
server,不建议手动bind特定的
cppstruct sockaddr_in local; local.sin_addr.s_addr = INADDR_ANY; //任意IP地址bind /* Address to accept any incoming messages. */ //#define INADDR_ANY ((in_addr_t) 0x00000000)
这样的好处是: 只要发给这台机器bind指定的端口号的报文都可以接受。