本篇,紀錄一個在 windows上使用 socket進行通信的例子,代碼源自於網上。由於時間過去挺久了,當時我也沒有加書籤,現在暫時還不好找出處。 文中給出一些關鍵代碼片段,一方面用於鞏固我所學的知識,另一方面,用於縱向的技術對比,加深理解。完整的地址在這裏:地址。
首先看看項目結構:
服務端的關鍵代碼:
int main(){
//加載Winsock庫,初始化socket資源
initialization();
//創建套接字
SOCKET s_server;
s_server = socket(AF_INET,SOCK_STREAM,0);
//綁定信息
bindInfo(&s_server);
//將socket設置爲lisen狀態
changtoListen(&s_server);
SOCKET commandSock;
responseAccept(commandSock,&s_server);
//通信
onInteract(&commandSock);
//結束
onClose(&commandSock,&s_server);
return 0;
}
總體而言,就是經過標準的socket步驟: 生成本地socket->bind() -> listen() -> accept() -> 通信傳輸 -> 關閉;
這個過程中,關鍵點在於accept(),在本次代碼中,它是同步阻塞的,代碼如下:
//響應連接狀態
int responseAccept(SOCKET& _sock,SOCKET* _s_server){
int len = sizeof(SOCKADDR);
SOCKADDR_IN accept_addr;
SOCKET s_accept= accept(*_s_server, (SOCKADDR *)&accept_addr, &len);
if (s_accept==SOCKET_ERROR){
std::cout << "連接失敗!錯誤碼:"<<WSAGetLastError() <<std::endl;
WSACleanup();
return 0;
}
_sock = s_accept;
std::cout << "連接建立,準備接受數據!" <<std::endl;
}
接收的結果是 套接字對象;同時,這個接收的過程是 阻塞的。 與客戶端的所有通信將基於當前接收的套接字對象進行。
我將 服務器與客戶端的通信 抽象爲一個方法,稱之爲 互動 onInteract(),代碼如下:
//利用返回的套接字進行通信
void onInteract(SOCKET* s_accept){
int recv_len =-1;
int send_len = -1;
char recv_buf[100];
char send_buf[100];
while (1){
recv_len = recv(*s_accept,recv_buf,100,0);
if (recv_len<0){
std::cout <<"接受失敗!錯誤碼:"<<WSAGetLastError()<<std::endl;
break;
}else if(recv_len==0){
std::cout <<"會話結束!"<<std::endl;
} else{
std::cout <<"客戶端信息:"<<recv_buf<<std::endl;
}
std::cout <<"請輸入回覆信息:";
std::cin>> send_buf;
send_len = send(*s_accept,send_buf,100,0);
if(send_len<0){
std::cout <<"發送失敗!錯誤碼:"<<WSAGetLastError() <<std::endl;
break;
}
}
}
本地代碼的關鍵點在於: recv(),與 send() 都是阻塞的方式。 它的表現是: 一次發送,一次接收 如此循環往復。 此外,還應對接收到的信息進行判別,用於控制通信的結束,也就形成了實際上的 應用層協議(但是在本例子中還未加,由於此時不想修改代碼,確實沒有精力)。
接着,再看一看客戶端的代碼:
int main(){
initialization();
SOCKET s_server;
s_server = socket(AF_INET,SOCK_STREAM,0);
onConnect(s_server);
onActive(s_server);
onClose(s_server);
return 0;
}
它的標準流程則是: 生成本地socket -> connect() -> 消息通信的過程 -> 關閉。
這其中的注意的是: connect()該方法是立即返回的,連接由tcp/udp進行控制。 之後通信的過程,我依然是抽象成的一個方法叫做: onActive(),代碼如下:
//與服務器進行通信
void onActive(SOCKET& s_server){
char send_buf[100];
char recv_buf[100];
int recv_len;
int send_len;
while (1){
std::cout<<"請輸入發送信息:";
std::cin>>send_buf;
send_len = send(s_server,send_buf,100,0);
if (send_len<0){
std::cout <<"發送失敗!"<<std::endl;
break;
}
recv_len = recv(s_server,recv_buf,100,0);
if (recv_len<0){
std::cout <<"接收失敗!"<<std::endl;
break;
} else{
std::cout <<"服務端信息:"<<recv_buf<<std::endl;
}
}
}
其中的關鍵仍然是send()方法,recv()方法,它們當然也是阻塞的。
最終的效果如下:
總結:
1.在實踐過程中,空格字符會被 處理成 分段操作,譬如 "ping hello", 會理解爲兩次 send。這說明了在socket的讀寫字節流中,控制字符有可能大部分要經過轉義。
2.當前方式實現中,讀寫操作是交替運行的。並且,讀寫操作是阻塞的(發送和接收是一種另類的讀寫操作)。
3.當前實現中,服務器是單線程的,不要搞混淆了,接收到的是 socket,而不是thread。 這也意味着,一次只能接入一個會話(或者接收到的多個會話進行線性處理。)。(因爲通話的過程是基於接收到的socket句柄而進行的.) 。 此外,socket與線程並沒有必然聯繫,它們實際上代表了兩種資源。