一、基於TCP的套接字編程實現流程:
1. 服務器端流程簡介:
(1)創建套接字(socket)
(2)將套接字綁定到一個本地地址和端口上(bind)
(3)將套接字設定為監聽模式,准備接受客戶端請求(listen)
(4)阻塞等待客戶端請求到來。當請求到來后,接受連接請求,返回一個新的對應於此客戶端連接的套接字sockClient(accept)
(5)用返回的套接字sockClient和客戶端進行通信(send/recv);
(6)返回,等待另一個客戶端請求(accept)
(7)關閉套接字(close)
2. 客戶端流程簡介:
(1) 創建套接字(socket)
(2) 向服務器發出連接請求(connect)
(3) 和服務器進行通信(send/recv)
(4) 關閉套接字(close)

二、 send和recv函數的理解:
當調用socket創建套接字時,同時在內核中生成發送和接收緩沖區。
-
設置為connect模式時(客戶端模式),調用send會將用戶自定義的buff中的數據拷貝到發送緩沖區,緩沖區數據的發送由TCP/IP模型完成;
-
設置為listen模式時(服務器端模式),發送緩沖區不再使用,接收緩沖區只存放客戶端的連接請求。而accpet函數返回的新建套接字sockfd會再生成兩個新緩沖區,發送和接收緩沖區。當調用recv時,recv先等待sockfd的發送緩沖區中數據按協議傳送完畢,再檢查sockfd的接收緩沖區,如果接收緩沖區沒有數據或正在傳送,則recv等待;否則recv將接收緩沖區中的數據拷貝到用戶定義的buff中(ps:當接收緩沖區中數據長度大於buff長度時,recv要調用多次才能完全拷貝完成)。recv返回的是每次實際拷貝的數據長度,若拷貝出錯則返回SOCKET_ERROR,若網絡中斷則返回0。
-
send和recv只是從發送/接收緩沖區中拷貝數據,真正的讀寫數據是由TCP/IP協議完成的
三、C++客戶端/服務器端的簡單實現:
(1)Sever實現:
#include <Winsock2.h> #include <cstdio> #include <iostream> #pragma comment(lib,"ws2_32.lib") int main() { //1. 加載socket函數庫 WSADATA wsaData; SOCKET sockServer; SOCKADDR_IN addrServer; SOCKET sockClient; SOCKADDR_IN addrClient; WSAStartup(MAKEWORD(2,2),&wsaData); //加載套接字庫 //2. 創建套接字 addrServer.sin_addr.S_un.S_addr = htonl(INADDR_ANY); //INADDR_ANY表示任何IP,即允許所有的客戶端連接到本地服務端 addrServer.sin_family = AF_INET; //設置為IP通信 addrServer.sin_port = htons(6000); //綁定端口6000 sockServer = socket(AF_INET,SOCK_STREAM,0); //創建流式套接字 std::cout<<"創建套接字成功"<<std::endl; //3. 將套接字綁定到 固定IP和固定端口 bind(sockServer, (SOCKADDR*)&addrServer, sizeof(SOCKADDR)); //進行端口和IP地址的綁定 std::cout<<"綁定套接字成功"<<std::endl; //4. 將套接字設置為監聽模式,等待連接到來 listen(sockServer,5); //監聽隊列為5 std::cout<<"監聽連接中......"<<std::endl; int len = sizeof(SOCKADDR); char sendBuf[100]; //發送至客戶端的字符串 char recvBuf[100]; //接受客戶端返回的字符串 //5. 阻塞服務端的進程,直到有客戶端連接為止 sockClient = accept(sockServer, (SOCKADDR*)&addrClient, &len); //sockClinet為返回的新的客戶端連接 std::cout<<"收到連接:"<<inet_ntoa(addrClient.sin_addr)<<std::endl; //6. 接收並打印客戶端數據 int recvlen = 0; if((recvlen = recv(sockClient,recvBuf,100,0)) > 0) { std::cout<<recvBuf<<std::endl; } send(sockClient, "Receive the Client Data", 23, 0); //7. 關閉socket closesocket(sockClient); WSACleanup(); return 0; }
(2)Client實現:
#include <WinSock2.h> #include <stdio.h> #include <iostream> #include <cstring> #pragma comment(lib, “ws2_32.lib”) int main() { //1. 加載套接字 WSADATA wsaData; if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { printf(”Failed to load Winsock”); return; } //2. 創建套接字 SOCKADDR_IN addrSrv; addrSrv.sin_family = AF_INET; addrSrv.sin_port = htons(6000); //端口號6000 addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); //訪問的服務器IP SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0); //創建客戶端套接字 //3. 向服務器發出連接請求 if(connect(sockClient, (struct sockaddr*)&addrSrv, sizeof(addrSrv)) == INVALID_SOCKET) { std::cout<<"Connect failed:"<<WSAGetLastError()<<std::endl; return; } //4.接收和發送數據 char recvbuff[1024]; memset(recvbuff, 0, sizeof(recvbuff)); recv(sockClient, recvbuff, sizeof(recvbuff), 0); std::string sendbuf = "hello, this is a Client…"; send(sockClient, sendbuf.c_str(), sendbuf.size(), 0); //5. 關閉套接字 closesocket(sockClient); WSACleanup(); return 0; }