网络编程——socket定义和地址格式
目录
socket 是什么? 套接字地址格式
1. socket 是什么?
网络编程中, socket 翻译为套接字或套接口,指可以通过插口接入的方式,快速完成网络连接和数据收发。 上图表示网络编程中,客户端和服务器工作的核心逻辑。 服务器端,在客户端发起连接请求之前,服务器端必须初始化好。
初始化 socket。 执行 bind 函数,将服务能力绑定在一个可知的地址和端口上。 然后执行 listen 操作,将原先的 socket 转化为服务端的 socket。 服务端最后阻塞在 accept 上等待客户端请求。 当服务器端已经准备就绪,客户端需要先初始化 socket,再执行 connect 向服务器端的地址和端口发起连接请求,这里的地址和端口必须是客户端预先知道的。connect请求就是TCP 三次握手 (Three-way Handshake)。 三次握手完成后,客户端和服务器端建立连接,就进入了数据传输过程。
客户端进程向操作系统内核发起 write 字节流写操作,内核协议栈将字节流通过网络设备传输到服务器端,服务器端从内核得到信息,将字节流从内核读入到进程中,并开始业务逻辑的处理,完成之后,服务器端再将得到的结果以同样的方式写给客户端。 所以一旦连接建立,数据的传输就不再是单向的,而是双向的 。 当客户端完成和服务器端的交互后,比如执行一次 Telnet 操作,或者一次 HTTP 请求,需要和服务器端断开连接时,就会执行 close 函数。
操作系统内核此时会通过原先的连接链路向服务器端发送一个 FIN 包,服务器收到之后执行被动关闭,这时候整个链路处于半关闭状态。 此后,服务器端也会执行 close 函数,整个链路才会真正关闭。 半关闭的状态下,发起 close 请求的一方在没有收到对方 FIN 包之前都认为连接是正常的。 而在全关闭的状态下,双方都感知连接已经关闭。 以上所有的操作,都是通过 socket 来完成的。无论是客户端的 connect,还是服务端的 accept,或者 read/write 操作等,socket 是用来建立连接,传输数据的唯一途径 。
2. 套接字地址格式
在使用套接字时,首先要解决通信双方寻址的问题,需要套接字的地址建立连接,套接字的地址格式如下。
1. 通用套接字地址格式
套接字的通用 地址结构:
typedef unsigned short int sa_family_t;
struct sockaddr {
sa_family_t sa_family;
char sa_data[ 14 ] ;
} ;
第一个字段是地址族,表示使用什么样的方式对地址进行解释和保存。地址族在 glibc 里的定义非常多,常用的有以下几种:
AF_LOCAL:表示的是本地地址,对应的是 Unix 套接字,一般用于本地 socket 通信,也可以写成 AF_UNIX、AF_FILE。 AF_INET:因特网使用的 IPv4 地址。 AF_INET6:因特网使用的 IPv6 地址。 AF_ 表示的含义是 Address Family, PF_表示Protocol Family协议族,比如 PF_INET、PF_INET6 等。用 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
sockaddr 是一个通用的地址结构,适用于多种地址族。
2. IPv4 套接字格式地址
常用的 IPv4 地址族的结构:
typedef uint32_t in_addr_t;
struct in_addr
{
in_addr_t s_addr;
} ;
struct sockaddr_in
{
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[ 8 ] ;
} ;
和 sockaddr 一样,都有一个 16-bit 的 sin_family 字段,对于 IPv4 来说值为 AF_INET。 端口号,端口号最多是 16-bit,即最大支持 2 的 16 次方(65535)。
保留端口是约定俗成的,已经被对应服务使用的端口,比如 ftp 的 21 端口,ssh 的 22 端口,http 的 80 端口等。 一般大于 5000 的端口可以作为自己应用程序的端口使用。 下面是 glibc 定义的保留端口。
enum
{
IPPORT_ECHO = 7 ,
IPPORT_DISCARD = 9 ,
IPPORT_SYSTAT = 11 ,
IPPORT_DAYTIME = 13 ,
IPPORT_NETSTAT = 15 ,
IPPORT_FTP = 21 ,
IPPORT_TELNET = 23 ,
IPPORT_SMTP = 25 ,
IPPORT_TIMESERVER = 37 ,
IPPORT_NAMESERVER = 42 ,
IPPORT_WHOIS = 43 ,
IPPORT_MTP = 57 ,
IPPORT_TFTP = 69 ,
IPPORT_RJE = 77 ,
IPPORT_FINGER = 79 ,
IPPORT_TTYLINK = 87 ,
IPPORT_SUPDUP = 95 ,
IPPORT_EXECSERVER = 512 ,
IPPORT_LOGINSERVER = 513 ,
IPPORT_CMDSERVER = 514 ,
IPPORT_EFSSERVER = 520 ,
IPPORT_BIFFUDP = 512 ,
IPPORT_WHOSERVER = 513 ,
IPPORT_ROUTESERVER = 520 ,
IPPORT_RESERVED = 1024 ,
IPPORT_USERRESERVED = 5000
IPv4 地址是一个 32-bit 的字段,最多支持的地址数是 2 的 32 次方,大约是 42 亿。
3. IPv6 套接字地址格式
IPv6 的地址结构:
struct sockaddr_in6
{
sa_family_t sin6_family;
in_port_t sin6_port;
uint32_t sin6_flowinfo;
struct in6_addr sin6_addr;
uint32_t sin6_scope_id;
} ;
整个结构体长度是 28 个字节,其中流控信息和域 IP 一个在 glibc 的官网上没出现,另一个是当前未使用的字段。地址族是 AF_INET6,端口同 IPv4 地址一样,关键的地址从 32 位升级到 128 位,完全解决了寻址数字不够的问题。 IPv4 和 IPv6 的地址格式都是因特网套接字的格式,还有一种本地套接字格式,用来作为本地进程间的通信, 即 AF_LOCAL。
struct sockaddr_un {
unsigned short sun_family;
char sun_path[ 108 ] ;
} ;
4. 几种套接字地址格式比较
几种地址的比较见下图,IPv4 和 IPv6 套接字地址结构的长度是固定的,而本地地址结构的长度是可变的。