3 服务器

1 初始化Winsock

WSADATA wsaData;
int iResult;

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

2 为服务器创建套接字

2.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是与客户端将连接到的服务器关联的端口号。

#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.2 为服务器创建一个名为ListenSocket 的SOCKET对象,以侦听客户端连接。

SOCKET ListenSocket = INVALID_SOCKET;

2.3 调用套接字函数,并将其值返回到ListenSocket变量。

对于此服务器应用程序,使用调用返回的第一个IP地址获取与addsinfo参数(在hints参数中指定的地址系列,套接字类型和协议)相匹配的getaddrinfo。在此示例中,使用IPv4的地址族,SOCK_STREAM的套接字类型和IPPROTO_TCP的协议请求了IPv4的TCP流套接字。因此,为ListenSocket请求了一个IPv4地址。

如果服务器应用程序要侦听IPv6,则需要在hints参数中将地址族设置为AF_INET6 。如果服务器要同时监听IPv6和IPv4,则必须创建两个监听套接字,一个用于IPv6,另一个用于IPv4。这两个套接字必须由应用程序分别处理。

Windows Vista和更高版本提供了创建单个IPv6套接字的功能,该套接字置于双堆栈模式下,可以同时侦听IPv6和IPv4。有关此功能的更多信息,请参见Dual-Stack Sockets。

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

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

2.4 检查错误以确保该套接字是有效的套接字。

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

3 绑定套接字

该sockaddr的结构保存有关家庭地址,IP地址和端口号的信息。

调用bind函数,将从getaddrinfo函数返回的创建的套接字和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;
}

一旦绑定函数被调用,在返回的地址信息的getaddrinfo功能不再需要。该freeaddrinfo函数被调用,以释放该分配的内存的getaddrinfo这个地址信息的功能。

freeaddrinfo(result);

4 监听套接字

调用listen函数,将创建的套接字和backlog的值作为参数传递,backlog的值是要接受的未决连接队列的最大长度。在此示例中,backlog参数设置为SOMAXCONN。该值是一个特殊的常数,它指示Winsock提供程序为此套接字允许在队列中允许最大合理数量的挂起连接。检查返回值是否存在一般错误。

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

5 接受连接

5.1 创建一个名为ClientSocket 的临时SOCKET对象,以接受来自客户端的连接。

SOCKET ClientSocket;

5.2 通常,服务器应用程序将被设计为侦听来自多个客户端的连接。对于高性能服务器,通常使用多个线程来处理多个客户端连接。

使用Winsock有几种不同的编程技术,可用于侦听多个客户端连接。一种编程技术是创建一个连续循环,该循环使用listen函数检查连接请求(请参见在Socket上侦听)。如果发生连接请求,则应用程序将调用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;
}

5.3 接受客户端连接后,服务器应用程序通常会将接受的客户端套接字(上述示例代码中的ClientSocket变量)传递给工作线程或I / O完成端口,然后继续接受其他连接。在此基本示例中,服务器继续进行下一步。

还有许多其他编程技术可用于侦听和接受多个连接。这些包括使用select或WSAPoll函数。Microsoft Windows软件开发工具包(SDK)附带的Advanced Winsock Samples中说明了其中各种编程技术的示例。

在Unix系统上,服务器的一种常见编程技术是使应用程序侦听连接。接受连接后,父进程将调用fork函数来创建一个新的子进程来处理客户端连接,并从父进程继承套接字。Windows不支持该编程技术,因为不支持fork功能。这种技术通常也不适合高性能服务器,因为创建新进程所需的资源比线程所需的资源大得多。

6 在套接字上接收和发送数据

#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);

的发送和recv的功能都返回分别发送或接收的,字节数,或错误的一个整数值。每个函数还采用相同的参数:活动套接字,char缓冲区,要发送或接收的字节数以及要使用的任何标志。

7 断开服务器

7.1 服务器完成向客户端的数据发送后,可以通过指定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;
}

7.2 客户端应用程序完成数据接收后,将调用closesocket函数关闭套接字。

使用Windows套接字DLL完成客户端应用程序后,将调用WSACleanup函数以释放资源。

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

return 0;

完整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;
}
文章目录