Socket到底是什麼?
網絡編程中到底如何理解socket,先看下面的圖
上面的圖表達了網絡編程中客戶端和服務器模型的核心邏輯。
服務器端:首選服務器端要先初始化號好socket,之後服務器端執行bind函數將自己的服務能力綁定在一個特定的地址和端口上,緊接着服務器端調用listen函數將原先的socket轉化爲服務端的socket,最後服務端阻塞在accept上等待客戶端的連接。
客戶端:以上服務端已經準備就緒,客戶端也需要首先初始化socket,再執行connect函數向服務器端的地址和端口發起連接請求,這一過程就是註明的TCP三次握手過程,握手成功後雙方已經建立連接即可以進行真正的數據交互。
具體來說客戶端進程向操作系統內核發起write寫操作,內核協議棧將字節流數據通過網絡設備傳輸到服務器端,服務器端從內核得到消息,將字節流數據從內核讀入到進程中,並開始業務邏輯的處理,完成之後服務端再將結構以同樣的方式返回給客戶端。一旦建立連接後數據傳輸的過程就不再是單向的而是雙向的,這也是TCP傳輸的一個顯著特徵。
當客戶端和服務器端數據交互完畢後,需要和服務器端斷開連接時,就會執行close操作,操作系統內核此時會通知原先的連接鏈路向服務器端發送一個FIN包,服務器端收到該FIN包後執行被動關閉,這個時候整個鏈路處於半關閉的狀態,此後服務器端也執行close操作,整個鏈路才真正的關閉。其中半關閉的狀態下,發起close請求的一方在沒有收到對方FIN包之前都認爲連接時正常的,而在全關閉狀態下,雙方都感知連接已經斷開。
以上所有的操作都是使用socket來完成的,socket是我們用來建立連接數據交互的唯一通道。
更好的理解socket,一個直觀的結解釋
我們可以把整個TCP的網絡交互和數據傳輸想象成是打電話,socket就好像是我們手裏的電話機,connect就好像拿起電話撥打手機號,而服務端的bind就好像是拿着電話號碼去電信公司註冊將電話號碼和家裏的電話綁定,listen就好像是聽到電話響,accept就好比是拿起電話開始接聽。至此三次握手建立完了,雙方可以進行電話溝通了。
在整個電話的溝通過程中,電話是我們和外界通信的工具,對應到網絡編程中socket就是我們和外界進行網絡通信的重要工具。
套接字地址格式
在使用套接字時首先要解決的是雙方的尋址問題。
通用套接字地址格式
/* 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 */
};
在這個結構體裏第一個字段sa_family是地址族,它表示使用什麼樣的方式來對地址進行解釋和保存,地址族在glibc中有很多的定義,常用的主要有如下的三種:
AF_LOCAL:表示的是本地地址,對應的是unix套接字,這個一般用於本地通信,也可以寫成是AF_UNIX、AF_FILE。
AF_INET:因特網使用的ipv4地址
AF_INET6:因特網使用的ipv6地址。
這裏的AF表示的address family的意思,有時候也會有PF_XXX,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地址族的數據格式如下:
/* 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];
};
struct sockaddr_in的第一個字段也是sin_family地址族對應ipv4就是AF_INET。然後是端口號,端口號爲16位即最多就是65536個端口,而保留端口則是值大家衆所周知的已經被對應的服務光廣泛使用的端口,如ftp服務的21端口、ssh服務的22端口以及http服務的80端口等。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套接字地址格式
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個字節。其中sin6_addr升級爲128位,完全解決了尋址地址不夠的問題。
本地套接字地址格式
struct sockaddr_un {
unsigned short sun_family; /* 固定爲 AF_LOCAL */
char sun_path[108]; /* 路徑名 */
};
本地套接字主要用於本地的進程間通信,如前面提到的AF_LOCAL.
幾種套接字地址格式的比較
幾種套接字的格式比較如下,其中ipv4和ipv6的套接字地址結構的長度是固定的,而本地套接字地址結構的長度是可變的。