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 编程的实际应用

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

相关推荐
Predestination王瀞潞1 小时前
4.3.3 存储->微软文件系统标准(微软,自有技术标准):VFAT(Virtual File Allocation Table)虚拟文件分配表系统
linux·microsoft·vfat
二进制person2 小时前
JavaEE初阶 --网络初识
运维·服务器·网络
IMPYLH2 小时前
Linux 的 cp 命令
linux·运维·服务器
@syh.2 小时前
【linux】多线程
linux
贝锐2 小时前
立航货运携手贝锐向日葵,大型物流园区如何进行远程运维升级
运维·远程
RisunJan2 小时前
Linux命令-man(查看Linux中的指令帮助)
linux·运维·服务器
REDcker2 小时前
CentOS 与主流 Linux 发行版:版本与时间表(年表)
linux·运维·centos
bai_lan_ya3 小时前
使用linux的io文件操作综合实验_处理表格
linux·服务器·算法
扁舟·TF3 小时前
VirtuaBox: 修改 Host-Only 网络的 IP 地址
服务器·网络·tcp/ip