套接字

套接字和地址

socket (套接字)

寓意是可以通过接口插入的方式,快速完成网络连接和数据收发。你可以把它想象成现实世界的电源插口,或者是早期上网需要的网络插槽,也可以看做是对物理世界的直接映射。

客户端 服务端

connect ->(连接请求tcp三次握手) socket
bind
listen
accept
write read
read write
close close

我们先从右侧的服务器端开始看,因为在客户端发起连接请求之前,服务器端必须初始化好,右侧的图显示的是服务器端初始化的过程,首先初始化socket,之后服务器端需要执行bind函数,将自己的服务能力绑定在一个众所周知的地址和端口上,紧接着,服务器端执行listen操作,将原先的socket转化为服务端的socket,服务端最后阻塞在accept上等待客户端请求的到来。
此时,服务器端已经准备就绪,客户端需要先初始化socket,再执行cennect向服务器端的地址和端口发起连接请求,这里的地址和端口必须是客户端预先知晓的。这个过程,就是著名的tcp三次握手。
具体来说,客户端进程向操作系统内核发起write字节流写操作,内核协议栈将字节流通过网络设备传输到服务端,服务端从内核得到信息,将字节流从内核读入到进程中,并开始业务逻辑的处理,完成之后,服务端再将得到的结果以同样的方式写给客户端,可以看到,一旦连接建立,数据的传输就不再是单向的,而是双向的,这也是tcp的一个显著特性
当客户端完成和服务器端的交互后,比如执行一次telnet操作,或者一次http请求,需要和服务器端端开连接时,就会执行close函数,操作系统内核此时会通过原先的连接链路向服务器端发送一个fin包,就会执行close函数,整个链路才会真正关闭。半关闭的状态下,发起close请求的一方在没有收到对方的fin包之前都会认为连接是正常的;而在全关闭的状态下,双方都感知连接已经关闭。
以上的所有的操作,都是通过socket来完成的,无论是客户端的connect,还是服务端的accept,或者read/write操作,socket都是我们用来建立连接,传输数据的唯一途径。

一个更直观的解释

tcp网络交互和传输可以想象成打电话,connect就好比电话机拨号,服务端的bind就好比是在电信开户,将电话号码和我们的电话机绑定,这样别人就可以用这个号码找到你,listen就好比人们在家里听到了响铃,accept就好比是被叫的一方拿起电话开始应答,至此,三次握手就完成了。
然后开始read/write的数据传输。
挂上电话,自然就是close。

套接字地址格式

在使用套接字时,首先要解决通信双方的寻址问题,我们需要知道套接字的地址才能开始建立连接,就像打电话时需要先找电话簿,找到你想要联系的那个人,你才可以开始连接,开始交流。

通用套接字地址格式

/* 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这样的值来chushihuasocket。我们在<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

sockaddr是一个通用的地址结构(使用于多种地址族)

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.
端口号最多是16-bit,也就是说最大支持2的16次方,65536.

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的字段,大概是40亿,不过也渐渐不太够用了,所以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个字节,其中流控信息和域ip在glibc官网没出现,另一个是当前还没使用的字段
这里的地址族是AF_INET6,端口和ipv4一样,地址从32位升级到128位。
ipv4,ipv6都是因特网套接字的格式,还有一种本地套接字格式,用来做位本地进程间的通信,也就是AF_LOCAL.

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

几种套接字地址格式比较

All posts

Other pages

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注