(1) socket到底是什么?


一旦三次握手完成,客户端和服务器端建立连接,就进入了数据传输过程。
一旦连接建立,数据的传输就不再是单向的,而是双向的,这也是 TCP 的一个显著特性。
socket 是我们用来建立连接,传输数据的唯一途径。

(2) 套接字地址格式

通用套接字地址格式

/* POSIX.1g 规范规定了地址族为2字节的值. */
typedef unsigned short int sa_family_t;
/* 描述通用套接字地址 */
struct sockaddr{
    sa_family_t sa_family;  /* 地址族.  16-bit*/
    char sa_data[14];      /* 具体的地址值 112-bit */
}; 
地址族在 glibc 里的定义非常多,常用的有以下几种:
  • AF_LOCAL:表示的是本地地址,对应的是 Unix 套接字,这种情况一般用于本地 socket 通信,很多情况下也可以写成 AF_UNIX、AF_FILE;
  • AF_INET:因特网使用的 IPv4 地址;
  • AF_INET6:因特网使用的 IPv6 地址。
这里的 AF_ 表示的含义是 Address Family,但是很多情况下,我们也会看到以 PF_ 表示的宏,实际上 PF_ 的意思是 Protocol Family。我们在 <sys/socket.h> 头文件中可以清晰地看到,这两个值本身就是一一对应的。
/* 各种地址族的宏定义 */
#define AF_UNSPEC PF_UNSPEC
#define AF_LOCAL  PF_LOCAL
#define AF_UNIX   PF_UNIX
#define AF_FILE   PF_FILE
#define AF_INET   PF_INET
#define AF_AX25   PF_AX25
#define AF_IPX    PF_IPX
#define AF_APPLETALK  PF_APPLETALK
#define AF_NETROM PF_NETROM
#define AF_BRIDGE PF_BRIDGE
#define AF_ATMPVC PF_ATMPVC
#define AF_X25    PF_X25
#define AF_INET6  PF_INET6

IPv4 套接字格式地址

/* IPV4套接字地址,32bit值. */
typedef uint32_t in_addr_t;
struct in_addr
{
    in_addr_t s_addr;
};
  
/* 描述IPV4的套接字地址格式 */
struct sockaddr_in
{
    sa_family_t sin_family;     /* 16-bit */
    in_port_t sin_port;     /* 端口号  16-bit*/
    struct in_addr sin_addr;  /* Internet address. 32-bit */


    /* 这里仅仅用作占位符,不做实际用处 */
    unsigned char sin_zero[8];
};
可以发现和 sockaddr 一样,都有一个 16-bit 的 sin_family 字段,对于 IPv4 来说这个值就是 AF_INET。

glibc 定义的保留端口

/* Standard well-known ports. */
enum
{
    IPPORT_ECHO = 7,     /* Echo service. */
    IPPORT_DISCARD = 9,   /* Discard transmissions service. */
    IPPORT_SYSTAT = 11,   /* System status service. */
    IPPORT_DAYTIME = 13,   /* Time of day service. */
    IPPORT_NETSTAT = 15,   /* Network status service. */
    IPPORT_FTP = 21,     /* File Transfer Protocol. */
    IPPORT_TELNET = 23,   /* Telnet protocol. */
    IPPORT_SMTP = 25,     /* Simple Mail Transfer Protocol. */
    IPPORT_TIMESERVER = 37, /* Timeserver service. */
    IPPORT_NAMESERVER = 42, /* Domain Name Service. */
    IPPORT_WHOIS = 43,    /* Internet Whois service. */
    IPPORT_MTP = 57,




    IPPORT_TFTP = 69,     /* Trivial File Transfer Protocol. */
    IPPORT_RJE = 77,
    IPPORT_FINGER = 79,   /* Finger service. */
    IPPORT_TTYLINK = 87,
    IPPORT_SUPDUP = 95,   /* SUPDUP protocol. */


    IPPORT_EXECSERVER = 512,  /* execd service. */
    IPPORT_LOGINSERVER = 513,  /* rlogind service. */
    IPPORT_CMDSERVER = 514,
    IPPORT_EFSSERVER = 520,


    /* UDP ports. */
    IPPORT_BIFFUDP = 512,
    IPPORT_WHOSERVER = 513,
    IPPORT_ROUTESERVER = 520,


    /* Ports less than this value are reserved for privileged processes. */
    IPPORT_RESERVED = 1024,


    /* Ports greater this value are reserved for (non-privileged) servers. */
    IPPORT_USERRESERVED = 5000

IPv6 套接字地址

struct sockaddr_in6
  {
    sa_family_t sin6_family; /* 16-bit */
    in_port_t sin6_port;    /* 传输端口号 # 16-bit */
    uint32_t sin6_flowinfo;  /* IPv6流控信息 32-bit*/
    struct in6_addr sin6_addr;  /* IPv6地址128-bit */
    uint32_t sin6_scope_id;    /* IPv6域ID 32-bit */
  };
整个结构体长度是 28 个字节,其中流控信息和域 ID 先不用管,这两个字段,一个在 glibc 的官网上根本没出现,另一个是当前未使用的字段。
这里的地址族显然应该是 AF_INET6,端口同 IPv4 地址一样,关键的地址从 32 位升级到 128 位。

本地套接字

struct sockaddr_un {
    unsigned short sun_family; /* 固定为 AF_LOCAL */
    char sun_path[108];   /* 路径名 */
};
无论 IPv4 还是 IPv6 的地址格式都是因特网套接字的格式,还有一种本地套接字格式,用来作为本地进程间的通信, 也就是前面提到的 AF_LOCAL。

几种套接字地址格式比较


IPv4 和 IPv6 套接字地址结构的长度是固定的,而本地地址结构的长度是可变的

思考

1. 想一想 IPv4、IPv6、本地套接字格式以及通用地址套接字,它们有什么共性呢?
像sock_addr的结构体里描述的那样,几种套接字都要有地址族地址两个字段。这容易理解,你要与外部通信,肯定要至少告诉计算机对方的地址和使用的是哪一种地址。与远程计算机的通信还需要一个端口号。而本地socket的不同之处在于不需要端口号 (需要一个本地文件地址)
2. 为什么本地套接字格式不需要端口号,而 IPv4 和 IPv6 套接字格式却需要端口号呢?
本地socket本质上是在访问本地的文件系统,所以自然不需要端口。远程socket是直接将一段字节流发送到远程计算机的一个进程,而远程计算机可能同时有多个进程在监听,所以用端口号标定要发给哪一个进程