创建套接字

1
2
3
#include <sys/socket.h>

int socket(int domain, int type, int protocol);
  • domain: 套接字中使用的协议族(Protocol Family)信息。
  • type: 套接字数据传输类型信息。
  • protocol: 计算机间通信中使用的协议信息。

协议族(Protocol Family)

套接字通信中的协议具有一些分类。通过 socket 函数的第一个参数传递套接字中使用的协议分类信息。此协议分类信息称为协议族,可分为如下几类。
|名称|协议族|
|-|-|
|PF_INET|IPv4 互联网协议族|
|PF_INET6|IPv6 互联网协议族|
|PF_LOCAL|本地通信的 UNIX 协议族|
|PF_PACKET|底层套接字的协议族|
|PF_IPX|IPX Novell 协议族|

本博客将着重讲解上表中的 PF_INET 对应的 IPv4 互联网协议族。其他协议族并不常用或尚未普及,因此本博客将重点放在 PF_INET 协议族上。另外,套接字中实际采用的最终信息是通过 socket 函数的第三个参数传递的。在指定的协议族范围内通过第一个参数决定第三个参数。

套接字类型(Type)

套接字类型指的是套接字的数据传输方式,通过 socket 函数的第二个参数传递,只有这样才能决定创建的套接字的数据传输方式。这种说法可能会使人感到疑惑。已通过第一个参数传递了协议族信息,还要决定数据的传输方式?问题就在于,决定了协议族并不能同时决定数据传输方式,换言之,socket 函数的第一个参数 PF_INET 协议族中也存在多种数据传输方式。
下面介绍 2 种具有代表性的数据传输方式。这是理解好套接字的重要前提,务必掌握。

套接字类型 1:面向连接的套接字(SOCK_STREAM)

如果向 socket 函数的第二个参数传递 SOCK_STEARM,将创建面向连接的套接字。面向连接的套接字的特点如下:

  • 传输过程中数据不会丢失。
  • 按序传输数据。
  • 传输的数据不存在数据边界(Boundary)。

只要网络链路本身没有问题,就能保证数据不丢失。同时,较晚传递的数据也不会先到达。
收发数据的的套接字内部有缓冲(buffer),简言之就是字节数组。通过套接字传输的数据将保存到该数组。因此,收到数据并不意味着马上调用 read 函数。只要不超过数组容量,则有可能再数据填充满缓冲后通过 1 次 read 调用读取全部,也有可能分成多次 read 函数调用进行读取。也就是说,在面向连接的套接字中,read 函数和 write 函数的调用次数并无太大意义。所以说面向连接的套接字不存在数据便捷。

  • 面向连接的套接字还有如下特点:
    1. 套接字连接必须一一对应。
    2. 可靠的、按序传递的、基于自己的面向连接的数据传输方式的套接字。

  • Tip:套接字缓冲已满是否意味着数据丢失?

不会。因为传输端套接字将停止传输。也就是说,面向连接的套接字会根据接收端的状态传输数据,如果传输出错还会提供重传服务。因此,面向连接的套接字除特殊情况外不会发生数据丢失。

套接字类型 2:面向消息的套接字(SOCK_DGRAM)

如果想 socket 函数的第二个参数传递 SOCK_RGRAM,则将创建面向消息的套接字。面向消息的套接字特点如下:

  • 强调快速传输而非传输顺序。
  • 传输的数据可能丢失也可能损毁。
  • 传输的数据有数据边界。
  • 限制每次传输的数据大小。

在面向消息的套接字传输方式中,只需要把数据以最快的速度送到接收端,而且数据大小有一定的限制。因此若要传递大量数据,则需要分批发送。如果数据分成 3 部分分批发送,则接受端也需要接收 3 次。这种特性就是“传输的数据具有数据边界”。

面向消息的套接字特性总结如下:

  • 不可靠的、不按序传递的、以数据的告诉传输为目的的套接字。
  • 面向消息的套接字不存在连接的概念。

协议的最终选择

下面讲解 socket 函数的第三个参数,该参数决定最终采用的协议。也许大家会有疑惑,前面已经通过 socket 函数的前两个参数传递了协议族信息和套接字数据传输方式,这些信息不足以决定采用的协议吗?为什么还需要传递第 3 个参数呢?
正如大家所想,传递前两个参数即可创建所需套接字。所以大部分情况下可以向第三个参数传递 0,除非遇到以下这种情况:

  • 同一协议族中存在多个数据传输方式相同的协议。

数据传输方式相同,但协议不同。此时需要通过第三个参数具体指定协议信息。

下面以前面讲解的内容为基础,构建 socket 函数传递的参数。

  1. 首先创建满足如下要求的套接字:IPv4 协议族中面向连接的套接字。
    • 参数 PF_INET 只 IPv4 网络协议簇,SOCK_STREAM 是面向连接的数据传输。满足这 2 个条件的协议只有 IPPROTO_TCP,因此可以如下调用 socket 函数创建套接字,这种套接字称为 TCP 套接字。
    • int tcp_socket = socket(PF_INET, SOCK_STREAN, IPPROTO_TCP);
  2. 下面创建满足如下要求的套接字:IPv4 协议族中面向消息的套接字。
    • SOCK_DGRAM 指的是面向消息的数据传输方式,满足上述条件的协议只有 IPPROTO_UDP。因此,可以用如下调用 socket 函数创建套接字。这种套接字称为 UDP 套接字。
    • int udp_socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);