网络:2.Socket编程UDP

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: IPv6
      • AF_UNIX : 本地进程间通信(Unix 域)
    • type(套接字类型)
      • SOCK_DGRAM : 数据报(UDP)
      • SOCK_STREAM : 字节流(TCP)
    • protocol(协议编号)
      • 0就行,让系统按domain+type自动选择合适的协议.
  • 返回值
    • 成功:返回一个套接字的文件描述符
    • 失败:返回-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*)。

      • 关键字段:

        cpp 复制代码
        struct 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 的数字,用点分隔).
  • 返回值

    • 成功: 返回对应的 网络字节序 IP 地址(in_addr_t 类型,实质为 uint32_t);
    • 失败: 返回常量 INADDR_NONE(即 0xFFFFFFFF).

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
  • 返回值
    • 成功: 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
    • dst(目标缓冲区):用于保存转换后的字符串;
      • 一般定义为 char buf[INET_ADDRSTRLEN](IPv4)或 char buf[INET6_ADDRSTRLEN](IPv6)。
    • size(缓冲区大小)
  • 返回值

    • 成功: 返回指向 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
    • addrlen:地址结构体(dest_addr)大小
  • 返回值
    • 成功: 返回实际发送的字节数;
    • 失败: 返回 -1,并设置 errno.

二.命令补充

1. netstat(系统命令)

作用: 显示网络连接、路由表、接口状态、端口监听情况等网络统计信息。

命令格式

shell 复制代码
netstat [选项]
  • 常用选项组合

    • -a :显示所有连接和监听端口(all)
    • -l:仅显示处于监听(listening)状态的端口
    • -n:以数字形式显示 IP 地址和端口号(不解析主机名)
    • -u:仅显示 UDP 协议连接
    • -t:仅显示 TCP 协议连接
    • -p:显示对应的进程号与程序名(需要 root 权限)
  • 常见用法举例

    shell 复制代码
    netstat -anup

    作用:

    显示系统中所有 UDP 套接字(包括监听与已连接的),并显示使用这些端口的程序信息。

  • 输出示例

    shell 复制代码
    Proto 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问题

  1. 问题:client要不要bind?需要bind.

    client要不要显式的bind?不要!首次发送消息,OS会自动给client进行bind,OS知道IP,端口号采用随机端口号的方式

  2. 为什么?一个端口号,只能被一个进程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特定的

    cpp 复制代码
    struct 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指定的端口号的报文都可以接受。

相关推荐
sunfove9 小时前
光网络的立交桥:光开关 (Optical Switch) 原理与主流技术解析
网络
Kevin Wang72712 小时前
欧拉系统服务部署注意事项
网络·windows
min18112345612 小时前
深度伪造内容的检测与溯源技术
大数据·网络·人工智能
汤愈韬12 小时前
NAT策略
网络协议·网络安全·security·huawei
汤愈韬12 小时前
Full Cone Nat
网络·网络协议·网络安全·security·huawei
zbtlink13 小时前
现在还需要带电池的路由器吗?是用来干嘛的?
网络·智能路由器
桌面运维家13 小时前
vDisk配置漂移怎么办?VOI/IDV架构故障快速修复
网络·架构
dalerkd13 小时前
忙里偷闲叙-谈谈最近两年
网络·安全·web安全
汤愈韬14 小时前
NAT ALG (应用层网关)
网络·网络协议·网络安全·security·huawei
运维栈记15 小时前
虚拟化网络的根基-网络命名空间
网络·docker·容器