Linux:socket套接字编程的基础概念

一、Socket 编程是什么

Socket(套接字)是网络通信的编程接口,是应用层与 TCP/IP 协议族通信的中间软件抽象层,简单来说,它是两个网络程序之间实现数据传输的 “桥梁”。无论是 TCP 还是 UDP 协议,都可以通过 Socket 接口实现跨主机、跨网络的进程间通信,也是实现网络编程的基础核心

Socket 编程主要分为TCP SocketUDP Socket两类:

  • TCP Socket:基于面向连接的 TCP 协议,提供可靠、有序、字节流的传输,适用于文件传输、登录认证等对数据可靠性要求高的场景
  • UDP Socket:基于无连接的 UDP 协议,提供无可靠保证、面向数据报的传输,传输速度快、开销小,适用于聊天、音视频传输、广播等对实时性要求高的场景

本文重点讲解UDP Socket的核心基础与常用接口(也是后续将写的三个实战项目的基础),TCP Socket 会在后续文章补充

二、UDP Socket 编程核心特点

UDP 是无连接的传输层协议,决定了 UDP Socket 编程的核心特性,也是与 TCP Socket 的核心区别:

  1. 无连接:通信双方无需提前建立连接,客户端直接向服务端发送数据报,服务端直接接收即可
  2. 面向数据报:数据以 “数据报” 为单位传输,每次发送 / 接收都是一个完整的数据报,数据报大小有限制(通常小于 64K)
  3. 无需维护连接状态:服务端可同时接收多个客户端的数据,无需为每个客户端维护连接,资源开销小
  4. 无可靠保证:数据传输可丢失、乱序,UDP 协议不提供重传、确认机制,可靠性由应用层自行实现
  5. 全双工通信:一个 Socket 描述符(fd)既可以用于读取数据,也可以用于写入数据,支持同时收发

三、UDP Socket 编程核心接口(Linux 下 C/C++)

UDP Socket 编程的接口均来自 Linux 系统的网络编程头文件,核心头文件包含:

#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <cstring> 

所有接口的返回值均为int/ssize_t,返回 - 1 表示调用失败,可通过errnostrerror(errno)查看错误原因。

1. socket () —— 创建套接字描述符

功能:创建一个 Socket 描述符,作为后续网络通信的句柄,相当于打开一个 “网络文件”。函数原型

int socket(int domain, int type, int protocol); 

参数说明

  • domain:协议域,指定网络层协议,UDP/TCP 均使用AF_INET(IPv4 协议);
  • type:套接字类型,UDP 使用SOCK_DGRAM(数据报套接字),TCP 使用SOCK_STREAM(字节流套接字);
  • protocol:指定具体协议,填 0 表示根据domaintype自动选择(UDP 为 IPPROTO_UDP,TCP 为 IPPROTO_TCP)。示例:创建 UDP Socket
int sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd < 0) { perror("socket error"); // 打印错误信息 exit(1); } 

2. bind () —— 绑定 IP 与端口

功能:将 Socket 描述符与本地 IP 地址、端口号绑定,让系统知道该 Socket 监听哪个端口的网络数据。核心说明

  • 服务端必须显式绑定:服务端的端口需要是 “众所周知” 的固定值,让客户端能准确发送数据,IP 推荐使用INADDR_ANY(表示绑定本机所有网卡的 IP,接收来自任意网卡的数据)
  • 客户端无需显式绑定:客户端会在首次调用sendto()时,由系统自动绑定一个随机端口和本机 IP,避免端口冲突。函数原型
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 

参数说明

  • sockfd:由socket()创建的 Socket 描述符;
  • addr:指向套接字地址结构的指针,UDP/TCP 使用struct sockaddr_in(IPv4 专用),需强制转换为struct sockaddr*
  • addrlen:套接字地址结构的大小,sizeof(struct sockaddr_in)套接字地址结构(struct sockaddr_in)
struct sockaddr_in { sa_family_t sin_family; // 协议域,与socket()的domain一致,AF_INET in_port_t sin_port; // 端口号,需转换为**网络字节序**(htons()) struct in_addr sin_addr; // IP地址结构 }; struct in_addr { in_addr_t s_addr; // IP地址,需转换为**网络字节序**(inet_addr()/inet_pton()) }; 

示例:服务端绑定端口 8888,IP 为 INADDR_ANY

struct sockaddr_in local; memset(&local, 0, sizeof(local)); // 初始化结构体,置0 local.sin_family = AF_INET; local.sin_port = htons(8888); // 主机字节序转网络字节序 local.sin_addr.s_addr = INADDR_ANY; // 绑定本机所有IP int ret = bind(sockfd, (struct sockaddr*)&local, sizeof(local)); if (ret < 0) { perror("bind error"); exit(2); } 

3. sendto () —— 发送数据报

功能:向指定的目标 IP 和端口发送 UDP 数据报,是 UDP 编程的核心发送接口。函数原型

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); 

参数说明

  • sockfd:Socket 描述符
  • buf:指向要发送数据的缓冲区指针
  • len:要发送数据的字节数
  • flags:发送标志,填 0 表示默认(阻塞发送)
  • dest_addr:目标端的套接字地址结构(包含目标 IP 和端口)
  • addrlen:目标地址结构的大小,sizeof(struct sockaddr_in)返回值:成功返回发送的字节数,失败返回 - 1。示例:向服务端(192.168.1.100:8888)发送数据
std::string data = "hello udp"; struct sockaddr_in server; memset(&server, 0, sizeof(server)); server.sin_family = AF_INET; server.sin_port = htons(8888); server.sin_addr.s_addr = inet_addr("192.168.1.100"); // 点分十进制转网络字节序 ssize_t n = sendto(sockfd, data.c_str(), data.size(), 0, (struct sockaddr*)&server, sizeof(server)); if (n < 0) { perror("sendto error"); } 

4. recvfrom () —— 接收数据报

功能:接收来自任意客户端的 UDP 数据报,并获取发送方的 IP 和端口号,是 UDP 编程的核心接收接口。函数原型

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); 

参数说明

  • sockfd:Socket 描述符
  • buf:指向接收数据的缓冲区指针
  • len:接收缓冲区的大小
  • flags:接收标志,填 0 表示默认(阻塞接收)
  • src_addr:输出参数,用于存储发送方的套接字地址结构(获取发送方 IP 和端口)
  • addrlen:输入输出参数,入参为src_addr的大小,出参为实际的地址结构大小。返回值:成功返回接收的字节数,失败返回 - 1,返回 0 表示连接关闭(UDP 几乎不会出现)。示例:接收数据并获取发送方信息
char buffer[1024] = {0}; struct sockaddr_in peer; // 存储发送方信息 socklen_t len = sizeof(peer); ssize_t m = recvfrom(sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len); if (m > 0) { buffer[m] = 0; // 手动添加字符串结束符 // 获取发送方IP和端口(网络字节序转主机字节序) std::string peer_ip = inet_ntoa(peer.sin_addr); uint16_t peer_port = ntohs(peer.sin_port); std::cout << "[" << peer_ip << ":" << peer_port << "]# " << buffer << std::endl; } 

5. close () —— 关闭套接字

功能:关闭 Socket 描述符,释放系统分配的网络资源,与文件操作的close()一致。函数原型

int close(int sockfd); 

示例

close(sockfd); // 关闭后,sockfd不可再使用 

四、UDP Socket 编程基本流程

服务端流程(固定步骤)

  1. 调用socket()创建 UDP Socket 描述符
  2. 调用bind()绑定固定端口和INADDR_ANY
  3. 循环调用recvfrom()接收客户端数据
  4. (可选)调用sendto()向客户端返回响应数据
  5. 通信结束后,调用close()关闭 Socket

客户端流程(固定步骤)

  1. 调用socket()创建 UDP Socket 描述符
  2. 填充服务端的struct sockaddr_in结构(IP + 端口)
  3. 调用sendto()向服务端发送数据(系统自动绑定随机端口)
  4. (可选)调用recvfrom()接收服务端响应
  5. 通信结束后,调用close()关闭 Socket

五、核心网络字节序转换接口

网络传输的数据采用大端序(网络字节序),而主机的字节序可能是大端或小端(x86 架构为小端),因此需要通过接口完成主机字节序网络字节序的转换:

  1. htons(uint16_t n):主机字节序(16 位)转网络字节序,用于端口号转换
  2. ntohs(uint16_t n):网络字节序(16 位)转主机字节序,用于获取端口号
  3. inet_addr(const char *cp):点分十进制 IP 字符串转网络字节序 32 位整数,简单但有兼容性问题
  4. inet_pton(int family, const char *cp, void *addr):推荐使用,点分十进制 IP 字符串转网络字节序,支持 IPv4/IPv6
  5. inet_ntoa(struct in_addr in):网络字节序 IP 转点分十进制字符串,非线程安全
  6. inet_ntop(int family, const void *addr, char *cp, size_t len):推荐使用,网络字节序 IP 转点分十进制字符串,线程安全,支持 IPv4/IPv6

Socket 编程是网络编程的基础,而 UDP Socket 因无连接、开销小、实时性高的特点,成为轻量级网络通信的首选。核心需掌握 5 个接口:socket()(创建)、bind()(绑定)、sendto()(发送)、recvfrom()(接收)、close()(关闭),以及网络字节序与主机字节序的转换

后续三篇博客将基于 UDP Socket 编程基础,实现三个实战项目:英译汉翻译服务器简单 UDP 通信程序UDP 群聊聊天室,从简单到复杂,逐步掌握 Socket 编程的实际应用

看到这里,不打算给我一个可爱的赞嘛~~

Read more

IBus vs. Fcitx5:一场 Linux 输入法框架的正面交锋

在 Linux 系统上选择一个输入法框架,可能是一个出人意料的、充满个人色彩且影响深远的决定,特别是对于输入非英语语言的用户而言。多年来,主要的竞争者一直是 IBus(Intelligent Input Bus)和 Fcitx(Flexible Input Method Framework)。随着更现代化的 Fcitx5 的发布,这场竞争变得更加激烈。虽然两者的目标都是提供无缝的多语言输入体验,但它们在设计哲学、功能和理想使用场景上存在显著差异。 简而言之,Fcitx5 现在被广泛认为是更现代化、功能更丰富、通用性更强的选择,尤其适合 KDE Plasma 用户以及那些高度重视性能和定制化能力的用户。而 IBus 依然是一个可靠、简洁的选择,特别适合追求开箱即用体验的 GNOME 用户。 下面是这两个输入法框架的详细对比: 一览:IBus vs. Fcitx5 特性IBus (Intelligent Input Bus)

By Ne0inhk
Flutter for OpenHarmony:Flutter 三方库 slugid — 紧凑型极短唯一 ID 生成实战(适配鸿蒙 HarmonyOS Next ohos)

Flutter for OpenHarmony:Flutter 三方库 slugid — 紧凑型极短唯一 ID 生成实战(适配鸿蒙 HarmonyOS Next ohos)

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net。 Flutter for OpenHarmony:Flutter 三方库 slugid — 紧凑型极短唯一 ID 生成实战(适配鸿蒙 HarmonyOS Next ohos) 前言 在鸿蒙(OpenHarmony)应用中,标识符(ID)广泛用于日志追踪、文件命名或短链接。相比 36 字符的长 UUID,slugid 能将 ID 压缩为 22 字符且 URL 安全的字符串,更符合移动端界面展示与存储的极简需求。 一、核心价值 1.1 基础概念 slugid 实质上是对 V4 版 UUID 的一种编码压缩。

By Ne0inhk

OpenCore 终极完整教程:PC上完美安装macOS指南

OpenCore 终极完整教程:PC上完美安装macOS指南 【免费下载链接】OpenCore-Install-GuideRepo for the OpenCore Install Guide 项目地址: https://gitcode.com/gh_mirrors/op/OpenCore-Install-Guide OpenCore是一款革命性的引导加载器,专门为在普通PC硬件上运行macOS系统而设计。与传统的Clover引导工具相比,OpenCore更加注重安全性和稳定性,支持macOS的完整安全特性,包括系统完整性保护(SIP)和FileVault加密。本教程将带领你从零开始,完成整个安装过程。🚀 一、项目核心价值与快速概览 OpenCore不仅仅是一个引导工具,它是一个完整的生态系统,让你能够在非苹果硬件上享受原生的macOS体验。主要优势包括: * ✅ 原生安全性:完全支持Apple的安全启动链 * ✅ 持续更新:活跃的开发者社区和定期更新 * ✅ 硬件兼容性:支持Intel和AMD的最新处理器 * ⚡ 性能优化:比传统方案更快的启动速度 图:Ope

By Ne0inhk
Flutter 组件 sw 的适配 鸿蒙Harmony 实战 - 驾驭高性能微服务路由架构、实现鸿蒙端 HTTP 流量语义分发与逻辑守卫方案

Flutter 组件 sw 的适配 鸿蒙Harmony 实战 - 驾驭高性能微服务路由架构、实现鸿蒙端 HTTP 流量语义分发与逻辑守卫方案

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 sw 的适配 鸿蒙Harmony 实战 - 驾驭高性能微服务路由架构、实现鸿蒙端 HTTP 流量语义分发与逻辑守卫方案 前言 在鸿蒙(OpenHarmony)生态的分布式业务网关、多端协同数据中转站以及需要实现极端细粒度接口管控的各种后端闭环应用开发中,“请求路由的执行效率与逻辑灵活性”是决定系统能否支撑起高并发访问请求的命门所在。面对包含上百个动态参数的 RESTful API 契约、需要针对鸿蒙手机、自研设备等不同终端执行差异化鉴权的复杂路由逻辑。如果仅仅依靠原始的 if-else 显式判定或性能低下的线性字符串匹配。不仅会导致路由分发的延迟随着接口数量增加而呈指数级上升,更会因为缺乏一套工业级的“语义化(Semantic)”路由映射规范。引发严重的服务逻辑归属混乱与权限越界风险。 我们需要一种“语义分发、匹配自洽”的路由艺术。 sw(在 Shelf 生态中常指高效的 Switch/Router 增强件)是一套专注于实现极致性能与

By Ne0inhk