Socket基础:什么是Socket、Socket类型与地址结构

Socket,说白了就是网络通信的「门」。

我刚开始学网络编程时,总觉得这玩意儿很玄乎。后来做了几个项目才明白——它其实就是操作系统提供的一个接口,让两个程序能在网络上互相发数据。你想想看,你在浏览器里输入网址,浏览器和服务器之间怎么通信?就是通过Socket。

什么是Socket?

Socket这个词,直译过来是「插座」。这个比喻其实挺形象的——你把一个程序插到网络上,它就有了收发数据的能力。

从程序员的角度看,Socket就是一个文件描述符。你可以像读写文件一样,往Socket里写数据,从Socket里读数据。只不过,文件是读写硬盘,Socket是读写网络。

核心要点:Socket是应用层与传输层之间的桥梁。你不需要关心数据怎么打包、怎么路由、怎么纠错——这些都由内核帮你搞定了。你只需要创建Socket,然后读写它。

我记得第一次写网络程序时,死活想不通:为什么创建Socket要指定三个参数?后来才明白,这三个参数决定了你用什么协议、什么方式通信。嗯,这就是我们接下来要聊的。

Socket类型:流式与数据报

Socket有两种最常用的类型,我工作中几乎天天跟它们打交道。

类型 名称 协议 特点
SOCK_STREAM 流式Socket TCP 可靠、有序、面向连接
SOCK_DGRAM 数据报Socket UDP 不可靠、无序、无连接

流式Socket(SOCK_STREAM)

流式Socket基于TCP协议。它的核心特点是:保证数据到达,而且顺序不乱

我在项目中遇到过一个问题:两个服务之间需要传输大文件。如果用数据报Socket,数据包可能乱序到达,还得自己拼装。用流式Socket就省心多了——你只管往里面写数据,对面读到的顺序跟你写的一模一样。

流式Socket的工作流程是这样的:

  • 服务端:创建Socket → 绑定地址 → 监听 → 接受连接 → 收发数据
  • 客户端:创建Socket → 连接服务端 → 收发数据

说白了,就像打电话——你得先拨号,对方接了才能说话。而且你说的话,对方听到的顺序不会乱。

数据报Socket(SOCK_DGRAM)

数据报Socket基于UDP协议。它不保证数据一定到达,也不保证顺序。

你可能会问:那这东西有什么用?

用处大了。比如视频通话、在线游戏——偶尔丢一帧画面没关系,但你不能等重传。延迟比可靠性更重要。我做过一个实时监控系统,用的就是数据报Socket。丢几个数据包无所谓,但画面不能卡。

数据报Socket的工作流程更简单:

  • 服务端:创建Socket → 绑定地址 → 收发数据
  • 客户端:创建Socket → 收发数据

不需要连接,直接发。就像寄明信片——你写好地址扔邮筒里,对方能不能收到、什么时候收到,你没法保证。

我的建议:初学者先学流式Socket。它更直观,调试也方便。等把TCP玩熟了,再碰UDP不迟。

Socket地址结构:sockaddr_in

创建Socket之后,你得告诉它「跟谁通信」。这个信息就存在地址结构里。

在C语言中,IPv4的地址结构是struct sockaddr_in。它的定义长这样:

#include <netinet/in.h>

struct sockaddr_in {
    sa_family_t    sin_family;   // 地址族,AF_INET
    in_port_t      sin_port;     // 端口号,网络字节序
    struct in_addr sin_addr;     // IP地址
    char           sin_zero[8];  // 填充,保持与sockaddr大小一致
};

这里有几个坑,我当年都踩过:

  • sin_family:必须设为AF_INET。我见过有人写成AF_UNIX,结果bind一直失败。
  • sin_port:要用htons()转换字节序。端口号在网络传输中是big-endian,而你的机器可能是little-endian。不转换的话,端口就错了。
  • sin_addr:IP地址要用inet_pton()inet_addr()转换。不能直接写字符串。

我曾经踩过的坑:有一次写服务端,bind总是返回-1。查了半天,发现是忘记调用htons()转换端口号。端口写成了0x1234,实际绑到了0x3412。从那以后,我每次写端口号都会下意识检查字节序转换。

下面是一个完整的地址初始化示例:

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

int main() {
    struct sockaddr_in addr;
    
    // 清零,防止残留数据
    memset(&addr, 0, sizeof(addr));
    
    // 设置地址族
    addr.sin_family = AF_INET;
    
    // 设置端口(8080),注意字节序转换
    addr.sin_port = htons(8080);
    
    // 设置IP地址(本机所有地址)
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    // 或者指定IP:inet_pton(AF_INET, "192.168.1.100", &addr.sin_addr);
    
    printf("地址结构初始化完成\n");
    printf("端口: %d\n", ntohs(addr.sin_port));
    
    return 0;
}

你可能会问:为什么不用struct sockaddr?因为那个结构太古老了,只保留了地址族和14字节的数据。用sockaddr_in更清晰,而且最后可以强制转换成struct sockaddr *传给bind、connect等函数。

知识体系总览

下面这张图,把本章的核心内容串起来了:

Socket基础知识体系 Socket 定义:网络通信的门 类型:流式 vs 数据报 地址结构:sockaddr_in SOCK_STREAM (TCP) SOCK_DGRAM (UDP) sin_family sin_port sin_addr 创建Socket → 绑定地址 → 监听/连接 → 收发数据 这是所有网络程序的基本流程 流式Socket保证可靠有序,数据报Socket追求低延迟

小结

这一章我们聊了三件事:

  • Socket是什么——网络通信的接口,本质上是一个文件描述符
  • Socket的两种类型——流式(TCP,可靠有序)和数据报(UDP,不可靠但快)
  • sockaddr_in地址结构——告诉Socket跟谁通信,注意字节序转换

嗯,基础打牢了,后面写代码才不会慌。下一章我们直接上手写一个最简单的TCP服务端和客户端,到时候你就知道这些概念怎么用了。


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