socket經常使用接口

socket核心定義:html

  socket是用來讓不一樣電腦之間,不一樣進程之間互相通信的一套接口;是在應用層和傳輸層之間的一個抽象層,它把TCP/IP層複雜的操做抽象爲幾個簡單的接口供應用層調用已實現進程在網絡中通訊。程序員

  socket起源於UNIX,在Unix一切皆文件哲學的思想下,socket是一種"打開—讀/寫—關閉"模式的實現,服務器和客戶端各自維護一個"文件",在創建鏈接打開後,能夠向本身文件寫入內容供對方讀取或者讀取對方內容,通信結束時關閉文件。緩存

 

 

 

socket支持TCP和UDP兩種協議:服務器

TCP:對應流式socket,面向鏈接,即時刻保持鏈接,數據以流形式傳輸。網絡

UDP:對應數據報式socket,無鏈接,數據中途可能丟失,但速度更快。數據結構

 

經常使用接口:併發

socket():建立socketdom

bind():綁定socket到本地地址和端口,一般由服務端調用socket

listen():TCP專用,開啓監聽模式函數

accept():TCP專用,服務器等待客戶端鏈接,通常是阻塞態

connect():TCP專用,客戶端主動鏈接服務器

send():TCP專用,發送數據

recv():TCP專用,接收數據

sendto():UDP專用,發送數據到指定的IP地址和端口

recvfrom():UDP專用,接收數據,返回數據遠端的IP地址和端口

closesocket():關閉socket

                                           

 

 

 

 

2.1 socket()

原型:int socket (int domain, int type, int protocol)

功能描述:初始化建立socket對象,一般是第一個調用的socket函數。 成功時,返回非負數的socket描述符;失敗是返回-1。socket描述符是一個指向內部數據結構的指針,它指向描述符表入口。調用socket()函數時,socket執行體將創建一個socket,實際上"創建一個socket"意味着爲一個socket數據結構分配存儲空間。socket執行體爲你管理描述符表。

參數解釋:

domain -- 指明使用的協議族。經常使用的協議族有,AF_INET、AF_INET六、AF_LOCAL(或稱AF_UNIX,Unix域socket)、AF_ROUTE等等。協議族決定了socket的地址類型,在通訊中必須採用對應的地址,如AF_INET決定了要用ipv4地址(32位的)與端口號(16位的)的組合、AF_UNIX決定了要用一個絕對路徑名做爲地址。須要記住127.0.0.1,它是一個特殊IP地址,表示本機地址

type -- 指明socket類型,有3種:

                                                 SOCK_STREAM -- TCP類型,保證數據順序及可靠性;

                                                 SOCK_DGRAM --  UDP類型,不保證數據接收的順序,非可靠鏈接;

                                                 SOCK_RAW -- 原始類型,容許對底層協議如IP或ICMP進行直接訪問,不太經常使用。

protocol -- 一般賦值"0",由系統自動選擇。

2.2 bind()

原型:int bind(int sockfd, const struct sockaddr* myaddr, socklen_t addrlen)

功能描述:將建立的socket綁定到指定的IP地址和端口上,一般是第二個調用的socket接口。返回值:0 -- 成功,-1 -- 出錯。當socket函數返回一個描述符時,只是存在於其協議族的空間中,並無分配一個具體的協議地址(這裏指IPv4/IPv6和端口號的組合),bind函數能夠將一組固定的地址綁定到sockfd上。

一般服務器在啓動的時候都會綁定一個衆所周知的協議地址,用於提供服務,客戶就能夠經過它來接連服務器;而客戶端能夠指定IP或端口也能夠都不指定,未分配則系統自動分配。這就是爲何一般服務器端在listen以前會調用bind(),而客戶端就不會調用,而是在connect()時由系統隨機生成一個。

注意:

(1) 若是有多個可用的鏈接(多個IP),內核會根據優先級選擇一個IP做爲源IP使用。

(2) 若是socket使用bind綁定到特定的IP和port,則不管是TCP仍是UDP,都會從指定的IP和port發送數據。

參數解釋:

sockfd -- socket()函數返回的描述符;

myaddr -- 指明要綁定的本地IP和端口號,使用網絡字節序,即大端模式(詳見3.1)。

addrlen -- 常被設置爲sizeof(struct sockaddr)。

能夠利用下邊的賦值語句,自動綁定本地IP地址和隨機端口:

my_addr.sin_port = 0; /* 系統隨機選擇一個未被使用的端口號 */ my_addr.sin_addr.s_addr = INADDR_ANY; /* 填入本機IP地址 */ 

另外要注意的是,當調用函數時,通常不要將端口號置爲小於1024的值,由於1~1024是保留端口號,你可使用大於1024中任何一個沒有被佔用的端口號。

2.3 listen()

原型:int listen(int sockfd, int backlog)

功能描述:listen()函數僅被TCP類型的服務器程序調用,實現監聽服務,所謂被動監聽,是指當沒有客戶端請求時,套接字處於「睡眠」狀態,只有當接收到客戶端請求時,套接字纔會被「喚醒」來響應請求。

請求隊列
當套接字正在處理客戶端請求時,若是有新的請求進來,套接字是無法處理的,只能把它放進緩衝區,待當前請求處理完畢後,再從緩衝區中讀取出來處理。若是不斷有新的請求進來,它們就按照前後順序在緩衝區中排隊,直到緩衝區滿。這個緩衝區,就稱爲請求隊列(Request Queue)。

緩衝區的長度(能存放多少個客戶端請求)能夠經過 listen() 函數的 backlog 參數指定,但究竟爲多少並無什麼標準,能夠根據你的需求來定,併發量小的話能夠是10或者20。

當請求隊列滿時,就再也不接收新的請求,對於 Linux,客戶端會收到 ECONNREFUSED 錯誤

注意:listen() 只是讓套接字處於監聽狀態,並無接收請求。接收請求須要使用 accept() 函數。當套接字處於監聽狀態時,能夠經過 accept() 函數來接收客戶端請求。

listen()成功時返回0,錯誤時返回-1。

參數解釋:

sockfd -- socket()函數返回的描述符;

backlog -- 指定內核爲此套接字維護的最大鏈接個數,包括「未完成鏈接隊列--未完成3次握手」、「已完成鏈接隊列--已完成3次握手,創建鏈接」。大多數系統缺省值爲20。

2.4 accept()

原型: int accept (int sockfd, struct sockaddr *addr, socklen_t *addrlen)

功能描述:accept()函數僅被TCP類型的服務器程序調用,從已完成鏈接隊列返回下一個創建成功的鏈接,若是已完成鏈接隊列爲空,線程進入阻塞態睡眠狀態。成功時返回套接字描述符,錯誤時返回-1。

若是accpet()執行成功,返回由內核自動生成的一個全新socket描述符,用它引用與客戶端的TCP鏈接。一般咱們把accept()第一個參數成爲監聽套接字(listening socket),把accept()功能返回值成爲已鏈接套接字(connected socket)。一個服務器一般只有1個監聽套接字,監聽客戶端的鏈接請求;服務器內核爲每個客戶端的TCP鏈接維護1個已鏈接套接字,用它實現數據雙向通訊。

最後須要說明的是:listen() 只是讓套接字進入監聽狀態,並無真正接收客戶端請求,listen() 後面的代碼會繼續執行,直到遇到 accept()。accept() 會阻塞程序執行(後面代碼不能被執行),直到有新的請求到來。

參數解釋:

sockfd -- socket()函數返回的描述符;

addr -- 輸出一個的sockaddr_in變量地址,該變量用來存放發起鏈接請求的客戶端的協議地址;

addrten -- 做爲輸入時指明緩衝器的長度,做爲輸出時指明addr的實際長度。

2.5 connetct()

原型: int connect(int sockfd, struct sockaddr *serv_addr, int addrlen)

功能描述:connect()一般由TCP類型客戶端調用,用來與服務器創建一個TCP鏈接,實際是發起3次握手過程,鏈接成功返回0,鏈接失敗返回1。

注意:

(1) 能夠在UDP鏈接使用使用connect(),做用是在UDP套接字中記住目的地址和目的端口。
(2) UDP套接字使用connect後,若是數據報不是connect中指定的地址和端口,將被丟棄。沒有調用connect的UDP套接字,將接收全部到達這個端口的UDP數據報,而不區分源端口和地址。

參數解釋:

sockfd -- 本地客戶端額socket描述符;

serv_addr -- 服務器協議地址;

addrlen -- 地址緩衝區的長度。

2.6 send()

原型:int send(int sockfd, const void *msg, int len, int flags)

功能描述:TCP類型的數據發送。

每一個TCP套接口都有一個發送緩衝區,它的大小能夠用SO_SNDBUF這個選項來改變。調用send函數的過程,實際是內核將用戶數據拷貝至TCP套接口的發送緩衝區的過程:並不當即向網絡中傳輸數據,而是先將數據寫入緩衝區中,再由TCP協議將數據從緩衝區發送到目標機器。一旦將數據寫入到緩衝區,函數就能夠成功返回,無論它們有沒有到達目標機器,也無論它們什麼時候被髮送到網絡,這些都是TCP協議負責的事情。

TCP協議獨立於 write()/send() 函數,數據有可能剛被寫入緩衝區就發送到網絡,也可能在緩衝區中不斷積壓,屢次寫入的數據被一次性發送到網絡,這取決於當時的網絡狀況、當前線程是否空閒等諸多因素,不禁程序員控制。

若len大於發送緩衝區大小,則返回-1;不然,查看緩衝區剩餘空間是否容納得下要發送的len長度,若不夠,則拷貝一部分,並返回拷貝長度(指的是非阻塞send,若爲阻塞send,則必定等待全部數據拷貝至緩衝區才返回,所以阻塞send返回值一定與len相等);若緩衝區滿,則等待發送,有剩餘空間後拷貝至緩衝區;若在拷貝過程出現錯誤,則返回-1。關於錯誤的緣由,查看errno的值。

若是send在等待協議發送數據時出現網絡斷開的狀況,則會返回-1。注意:send成功返回並不表明對方已接收到數據,若是後續的協議傳輸過程當中出現網絡錯誤,下一個send便會返回-1發送錯誤。TCP給對方的數據必須在對方給予確認時,方可刪除發送緩衝區的數據。不然,會一直緩存在緩衝區直至發送成功(TCP可靠數據傳輸決定的)。

參數解釋:

sockfd -- 發送端套接字描述符(非監聽描述符)。

msg -- 待發送數據的緩衝區。

len -- 待發送數據的字節長度。

flags -- 通常狀況下置爲0。

2.7 recv()

原型:int recv(int sockfd, void *buf, int len, unsigned int flags)

功能描述:TCP類型的數據接收。

recv()從接收緩衝區拷貝數據。成功時,返回拷貝的字節數,失敗返回-1。阻塞模式下,recv/recvfrom將會阻塞到緩衝區裏至少有一個字節(TCP)/至少有一個完整的UDP數據報才返回,沒有數據時處於休眠狀態。若非阻塞,則當即返回,有數據則返回拷貝的數據大小,不然返回錯誤-1。

參數解釋:

sockefd -- 接收端套接字描述符(非監聽描述符);

buf -- 接收緩衝區的基地址;

len -- 以字節計算的接收緩衝區長度;

flags -- 通常狀況下置爲0。

2.8 sendto()

原型:int sendto(int sockfd, const void *msg, int len, unsigned int flags, const struct sockaddr *dst_addr, int addrlen)

功能描述:用於非可靠鏈接(UDP)的數據發送,由於UDP方式未創建鏈接socket,所以須要制定目的協議地址。

當本地與不一樣目的地址通訊時,只需指定目的地址,可以使用同一個UDP套接口描述符sockfd,而TCP要預先創建鏈接,每一個鏈接都會產生不一樣的套接口描述符,體如今:客戶端要使用不一樣的fd進行connect,服務端每次accept產生不一樣的fd。

由於UDP沒有真正的發送緩衝區,由於是不可靠鏈接,沒必要保存應用進程的數據拷貝,應用進程中的數據在沿協議棧向下傳遞時,以某種形式拷貝到內核緩衝區,當數據鏈路層把數據傳出後就把內核緩衝區中數據拷貝刪除。所以它不須要一個發送緩衝區。寫UDP套接口的sendto/write返回表示應用程序的數據或數據分片已經進入鏈路層的輸出隊列,若是輸出隊列沒有足夠的空間存放數據,將返回錯誤ENOBUFS.

參數解釋:

sockfd -- 發送端套接字描述符(非監聽描述符);

msg -- 待發送數據的緩衝區;

len -- 待發送數據的字節長度;

flags -- 通常狀況下置爲0;

dst_addr -- 數據發送的目的地址;

addrlen -- 地址長度。

2.9 recvfrom()

原型:int recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, int*fromlen)

功能描述:用於非可靠鏈接(UDP)的數據接收。

參數解釋:

sockfd -- 接收端套接字描述;

buf -- 用於接收數據的應用緩衝區地址;

len -- 指名緩衝區大小;

flags -- 一般爲0;

src_addr -- 數據來源端的地址;

fromlen -- 做爲輸入時,fromlen常置爲sizeof(struct sockaddr);當輸出時,fromlen包含實際存入buf中的數據字節數。

 

代碼參考

服務端;

#include "stdafx.h" #include <stdio.h> #include <winsock2.h> #pragma comment(lib,"ws2_32.lib") int main(int argc, char* argv[]) { //初始化WSA WORD sockVersion = MAKEWORD(2,2); WSADATA wsaData; if(WSAStartup(sockVersion, &wsaData)!=0) { return 0; } //建立套接字 SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if(slisten == INVALID_SOCKET) { printf("socket error !"); return 0; } //綁定IP和端口  sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_port = htons(8888); sin.sin_addr.S_un.S_addr = INADDR_ANY; if(bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR) { printf("bind error !"); } //開始監聽 if(listen(slisten, 5) == SOCKET_ERROR) { printf("listen error !"); return 0; } //循環接收數據  SOCKET sClient; sockaddr_in remoteAddr; int nAddrlen = sizeof(remoteAddr); char revData[255]; while (true) { printf("等待鏈接...\n"); sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen); if(sClient == INVALID_SOCKET) { printf("accept error !"); continue; } printf("接受到一個鏈接:%s \r\n", inet_ntoa(remoteAddr.sin_addr)); //接收數據 int ret = recv(sClient, revData, 255, 0); if(ret > 0) { revData[ret] = 0x00; printf(revData); } //發送數據 char * sendData = "你好,TCP客戶端!\n"; send(sClient, sendData, strlen(sendData), 0); closesocket(sClient); } closesocket(slisten); WSACleanup(); return 0; }

客戶端:

#include "stdafx.h" #include <WINSOCK2.H> #include <STDIO.H> #pragma comment(lib,"ws2_32.lib") int main(int argc, char* argv[]) { WORD sockVersion = MAKEWORD(2,2); WSADATA data; if(WSAStartup(sockVersion, &data) != 0) { return 0; } SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if(sclient == INVALID_SOCKET) { printf("invalid socket !"); return 0; } sockaddr_in serAddr; serAddr.sin_family = AF_INET; serAddr.sin_port = htons(8888); serAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); if (connect(sclient, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR) { printf("connect error !"); closesocket(sclient); return 0; } char * sendData = "你好,TCP服務端,我是客戶端!\n"; send(sclient, sendData, strlen(sendData), 0); char recData[255]; int ret = recv(sclient, recData, 255, 0); if(ret > 0) { recData[ret] = 0x00; printf(recData); } closesocket(sclient); WSACleanup(); return 0; }

參考資料:http://www.javashuo.com/article/p-wieubqux-mx.html

相關文章
相關標籤/搜索