Socket编程准备

一、IP和端口号

在互联网中,一台主机可以拥有多个 IP 地址(如多网卡、虚拟机、内外网 IP 共存),但在同一网络范围内,每个 IP 地址通常是唯一的。当我们使用软件客户端时,服务端要与客户端进程通信,本质上是通过IP + 端口来定位通信端点。

例如:你的电脑同时有:

  • WiFi IP:192.168.1.10
  • 网线 IP:192.168.2.20
  • 虚拟机 IP:192.168.100.5
  1. 访问家里路由器后台 → 走 WiFi IP
  2. 插着网线访问公司内网 → 走 网线 IP
  3. 虚拟机和主机互传文件 → 走 虚拟网卡 IP

客户端找服务端

服务端通常会使用固定的 IP 地址和固定监听端口,这些地址信息一般在开发阶段就配置在客户端中(或通过配置中心、域名解析获取)。用户启动客户端后,客户端主动根据预设的服务端 IP 和端口发起连接请求,从而找到并连接到对应的服务端进程。

服务端找客户端

  1. 客户端启动并发起网络连接时,操作系统会为该连接随机分配一个临时端口(部分内部开发场景会约定固定端口)。
  2. 网络通信中,IP 用于定位唯一主机,端口用于定位主机上的具体进程,服务端正是通过客户端的 IP 和端口来识别并与对应客户端进程通信。
  3. 但在公网环境下,大多数客户端位于内网并经过 NAT 转换,服务端看到的并非客户端真实内网 IP + 端口,而是 NAT 映射后的公网地址因此服务端通常无法主动发起连接,而是依赖客户端先建立连接并保持长连接,再通过已建立的链路进行双向通信

二、进程ID和端口号

**进程ID(PID)**是操作系统用于标识本机内部运行程序的编号,只在本机有效,用于系统管理进程,每次启动程序PID通常会变化,外部网络无法通过PID找到对应程序;

端口号则是用于网络通信的标识,作用是让操作系统收到网络数据时能准确转发给对应的进程,可固定也可随机分配,外部设备通过IP+端口定位目标机器上的服务进程。

一个进程可绑定多个端口,但同一端口同一时间只能被一个进程占用,简单来说PID是系统内部管程序用的,端口是网络找程序用的,网络通信只认IP+端口,不认PID。

三、源端口和目的端口

源端口号是客户端发起网络请求时系统随机分配的本地端口,用来标识本机上的通信进程,方便服务端回复数据时能准确找到对应客户端程序;

目的端口号 是客户端要访问的服务端固定监听端口(如80、443、3306),用来定位服务端上的具体服务进程,数据传输时双方通过数据包里的源IP+源端口、目的IP+目的端口建立唯一通信通道,服务端接收请求后就以客户端的源IP和源端口作为目的来回传数据

IP 地址用来标识网络中对应的主机(同一网段内唯一),端口号用来标识该主机上进行网络通信的进程。IP+Port 就可以唯一标识网络中的一个通信端点。

通信的本质,就是两个网络进程之间的数据交互,由 {源 IP, 源端口,目的 IP, 目的端口} 这个四元组唯一标识一条连接、区分两个进程。

因此,网络通信本质上也属于跨主机的进程间通信,而我们把 IP+Port 这个组合称为一个套接字(Socket)

四、TCP和UDP协议初识

TCP 和 UDP 都是传输层协议,作用都是基于 IP+Port 实现进程间通信,只是可靠性和使用场景完全不同。

TCP 面向连接、可靠传输,通信前必须三次握手建立连接,会保证数据不丢、不乱、不重复,像打电话,必须接通才能说话,适合文件传输、网页浏览、接口请求等需要可靠数据的场景;

UDP 无连接、不可靠,不用建立连接直接发包,不管对方是否收到、数据是否乱序,速度更快、延迟更低,像寄信或广播,适合视频通话、直播、游戏、DNS 查询等允许少量丢包但追求速度的场景,简单说就是 TCP 求稳,UDP 求快。

传输的可靠性应该视为特性,而不是缺陷。

五、网络字节序

首先,网络数据流是一串连续字节,同样存在字节先后顺序的问题,和内存、文件一样有大小端之分。目前计算机有大端和小端两种模式:大端序是高位字节存放在低地址,先发高位;小端序是低位字节存放在低地址,先发低位。

如果两种不同字节序的机器直接通信,就会出现数据解析错乱,比如大端发送 11223344,小端可能收到 44332211。为了解决这个问题,TCP/IP 规定网络通信统一使用大端字节序,也就是网络字节序,发送和接收时的字节序转换由操作系统自动完成

字节序转化函数

函数命名规则很清晰:h 代表 host(主机),n 代表 network(网络),l 代表 32 位长整数,s 代表 16 位短整数。

htons:把 16 位端口从主机序转网络序;htonl:把 32 位 IP 从主机序转网络序;ntohs、ntohl 则是反向转换。

  • 主机是小端:把字节翻转,转成大端再返回;

  • 主机是大端:本来就符合网络要求,直接原样返回,不做任何转换。

    复制代码
    //把本机端口号转换成网络字节序,赋值给套接字地址结构体的端口字段。
    local.sin_port = htons(_port); 

六、Socket编程接口

Socket常见函数

函数原型 作用
int socket(int domain, int type, int protocol); 创建 socket 文件描述符
int bind(int sockfd, const struct sockaddr *addr, socklen_t len); 绑定 IP + 端口
int listen(int sockfd, int backlog); 启动监听,等待连接
int accept(int sockfd, ...); 接受客户端连接
int connect(int sockfd, ...); 主动连接服务器

socket

复制代码
int socket(int domain, int type, int protocol);

作用:创建一个套接字(网络通信的基础文件描述符),客户端、服务端、TCP/UDP 都必须先调用它。

参数

  • domain:地址族,常用 AF_INET(IPv4)。
  • type:套接字类型,SOCK_STREAM(TCP)、SOCK_DGRAM(UDP)。
  • protocol:协议,一般填 0 自动匹配。

返回值:成功返回一个文件描述符(fd),失败返回 -1。

bind

复制代码
int bind(int sockfd, const struct sockaddr *address, socklen_t address_len);

作用:给服务端 socket 绑定固定的 IP 和端口,让客户端能找到。

参数

  • sockfd:socket () 返回的文件描述符。
  • address:结构体,存放要绑定的 IP、端口、协议。
  • address_len:上述结构体的长度。

返回值:成功 0,失败 -1。

listen

复制代码
int listen(int sockfd, int backlog);

作用:TCP 服务端专用,将 socket 设为监听模式,开始等待客户端连接。

参数

  • sockfd:监听用的 socket fd。
  • backlog:等待连接队列的最大长度。

返回值:成功 0,失败 -1。

accept

复制代码
int accept(int sockfd, struct sockaddr *address, socklen_t *address_len);

作用:TCP 服务端专用,阻塞等待并接收一个客户端连接,返回用于通信的新 fd。

参数

  • sockfd:监听 socket 的 fd。
  • address:输出客户端的 IP 和端口。
  • address_len:输入输出长度。

返回值 :成功返回新的通信 fd,失败返回 -1。

connect

复制代码
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

作用:TCP 客户端专用,主动向服务端发起连接(三次握手)。

参数

  • sockfd:客户端的 socket fd。
  • addr:服务端的 IP + 端口。
  • addrlen:结构体长度。

返回值:成功 0,失败 -1。

Sockaddr结构体

struct sockaddr:通用地址结构体("基类")

这是所有地址结构体的父类型 / 通用类型 ,作用是给 bindconnectaccept 等系统调用提供统一的参数类型,让函数能兼容不同协议的地址。

复制代码
struct sockaddr {
    sa_family_t sa_family;  // 16位地址类型(地址族)
    char        sa_data[14];// 14字节的地址数据(占位用)
};
  • sa_family :核心标识,用来区分地址类型:
    • AF_INET:IPv4 网络通信(对应 sockaddr_in
    • AF_UNIX/AF_LOCAL:本地进程间通信(对应 sockaddr_un
    • AF_INET6:IPv6 网络通信(对应 sockaddr_in6
  • sa_data[14]:占位缓冲区,用来存放具体的地址信息,实际开发中不会直接操作它。

所有网络编程的系统调用(bindconnect 等)的参数都定义为 struct sockaddr*,这样函数可以接收任意类型的地址结构体,通过 sa_family 区分具体类型,实现接口统一

struct sockaddr_in:IPv4 专用地址结构体(最常用)

这是IPv4 网络通信专用 的地址结构体,用来存「IP 地址 + 端口号」,是我们写 TCP/UDP 网络程序时真正用来赋值的结构体。

复制代码
struct sockaddr_in {
    sa_family_t    sin_family;  // 16位地址类型,固定填 AF_INET
    in_port_t      sin_port;    // 16位端口号(必须用网络字节序)
    struct in_addr sin_addr;    // 32位IPv4地址(必须用网络字节序)
                                //注意sin_addr是结构体类型
    unsigned char  sin_zero[8]; // 8字节填充,对齐内存,无实际意义
};

// 其中 in_addr 是IP地址的封装
struct in_addr {
    uint32_t s_addr; // 32位IPv4地址,网络字节序
};
  • sin_family :必须填 AF_INET,标识这是 IPv4 地址
  • sin_port :端口号,必须用 htons() 转成网络字节序再赋值
  • sin_addr.s_addr :IPv4 地址,必须用 inet_addr()/inet_pton() 转成网络字节序再赋值
  • sin_zero[8] :填充字节,为了和 struct sockaddr 总长度对齐(16 字节),必须全填 0

核心作用

实际开发中,我们只操作 sockaddr_in ,赋值完成后,再强制类型转换为 struct sockaddr* 传给系统调用,比如:

复制代码
struct sockaddr_in serv_addr;
// 给 serv_addr 赋值...
bind(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

struct sockaddr_un:本地进程间通信(IPC)专用结构体

这是UNIX 域套接字(本地通信)专用的地址结构体,用来存本地文件路径,实现同一台机器上两个进程的通信(不需要走网络协议栈,速度更快)。

复制代码
struct sockaddr_un {
    sa_family_t sun_family;  // 16位地址类型,固定填 AF_UNIX/AF_LOCAL
    char        sun_path[108];// 108字节的本地文件路径名
};
  • sun_family :必须填 AF_UNIX(或 AF_LOCAL,二者等价)
  • sun_path :本地套接字文件的路径,比如 /tmp/my_socket,进程通过这个文件建立连接

核心作用

用于本地进程间通信,和网络通信的 sockaddr_in 完全隔离,通过 sa_family 区分。

关键注意事项

  1. 永远不要直接操作 struct sockaddr :它只是通用接口,实际赋值、读取都用专用结构体(sockaddr_in/sockaddr_un
  2. 字节序问题sockaddr_in 中的端口号和 IP 地址,必须转成网络字节序(大端),否则会出现端口错乱、IP 解析错误
  3. IPv6 扩展 :还有 struct sockaddr_in6 用于 IPv6,结构和 sockaddr_in 类似,只是 IP 地址长度为 128 位

struct sockaddr通用地址壳子 ,给系统调用做统一接口;sockaddr_in/sockaddr_un具体地址内容,分别对应网络通信和本地通信,开发时用专用结构体赋值,再转成通用结构体传参。

相关推荐
羌俊恩2 小时前
Vim modeline 命令执行漏洞(CVE-2026-34714)修复指导
linux·编辑器·vim·漏洞·cve-2026-34714
wang09072 小时前
Linux性能优化之中断
linux·运维·性能优化
bukeyiwanshui2 小时前
20260410 系统启动原理
linux
huanmieyaoseng10032 小时前
Linux 安装配置 Tomcat超详细2026新(附安装包)
linux·运维·tomcat
光电大美美-见合八方中国芯2 小时前
用于无色波分复用光网络的 10.7 Gb/s 反射式电吸收调制器与半导体光放大器单片集成
网络·后端·ai·云计算·wpf·信息与通信·模块测试
WangJunXiang62 小时前
Python网络编程
开发语言·网络·python
_下雨天.2 小时前
Python 网络编程
开发语言·网络·python
一只小鱼儿吖2 小时前
长效代理IP:构建稳定高效的网络数据通
网络·网络协议·tcp/ip
charlie1145141912 小时前
嵌入式Linux模块学习——insmod 底层全流程解剖:从用户命令到内核内存
linux·c·嵌入式linux