1. 程式人生 > >Winsock(5) TCP服務端向客戶端傳送Hello World

Winsock(5) TCP服務端向客戶端傳送Hello World

本章將詳解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個位元組。