C++ Socket通訊總結(附C++實現)
因為專案需要,服務端需要一個SOCKET來接收客戶端的請求,好吧,沒辦法度娘哇,結果很多都是linux的例子,功夫不負有心人啊,終於找到個demo,並且客戶端程式碼詳盡,記錄之,以便以後檢視。
一、Socket是什麼
Socket是應用層與TCP/IP協議族通訊的中間軟體抽象層,它是一組介面。在設計模式中,Socket其實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在Socket介面後面,對使用者來說,一組簡單的介面就是全部,讓Socket去組織資料,以符合指定的協議。
二、一些基本概念
-
TCP/IP
TCP/IP是個協議組,可分為三個層次:網路層、傳輸層和應用層。
在網路層有IP協議、ICMP協議、ARP協議、RARP協議和BOOTP協議。
在傳輸層中有TCP協議與UDP協議。
在應用層有:TCP包括FTP、HTTP、TELNET、SMTP等協議
UDP包括DNS、TFTP等協議 -
短連線:
連線->傳輸資料->關閉連線
HTTP是無狀態的,瀏覽器和伺服器每進行一次HTTP操作,就建立一次連線,但任務結束就中斷連線。也可以這樣說:短連線是指SOCKET連線後傳送後接收完資料後馬上斷開連線。 -
長連線:
連線->傳輸資料->保持連線 -> 傳輸資料-> 。。。 ->關閉連線。
長連線指建立SOCKET連線後不管是否使用都保持連線,但安全性較差。 -
http的長連線:
HTTP也可以建立長連線的,使用Connection:keep-alive,HTTP 1.1預設進行持久連線。HTTP1.1和HTTP1.0相比較而言,最大的區別就是增加了持久連線支援(貌似最新的 http1.0 可以顯示的指定 keep-alive),但還是無狀態的,或者說是不可以信任的。
什麼時候用長連線,短連線?
長連線多用於操作頻繁,點對點的通訊,而且連線數不能太多情況,。每個TCP連線都需要三步握手,這需要時間,如果每個操作都是先連線,再操作的話那麼處理速度會降低很多,所以每個操作完後都不斷開,次處理時直接傳送資料包就OK了,不用建立TCP連線。例如:資料庫的連線用長連線, 如果用短連線頻繁的通訊會造成socket錯誤,而且頻繁的socket 建立也是對資源的浪費。
而像WEB網站的http服務一般都用短連結,因為長連線對於服務端來說會耗費一定的資源,而像WEB網站這麼頻繁的成千上萬甚至上億客戶端的連線用短連線會更省一些資源,如果用長連線,而且同時有成千上萬的使用者,如果每個使用者都佔用一個連線的話,那可想而知吧。所以併發量大,但每個使用者無需頻繁操作情況下需用短連好。
總之,長連線和短連線的選擇要視情況而定 -
傳送接收方式
1、非同步
報文傳送和接收是分開的,相互獨立的,互不影響。這種方式又分兩種情況:
(1)非同步雙工:接收和傳送在同一個程式中,由兩個不同的子程序分別負責傳送和接收
(2)非同步單工:接收和傳送是用兩個不同的程式來完成。
2、同步
報文傳送和接收是同步進行,既報文傳送後等待接收返回報文。 同步方式一般需要考慮超時問題,即報文發出去後不能無限等待,需要設定超時時間,超過該時間傳送方不再等待讀返回報文,直接通知超時返回。
在長連線中一般是沒有條件能夠判斷讀寫什麼時候結束,所以必須要加長度報文頭。讀函式先是讀取報文頭的長度,再根據這個長度去讀相應長度的報文。
三、java socket建立連線的過程
1、 首先呼叫Socket類的建構函式,以伺服器的指定的IP地址或指定的主機名和指定的埠號為引數,建立一個Socket流,在建立Socket流的過程中包含了向伺服器請求建立通訊連線的過程實現。
2、 建立了客戶端通訊Socket後。就可以使用Socket的方法getInputStream()和getOutputStream()來建立輸入/輸出流。這樣,使用Socket類後,網路輸入輸出也轉化為使用流物件的過程。
3、 使用輸入輸出流物件的相應方法讀寫位元組流資料,因為流連線著通訊所用的Socket,Socket又是和伺服器端建立連線的一個端點,因此資料將通過連線從伺服器得到或發向伺服器。這時我們就可以對位元組流資料按客戶端和伺服器之間的協議進行處理,完成雙方的通訊任務。
4、 待通訊任務完畢後,我們用流物件的close()方法來關閉用於網路通訊的輸入輸出流,在用Socket物件的close()方法來關閉Socket。
四、Socket 通訊示例
主機 A 的應用程式要能和主機 B 的應用程式通訊,必須通過 Socket 建立連線,而建立 Socket 連線必須需要底層 TCP/IP 協議來建立 TCP 連線。建立 TCP 連線需要底層 IP 協議來定址網路中的主機。我們知道網路層使用的 IP 協議可以幫助我們根據 IP 地址來找到目標主機,但是一臺主機上可能執行著多個應用程式,如何才能與指定的應用程式通訊就要通過 TCP 或 UPD 的地址也就是埠號來指定。這樣就可以通過一個 Socket 例項唯一代表一個主機上的一個應用程式的通訊鏈路了。
五、建立通訊鏈路
當客戶端要與服務端通訊,客戶端首先要建立一個 Socket 例項,作業系統將為這個 Socket 例項分配一個沒有被使用的本地埠號,並建立一個包含本地和遠端地址和埠號的套接字資料結構,這個資料結構將一直儲存在系統中直到這個連線關閉。在建立 Socket 例項的建構函式正確返回之前,將要進行 TCP 的三次握手協議,TCP 握手協議完成後,Socket 例項物件將建立完成,否則將丟擲 IOException 錯誤。
與之對應的服務端將建立一個 ServerSocket 例項,ServerSocket 建立比較簡單隻要指定的埠號沒有被佔用,一般例項建立都會成功,同時作業系統也會為 ServerSocket 例項建立一個底層資料結構,這個資料結構中包含指定監聽的埠號和包含監聽地址的萬用字元,通常情況下都是“*”即監聽所有地址。之後當呼叫 accept() 方法時,將進入阻塞狀態,等待客戶端的請求。當一個新的請求到來時,將為這個連線建立一個新的套接字資料結構,該套接字資料的資訊包含的地址和埠資訊正是請求源地址和埠。這個新建立的資料結構將會關聯到
ServerSocket 例項的一個未完成的連線資料結構列表中,注意這時服務端與之對應的 Socket 例項並沒有完成建立,而要等到與客戶端的三次握手完成後,這個服務端的 Socket 例項才會返回,並將這個 Socket 例項對應的資料結構從未完成列表中移到已完成列表中。所以 ServerSocket 所關聯的列表中每個資料結構,都代表與一個客戶端的建立的 TCP 連線。
備註:
Windows 下單機最大TCP連線數
調整系統引數來調整單機的最大TCP連線數,Windows 下單機的TCP連線數有多個引數共同決定:
以下都是通過修改登錄檔[HKEY_LOCAL_MACHINE \System \CurrentControlSet \Services \Tcpip \Parameters]
1. 最大TCP連線數 TcpNumConnections
2. TCP關閉延遲時間 TCPTimedWaitDelay (30-240)s
3. 最大動態埠數 MaxUserPort (Default = 5000, Max = 65534) TCP客戶端和伺服器連線時,客戶端必須分配一個動態埠,預設情況下這個動態埠的分配範圍為 1024-5000 ,也就是說預設情況下,客戶端最多可以同時發起3977 個Socket 連線
4. 最大TCB 數量 MaxFreeTcbs系統為每個TCP 連線分配一個TCP 控制塊(TCP control block or TCB),這個控制塊用於快取TCP連線的一些引數,每個TCB需要分配 0.5 KB的pagepool 和 0.5KB 的Non-pagepool,也就說,每個TCP連線會佔用 1KB 的系統記憶體。非Server版本,MaxFreeTcbs 的預設值為1000 (64M 以上實體記憶體)Server 版本,這個的預設值為 2000。也就是說,預設情況下,Server
版本最多同時可以建立並保持2000個TCP 連線。
5. 最大TCB Hash table 數量 MaxHashTableSize TCB 是通過Hash table 來管理的。這個值指明分配 pagepool 記憶體的數量,也就是說,如果MaxFreeTcbs = 1000 , 則 pagepool 的記憶體數量為 500KB那麼 MaxHashTableSize 應大於 500 才行。這個數量越大,則Hash table 的冗餘度就越高,每次分配和查詢 TCP 連線用時就越少。這個值必須是2的冪,且最大為65536.
六、服務端程式碼:
- /*
- * testSocketService.c
- *
- * Created on: 2012-8-16
- * Author: 皓月繁星
- */
- #include <WINSOCK2.H>
- #include <stdio.h>
- #define PORT 5150
- #define MSGSIZE 1024
- #pragma comment(lib, "ws2_32.lib")
- int main()
- {
- WSADATA wsaData;
- SOCKET sListen;
- SOCKET sClient;
- SOCKADDR_IN local;
- SOCKADDR_IN client;
- char szMessage[MSGSIZE];
- int ret;
- int iaddrSize = sizeof(SOCKADDR_IN);
- WSAStartup(0x0202, &wsaData);
- sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
- local.sin_family = AF_INET;
- local.sin_port = htons(PORT);
- local.sin_addr.s_addr = htonl(INADDR_ANY);
- bind(sListen, (struct sockaddr *) &local, sizeof(SOCKADDR_IN));
- listen(sListen, 1);
- sClient = accept(sListen, (struct sockaddr *) &client, &iaddrSize);
- printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr),
- ntohs(client.sin_port));
- while (TRUE) {
- ret = recv(sClient, szMessage, MSGSIZE, 0);
- szMessage[ret] = '\0';
- printf("Received [%d bytes]: '%s'\n", ret, szMessage);
- }
- return 0;
- }
七、客戶端程式碼
- /*
- * testSocketClient.c
- *
- * Created on: 2012-8-16
- * Author: 皓月繁星
- */
- #include <WINSOCK2.H>
- #include <stdio.h>
- //定義程式中使用的常量
- #define SERVER_ADDRESS "127.0.0.1" //伺服器端IP地址
- #define PORT 5150 //伺服器的埠號
- #define MSGSIZE 1024 //收發緩衝區的大小
- #pragma comment(lib, "ws2_32.lib")
- int main()
- {
- WSADATA wsaData;
- //連線所用套節字
- SOCKET sClient;
- //儲存遠端伺服器的地址資訊
- SOCKADDR_IN server;
- //收發緩衝區
- char szMessage[MSGSIZE];
- //成功接收位元組的個數
- int ret;
- // Initialize Windows socket library
- WSAStartup(0x0202, &wsaData);
- // 建立客戶端套節字
- sClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //AF_INET指明使用TCP/IP協議族;
- //SOCK_STREAM, IPPROTO_TCP具體指明使用TCP協議
- // 指明遠端伺服器的地址資訊(埠號、IP地址等)
- memset(&server, 0, sizeof(SOCKADDR_IN)); //先將儲存地址的server置為全0
- server.sin_family = PF_INET; //宣告地址格式是TCP/IP地址格式
- server.sin_port = htons(PORT); //指明連線伺服器的埠號,htons()用於 converts values between the host and network byte order
- server.sin_addr.s_addr = inet_addr(SERVER_ADDRESS); //指明連線伺服器的IP地址
- //結構SOCKADDR_IN的sin_addr欄位用於儲存IP地址,sin_addr欄位也是一個結構體,sin_addr.s_addr用於最終儲存IP地址
- //inet_addr()用於將 形如的"127.0.0.1"字串轉換為IP地址格式
- //連到剛才指明的伺服器上
- connect(sClient, (struct sockaddr *) &server, sizeof(SOCKADDR_IN)); //連線後可以用sClient來使用這個連線
- //server儲存了遠端伺服器的地址資訊
- while (TRUE) {
- printf("Send:");
- //從鍵盤輸入
- gets(szMessage); //The gets() functionreads characters from stdin and loads them into szMessage
- // 傳送資料
- send(sClient, szMessage, strlen(szMessage), 0); //sClient指明用哪個連線傳送; szMessage指明待發送資料的儲存地址 ;strlen(szMessage)指明資料長度
- }
- // 釋放連線和進行結束工作
- closesocket(sClient);
- WSACleanup();
- return 0;
- }