Socket编程入门:什么是Socket、Socket类型与地址结构

各位同学,今天我们来聊聊Socket编程的入门知识。说实话,Socket这个概念刚接触时确实有点抽象,我当年学的时候也绕了好一阵子。但别担心,咱们一步步来,把它拆开揉碎了讲清楚。

一、什么是Socket?

Socket,中文常翻译为“套接字”,但我觉得这个翻译反而让人更迷糊了。说白了,Socket就是网络通信的“门”。你想啊,两台机器要通信,总得有个入口和出口吧?这个入口就是Socket。

我习惯这么理解:Socket = IP地址 + 端口号。IP地址找到机器,端口号找到机器上的具体进程。就像寄快递,IP是小区地址,端口是几栋几号。

核心要点:Socket是应用层与传输层之间的抽象接口。它隐藏了底层TCP/IP协议的复杂性,让我们可以像读写文件一样进行网络通信。

为什么会设计Socket?你想想看,如果没有Socket,我们得直接操作网卡、处理各种协议栈细节,那写网络程序就变成噩梦了。Socket的出现,让网络编程变得相对简单。

二、Socket类型:流式与数据报

Socket主要分两种类型,我当年面试时被问过无数次。咱们一个一个说。

1. 流式Socket(SOCK_STREAM)

流式Socket基于TCP协议。它的特点是:面向连接、可靠、有序、无边界

  • 面向连接:通信前必须先建立连接,就像打电话要先拨号
  • 可靠:数据不会丢失,不会重复,顺序不会乱
  • 无边界:数据像水流一样,没有明确的分界线

我在项目中遇到过一个问题:用流式Socket传文件,接收端总是收不全。后来发现是没处理好“粘包”问题。嗯,这里要注意,流式Socket虽然可靠,但数据边界需要自己管理。

个人经验:流式Socket适合HTTP、FTP、邮件传输等场景。我建议初学者先从流式Socket入手,因为它更直观,调试也方便。

2. 数据报Socket(SOCK_DGRAM)

数据报Socket基于UDP协议。它的特点是:无连接、不可靠、有边界

  • 无连接:直接发数据,不需要提前建立连接
  • 不可靠:数据可能丢失、乱序、重复
  • 有边界:每次发送都是一个独立的数据报,接收方一次读取一个完整报文

我曾经做过一个视频直播项目,用的就是数据报Socket。为什么?因为视频丢几帧没关系,但延迟大了可不行。数据报Socket虽然不可靠,但速度快、开销小。

避坑指南:我曾经在项目中直接用UDP传重要配置数据,结果丢包了也没发现,导致设备配置出错。后来我加了一层应用层确认机制才解决。记住:UDP不保证送达,需要自己处理可靠性。

特性 流式Socket (TCP) 数据报Socket (UDP)
连接方式 面向连接 无连接
可靠性 可靠 不可靠
数据边界 无边界 有边界
速度 较慢 较快
典型应用 HTTP、FTP、SMTP DNS、视频直播、游戏

三、Socket地址结构

地址结构是Socket编程的基础数据结构。说白了,就是用来存放IP地址和端口号的结构体。不同协议族有不同的地址结构,但最常用的是IPv4的sockaddr_in

1. 通用地址结构:sockaddr

这是最基础的地址结构,所有地址结构都可以强制转换为它。但实际编程中很少直接用它,因为它太“通用”了,不好用。

struct sockaddr {
    sa_family_t sa_family;  // 地址族,如AF_INET
    char        sa_data[14]; // 地址数据
};

2. IPv4地址结构:sockaddr_in

这才是我们真正常用的。我刚开始学的时候,老记不住这个结构体的成员,后来发现其实就三个核心字段:地址族、端口、IP地址。

struct sockaddr_in {
    sa_family_t    sin_family;   // 地址族,必须为AF_INET
    in_port_t      sin_port;     // 端口号,网络字节序
    struct in_addr sin_addr;     // IPv4地址
    char           sin_zero[8];  // 填充字段,一般置0
};

关键点:端口号和IP地址必须使用网络字节序。我见过太多新手直接赋值导致通信失败的案例了。记住用htons()htonl()转换。

3. IPv6地址结构:sockaddr_in6

随着IPv6的普及,这个结构也越来越重要。它比IPv4的结构要大一些,因为IPv6地址是128位的。

struct sockaddr_in6 {
    sa_family_t     sin6_family;   // 地址族,必须为AF_INET6
    in_port_t       sin6_port;     // 端口号
    uint32_t        sin6_flowinfo; // 流信息
    struct in6_addr sin6_addr;     // IPv6地址
    uint32_t        sin6_scope_id; // 作用域ID
};

4. 地址转换函数

实际编程中,我们经常需要在“点分十进制”字符串和二进制地址之间转换。这里有两个常用函数:

// 字符串转二进制(推荐用这个)
int inet_pton(int af, const char *src, void *dst);

// 二进制转字符串
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

我个人习惯用inet_ptoninet_ntop,它们同时支持IPv4和IPv6,比老旧的inet_addrinet_ntoa好用多了。

小技巧:写代码时,我习惯把地址结构体用memset清零后再赋值。这样可以避免一些奇怪的bug,尤其是sin_zero字段如果不置零,在某些系统上会出问题。

四、知识体系结构图

下面这张图总结了本章的核心知识点,我建议你保存下来,复习时一目了然。

Socket编程入门知识体系 Socket 什么是Socket IP + 端口 = 通信入口 应用层与传输层的抽象接口 Socket类型 流式 (SOCK_STREAM) TCP · 可靠 · 面向连接 数据报 (SOCK_DGRAM) UDP · 不可靠 · 无连接 地址结构 sockaddr_in (IPv4) sockaddr_in6 (IPv6) inet_pton / inet_ntop 核心:理解Socket本质,掌握类型选择,熟练地址操作

五、实战小例子

光说不练假把式。我写一个最简单的例子,展示如何初始化一个IPv4的Socket地址结构:

#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>

int main() {
    struct sockaddr_in server_addr;
    
    // 清零,避免垃圾数据
    memset(&server_addr, 0, sizeof(server_addr));
    
    // 设置地址族
    server_addr.sin_family = AF_INET;
    
    // 设置端口(注意网络字节序)
    server_addr.sin_port = htons(8080);
    
    // 设置IP地址
    inet_pton(AF_INET, "192.168.1.100", &server_addr.sin_addr);
    
    printf("地址结构初始化完成\n");
    printf("端口: %d\n", ntohs(server_addr.sin_port));
    
    char ip_str[INET_ADDRSTRLEN];
    inet_ntop(AF_INET, &server_addr.sin_addr, ip_str, sizeof(ip_str));
    printf("IP: %s\n", ip_str);
    
    return 0;
}

这段代码虽然简单,但包含了地址结构初始化的所有关键步骤。我建议你亲手敲一遍,感受一下。

常见错误:忘记用htons()转换端口号。我刚开始学的时候犯过这个错,结果服务端监听端口死活不对。还有,inet_pton的返回值一定要检查,它返回0表示地址格式无效,返回-1表示出错。

好了,Socket入门的内容就讲到这里。记住:Socket是网络编程的基石,地址结构是Socket编程的基础操作。把这些搞明白了,后面的内容学起来就轻松多了。


公众号:蓝海资料掘金营,微信deep3321