1.Socket通信
本博客部分內容參考教程來自C語言中文網。
進入socket通信首先要了解兩個概念,第一:服務器端,第二:客戶端。(區別是兩者的服務對象不同。服務器端是為客戶端服務的,客戶端就是為真正的“客戶”來服務的,所以這兩者之間不同,但又密切相連,客戶端是請求方或者說是指令發出方,而服務器端是響應方。)
服務器端:顧名思義是服務的,客戶端發送的請求交給服務器端處理,是以response對象存在,服務器端處理完畢后反饋給客戶端。(申明自身的port和IP(一般使用任意IP))
客戶端:在web中是以request對象存在的,發送請求給服務器端處理,具體的使用方法可以查找javaee的servletrequest以及其子類。(服務端的端口號和IP地址)
一、什么是TCP/IP?
? ? ? ? TCP提供基于IP環境下的數據可靠性傳輸,事先需要進行三次握手來確保數據傳輸的可靠性。詳細的博主不再贅述,感興趣的朋友可以去search一下。
二、什么是socket? ? ?
? ? ? ? socket顧名思義就是套接字的意思,用于描述地址和端口,是一個通信鏈的句柄。應用程序通過socket向網絡發出請求或者回應。
? ? ? ? socket編程有三種,流式套接字(SOCK_STREAM),數據報套接字(SOCK_DGRAM),原始套接字(SOCK_RAW),前兩者較常用。基于TCP的socket編程是流式套接字。
三、client/server即C/S模式:
? ? ?? TCP/IP通信中,主要是進行C/S交互。廢話不多說,下面看看具體交互內容:
? ? ?? 服務端:建立socket,申明自身的port和IP,并綁定到socket,使用listen監聽,然后不斷用accept去查看是否有連接。如果有,捕獲socket,并通過recv獲取消息的內容,通信完成后調用closeSocket關閉這個對應accept到的socket。如果不需要等待任何客戶端連接,那么用closeSocket直接關閉自身的socket。
? ? ? ? 客戶端:建立socket,通過端口號和地址確定目標服務器,使用Connect連接到服務器,send發送消息,等待處理,通信完成后調用closeSocket關閉socket。
一般我們訪問網站,都是客戶端(瀏覽器、app)發出請求,然后對方服務器端(sina,sohu)響應,結果就是返回了頁面路徑給我們,我們再根據路徑看到了網頁。
//服務器端代碼 server.cpp
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>int main(){//創建套接字int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);//參數分別是IPV4,面向連接套接字,TCP協議//將套接字和IP、端口綁定struct sockaddr_in serv_addr;memset(&serv_addr, 0, sizeof(serv_addr)); //每個字節都用0填充serv_addr.sin_family = AF_INET; //使用IPv4地址serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具體的IP地址//將一個字符串的IP轉換成一個長整型serv_addr.sin_port = htons(1234); //端口 //將整數轉換為網絡字節bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));//將address指向的sockaddr結構體中描述的一些屬性(IP地址、端口號、地址簇)與socket套接字綁定,也叫給套接字命名。//采用TCP通信時,客戶端不需要bind()他自己的IP和端口號,而服務器必須要bind()自己本機的IP和端號;
/*因為服務器是時時在監聽有沒有客戶端的連接,如果服務器不綁定IP和端口的話,客戶端上線的時候怎么連到服務器呢,所以服務器要綁定IP和端口,而客戶端就不需要了,客戶端上線是主動向服務器發出請求的,因為服務器已經綁定了IP和端口,所以客戶端上線的就向這個IP和端口發出請求,這時因為客戶開始發數據了(發上線請求),系統就給客戶端分配一個隨機端口,這個端口和客戶端的IP會隨著上線請求一起發給服務器,服務收到上線請求后就可以從中獲起發此請求的客戶的IP和端口,接下來服務器就可以利用獲起的IP和端口給客戶端回應消息了。*///進入監聽狀態,等待用戶發起請求listen(serv_sock, 20);//接收客戶端請求struct sockaddr_in clnt_addr;socklen_t clnt_addr_size = sizeof(clnt_addr);int clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);//向客戶端發送數據char str[] = "http://c.biancheng.net/socket/";write(clnt_sock, str, sizeof(str));//關閉套接字close(clnt_sock);close(serv_sock);return 0;
}
//客戶端代碼 client.cpp
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>int main(){//創建套接字int sock = socket(AF_INET, SOCK_STREAM, 0);//向服務器(特定的IP和端口)發起請求struct sockaddr_in serv_addr;memset(&serv_addr, 0, sizeof(serv_addr)); //每個字節都用0填充serv_addr.sin_family = AF_INET; //使用IPv4地址serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具體的IP地址serv_addr.sin_port = htons(1234); //端口connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));//讀取服務器傳回的數據char buffer[40];read(sock, buffer, sizeof(buffer)-1);printf("Message form server: %s\n", buffer);//關閉套接字close(sock);return 0;
}
啟動一個終端(Shell),先編譯 server.cpp 并運行:
$ g++ server.cpp -o server
$ ./server
正常情況下,程序運行到?accept() 函數就會被阻塞,等待客戶端發起請求。
接下再啟動一個終端,編譯 client.cpp 并運行:
$ g++ client.cpp -o client
$ ./client
Message form server:?http://c.biancheng.net/socket/
client 接收到從 server發送過來的字符串就運行結束了,同時,server 完成發送字符串的任務也運行結束了。大家可以通過兩個打開的終端來觀察。
client 運行后,通過 connect() 函數向 server 發起請求,處于監聽狀態的 server 被激活,執行 accept() 函數,接受客戶端的請求,然后執行 write() 函數向 client 傳回數據。client 接收到傳回的數據后,connect() 就運行結束了,然后使用 read() 將數據讀取出來。
server 只接受一次 client 請求,當 server 向 client 傳回數據后,程序就運行結束了。如果想再次接收到服務器的數據,必須再次運行 server,不能夠一直接受客戶端的請求。
?
PS:
用socket創建網絡編程接口------文件描述符。 “地方”定義了,下面就需要將socket放置在這個“地方”(TCP),將他們緊緊地捆綁在一起,用bind函數吧, 我們來看看函數原型:
int PASCAL FAR bind (SOCKET s, const struct sockaddr FAR *addr, int namelen);
?????第一個參數當然是待綁定的套接字啦,第二個參數是標識綁定在哪個“地方”, 第三個參數是這個“地方”的占地大小。
???? 返回值表示綁定操作是否成功,0表示成功, -1表示不成功。函數的返回值千萬不要忽視,上次就被人說了。
?
??? 一般是這么調用的:
?? ? iRet = bind(sockSrv,(SOCKADDR*)&addrSrv, sizeof(SOCKADDR)); // 注意強制轉換
?
? ? ?我們來對比一下文件I/O操作和網絡I/O操作: 打開一個文件后, 便可以對文件進行讀寫操作了, 但是, 網絡I/O實際上有三個步驟來完成這個功能:
? ? ?1. 打開/創建socket
? ? ?2. 命名socket, 我們知道, socket名稱包含"協議, ip地址, ?端口號"這三個要素, 而命名就是通過調用bind函數把socket與這三個要素綁定一起來。
? ? ?3. 建立連接
accept() 函數
當套接字處于監聽狀態時,可以通過 accept() 函數來接收客戶端請求。它的原型為:
int accept(int sock, struct sockaddr *addr, socklen_t *addrlen); //Linux
SOCKET accept(SOCKET sock, struct sockaddr *addr, int *addrlen); //Windows
它的參數與 listen() 和 connect() 是相同的:sock 為服務器端套接字,addr 為 sockaddr_in 結構體變量,addrlen 為參數 addr 的長度,可由 sizeof() 求得。
accept() 返回一個新的套接字來和客戶端通信,addr 保存了客戶端的IP地址和端口號,而 sock 是服務器端的套接字,大家注意區分。后面和客戶端通信時,要使用這個新生成的套接字,而不是原來服務器端的套接字。
最后需要說明的是:listen() 只是讓套接字進入監聽狀態,并沒有真正接收客戶端請求,listen() 后面的代碼會繼續執行,直到遇到 accept()。accept() 會阻塞程序執行(后面代碼不能被執行),直到有新的請求到來。
?
總結
以上是生活随笔為你收集整理的1.Socket通信的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Ubuntu下编译并运行C++代码
- 下一篇: PCL两种方式的点云读写