Linux网络编程 - 套接字和地址

1. socket 到底是什么?

在网络编程,socket,中文翻译为套接字,有的时候也叫套接口。它的寓意是通过插口接入的方式,快速完成网络连接和数据收发。可以把它从想象成现实世界的电源插槽,或者早起上网需要的网络插槽,所以 socket也可以看做是对物理世界的直接映射。

先上一张图,可以看看:

                          

这张图是网络编程中,客户端和服务端工作的核心逻辑。先从右侧服务端开始看,因为在客户端发起连接请求之前,服务端必须先初始化好。首先初始化socket,之后服务器执行 bind() 绑定,将自己的服务能力绑定在一个众所周知的地址和端口上。紧接着,执行 listen() ,将原先的 socket转化为服务端的socket,服务端最后阻塞在 accept() 等待客户端请求的到来。

服务端准备就绪后,客户端需要先初始化 socket,再执行 connect() 向服务端的地址和端口发起连接请求,这里的地址必须是客户端预先知晓的。这个过程就是 TCP三次握手,具体的后面会详细阐述。

三次握手成功后,客户端和服务端之间就可以进行数据传输。具体来说,客户端向内核发起write字节流写操作,内核协议栈将字节流从网络设备发送到服务端,服务端从内核得到信息,将字节流从内核读到进程中,并开始业务逻辑的处理,完成之后,服务端再将得到的结果以同样的方式写回到客户端。可以看到,一旦连接建立,数据的传输就不再是单向传输,而是双向的,这也是 TCP的一个显著特性。

当两边交互完之后,比如执行一次 telnet操作,或者一次 HTTP请求,客户端需要和服务器断开连接,就会执行close函数。系统内核此时通过原先的连接链路,向服务端发送一个 FIN包,服务端收到之后被动关闭,整个链路处于半关闭状态,服务器也会执行close(),整个链路才会真正关闭。半关闭状态下,发起close请求的一方,在收到对方 FIN包之前,都会认为链接是正常的,而全关闭情况下,双方都感知连接是关闭状态。

记住,以上所讲这些,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_INET,PF_INET6等,PF 代表 Protocol Family,协议族的意思。我们用 AF_XXX这样的来初始化 socket地址,用 PF_XXX来初始化 socket。在 /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一样,都有一个 16bit 的sin_family字段,这里就是AF_INET。接下来是端口号,可支持寻址的端口号有 65535个。这里说说保留端口,就是被对应服务广为使用的端口,比如ftp 的21,ssh 的 22,http的80.一般而言, 大于5000的端口可以作为我们自己的应用程序使用。下面是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

实际的IPV4 地址是一个 32 bit的字段,最多支持 约 42亿的地址。但是,怎奈互联网的蓬勃发展,这个数字现在已经明显不够了,所以 IPV6登场了。

  • IPV6 套接字地址格式
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 bytes,其中流控信息 和 ID先不管,前者在 glibc官网根本没出现,后者是当前未使用字段。这里的地址族显然应该是 AF_INET6,端口和 IPV4一样,主要是地址从 32 bit 提高到 128 bit,完全解决了 寻址数字不够的情况。

以上无论是ipv4 还是 ipv6的地址格式,都是因特网套接字格式。还有一种本地套接字格式,用来本地进程间通信,AF_LOCAL。本地socket本质上是访问本地文件系统,自然不需要端口号。

struct sockaddr_un {
    unsigned short sun_family; /* 固定为 AF_LOCAL */
    char sun_path[108];   /* 路径名 */
};

下面展示出几种套接字地址格式的比较,ipv4 和 ipv6 套接字地址结构长度是固定的,本地地址结构是可变的。

                          

从这一篇开始,关于网络编程的温故而知新。

发布了39 篇原创文章 · 获赞 22 · 访问量 7391

猜你喜欢

转载自blog.csdn.net/qq_24436765/article/details/104049193