系列目录

上一篇:白骑士的C++教学实战项目篇 4.2 学生成绩管理系统

        在这一节中,我们将实现一个多线程网络服务器项目,通过该项目,我们将学习套接字编程的基础知识以及如何使用多线程处理并发连接。此外,我们还将实现一个简单的客户端来与服务器进行通信。

项目简介

        多线程网络服务器是一种可以同时处理多个客户端请求的服务器。通过多线程技术,我们可以使服务器在处理一个客户端请求时,不阻塞其他客户端的连接请求。本项目将涉及以下内容:

  • 套接字编程基础
  • 多线程处理
  • 客户端与服务器的实现

套接字编程基础

        套接字(Socket)是网络编程的基础,它提供了在网络上进行数据通信的机制。在C++中,我们可以使用POSIX标准库(如 ‘sys/socket.h‘、‘arpa/inet.h‘ 等)来进行套接字编程。下面是一个基本的服务器和客户端通信的示例。

服务器端代码

#include <iostream>
#include <unistd.h>
#include <netinet/in.h>
#include <string.h>
#include <thread>


const int PORT = 8080;


void handleClient(int clientSocket) {
    char buffer[1024] = {0};

    read(clientSocket, buffer, 1024);

    std::cout << "Message from client: " << buffer << std::endl;

    send(clientSocket, buffer, strlen(buffer), 0);
    close(clientSocket);
}


int main() {
    int serverFd, clientSocket;
    struct sockaddr_in address;
    int opt = 1;
    int addrlen = sizeof(address);

    // 创建服务器端套接字
    if ((serverFd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");

        exit(EXIT_FAILURE);
    }

    // 设置套接字选项
    if (setsockopt(serverFd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
        perror("setsockopt failed");

        exit(EXIT_FAILURE);
    }

    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    // 绑定套接字
    if (bind(serverFd, (struct sockaddr*)&address, sizeof(address)) < 0) {
        perror("bind failed");

        exit(EXIT_FAILURE);
    }

    // 监听连接
    if (listen(serverFd, 3) < 0) {
        perror("listen failed");

        exit(EXIT_FAILURE);
    }

    while (true) {
        std::cout << "Waiting for connections...\n";

        if ((clientSocket = accept(serverFd, (struct sockaddr*)&address, (socklen_t*)&addrlen)) < 0) {
            perror("accept failed");

            exit(EXIT_FAILURE);
        }

        std::thread clientThread(handleClient, clientSocket);

        clientThread.detach(); // 将线程分离,以便独立运行
    }

    close(serverFd);

    return 0;
}

客户端代码

#include <iostream>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>


const int PORT = 8080;


int main() {
    int sock = 0;
    struct sockaddr_in serv_addr;
    char* message = "Hello from client";
    char buffer[1024] = {0};

    // 创建客户端套接字
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        std::cerr << "Socket creation error" << std::endl;

        return -1;
    }

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);

    // 将IP地址转换为二进制格式
    if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
        std::cerr << "Invalid address/ Address not supported" << std::endl;

        return -1;
    }

    // 连接服务器
    if (connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
        std::cerr << "Connection failed" << std::endl;

        return -1;
    }

    send(sock, message, strlen(message), 0);

    std::cout << "Message sent" << std::endl;

    read(sock, buffer, 1024);

    std::cout << "Message from server: " << buffer << std::endl;

    close(sock);

    return 0;
}

多线程处理

        在上述服务器代码中,我们使用了 ‘std::thread‘ 来为每个客户端连接创建一个新线程,并使用 ‘detach‘ 将其分离,使线程能够独立运行而不阻塞主线程。这种方式可以有效地处理多个客户端的并发连接。

客户端与服务器的实现

        我们已经实现了基本的客户端和服务器通信。为了使系统更健壮,可以考虑添加以下功能:

  • 异常处理:处理各种可能的网络异常,如连接失败、读写失败等。
  • 日志记录:记录客户端连接和断开时间、传输的数据等信息。
  • 并发控制:限制同时连接的客户端数量,防止服务器过载。

完整代码示例

#include <iostream>

#include <unistd.h>

#include <netinet/in.h>

#include <string.h>
#include <thread>
#include <vector>
#include <mutex>


const int PORT = 8080;


std::mutex coutMutex;


void handleClient(int clientSocket) {
    char buffer[1024] = {0};

    read(clientSocket, buffer, 1024);

    {
        std::lock_guard<std::mutex> lock(coutMutex);
        std::cout << "Message from client: " << buffer << std::endl;
    }

    send(clientSocket, buffer, strlen(buffer), 0);

    close(clientSocket);
}


int main() {
    int serverFd, clientSocket;
    struct sockaddr_in address;
    int opt = 1;
    int addrlen = sizeof(address);

    // 创建服务器端套接字
    if ((serverFd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");

        exit(EXIT_FAILURE);
    }

    // 设置套接字选项
    if (setsockopt(serverFd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
        perror("setsockopt failed");

        exit(EXIT_FAILURE);
    }

    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    // 绑定套接字
    if (bind(serverFd, (struct sockaddr*)&address, sizeof(address)) < 0) {
        perror("bind failed");

        exit(EXIT_FAILURE);
    }

    // 监听连接
    if (listen(serverFd, 3) < 0) {
        perror("listen failed");

        exit(EXIT_FAILURE);
    }

    while (true) {
        std::cout << "Waiting for connections...\n";

        if ((clientSocket = accept(serverFd, (struct sockaddr*)&address, (socklen_t*)&addrlen)) < 0) {
            perror("accept failed");

            exit(EXIT_FAILURE);
        }

        std::thread clientThread(handleClient, clientSocket);

        clientThread.detach();
    }

    close(serverFd);

    return 0;
}

        通过以上代码,我们实现了一个简单的多线程网络服务器,能够同时处理多个客户端的连接请求。这个项目不仅帮助我们掌握了套接字编程的基础知识,还了解了多线程处理的基本方法。

总结

        多线程网络服务器项目使我们掌握了网络编程和多线程处理的基本知识。通过实践套接字编程和多线程技术,我们可以开发出更高效、更稳定的网络应用程序。

下一篇:白骑士的C++教学附加篇 5.1 C++开发工具

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部