Winsock(5) TCP服務端向客戶端傳送Hello World
阿新 • • 發佈:2018-12-12
本章將詳解send()/WSASend() 、 recv()/WSARecv() 和 函式,然後你就可以編寫一個可執行的通訊程式了
程式包括一個服務端和一個客戶端,服務端向客戶端傳送一個Hello World! 對,你沒看錯,所有程式的開端,Hello World!
程式執行結果如下所示
服務端執行結果 客戶端執行結果
send()/WSASend():
int send(
Socket s, //即將傳送資料的服務端程序
const char FAR * buf; //待發送資料指標
int len; //待發送資料長度
int flags //標誌位
) ;
flags 標誌位可選:0 | MSG_DONTROUTE | MSG_OOB 可以用 OR 運算子連線 通常用0,後面兩個不常用。
函式執行正確返回傳送的位元組數,錯誤時返回SOCKET_ERROR, 常見錯誤碼:
- WSAECONNABORTED: 超時,協議錯誤等
- WSAECONNRESET: 伺服器關閉、重啟
- WSAEWOULDBLOCK: 特定方法無法被完成,使用了非阻塞、非同步socket
- WSAETIMEOUT: 超時,網路不通
當send()返回錯誤時該socket應該立即關閉,因為不可用了。
int WSASend(
SOCKET s,
LPWSABUF lpBuffers, //待發送資料指標
DWORD dwBufferCount, //待發送資料長度
LPDWORD lpNumberOfBytesSent, //傳送位元組數,函式執行後將設定該值
DWORD dwFlags, //標誌位,同send()
LPWSAOVERLAPPED lpOverlapped, //後面兩個引數用於重疊I/O,後面會講到,此處可以無視
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
函式執行正確返回0,函式執行錯誤返回值類似send()。
recv()/WSARecv ()
int recv
(
SOCKET s, //待接收資料的客戶端socket
char FAR * buf, //準備儲存資料的緩衝區
int len, //緩衝區長度
int flags //標誌位
);
標誌位flags可取的值為:0 | MSG_PEEK | MSG_OOB 可以用OR運算子連線 通常用0,MSG_PEEK表示資料將被複制進緩衝區,但並不從輸入佇列中刪除。 函式執行正確返回接收的位元組數,函式執行錯誤返回SOCKET_ERROR。 常見錯誤碼:
- WSAEMSGSIZE:資料過大,超過緩衝區。該錯誤只會發生在面向訊息協議中,不會發生在流式訊息中,因為TCP有流量控制機制
int WSARecv(
SOCKET s, //待接收資料的客戶端socket
LPWSABUF lpBuffers, //準備儲存資料的緩衝區
dwBufferCount, //接收緩衝區大小
lpNumberOfBytesRecvd, //接收位元組數,函式執行後將設定該值
LPDWORD lpFlags, //標誌位,一般用0
LPWSAOVERLAPPED lpOverlapped, //後面兩個引數用於重疊I/O,後面會講到,此處可以無視
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
另外,還有一對不常用的WSASendDisconnect()/WSARecvDisconnect(), 它們用於傳送連線斷開資料,執行後會關閉連線。 現在終於可以編寫一個完整的 Winsock TCP/IP 通訊程式了! 終於邁出了 Hello World! 的重要一步!
服務端程式碼
#include <winsock2.h>
#include <iostream>
#define PORT 5000
using namespace std;
int main(void)
{
WSADATA wsaData; //Winsock資料結構
SOCKET ServerSocket; //服務端socket
SOCKET AcceptSocket; //從客戶端接收到的socket
SOCKADDR_IN ServerAddr; //服務端SOCKADDR地址
SOCKADDR_IN ClientAddr; //客戶端SOCKADDR地址
int port=PORT; //埠號
int ClientAddrLen; //客戶區地址長度
char s[]="Hello World!"; //要傳輸的字串
WSAStartup(MAKEWORD(2,2),&wsaData); //初始化 Winsock 2.2 版本
ServerSocket=socket(AF_INET,SOCK_STREAM,0); //建立一個 socket 來監聽客戶連線
if(ServerSocket!=INVALID_SOCKET) {
cout<<"socket()建立ServerSocket成功!\n";
}
else {
cout<<"socket()建立ServerSocket失敗!\n"<<WSAGetLastError();
}
ServerAddr.sin_family=AF_INET; //填充 SOCKADDR_IN 資料結構
ServerAddr.sin_port=htons(port);
ServerAddr.sin_addr.s_addr=htonl(INADDR_ANY);
if(bind(ServerSocket,(SOCKADDR*)&ServerAddr,sizeof(ServerAddr))==SOCKET_ERROR) {
cout<<"bind()繫結周知地址失敗!\n"<<WSAGetLastError();
}
else {
cout<<"bind()繫結周知地址成功!\n";
}
if(listen(ServerSocket,5)!=SOCKET_ERROR) {
cout<<"listen()監聽成功!\n";
}
else {
cout<<"listen()監聽失敗!\n";
}
ClientAddrLen=sizeof(ClientAddr); //顯示指定ClientAddrLen大小
while(1)
{
AcceptSocket=accept(ServerSocket,(SOCKADDR*)&ClientAddr,&ClientAddrLen);
//接受一個到來的連線,注意!最後一個引數需要自己顯示指定!
/*
這裡你通過這些socket可以做兩件事
1.通過ListeningSocket再次呼叫accept()來接受其他連線
2.通過NewConnection來發送/接受資料
當你做完這兩件事情時必須要關閉這些socket
socket的關閉將在後面介紹
*/
if(INVALID_SOCKET!=AcceptSocket) {
cout<<"accept()接收客戶端連線成功!\n";
int sendLen=send(AcceptSocket,s,sizeof(s),0); //傳送資料
if(sendLen==SOCKET_ERROR) {
cout<<"send()傳送資料失敗!\n"<<WSAGetLastError();
}
else {
cout<<"send()傳送資料成功!傳送的位元組數:"<< sendLen;
}
closesocket(AcceptSocket); //關閉該連線
break; //退出迴圈
}
}
closesocket(ServerSocket); //關閉ServerSocket
WSACleanup(); //關閉Winsock
int nothing; //與程式無關,為了讓控制檯不直接關閉
cin>>nothing;
return 0;
}
客戶端程式碼
#include <Winsock2.h>
#include <iostream>
#define BUFFER 1024
#define PORT 5000
using namespace std;
int main(void)
{
WSADATA wsaData; //Winsock資料結構
SOCKET ClientSocket; //客戶端socket
SOCKADDR_IN ServerAddr; //伺服器地址
int port=PORT; //埠號
char buf[BUFFER]; //接收的字元緩衝區
memset(buf,0,sizeof(buf)); //清空快取
WSAStartup(MAKEWORD(2,2),&wsaData); //初始化 Winsock 2.2 版本
ClientSocket=socket(AF_INET,SOCK_STREAM,0); //建立客戶端socket
if(ClientSocket==INVALID_SOCKET) {
cout<<"socket()建立ClientSocket失敗!\n"<<WSAGetLastError();
}
else {
cout<<"socket()建立ClientSocket成功!\n";
}
ServerAddr.sin_family=AF_INET; //填充 SOCKADDR_IN 資料結構
ServerAddr.sin_port=htons(port);
ServerAddr.sin_addr.s_addr=inet_addr("127.0.0.1");
if(connect(ClientSocket,(SOCKADDR*)&ServerAddr,sizeof(ServerAddr))==INVALID_SOCKET) {
cout<<"connect()連線服務端失敗!\n"<<WSAGetLastError();
}
else {
cout<<"connect()連線服務端成功!\n";
}
int recvLen=recv(ClientSocket,buf,sizeof(buf),0);
if(recvLen==0) {
cout<<"接收長度為0!\n";
}
else if(recvLen==SOCKET_ERROR) {
cout<<"recv()接收失敗!\n"<<WSAGetLastError();
}
else {
cout<<"recv()接收成功!\n"<<buf<<" 接收資料位元組數為:"<<recvLen;
}
closesocket(ClientSocket); //關閉socket
WSACleanup(); //關閉Winsock
int nothing; //與程式無關,為了讓控制檯不直接關閉
cin>>nothing;
return 0;
}
程式必須先執行服務端,再執行客戶端,因為先要讓服務端開啟監聽。
程式需要匯入必要的庫檔案,詳情參見 Windows網路程式設計學習筆記(1)。
Hello World!傳送時需要算上最後一個/0結束符,因此大小是13個位元組。