1. 程式人生 > >使用Winsock:Winsock入門

使用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;
}