socket模型創(chuàng)建流程圖
socket函數(shù)
#include?<sys/types.h>?/* See NOTES */
#include?<sys/socket.h>
int?socket(int?domain,?int?type,?int?protocol);
domain:
AF_INET 這是大多數(shù)用來產(chǎn)生socket的協(xié)議,使用TCP或UDP來傳輸,用IPv4的地址。
AF_INET6 與上面類似,不過是來用IPv6的地址。
AF_UNIX 本地協(xié)議,使用在Unix和Linux系統(tǒng)上,一般都是當客戶端和服務器在同一臺及其上的時候使用。
type:
SOCK_STREAM 這個協(xié)議是按照順序的、可靠的、數(shù)據(jù)完整的基于字節(jié)流的連接。這是一個使用最多的socket類型,這個socket是使用TCP來進行傳輸。
SOCK_DGRAM 這個協(xié)議是無連接的、固定長度的傳輸調用。該協(xié)議是不可靠的,使用UDP來進行它的連接。
SOCK_SEQPACKET該協(xié)議是雙線路的、可靠的連接,發(fā)送固定長度的數(shù)據(jù)包進行傳輸。必須把這個包完整的接受才能進行讀取。
SOCK_RAW socket類型提供單一的網(wǎng)絡訪問,這個socket類型使用ICMP公共協(xié)議。(ping、traceroute使用該協(xié)議)
SOCK_RDM 這個類型是很少使用的,在大部分的操作系統(tǒng)上沒有實現(xiàn),它是提供給數(shù)據(jù)鏈路層使用,不保證數(shù)據(jù)包的順序。
protocol:
傳0 表示使用默認協(xié)議。
返回值:
成功:返回指向新創(chuàng)建的socket的文件描述符,失敗:返回-1,設置errno。
socket()打開一個網(wǎng)絡通訊端口,如果成功的話,就像open()一樣返回一個文件描述符,應用程序可以像讀寫文件一樣用read/write在網(wǎng)絡上收發(fā)數(shù)據(jù),如果socket()調用出錯則返回-1。對于IPv4,domain參數(shù)指定為AF_INET。對于TCP協(xié)議,type參數(shù)指定為SOCK_STREAM,表示面向流的傳輸協(xié)議。如果是UDP協(xié)議,則type參數(shù)指定為SOCK_DGRAM,表示面向數(shù)據(jù)報的傳輸協(xié)議。protocol參數(shù)的介紹從略,指定為0即可。
bind函數(shù)
#include?<sys/types.h>?/* See NOTES */
#include?<sys/socket.h>
int?bind(int?sockfd,?const?struct?sockaddr *addr,?socklen_t?addrlen);
sockfd:
socket文件描述符。
addr:
構造出IP地址加端口號。
addrlen:
sizeof(addr)長度。
返回值:
成功返回0,失敗返回-1, 設置errno。
服務器程序所監(jiān)聽的網(wǎng)絡地址和端口號通常是固定不變的,客戶端程序得知服務器程序的地址和端口號后就可以向服務器發(fā)起連接,因此服務器需要調用bind綁定一個固定的網(wǎng)絡地址和端口號。
bind()的作用是將參數(shù)sockfd和addr綁定在一起,使sockfd這個用于網(wǎng)絡通訊的文件描述符監(jiān)聽addr所描述的地址和端口號。前面講過,struct sockaddr *是一個通用指針類型,addr參數(shù)實際上可以接受多種協(xié)議的sockaddr結構體,而它們的長度各不相同,所以需要第三個參數(shù)addrlen指定結構體的長度。如:
struct?sockaddr_in servaddr;
bzero(&servaddr,?sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(6666);
首先將整個結構體清零,然后設置地址類型為AF_INET,網(wǎng)絡地址為INADDR_ANY,這個宏表示本地的任意IP地址,因為服務器可能有多個網(wǎng)卡,每個網(wǎng)卡也可能綁定多個IP地址,這樣設置可以在所有的IP地址上監(jiān)聽,直到與某個客戶端建立了連接時才確定下來到底用哪個IP地址,端口號為6666。
listen函數(shù)
#include?<sys/types.h>?/* See NOTES */
#include?<sys/socket.h>
int?listen(int?sockfd,?int?backlog);
sockfd:
socket文件描述符。
backlog:
排隊建立3次握手隊列和剛剛建立3次握手隊列的鏈接數(shù)和。
查看系統(tǒng)默認backlog
cat?/proc/sys/net/ipv4/tcp_max_syn_backlog
典型的服務器程序可以同時服務于多個客戶端,當有客戶端發(fā)起連接時,服務器調用的accept()返回并接受這個連接,如果有大量的客戶端發(fā)起連接而服務器來不及處理,尚未accept的客戶端就處于連接等待狀態(tài),listen()聲明sockfd處于監(jiān)聽狀態(tài),并且最多允許有backlog個客戶端處于連接待狀態(tài),如果接收到更多的連接請求就忽略。listen()成功返回0,失敗返回-1。
accept函數(shù)
#include?<sys/types.h>? ? ?/* See NOTES */
#include?<sys/socket.h>
int?accept(int?sockfd,?struct?sockaddr *addr,?socklen_t?*addrlen);
sockdf:
socket文件描述符。
addr:
傳出參數(shù),返回鏈接客戶端地址信息,含IP地址和端口號。
addrlen:
傳入傳出參數(shù)(值-結果),傳入sizeof(addr)大小,函數(shù)返回時返回真正接收到地址結構體的大小。
返回值:
成功返回一個新的socket文件描述符,用于和客戶端通信,失敗返回-1,設置errno
三方握手完成后,服務器調用accept()接受連接,如果服務器調用accept()時還沒有客戶端的連接請求,就阻塞等待直到有客戶端連接上來。addr是一個傳出參數(shù),accept()返回時傳出客戶端的地址和端口號。addrlen參數(shù)是一個傳入傳出參數(shù)(value-result argument),傳入的是調用者提供的緩沖區(qū)addr的長度以避免緩沖區(qū)溢出問題,傳出的是客戶端地址結構體的實際長度(有可能沒有占滿調用者提供的緩沖區(qū))。如果給addr參數(shù)傳NULL,表示不關心客戶端的地址。
我們的服務器程序結構是這樣的:
while?(1) {
cliaddr_len = sizeof(cliaddr);
connfd =?accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
n =?read(connfd, buf, MAXLINE);
......
close(connfd);
}
整個是一個while死循環(huán),每次循環(huán)處理一個客戶端連接。由于cliaddr_len是傳入傳出參數(shù),每次調用accept()之前應該重新賦初值。accept()的參數(shù)listenfd是先前的監(jiān)聽文件描述符,而accept()的返回值是另外一個文件描述符connfd,之后與客戶端之間就通過這個connfd通訊,最后關閉connfd斷開連接,而不關閉listenfd,再次回到循環(huán)開頭listenfd仍然用作accept的參數(shù)。accept()成功返回一個文件描述符,出錯返回-1。
connect函數(shù)
#include?<sys/types.h>? ? ? ? ? ?/* See NOTES */
#include?<sys/socket.h>
int?connect(int?sockfd,?const?struct?sockaddr *addr,?socklen_t?addrlen);
sockdf:
socket文件描述符。
addr:
傳入?yún)?shù),指定服務器端地址信息,含IP地址和端口號。
addrlen:
傳入?yún)?shù),傳入sizeof(addr)大小。
返回值:
成功返回0,失敗返回-1,設置errno。
客戶端需要調用connect()連接服務器,connect和bind的參數(shù)形式一致,區(qū)別在于bind的參數(shù)是自己的地址,而connect的參數(shù)是對方的地址。connect()成功返回0,出錯返回-1。