C++网络编程之C/S模型

概述

        在网络编程中,客户端/服务器模型(即C/S模型)是一种常见的架构模式。在这种模式下,一个或多个客户端向服务器建立连接,并发送请求;服务器接受这些连接,并处理请求、返回响应。

        在C/S模型中,客户端会主动发起与服务器的连接,发送请求,并接收服务器的响应。客户端可以是任何能够发起网络连接的设备或应用程序。服务器通常运行在网络中的固定位置,监听特定端口以接受来自客户端的连接请求。

TCP客户端

        TCP客户端负责发起与服务器的连接,并发送请求和接收响应。TCP客户端主要包含以下五个步骤,分别为:创建套接字、连接到服务器、发送数据、接收数据、关闭套接字。

        1、创建Socket。首先,我们需要创建一个新的Socket实例。这一步,是通过调用socket函数来完成的。

        2、连接到服务器。我们需要指定服务器的IP地址和端口号,并尝试建立连接。这是通过设置sockaddr_in结构体,并调用connect函数来完成的。默认情况下,connect函数是阻塞的。也就是说,只有等客户端完全连接上服务器,或者发生了错误,connect函数才会返回。

        3、发送数据。一旦连接成功,我们就可以通过这个套接字向服务器发送数据了。这一步,是通过send或write函数来实现的。

        4、接收数据。除了向服务器发送数据,客户端还可以从服务器接收数据,因为TCP连接是双向的、全双工的。这一步,是通过recv或read函数来实现的。

        5、关闭套接字。当通信完成后,应当关闭套接字以释放系统资源。

        为了便于理解上面的步骤,我们可以参考下面的流程图。

        根据上面的流程图,我们可以编写下面的示例代码。该示例代码是一个简易的的TCP客户端程序,用于连接本机的8888端口,并进行简单的文字通信。

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

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

int main()
{
    WSADATA wsaData;
    // 初始化Winsock
    int nRet = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (nRet != 0)
    {
        printf("WSAStartup failed: %d\n", nRet);
        return 1;
    }

    SOCKET sockClient = INVALID_SOCKET;
    do
    {
        // 创建套接字
        sockClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if (sockClient == INVALID_SOCKET)
        {
            printf("Create socket failed: %d\n", WSAGetLastError());
            break;
        }

        // 连接到服务器
        struct sockaddr_in serverAddr;
        ZeroMemory(&serverAddr, sizeof(serverAddr));
        serverAddr.sin_family = AF_INET;
        serverAddr.sin_port = htons(8888);
        inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr);
        nRet = connect(sockClient, (struct sockaddr *)&serverAddr, sizeof(serverAddr));
        if (nRet == SOCKET_ERROR)
        {
            printf("Connect server failed: %d\n", WSAGetLastError());
            break;
        }

        // 发送数据
        char pszSendBuf[] = "Hello from client";
        nRet = send(sockClient, pszSendBuf, (int)strlen(pszSendBuf), 0);
        if (nRet == SOCKET_ERROR)
        {
            printf("Send data failed: %d\n", WSAGetLastError());
            break;
        }

        // 接收数据
        char pszRecvBuf[512] = { 0 };
        int nRecvBufLen = sizeof(pszRecvBuf);
        nRet = recv(sockClient, pszRecvBuf, nRecvBufLen, 0);
        if (nRet > 0)
        {
            printf("Received data: %s\n", pszRecvBuf);
        }
        else if (nRet == 0)
        {
            printf("Connection closed\n");
            break;
        }
        else
        {
            printf("Recv data failed: %d\n", WSAGetLastError());
            break;
        }
    } while (false);

    // 关闭套接字
    if (sockClient != INVALID_SOCKET)
    {
        closesocket(sockClient);
        sockClient = INVALID_SOCKET;
    }
    
    WSACleanup();
    getchar();
    return 0;
}

TCP服务器

        在网络编程的C/S模型中,服务器是提供服务的一方。它通常负责处理来自多个客户端的请求,执行相应的业务逻辑,并将结果返回给客户端。TCP服务器主要包含以下八个步骤,分别为:创建服务套接字、绑定套接字、监听连接请求、接受连接请求、接收数据、发送数据、关闭数据套接字、关闭服务套接字。

        1、创建服务套接字。首先,我们需要创建一个新的Socket实例作为服务套接字。这一步,是通过调用socket函数来完成的。

        2、绑定套接字。创建并初始化一个sockaddr_in结构体,用于存储服务器的IP地址和端口号。然后,使用bind函数将套接字绑定到指定的IP地址和端口。

        3、监听连接请求。使用listen函数使套接字进入监听状态,以便可以接受来自客户端的连接请求。

        4、接受连接请求。accept函数用于从已连接的客户端队列中接受一个连接请求。当一个客户端尝试连接到服务器时,服务器会将这个连接请求放入一个队列中(由listen函数设置的最大长度控制)。服务器可以通过调用accept函数来接受这些连接请求,并创建一个新的数据套接字来与该客户端进行通信。

        5、接收数据。使用recv或read函数通过已建立的数据套接字接收数据。

        6、发送数据:使用send或write函数通过已建立的数据套接字发送数据。

        7、关闭数据套接字。当不需要数据通信时,关闭客户端套接字。

        8、关闭服务套接字。在服务器退出时,关闭服务套接字。

        为了便于理解上面的步骤,我们可以参考下面的流程图。

        根据上面的流程图,我们可以编写下面的示例代码。该示例代码是一个简易的的TCP服务器程序,用于监听端口8888,并处理客户端连接,以进行简单的文字通信。

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

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

int main()
{
    // 初始化Winsock
    WSADATA wsaData;
    int nRet = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (nRet != 0)
    {
        printf("WSAStartup failed: %d\n", nRet);
        return 1;
    }

    SOCKET serverSocket = INVALID_SOCKET;
    SOCKET dataSocket = INVALID_SOCKET;
    do 
    {
        // 创建服务器套接字
        serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if (serverSocket == INVALID_SOCKET)
        {
            printf("Create socket failed: %d\n", WSAGetLastError());
            break;
        }

        // 绑定套接字
        struct sockaddr_in serverAddr;
        ZeroMemory(&serverAddr, sizeof(serverAddr));
        serverAddr.sin_family = AF_INET;
        serverAddr.sin_port = htons(8888);
        serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);                                         
        nRet = bind(serverSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr));
        if (nRet == SOCKET_ERROR)
        {
            printf("Bind socket failed: %d\n", WSAGetLastError());
            break;
        }

        // 监听连接请求
        nRet = listen(serverSocket, 10);
        if (nRet == SOCKET_ERROR)
        {
            printf("Listen failed: %d\n", WSAGetLastError());
            break;
        }

        printf("Server listening on port %d ...\n", 8888);

        // 接受连接请求
        struct sockaddr_in clientAddr;
        int clientAddrSize = sizeof(clientAddr);
        dataSocket = accept(serverSocket, (struct sockaddr *)&clientAddr, &clientAddrSize);
        if (dataSocket == INVALID_SOCKET)
        {
            printf("Accept failed: %d\n", WSAGetLastError());
            break;
        }

        // 接收数据
        char pRecvBuf[512] = { 0 };
        int nRecvBufLen = sizeof(pRecvBuf);
        nRet = recv(dataSocket, pRecvBuf, nRecvBufLen, 0);
        if (nRet > 0)
        {
            printf("Received data: %s\n", pRecvBuf);

            // 发送数据
            const char *pszSendBuf = "Hello from server";
            nRet = send(dataSocket, pszSendBuf, (int)strlen(pszSendBuf), 0);
            if (nRet == SOCKET_ERROR)
            {
                break;
            }
        }
        else if (nRet == 0)
        {
            printf("Connection closed\n");
            break;
        }
        else
        {
            printf("Recv data failed: %d\n", WSAGetLastError());
            break;
        }
    } while (false);

    // 关闭数据通信套接字
    if (dataSocket != INVALID_SOCKET)
    {
        closesocket(dataSocket);
        dataSocket = INVALID_SOCKET;
    }

    // 关闭服务器套接字
    if (serverSocket != INVALID_SOCKET)
    {
        closesocket(serverSocket);
        serverSocket = INVALID_SOCKET;
    }
    
    WSACleanup();
    getchar();
    return 0;
}

注意事项

        在开发C/S模型的客户端和服务器程序时,需要特别注意以下几点。

        1、错误处理。在整个过程中,应该注意检查每个操作是否成功,并妥善处理可能发生的错误。

        2、并发控制。如果客户端需要同时与多个服务器通信或者处理多条消息,可能需要考虑线程或异步IO等技术来提高效率。

        3、安全性。对于敏感信息的传输,应采用安全协议,比如:TLS/SSL加密通信。

猜你喜欢

转载自blog.csdn.net/hope_wisdom/article/details/143224652