Winsock库学习3-客户端编程

为客户端创建套接字

  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) {
        cout << "getaddrinfo failed:" << iResult << endl;
        WSACleanup();
        return 1;
    }
    
  3. 创建一个名为ConnectSocket的SOCKET对象。

    SOCKET ConnectSocket = INVALID_SOCKET;
    
  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);
    
  5. 检查是否有错误,以确保该套接字是有效的套接字。

    if (ConnectSocket == INVALID_SOCKET) {
        cout << "Error at socket():" << WSAGetLastError() << endl;
        freeaddrinfo(result);
        WSACleanup();
        return 1;
    }
    

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

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

连接到套接字

为了使客户端能够在网络上进行通信,它必须连接到服务器。

调用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) {
    cout << "Unable to connect to server!" << endl;
    WSACleanup();
    return 1;
}

所述的getaddrinfo函数被用来确定在该值的sockaddr结构。在此示例中,由getaddrinfo函数返回的第一个IP地址用于指定传递给connectsockaddr结构。如果connect调用失败到第一个IP地址,请尝试从getaddrinfo函数返回的链表中的下一个addrinfo结构。

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

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

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

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

#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) {
    cout << "send failed:" << WSAGetLastError() << endl;
    closesocket(ConnectSocket);
    WSACleanup();
    return 1;
}

cout << "Bytes Sent:" << iResult << endl;

// 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) {
    cout << "shutdown failed: " << WSAGetLastError() << endl;
    closesocket(ConnectSocket);
    WSACleanup();
    return 1;
}

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

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

断开客户端连接

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

  1. 当客户端完成向服务器的数据发送后,可以通过指定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) {
        cout << "shutdown failed:" << WSAGetLastError()<< endl;
        closesocket(ConnectSocket);
        WSACleanup();
        return 1;
    }
    
  2. 客户端应用程序完成数据接收后,将调用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) {
        cout<<"usage:"<<argv[0]<<" server-name"<<endl;
        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;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值