Linux Network Programming —— Socket
——by firo 2011.5.2
Cherish the memory of the Master —— W. Richard Stevens
Reference
man
Google
Understanding The Linux Kernel 3e 【ULK 3e】
Professional Linux Kernel Architecture 【PLKA】
Computer Systems: A Programmer‘s Perspective 2e 【CS:APP 2e】
Advanced Programming in the Unix Environment Second Edition 【APUE 2e】
Unix Network Programming The Sockets Networking API VOLUME 1 Third Edition 【UNPv1 3e】
The Linux Programming inTerface A Linux and UNIX System Programming Handbook 【TLPI】
卷首語
Sockets are a method of IPC that allow data to be exchanged between applications,either on the same host (computer) or on different hosts connected by a network.
——by Michael Kerrisk author of TLPI
what is Socket?
【1】Socket: An interface between an application process and transport layer.
【2】In Unix jargon, a socket is a file descriptor an integer associated with an open file.
【3】A socket is one endpoint of a two-way communication link between two programs running on the network. A socket is bound to a port number so that the TCP layer can identify the application that data is destined to be sent.
【4】In computer networking, an Internet socket or network socket is an endpoint of a bidirectional inter-process communication flow across an Internet Protocol-based computer network, such as theInternet.
int socket(int domain, int type, int protocol);
socket()的三個參數【1】domain:地址族,用來指定Socket對應的本機地址格式和Socket使用的領域、範圍比如PF_PACKET和AF_UNIX都是用於本機資料轉送,而AF_INET和AF_INET6是通過Internet進行資料轉送的。【2】type:Socket類型SOCK_STREAM or SOCK_DGRAM等;結合地址族用來表明這個Socke的用途。【3】protocol:資料轉送遵守的協議、規則,如IPPROTO_TCP,IPPROTO_UDP等。更多描述man 2 socket。
socket()通過調用sys_socket系統調用為我們建立了socket資料結構,該結構維護著主機或進程間進行資料傳遞操作需要的參數;對編程重要的成員有【1】網路檔案系統sockfs的i-node,由kernel建立;通過返回的file descriptor 檔案描述符關聯,我們可以對這個Socket進行操作:read、write。【2】本地主機的IP地址,由參數domain指定具體格式,由bind()系統調用初始化為具體的值。【3】Socket類型,由參數type初始化。【4】資料轉送協議,由參數protocol指定。【5】地址族,有domain指定。
通用Socket地址結構——struct sockaddr
因為不同的domain域決定了不同Socket的地址格式,而系統調用又要為所有的應用提供一致,統一的Interface,這就產生了矛盾;所以通過使用通用地址結構消除不同的Socket地址結構對系統調用的影響,使系統調用不特定於某一個地址結構。你想用這些系統調用,好啊,先轉化成我規定的通用結構struct sockaddr * 吧~~你可能會說,我通過void *指標也能實現呀何必再定義一個結構呢,但是,在1982年那會兒,ANSI C 的 void * 還沒出生~~
Byte order位元組序
你需要記住:Big endian大端、Network byte order網路位元組序、人書寫和識別多位元組類型的變數,他們三個順序是一致的:都是最高有效位的位元組在最初的地方,即最低地址,自己體會吧~~剩下的就是Linux的Little endian了~POSIX規定我們的TCP或UDP分組 的協議頭部中目標主機地址和本機地址,統一使用大端網路位元組序的儲存方式~~同樣地址結構中標識進程的連接埠port也應轉為網路位元組序使得TCP協議棧正確識別。淺談位元組序(Byte Order)及其相關操作 那麼,我們要通過網路在可能位元組序不一致的OS系統傳輸位元據呢?有兩種解決辦法【1】轉化為字串文字格式設定【2】顯示對傳輸資料的位元組序進行說明。需要注意,因為文本中的字元是不存在這種位元組序問題的:ASCII是單位元組編排每個字元的,自然不會出現多個位元組間的位元組序問題;而像UTF-8,UTF-16這樣的字元編碼集中有一位用來顯示指定資料使用的位元組序~~~
為什麼connect()失敗後就不能再重連呢?這真的是個問題,有待解決。
Google的結果是connect()失敗後Socket的狀態是未知的,再次connect()可能產生錯誤結果,需close後重建Socket重連~~不成功則成仁,硬,夠硬!硬不硬以後再說~~
什麼是TCP連接埠/UDP連接埠?
Port連接埠在TCP/IP協議棧上,可以看成一個資料結構和一個緩衝區。它用來標明一種服務類型只要你的應用提供這種服務,你就可以和Kernel申請佔用這個Port(調用bind()系統調用);比如大家最熟悉的服務是web網頁瀏覽服務,對應的Port連接埠號碼是80,像Tomcat和Apache這樣的Web 應用程式伺服器配置軟體都可以佔用這個連接埠;請記住【1】一個Port連接埠在同一時間只能被一個應用(進程或線程)監聽。在Unix(BSD)上可以設定SO_REUSEPORT這個Socket設定選項使多個Process進程或線程 ,同時監聽同一個Port連接埠,但是這會引發混亂問題:TCP/IP協議棧不能正確的識別並傳送資料給正確的進程;為了消除這個問題Linux上不支援SO_REUSEPORT這個Socket 選型,我在Ubuntu10.10上沒有這個常量的定義;【2】父子進程或多個線程之間同時可以使用 同一個Port來標註不同的進程或線程,而不會出現混亂:原因是Socket通訊的兩個進程或線程是通過Socket地址對(唯一的!)進行標識的。對於第二點UNPv2 2.9和2.10節有精彩的論述,貼一張UNPv2的原圖協助理解,實在不能理解看那就看UNPv2吧:
UNIX Domain Socket
講檔案系統的路徑名Bind 到Unix domain Socket 作為地址時,請注意:
【1】不能將已經存在的檔案路徑名bind到Unix domain Socket【2】為了不打麻煩,最好bind絕對檔案路徑名,你們懂的~【3】bind的檔案路徑名和Socket是Injective function一一映射的 【4】open()不能用於Unix domain Socket【5】在操作結束後,應該顯示的調用unlink() or remove()刪除建立的檔案。
The Linux Abstract Socket Namespace【LASN】——linux虛擬Socket命名空間
LASN提供了一種新的機制來建立UNIX Domain Socket 的地址,先來說說LASN的有點【1】不用擔心建立的檔案存在的問題~【2】不需要顯示的刪除建立的檔案,當Socket關閉時這個名字自動被刪除~【3】不方便讀寫檔案系統時,也可以使用Unix域Socket~~使用起來非常簡單,就是在檔案的正式名字前加上一個NULL,其他的不變,這是TLPI的代碼:
struct sockaddr_un addr;<br />memset(&addr, 0, sizeof(struct sockaddr_un)); /* Clear address structure */<br />addr.sun_family = AF_UNIX; /* UNIX domain address */<br />/* addr.sun_path[0] has already been set to 0 by memset() */<br />strncpy(&addr.sun_path[1], "xyz", sizeof(addr.sun_path) - 2);<br />/* Abstract name is "xyz" followed by null bytes */<br />sockfd = socket(AF_UNIX, SOCK_STREAM, 0);<br />if (sockfd == -1)<br /> errExit("socket");<br />if (bind(sockfd, (struct sockaddr *) &addr,sizeof(struct sockaddr_un)) == -1)<br /> errExit("bind");
Internet Domain Socket