4 客户端

客户端

1 为客户端创建一个socket

1.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;

1.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;
}

1.3 创建一个名为ConnectSocket的SOCKET对象。

SOCKET ConnectSocket = INVALID_SOCKET;

1.4 调用套接字函数,并将其值返回给ConnectSocket变量

对于此应用程序,使用调用返回的第一个IP地址到getaddrinfo,该地址与hints参数中指定的地址族,套接字类型和协议相匹配。

在此示例中,使用套接字类型SOCK_STREAM和协议IPPROTO_TCP指定了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);

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

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

可以为不同的实现更改传递给套接字函数的参数。

错误检测是成功的网络代码的关键部分。 如果套接字调用失败,则返回INVALID_SOCKET。 先前代码中的if语句用于捕获创建套接字时可能发生的任何错误。 WSAGetLastError返回与最近发生的错误关联的错误号。

2 连接socket

调用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地址用于指定传递给连接的sockaddr结构。

如果connect调用失败到第一个IP地址,请尝试从getaddrinfo函数返回的链表中的下一个addrinfo结构。

sockaddr结构中指定的信息包括:

客户端将尝试连接的服务器的IP地址。

客户端将连接到的服务器上的端口号。 当客户端调用getaddrinfo函数时,此端口被指定为端口27015。

3 在客户端上发送和接收数据

下面的代码演示了建立连接后客户端使用的send和recv函数。

#define DEFAULT_BUFLEN 512

int recvbuflen = DEFAULT_BUFLEN;

const 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函数都分别返回发送或接收的字节数的整数值,或者返回错误。

每个函数还采用相同的参数:活动套接字,char缓冲区,要发送或接收的字节数以及要使用的任何标志。

4 断开客户端连接

客户端完成发送和接收数据后,客户端将与服务器断开连接并关闭套接字。

4.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;
}

4.2 closesocket

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

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

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

return 0;

完整的客户端代码

#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;
    const 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;
}
文章目录