基础cpp网络编程多线程服务器端与客户端示例

  最近一直在学习c++的网络编程方面的内容,为了加深记忆,巩固理解,故作代码记录,以后会不断地完善。

一、预备知识网络套接字

  套接字就像“插座与插头”的配对机制,是客户端与服务端通信的桥梁。在一次典型的 TCP 网络连接中,服务端会首先创建一个“监听套接字”来等待连接请求(就像一个插座等待插头插入)。当客户端发起连接时,会创建自己的“通信套接字”发出连接请求。服务端一旦 accept() 成功,就会从监听套接字中“派生出”一个新的“通信套接字”来与该客户端专线通信。因此,一个完整的 TCP 连接,至少包含:

  • 客户端的通信套接字(插头)

  • 服务端的监听套接字(插座面板)

  • 服务端的通信套接字(插座上的孔)

  • 监听套接字(lfd)只用于等待连接,不传输数据;

  • 一旦连接建立,服务端与客户端各用一个通信套接字进行读写,彼此一对一独立传输;

  • 服务器的监听套接字可以持续 accept,接更多连接;

  • 每个客户端连接后,服务端就产生一个新的通信套接字,可由不同线程/进程处理。

在这里插入图片描述
这里附上一张经典的socket流程图,可能与我的代码不是完全对应,无所谓,能理解器大致流程就行
在这里插入图片描述

二、代码部分详解

其实这里还有很多代码我不是很了解,但也暂时无力去研究了,等后续再完整吧,现在只记录整体流程

2.1 服务端

#include <iostream>
#include <thread>           // std::thread 线程库
#include <vector>
#include <string>
#include <algorithm>        // std::transform 等
#include <cstring>          // C 风格字符串操作
#include <memory>
#include <unistd.h>         // POSIX 标准 API,如 read/write/close
#include <arpa/inet.h>      // 网络地址转换函数
#include <netinet/in.h>     // sockaddr_in 结构体

#define SERVER_PORT 9527    // 服务器端口号
#define BUFFER_SIZE 1024    // 缓冲区大小

// 处理客户端连接的函数(线程函数)
void handle_client(int client_fd, sockaddr_in client_addr) {
    // 获取当前线程 ID
    std::thread::id this_id = std::this_thread::get_id();

    // 构造发送给客户端的欢迎信息(包含线程ID)
    std::string thread_info = "Your handler thread ID is: " +
                              std::to_string(std::hash<std::thread::id>{}(this_id)) +
                              "\n";

    // 向客户端发送线程ID
    send(client_fd, thread_info.c_str(), thread_info.length(), 0);

    char buffer[BUFFER_SIZE];
    ssize_t n;

    // 循环读取客户端数据,转换为大写后回送
    while ((n = read(client_fd, buffer, sizeof(buffer))) > 0) {
        // 将收到的数据全部转换为大写
        for (int i = 0; i < n; ++i) {
            buffer[i] = toupper(buffer[i]);
        }

        // 把转换后的数据发回客户端
        send(client_fd, buffer, n, 0);
    }

    // 客户端断开连接后关闭 socket 并打印信息
    close(client_fd);
    std::cout << "Client disconnected (Thread " << this_id << ")" << std::endl;
}

int main() {
    // 创建服务器 socket(TCP 类型)
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == -1) {
        perror("socket");
        return 1;
    }

    // 准备服务器地址结构体
    sockaddr_in server_addr{}, client_addr{};
    socklen_t client_len = sizeof(client_addr);

    server_addr.sin_family = AF_INET;              // 使用 IPv4
    server_addr.sin_port = htons(SERVER_PORT);     // 设置端口(网络字节序)
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听本机所有IP地址

    // 绑定服务器 socket 到指定IP和端口
    if (bind(server_fd, (sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("bind");
        return 1;
    }

    // 设置 socket 为监听状态
    if (listen(server_fd, 128) < 0) {
        perror("listen");
        return 1;
    }

    std::cout << "Server listening on port " << SERVER_PORT << "..." << std::endl;

    // 主循环:接收客户端连接并创建新线程处理
    while (true) {
        // 接收客户端连接请求(阻塞)
        int client_fd = accept(server_fd, (sockaddr*)&client_addr, &client_len);
        if (client_fd < 0) {
            perror("accept");
            continue;
        }

        // 打印客户端地址信息
        char ip[INET_ADDRSTRLEN];
        inet_ntop(AF_INET, &client_addr.sin_addr, ip, sizeof(ip));
        std::cout << "Client connected from " << ip << ":" << ntohs(client_addr.sin_port) << std::endl;

        // 创建新线程处理客户端连接,并将 client_fd 传递进去
        std::thread client_thread(handle_client, client_fd, client_addr);

        // 设置线程分离,结束时自动释放资源
        client_thread.detach();
    }

    close(server_fd);
    return 0;
}

2.2、客户端代码

#include <iostream>
#include <string>
#include <cstring>          // C风格字符串处理函数
#include <unistd.h>         // read, write, close
#include <arpa/inet.h>      // inet_pton 等函数

#define SERVER_PORT 9527    // 服务端端口
#define BUFFER_SIZE 1024    // 缓冲区大小

int main() {
    // 创建客户端 socket
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        perror("socket");
        return 1;
    }

    // 准备服务器地址
    sockaddr_in server_addr{};
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT);
    inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr);  // 本机

    // 连接服务器
    if (connect(sockfd, (sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("connect");
        return 1;
    }

    char buffer[BUFFER_SIZE];

    // 接收来自服务器的线程 ID 欢迎信息
    ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);
    if (n > 0) {
        buffer[n] = '\0';
        std::cout << "Server: " << buffer;
    }

    // 进入消息交互循环
    while (true) {
        std::string input;
        std::cout << "Enter message (q to quit): ";
        std::getline(std::cin, input);

        if (input == "q") break;

        // 向服务器发送输入字符串
        send(sockfd, input.c_str(), input.length(), 0);

        // 读取服务器的响应
        n = read(sockfd, buffer, sizeof(buffer) - 1);
        if (n > 0) {
            buffer[n] = '\0';
            std::cout << "Server replied: " << buffer;
        }
    }

    close(sockfd);
    return 0;
}

2.3、在终端中如何调用

  • 在终端进行编译
    -g++ -std=c++11 -pthread -o server server.cpp g++ -std=c++11 -o client client.cpp
  • 运行代码:任意打开一个终端,进入虚拟机对应的目录下,先打开一个服务端./server
  • 然后仿造上面的步骤打开多个窗口,运行./client 即可进行多线程的调试。
参考自

https://www.yuque.com/boyhui/ukcpwf/ieg4t7k1d32hvbvb#u311z

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值