Socket也叫套接字,用來實現網絡通訊,通過調用系統提供的API,可以和遠程的機子傳輸數據。Socket有很多種協議,而這篇文章主要討論TCP部分的內容,也就是說后面說的內容主要是指TCP Socket。
Socket 的一般調用過程:
服務端:socket(), bind(),listen(),accept(),send(),recv(),close()
客戶端:socket(),connect(),send(),recv(),close()
阻塞socket(同步socket)
進程或線程執行到某些socket函數時必須等待該socket事件的發生,如果該事件沒有發生,socket函數不能立即返回,進程或線程被阻塞。
特點:使用簡單,適合一對一的應答場合,在服務端很少使用,或配合多線程使用
| 函數 | 返回值說明 | 阻塞情況 |
| accept() | 返回新的連接socket句柄。 | 緩沖區隊列沒有新的等待連接 |
| connect() | 返回-1說明連接失敗,其他正常。 | 連接過程阻塞。 |
| recv() | 返回值小於1代表接收失敗,其他代表接收數據的長度。 | 發送緩沖區有數據等待發送完成,或接收緩沖區沒數據時阻塞。 |
| send() | 返回-1代表發送失敗,其他為發送數據的長度 | 發送緩沖區沒有足夠空間保存此次發送數據時阻塞 |
非阻塞socket(異步socket)
進程或線程執行socket函數時不必非要等待該socket事件的發生,一旦執行立即返回。根據返回值的不同可以判斷函數的執行情況,如果事件發生則與阻塞方式相同,若事件沒有發生則返回一個代碼來告知事件未發生,而進程或線程可以不被阻塞,繼續執行。
特點:函數執行立即返回,不會阻斷進程,性能比阻塞高,適合在主線程直接調用,不會造成主線程卡頓現象
因為socket默認是阻塞的,所以要設置非阻塞模式:
#ifdef WIN32 DWORD nMode = 1; ioctlsocket(m_sock, FIONBIO, &nMode); #else int r = fcntl(fd, F_GETFL, 0)); fcntl(fd, F_SETFL, r|O_NONBLOCK); #endif
TCP與UDP 的區別
| 協議說明 | socket創建 | |
| TCP | 傳輸控制協議,可靠的連接服務。雙方先建立連接再傳輸數據。提供超時重發,數據檢驗,流量控制等機制,保證數據發送無誤。 | socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) |
| UDP | 用戶數據報協議,不可靠的連接服務。沒有建立連接就可以發送數據,沒有超時重發機制,傳輸速度很快。 | socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP) |
Socket例子
下面以一個簡單例子來說明服務端與客戶端的交互過程
服務端 server.cpp
#include <stdio.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
#define PORT 8080
int main()
{
//初始化winsock服務
WSADATA wsaData;
WSAStartup(MAKEWORD(2,2), &wsaData);
//創建socket
SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
struct sockaddr_in svraddr;
svraddr.sin_family = AF_INET;
svraddr.sin_port = htons(PORT);
svraddr.sin_addr.s_addr = htonl(INADDR_ANY);
//綁定socket
bind(sock, (struct sockaddr*)&svraddr, sizeof(svraddr));
//監聽socket
listen(sock, 5);
while(1)
{
struct sockaddr_in addr;
int len = sizeof(SOCKADDR);
char buf[1024] = {0};
//接受客戶端連接
SOCKET client = accept(sock, (struct sockaddr*)&addr, &len);
char* ip = inet_ntoa(addr.sin_addr);
printf("accept client: %s\r\n", ip);
//接收客戶端數據
if(recv(client, buf, 1024, 0) >0)
{
printf("recv client: %s\r\n", buf);
//向客戶端發送數據
send(client, "hello, client", strlen("hello, client"), 0);
}
closesocket(client);
}
//關閉socket
closesocket(sock);
//關閉winsock服務
WSACleanup();
return 0;
}
客戶端 client.cpp
#include <stdio.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
#define REMOTE_IP "127.0.0.1"
#define REMOTE_PORT 8080
int main()
{
//初始化winsock服務
WSADATA wsaData;
WSAStartup(MAKEWORD(2,2), &wsaData);
//創建socket
SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
struct sockaddr_in svraddr;
svraddr.sin_family = AF_INET;
svraddr.sin_port = htons(REMOTE_PORT);
svraddr.sin_addr.s_addr = inet_addr(REMOTE_IP);
//連接socket
if( connect(sock, (struct sockaddr*)&svraddr, sizeof(svraddr)) != -1)
{
//發送數據給服務端
if(send(sock, "hello, server", strlen("hello, server"), 0) != -1)
{
//接收服務端數據
char buf[1024] = {0};
if(recv(sock, buf, 1024, 0) >0)
{
printf("recv server: %s\r\n", buf);
}
}
}
else
{
printf("can not connect server\r\n");
}
//關閉socket
closesocket(sock);
//關閉winsock服務
WSACleanup();
getchar();
return 0;
}
