什么是Socket
Socket(套接字)是介于應用層和傳輸層之間(圖1)的應用程序編程接口。多數應用程序是由通信進程對組成,每對中的兩個進程互相發送報文,從一個進程向另一個進程發送的報文必須通過下面的網絡。進程通過Socket(套接字)軟件接口向網絡發送報文和從網絡接收報文。做個簡單的類比,進程可類比于一座房子,而它的套接字可以類比于它的門。當一個進程向位于另外一臺主機上的另一個進程發送報文時,它把報文推送出該門(套接字)。該發送進程假定該門到另外一側之間有運輸的基礎設施,該設施將把報文傳送到目的進程的門口。一旦該報文抵達目的主機,它通過進程的門(套接字)傳遞,然后接收進程對該報文進行處理。
幾種典型的Socket
1.Berkeley UNIX 操作系統定義了一種 API,稱為套接字接口(socket interface),簡稱套接字(socket)。
2.微軟公司在其操作系統中采用了套接字接口 API,形成了一個稍有不同的 API,并稱之為Windows Socket Interface,WINSOCK。
3.AT&T 為其 UNIX 系統 V 定義了一種 API,簡寫為 TLI (Transport Layer Interface)。
(本文以WINSOCK為例)
客戶端Socket API函數
TCP客戶端軟件流程
1. 確定服務器IP地址與端口號
2. 創建套接字
3. 分配本地端點地址(IP地址+端口號)(此步驟由操作系統完成)
4. 連接服務器(套接字)
5. 遵循應用層協議進行通信
6. 關閉/釋放連接
UDP客戶端軟件流程
1. 確定服務器IP地址與端口號
2. 創建套接字
3. 分配本地端點地址(IP地址+端口號)(此步驟由操作系統完成)
4. 指定服務器端點地址,構造UDP數據報
5. 遵循應用層協議進行通信
6. 關閉/釋放套接字
使用Socket的應用程序在使用Socket之前必須首先調用WSAStartup函數初始化Windows Sockets API
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
其中第一個參數指明程序請求使用的WinSock版本,其中高位字節指明副版本、低位字節指明主版本,第二個參數返回實際的WinSock的版本信息
int WSACleanup (void);
應用程序在完成對請求的Socket庫的使用,最后要調用WSACleanup函數解除與Socket庫的綁定,釋放Socket庫所占用的系統資源
創建socket
socket?sd = socket(protofamily,type,proto);
操作系統返回套接字描述符(sd),其中第一個參數(協議族): protofamily = PF_INET(TCP/IP),第二個參數(套接字類型):type = SOCK_STREAM,SOCK_DGRAM or SOCK_RAW(TCP/IP),第三個參數(協議號):0為默認
SOCKET sd=socket(PF_INET,SOCK_STREAM,0); (TCP)
SOCKET sd=socket(PF_INET,SOCK_DGRAM,0); (UDP)
TCP:可靠、面向連接、字節流傳輸、點對點
UDP:不可靠、無連接、數據報傳輸
int bind(sd,localaddr,addrlen);
綁定套接字的本地端點地址,IP地址+端口號,客戶程序一般不必調用bind函數
connect(sd,saddr,saddrlen);
客戶程序調用connect函數來使客戶套接字(sd)與特定計算機的特定端口(saddr)的套接字(服務)進行連接,connect函數僅用于客戶端,可用于TCP客戶端也可以用于UDP客戶端。
TCP客戶端:建立TCP連接
?UDP客戶端:指定服務器端點地址
int closesocket(SOCKET sd);
關閉一個描述符為sd的套接字,如果多個進程共享一個套接字,調用closesocket將套接字引用計數減1,減至0才關閉。如果一個進程中的多線程對一個套接字的使用無計數,當進程中的一個線程調用closesocket將一個套接字關閉,該進程中的其他線程也將不能訪問該套接字
服務端Socket API函數
服務端在創建完socket后也需要綁定本地地址,一般使用地址通配符:INADDR_ANY
int listen(sd,queuesize);
置服務器端的流套接字處于監聽狀態,僅服務器端調用面向TCP的scocket,設置連接請求隊列大小(queuesize)
newsock = accept(sd,caddr,caddrlen);
服務程序調用accept函數從處于監聽狀態的流套接字sd的客戶連接請求隊列中取出排在最前的一個客戶請求,并且創建一個新的套接字來與客戶套接字創建連接通道
send(sd,*buf,len,flags);
sendto(sd,*buf,len,flags,destaddr,addrlen);
send函數用于TCP套接字(客戶與服務器)或調用了connect函數的UDP客戶端套接字。sendto函數用于UDP服務器端套接字與未調用connect函數的UDP客戶端套接字
recv(sd,*buffer,len,flags);
recvfrom(sd,*buf,len,flags,senderaddr,saddrlen);
recv函數從TCP連接的另一端接收數據,或者從調用了connect函數的UDP客戶端套接字接收服務器發來的數據。recvfrom函數用于從UDP服務器端套接字與未調用connect函數的UDP客戶端套接字接收對端數據。
int setsockopt(int sd, int level, int optname, *optval, int optlen);
int getsockopt(int sd, int level, int optname, *optval, socklen_t *optlen);
setsockopt()函數用來設置套接字sd的選項參數。getsockopt()函數用于獲取任意類型、任意狀態套接口的選項當前值,并把結果存入optval。
網絡字節順序
1.TCP/IP定義了標準的用于協議頭中的二進制整數表示:網絡字節順序(network byte order)
2.某些Socket API函數的參數需要存儲為網絡字節順序(如IP地址、端口號等)
3.可以實現本地字節順序與網絡字節順序間轉換的函數
htons: 本地字節順序→網絡字節順序(16bits)
ntohs: 網絡字節順序→本地字節順序(16bits)
htonl: 本地字節順序→網絡字節順序(32bits)
ntohl: 網絡字節順序→本地字節順序(32bits)
4種類型基本服務器
1.循環無連接(Iterative connectionless)服務器
2.循環面向連接(Iterative connection-oriented)服務器
3.并發無連接(Concurrent connectionless)服務器
4.并發面向連接(Concurrent connection-oriented)服務器
循環無連接服務器基本流程
1. 創建套接字
2. 綁定端點地址(INADDR_ANY+端口號)
3. 反復接收來自客戶端的請求
4. 遵循應用層協議,構造響應報文,發送給客戶
服務器端不能使用connect()函數,無連接服務器使用sendto()函數發送數據報
調用recvfrom()函數接收數據時,自動提取客戶端點地址
循環面向連接服務器基本流程
1.創建(主)套接字,并綁定熟知端口號;
2.設置(主)套接字為被動監聽模式,準備用于服務器;
3.調用accept()函數接收下一個連接請求(通過主套接字),創建新套接字用于與該客戶建立連接;
4.遵循應用層協議,反復接收客戶請求,構造并發送響應(通過新套接字);
5.完成為特定客戶服務后,關閉與該客戶之間的連接,返回步驟3.
并發無連接服務器基本流程
主線程1: 創建套接字,并綁定熟知端口號;
主線程2: 反復調用recvfrom()函數,接收下一個 客戶請求,并創建新線程處理該客戶響應;
子線程1: 接收一個特定請求;
子線程2: 依據應用層協議構造響應報文,并調用sendto()發送;
子線程3: 退出(一個子線程處理一個請求后即終止)。
并發面向連接服務器基本流程
主線程1: 創建(主)套接字,并綁定熟知端口號;
主線程2: 設置(主)套接字為被動監聽模式,準備用于服務器;
主線程3: 反復調用accept()函數接收下一個連接請求(通過主套接字),并創建一個新 的子線程處理該客戶響應;
子線程1: 接收一個客戶的服務請求(通過新創建的套接字);
子線程2: 遵循應用層協議與特定客戶進行交互;子線程3: 關閉/釋放連接并退出(線程終止).