使用Winsock:Winsock入門

以下是Windows套接字編程入門的分步指南。 它旨在提供對基本Winsock函數和數據結構的理解,以及它們如何協同工作。

用於說明的客戶端和服務器應用程序是一個非常基本的客戶端和服務器。 Microsoft Windows軟件開發工具包(SDK)附帶的示例中包含更高級的代碼示例。

客戶端和服務器應用程序的前幾個步驟相同。

  • 關於服務器和客戶端
  • 創建一個基本的Winsock應用程序
  • 正在初始化Winsock

以下部分描述了創建Winsock客戶端應用程序的其餘步驟。

  • 爲客戶端創建套接字
  • 連接到插座
  • 在客戶端上發送和接收數據
  • 斷開客戶端連接

以下部分描述了創建Winsock服務器應用程序的其餘步驟。

  • 爲服務器創建套接字
  • 綁定套接字
  • 聽一個插座
  • 接受連接
  • 在服務器上接收和發送數據
  • 斷開服務器連接

這些基本示例的完整源代碼。

  • 運行Winsock客戶端和服務器代碼示例
  • 完整的Winsock客戶端代碼
  • 完整的Winsock服務器代碼

 

高級Winsock實例

Windows SDK附帶了幾個更高級的Winsock客戶端和服務器示例。 默認情況下,Winsock示例源代碼由Windows SDK for Windows 7安裝在以下目錄中:

C:\Program Files\Microsoft SDKs\Windows\v7.0\Samples\NetDs\winsock

在早期版本的Windows SDK中,上述路徑中的版本號將更改。 例如,Winsock示例源代碼由Windows SDK for Windows Vista安裝在以下默認目錄中

C:\Program Files\Microsoft SDKs\Windows\v6.0\Samples\NetDs\winsock

以下列出的高級樣本按從高到低的順序排列,可在以下目錄中找到:

  • iocp

該目錄包含三個使用I / O完成端口的示例程序。 這些程序包括一個使用WSAAccept函數的Winsock服務器(iocpserver),一個使用AcceptEx函數的Winsock服務器(iocpserverex),以及一個用於測試這些服務器中的任何一個的簡單多線程Winsock客戶端(iocpclient)。 服務器程序支持多個客戶端通過TCP / IP連接併發送任意大小的數據緩衝區,然後服務器回送給客戶端。 爲方便起見,開發了一個簡單的客戶端程序iocpclient,用於連接並不斷向服務器發送數據,以便使用多個線程對其進行壓力。 使用I / O完成端口的Winsock服務器提供最強大的性能。

  • overlap

此目錄包含使用重疊I / O的示例服務器程序。 示例程序使用AcceptEx函數和重疊I / O來有效地處理來自客戶端的多個異步連接請求。 服務器使用AcceptEx函數在單線程Win32應用程序中複用不同的客戶端連接。 使用重疊I / O可實現更高的可伸縮性。

  • WSAPoll

該目錄包含一個演示WSAPoll函數使用的基本示例程序。 組合的客戶端和服務器程序是非阻塞的,並使用WSAPoll函數來確定何時可以無阻塞地發送或接收。 此示例更多用於說明,而不是高性能服務器。‘

  • simple

該目錄包含三個基本示例程序,用於演示服務器使用多個線程。 這些程序包括一個簡單的TCP / UDP服務器(簡單),一個僅使用TCP的服務器(simples_ioctl),它使用Win32控制檯應用程序中的select函數來支持多個客戶端請求,以及一個客戶端TCP / UDP程序(simplec),用於測試服務器。 服務器演示了使用多個線程來處理多個客戶端請求。 此方法具有可伸縮性問題,因爲爲每個客戶端請求創建了單獨的線程。

  • accept

該目錄包含基本樣本服務器和客戶端程序。 服務器演示如何使用select函數使用非阻塞接受或使用WSAAsyncSelect函數使用異步接受。 此示例更多用於說明,而不是高性能服務器。’

 

 

一、關於服務器和客戶端

有兩種不同類型的套接字網絡應用程序:服務器和客戶端。

服務器和客戶端有不同的行爲; 因此,創建它們的過程是不同的。 以下是創建流式TCP / IP服務器和客戶端的一般模型。

 

一)、服務器

  1. 初始化Winsock。
  2. 創建一個套接字。
  3. 綁定套接字。
  4. 聽取客戶端的套接字。
  5. 接受來自客戶端的連接。
  6. 接收和發送數據。
  7. 斷開鏈接。

 

二)、客戶端

  1. 初始化Winsock。
  2. 創建一個套接字。
  3. 連接到服務器。
  4. 發送和接收數據。
  5. 斷開鏈接。

 

注意

對於客戶端和服務器,某些步驟是相同的。 這些步驟幾乎完全相同。 本指南中的某些步驟將特定於正在創建的應用程序類型。

 

 

二、創建一個基本的Winsock應用程序

1、爲了創建基本的Winsock應用程序

2、創建一個新的空項目。

3、將空C ++源文件添加到項目中。

4、確保構建環境引用Microsoft Windows軟件開發工具包(SDK)或早期平臺軟件開發工具包(SDK)的Include,Lib和Src目錄。確保構建環境鏈接到Winsock庫文件Ws2_32.lib。 使用Winsock的應用程序必須與Ws2_32.lib庫文件鏈接。 #pragma註釋向鏈接器指示需要Ws2_32.lib文件。

5、開始編寫Winsock應用程序。 通過包含Winsock 2頭文件來使用Winsock API。 Winsock2.h頭文件包含大多數Winsock函數,結構和定義。 Ws2tcpip.h頭文件包含在針對TCP / IP的WinSock 2協議特定附件文檔中引入的定義,其中包括用於檢索IP地址的較新功能和結構。

注意

Stdio.h用於標準輸入和輸出,特別是printf()函數。

#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>

#pragma comment(lib, "Ws2_32.lib")

int main() {
  return 0;
}

注意

如果應用程序正在使用IP Helper API,則需要Iphlpapi.h頭文件。 當需要Iphlpapi.h頭文件時,Winsock2.h頭文件的#include行應該放在Iphlpapi.h頭文件的#include行之前。

Winsock2.h頭文件內部包含來自Windows.h頭文件的核心元素,因此Winsock應用程序中的Windows.h頭文件通常沒有#include行。 如果Windows.h頭文件需要#include行,則應在#define WIN32_LEAN_AND_MEAN宏之前添加#include行。 由於歷史原因,Windows.h標頭默認包含Windows套接字1.1的Winsock.h頭文件。 Winsock.h頭文件中的聲明將與Windows Sockets 2.0所需的Winsock2.h頭文件中的聲明衝突。 WIN32_LEAN_AND_MEAN宏可防止Windows.h標頭包含Winsock.h。 示例說明如下所示。

#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif

#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iphlpapi.h>
#include <stdio.h>

#pragma comment(lib, "Ws2_32.lib")

int main() {
  return 0;
}

 

 

三、初始化Winsock

調用Winsock函數的所有進程(應用程序或DLL)必須在進行其他Winsock函數調用之前初始化Windows Sockets DLL的使用。 這也確保系統支持Winsock。

爲了初始化Winsock

1、創建一個名爲wsaData的WSADATA對象。

WSADATA wsaData;

2、調用WSAStartup並將其值作爲整數返回並檢查錯誤。

int iResult;

// Initialize Winsock
iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
if (iResult != 0) {
    printf("WSAStartup failed: %d\n", iResult);
    return 1;
}

調用WSAStartup函數以啓動WS2_32.dll的使用。

WSADATA結構包含有關Windows套接字實現的信息。 WSAStartup的MAKEWORD(2,2)參數在系統上發出對Winsock 2.2版的請求,並將傳遞的版本設置爲調用者可以使用的最高版本的Windows套接字支持。

 

 

四、Winsock客戶端應用程序

以下部分描述了創建Winsock客戶端應用程序的其餘步驟。 以下是創建流式TCP / IP客戶端的一般模型。

  • 爲客戶端創建套接字
  • 連接到插座
  • 在客戶端上發送和接收數據
  • 斷開客戶端連接

 

一)、爲客戶端創建套接字

初始化之後,必須實例化SOCKET對象以供客戶端使用。

爲了創建套接字

1、聲明包含sockaddr結構的addrinfo對象並初始化這些值。 對於此應用程序,未指定Internet地址系列,以便可以返回IPv6或IPv4地址。 應用程序請求套接字類型爲TCP協議的流套接字。

struct addrinfo *result = NULL,
                *ptr = NULL,
                hints;

ZeroMemory( &hints, sizeof(hints) );
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;

2、調用getaddrinfo函數,請求在命令行上傳遞的服務器名稱的IP地址。 客戶端將連接到的服務器上的TCP端口在此示例中由DEFAULT_PORT定義爲27015。 getaddrinfo函數將其值返回爲檢查錯誤的整數。

#define DEFAULT_PORT "27015"

// Resolve the server address and port
iResult = getaddrinfo(argv[1], DEFAULT_PORT, &hints, &result);
if (iResult != 0) {
    printf("getaddrinfo failed: %d\n", iResult);
    WSACleanup();
    return 1;
}

3、創建一個名爲ConnectSocket的SOCKET對象。

SOCKET ConnectSocket = INVALID_SOCKET;

4、調用套接字函數並將其值返回到ConnectSocket變量。 對於此應用程序,請使用調用getaddrinfo返回的第一個IP地址,該地址與hints參數中指定的地址系列,套接字類型和協議相匹配。 在此示例中,指定了TCP流套接字,其套接字類型爲SOCK_STREAM,協議爲IPPROTO_TCP。 地址系列未指定(AF_UNSPEC),因此返回的IP地址可以是服務器的IPv6或IPv4地址。

如果客戶端應用程序只想使用IPv6或IPv4進行連接,則需要在hints參數中將地址族設置爲IPv6的AF_INET6或IPv4的AF_INET。

// Attempt to connect to the first address returned by
// the call to getaddrinfo
ptr=result;

// Create a SOCKET for connecting to server
ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype, 
    ptr->ai_protocol);

5、檢查錯誤以確保套接字是有效的套接字。

if (ConnectSocket == INVALID_SOCKET) {
    printf("Error at socket(): %ld\n", WSAGetLastError());
    freeaddrinfo(result);
    WSACleanup();
    return 1;
}

傳遞給套接字函數的參數可以針對不同的實現進行更改。

錯誤檢測是成功的網絡代碼的關鍵部分。 如果套接字調用失敗,則返回INVALID_SOCKET。 上一代碼中的if語句用於捕獲創建套接字時可能發生的任何錯誤。 WSAGetLastError返回與上次發生的錯誤關聯的錯誤號。

注意

根據應用,可能需要進行更廣泛的錯誤檢查。

WSACleanup用於終止WS2_32 DLL的使用。

 

二)、連接到套接字

要使客戶端在網絡上進行通信,它必須連接到服務器。‘

爲了鏈接套接字

調用connect函數,將創建的套接字和sockaddr結構作爲參數傳遞。

// Connect to server.
iResult = connect( ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
if (iResult == SOCKET_ERROR) {
    closesocket(ConnectSocket);
    ConnectSocket = INVALID_SOCKET;
}

// Should really try the next address returned by getaddrinfo
// if the connect call failed
// But for this simple example we just free the resources
// returned by getaddrinfo and print an error message

freeaddrinfo(result);

if (ConnectSocket == INVALID_SOCKET) {
    printf("Unable to connect to server!\n");
    WSACleanup();
    return 1;
}

’getaddrinfo函數用於確定sockaddr結構中的值。 在此示例中,getaddrinfo函數返回的第一個IP地址用於指定傳遞給connect的sockaddr結構。 如果連接調用未能通過第一個IP地址,則嘗試從getaddrinfo函數返回的鏈表中的下一個addrinfo結構。

sockaddr結構中指定的信息包括:

  • 客戶端將嘗試連接的服務器的IP地址。
  • 客戶端將連接到的服務器上的端口號。 當客戶端調用getaddrinfo函數時,此端口被指定爲端口27015。

 

三)、在客戶端上發送和接收數據

以下代碼演示了建立連接後客戶端使用的send和recv函數。

客戶端

#define DEFAULT_BUFLEN 512

int recvbuflen = DEFAULT_BUFLEN;

char *sendbuf = "this is a test";
char recvbuf[DEFAULT_BUFLEN];

int iResult;

// Send an initial buffer
iResult = send(ConnectSocket, sendbuf, (int) strlen(sendbuf), 0);
if (iResult == SOCKET_ERROR) {
    printf("send failed: %d\n", WSAGetLastError());
    closesocket(ConnectSocket);
    WSACleanup();
    return 1;
}

printf("Bytes Sent: %ld\n", iResult);

// shutdown the connection for sending since no more data will be sent
// the client can still use the ConnectSocket for receiving data
iResult = shutdown(ConnectSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
    printf("shutdown failed: %d\n", WSAGetLastError());
    closesocket(ConnectSocket);
    WSACleanup();
    return 1;
}

// Receive data until the server closes the connection
do {
    iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0);
    if (iResult > 0)
        printf("Bytes received: %d\n", iResult);
    else if (iResult == 0)
        printf("Connection closed\n");
    else
        printf("recv failed: %d\n", WSAGetLastError());
} while (iResult > 0);

send和recv函數分別返回發送或接收的字節數的整數值或錯誤。 每個函數也採用相同的參數:活動套接字,字符緩衝區,要發送或接收的字節數,以及要使用的任何標誌。

 

四)、斷開客戶端連接

一旦客戶端完成發送和接收數據,客戶端將斷開與服務器的連接並關閉套接字。

爲了斷開連接並關閉套接字

1、當客戶端完成向服務器發送數據時,可以調用shutdown函數指定SD_SEND以關閉套接字的發送端。 這允許服務器釋放此套接字的一些資源。 客戶端應用程序仍然可以在套接字上接收數據。

// shutdown the send half of the connection since no more data will be sent
iResult = shutdown(ConnectSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
    printf("shutdown failed: %d\n", WSAGetLastError());
    closesocket(ConnectSocket);
    WSACleanup();
    return 1;
}

2、當客戶端應用程序完成接收數據時,將調用closesocket函數來關閉套接字。

使用Windows套接字DLL完成客戶端應用程序時,將調用WSACleanup函數以釋放資源。

// cleanup
closesocket(ConnectSocket);
WSACleanup();

return 0;

 

 

五、Winsock服務器應用程序

以下部分描述了創建Winsock服務器應用程序的其餘步驟。 以下是創建流式TCP / IP服務器的一般模型。

  • 爲服務器創建套接字
  • 綁定套接字
  • 聽一個插座
  • 接受連接
  • 在服務器上接收和發送數據
  • 斷開服務器連接

 

一)、爲服務器創建套接字

初始化之後,必須實例化SOCKET對象以供服務器使用。

爲服務器創建套接字

1、getaddrinfo函數用於確定sockaddr結構中的值:

  • AF_INET用於指定IPv4地址族。
  • SOCK_STREAM用於指定流套接字。
  • IPPROTO_TCP用於指定TCP協議。
  • AI_PASSIVE標誌表示調用者打算在調用bind函數時使用返回的套接字地址結構。 當設置AI_PASSIVE標誌並且getaddrinfo函數的nodename參數是NULL指針時,套接字地址結構的IP地址部分對於IPv4地址設置爲INADDR_ANY,對於IPv6地址設置爲IN6ADDR_ANY_INIT。
  • 27015是與客戶端將連接到的服務器關聯的端口號。

addadfo結構由getaddrinfo函數使用。

#define DEFAULT_PORT "27015"

struct addrinfo *result = NULL, *ptr = NULL, hints;

ZeroMemory(&hints, sizeof (hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;

// Resolve the local address and port to be used by the server
iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
if (iResult != 0) {
    printf("getaddrinfo failed: %d\n", iResult);
    WSACleanup();
    return 1;
}

2、爲服務器創建一個名爲ListenSocket的SOCKET對象以偵聽客戶端連接。

SOCKET ListenSocket = INVALID_SOCKET;

3、調用套接字函數並將其值返回給ListenSocket變量。 對於此服務器應用程序,請使用調用getaddrinfo返回的第一個IP地址,該地址與hints參數中指定的地址系列,套接字類型和協議相匹配。 在此示例中,請求IPv4的TCP流套接字,其地址系列爲IPv4,套接字類型爲SOCK_STREAM,協議爲IPPROTO_TCP。 因此,爲ListenSocket請求了一個IPv4地址。

如果服務器應用程序想要偵聽IPv6,則需要在hints參數中將地址族設置爲AF_INET6。 如果服務器想要同時偵聽IPv6和IPv4,則必須創建兩個偵聽套接字,一個用於IPv6,另一個用於IPv4。 這兩個插座必須由應用程序單獨處理。

Windows Vista及更高版本提供了創建單個IPv6套接字的功能,該套接字處於雙堆棧模式以偵聽IPv6和IPv4。 有關此功能的更多信息,請參閱雙棧套接字。

// Create a SOCKET for the server to listen for client connections

ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);

4、檢查錯誤以確保套接字是有效的套接字。

if (ListenSocket == INVALID_SOCKET) {
    printf("Error at socket(): %ld\n", WSAGetLastError());
    freeaddrinfo(result);
    WSACleanup();
    return 1;
}

 

二)、綁定套接字

要使服務器接受客戶端連接,必須將其綁定到系統中的網絡地址。 以下代碼演示瞭如何將已創建的套接字綁定到IP地址和端口。 客戶端應用程序使用IP地址和端口連接到主機網絡。

爲了綁定套接字

sockaddr結構保存有關地址系列,IP地址和端口號的信息。

調用bind函數,將從getaddrinfo函數返回的創建的socket和sockaddr結構作爲參數傳遞。 檢查一般錯誤。

// Setup the TCP listening socket
    iResult = bind( ListenSocket, result->ai_addr, (int)result->ai_addrlen);
    if (iResult == SOCKET_ERROR) {
        printf("bind failed with error: %d\n", WSAGetLastError());
        freeaddrinfo(result);
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }

調用bind函數後,不再需要getaddrinfo函數返回的地址信息。 調用freeaddrinfo函數以釋放getaddrinfo函數爲此地址信息分配的內存。

freeaddrinfo(result);

 

三)、監聽套接字

套接字綁定到系統上的IP地址和端口後,服務器必須偵聽該IP地址和端口以獲取傳入的連接請求。

爲了監聽套接字

調用listen函數,將創建的套接字作爲參數傳遞,並將backlog的值,待接受的掛起隊列的最大長度作爲接受。 在此示例中,backlog參數設置爲SOMAXCONN。 此值是一個特殊常量,它指示Winsock提供程序爲此套接字允許隊列中最大合理數量的掛起連接。 檢查一般錯誤的返回值。

if ( listen( ListenSocket, SOMAXCONN ) == SOCKET_ERROR ) {
    printf( "Listen failed with error: %ld\n", WSAGetLastError() );
    closesocket(ListenSocket);
    WSACleanup();
    return 1;
}

 

四)、接受連接

一旦套接字正在偵聽連接,程序必須處理該套接字上的連接請求。

爲了接受套接字上的連接

1、創建一個名爲ClientSocket的臨時SOCKET對象,用於接受來自客戶端的連接。

SOCKET ClientSocket;

2、通常,服務器應用程序將被設計爲偵聽來自多個客戶端的連接。 對於高性能服務器,通常使用多個線程來處理多個客戶端連接。

使用Winsock有幾種不同的編程技術可用於偵聽多個客戶端連接。 一種編程技術是創建一個連續循環,使用listen函數檢查連接請求(請參閱偵聽套接字)。 如果發生連接請求,應用程序將調用accept,AcceptEx或WSAAccept函數,並將工作傳遞給另一個線程來處理請求。 其他幾種編程技術也是可能的。

請注意,此基本示例非常簡單,不使用多個線程。 該示例也只是偵聽並接受單個連接。

ClientSocket = INVALID_SOCKET;

// Accept a client socket
ClientSocket = accept(ListenSocket, NULL, NULL);
if (ClientSocket == INVALID_SOCKET) {
    printf("accept failed: %d\n", WSAGetLastError());
    closesocket(ListenSocket);
    WSACleanup();
    return 1;
}

3、當客戶端連接被接受時,服務器應用程序通常會將接受的客戶端套接字(上面示例代碼中的ClientSocket變量)傳遞給工作線程或I / O完成端口,並繼續接受其他連接。 在此基本示例中,服務器繼續執行下一步。

有許多其他編程技術可用於偵聽和接受多個連接。 這些包括使用select或WSAPoll函數。 Microsoft Windows軟件開發工具包(SDK)附帶的Advanced Winsock示例中說明了這些各種編程技術中的一些示例。

注意

在Unix系統上,服務器的通用編程技術是用於監聽連接的應用程序。 當接受連接時,父進程將調用fork函數來創建一個新的子進程來處理客戶端連接,從父進程繼承套接字。 Windows不支持此編程技術,因爲不支持fork函數。 此技術通常也不適用於高性能服務器,因爲創建新進程所需的資源遠遠大於線程所需的資源。

 

五)、在服務器上接收和發送數據

以下代碼演示了服務器使用的recv和send函數。

爲了在套接字上接收和發送數據

#define DEFAULT_BUFLEN 512

char recvbuf[DEFAULT_BUFLEN];
int iResult, iSendResult;
int recvbuflen = DEFAULT_BUFLEN;

// Receive until the peer shuts down the connection
do {

    iResult = recv(ClientSocket, recvbuf, recvbuflen, 0);
    if (iResult > 0) {
        printf("Bytes received: %d\n", iResult);

        // Echo the buffer back to the sender
        iSendResult = send(ClientSocket, recvbuf, iResult, 0);
        if (iSendResult == SOCKET_ERROR) {
            printf("send failed: %d\n", WSAGetLastError());
            closesocket(ClientSocket);
            WSACleanup();
            return 1;
        }
        printf("Bytes sent: %d\n", iSendResult);
    } else if (iResult == 0)
        printf("Connection closing...\n");
    else {
        printf("recv failed: %d\n", WSAGetLastError());
        closesocket(ClientSocket);
        WSACleanup();
        return 1;
    }

} while (iResult > 0);

send和recv函數分別返回發送或接收的字節數的整數值或錯誤。 每個函數也採用相同的參數:活動套接字,字符緩衝區,要發送或接收的字節數,以及要使用的任何標誌。

 

六)、斷開服務器連接

一旦服務器完成從客戶端接收數據並將數據發送回客戶端,服務器將斷開與客戶端的連接並關閉套接字。

爲了斷開服務器連接

1、當服務器完成向客戶端發送數據時,可以調用shutdown函數指定SD_SEND以關閉套接字的發送端。 這允許客戶端釋放此套接字的一些資源。 服務器應用程序仍然可以在套接字上接收數據。

// shutdown the send half of the connection since no more data will be sent
iResult = shutdown(ClientSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
    printf("shutdown failed: %d\n", WSAGetLastError());
    closesocket(ClientSocket);
    WSACleanup();
    return 1;
}

2、當客戶端應用程序完成接收數據時,將調用closesocket函數來關閉套接字。

使用Windows套接字DLL完成客戶端應用程序時,將調用WSACleanup函數以釋放資源。

// cleanup
closesocket(ClientSocket);
WSACleanup();

return 0;

 

 

六、運行Winsock客戶端和服務器代碼示例

本節包含TCP / IP客戶端和服務器應用程序的完整源代碼:

  • 完整的Winsock客戶端代碼
  • 完整的Winsock服務器代碼

應在啓動客戶端應用程序之前啓動服務器應用程序。

要執行服務器,請編譯完整的服務器源代碼並運行可執行文件。 服務器應用程序偵聽TCP端口27015以供客戶端連接。 一旦客戶端連接,服務器就會從客戶端接收數據,並將收到的數據回送(發送)回客戶端。 當客戶端關閉連接時,服務器會關閉客戶端套接字,關閉套接字並退出。

要執行客戶端,請編譯完整的客戶端源代碼並運行可執行文件。 客戶端應用程序要求在執行客戶端時將運行服務器應用程序的計算機的名稱或IP地址作爲命令行參數傳遞。 如果客戶端和服務器在示例計算機上執行,則可以按如下方式啓動客戶端:

客戶端localhost

客戶端嘗試在TCP端口27015上連接到服務器。客戶端連接後,客戶端將數據發送到服務器並接收從服務器發回的任何數據。 然後客戶端關閉套接字並退出。

 

一)、完整的Winsock客戶端代碼

以下是基本Winsock TCP / IP客戶端應用程序的完整源代碼。

Winsock客戶端源代碼

#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>


// Need to link with Ws2_32.lib, Mswsock.lib, and Advapi32.lib
#pragma comment (lib, "Ws2_32.lib")
#pragma comment (lib, "Mswsock.lib")
#pragma comment (lib, "AdvApi32.lib")


#define DEFAULT_BUFLEN 512
#define DEFAULT_PORT "27015"

int __cdecl main(int argc, char **argv) 
{
    WSADATA wsaData;
    SOCKET ConnectSocket = INVALID_SOCKET;
    struct addrinfo *result = NULL,
                    *ptr = NULL,
                    hints;
    char *sendbuf = "this is a test";
    char recvbuf[DEFAULT_BUFLEN];
    int iResult;
    int recvbuflen = DEFAULT_BUFLEN;
    
    // Validate the parameters
    if (argc != 2) {
        printf("usage: %s server-name\n", argv[0]);
        return 1;
    }

    // Initialize Winsock
    iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
    if (iResult != 0) {
        printf("WSAStartup failed with error: %d\n", iResult);
        return 1;
    }

    ZeroMemory( &hints, sizeof(hints) );
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;

    // Resolve the server address and port
    iResult = getaddrinfo(argv[1], DEFAULT_PORT, &hints, &result);
    if ( iResult != 0 ) {
        printf("getaddrinfo failed with error: %d\n", iResult);
        WSACleanup();
        return 1;
    }

    // Attempt to connect to an address until one succeeds
    for(ptr=result; ptr != NULL ;ptr=ptr->ai_next) {

        // Create a SOCKET for connecting to server
        ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype, 
            ptr->ai_protocol);
        if (ConnectSocket == INVALID_SOCKET) {
            printf("socket failed with error: %ld\n", WSAGetLastError());
            WSACleanup();
            return 1;
        }

        // Connect to server.
        iResult = connect( ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
        if (iResult == SOCKET_ERROR) {
            closesocket(ConnectSocket);
            ConnectSocket = INVALID_SOCKET;
            continue;
        }
        break;
    }

    freeaddrinfo(result);

    if (ConnectSocket == INVALID_SOCKET) {
        printf("Unable to connect to server!\n");
        WSACleanup();
        return 1;
    }

    // Send an initial buffer
    iResult = send( ConnectSocket, sendbuf, (int)strlen(sendbuf), 0 );
    if (iResult == SOCKET_ERROR) {
        printf("send failed with error: %d\n", WSAGetLastError());
        closesocket(ConnectSocket);
        WSACleanup();
        return 1;
    }

    printf("Bytes Sent: %ld\n", iResult);

    // shutdown the connection since no more data will be sent
    iResult = shutdown(ConnectSocket, SD_SEND);
    if (iResult == SOCKET_ERROR) {
        printf("shutdown failed with error: %d\n", WSAGetLastError());
        closesocket(ConnectSocket);
        WSACleanup();
        return 1;
    }

    // Receive until the peer closes the connection
    do {

        iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0);
        if ( iResult > 0 )
            printf("Bytes received: %d\n", iResult);
        else if ( iResult == 0 )
            printf("Connection closed\n");
        else
            printf("recv failed with error: %d\n", WSAGetLastError());

    } while( iResult > 0 );

    // cleanup
    closesocket(ConnectSocket);
    WSACleanup();

    return 0;
}

 

二)、完整的Winsock服務器代碼

以下是基本Winsock TCP / IP Server應用程序的完整源代碼。

Winsock服務器源代碼

#undef UNICODE

#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>

// Need to link with Ws2_32.lib
#pragma comment (lib, "Ws2_32.lib")
// #pragma comment (lib, "Mswsock.lib")

#define DEFAULT_BUFLEN 512
#define DEFAULT_PORT "27015"

int __cdecl main(void) 
{
    WSADATA wsaData;
    int iResult;

    SOCKET ListenSocket = INVALID_SOCKET;
    SOCKET ClientSocket = INVALID_SOCKET;

    struct addrinfo *result = NULL;
    struct addrinfo hints;

    int iSendResult;
    char recvbuf[DEFAULT_BUFLEN];
    int recvbuflen = DEFAULT_BUFLEN;
    
    // Initialize Winsock
    iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
    if (iResult != 0) {
        printf("WSAStartup failed with error: %d\n", iResult);
        return 1;
    }

    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;
    hints.ai_flags = AI_PASSIVE;

    // Resolve the server address and port
    iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
    if ( iResult != 0 ) {
        printf("getaddrinfo failed with error: %d\n", iResult);
        WSACleanup();
        return 1;
    }

    // Create a SOCKET for connecting to server
    ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
    if (ListenSocket == INVALID_SOCKET) {
        printf("socket failed with error: %ld\n", WSAGetLastError());
        freeaddrinfo(result);
        WSACleanup();
        return 1;
    }

    // Setup the TCP listening socket
    iResult = bind( ListenSocket, result->ai_addr, (int)result->ai_addrlen);
    if (iResult == SOCKET_ERROR) {
        printf("bind failed with error: %d\n", WSAGetLastError());
        freeaddrinfo(result);
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }

    freeaddrinfo(result);

    iResult = listen(ListenSocket, SOMAXCONN);
    if (iResult == SOCKET_ERROR) {
        printf("listen failed with error: %d\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }

    // Accept a client socket
    ClientSocket = accept(ListenSocket, NULL, NULL);
    if (ClientSocket == INVALID_SOCKET) {
        printf("accept failed with error: %d\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }

    // No longer need server socket
    closesocket(ListenSocket);

    // Receive until the peer shuts down the connection
    do {

        iResult = recv(ClientSocket, recvbuf, recvbuflen, 0);
        if (iResult > 0) {
            printf("Bytes received: %d\n", iResult);

        // Echo the buffer back to the sender
            iSendResult = send( ClientSocket, recvbuf, iResult, 0 );
            if (iSendResult == SOCKET_ERROR) {
                printf("send failed with error: %d\n", WSAGetLastError());
                closesocket(ClientSocket);
                WSACleanup();
                return 1;
            }
            printf("Bytes sent: %d\n", iSendResult);
        }
        else if (iResult == 0)
            printf("Connection closing...\n");
        else  {
            printf("recv failed with error: %d\n", WSAGetLastError());
            closesocket(ClientSocket);
            WSACleanup();
            return 1;
        }

    } while (iResult > 0);

    // shutdown the connection since we're done
    iResult = shutdown(ClientSocket, SD_SEND);
    if (iResult == SOCKET_ERROR) {
        printf("shutdown failed with error: %d\n", WSAGetLastError());
        closesocket(ClientSocket);
        WSACleanup();
        return 1;
    }

    // cleanup
    closesocket(ClientSocket);
    WSACleanup();

    return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章