C++网络编程:TCP服务器与客户端的实现

C++网络编程:TCP服务器与客户端的实现

一、学习目标与重点

本章将深入探讨C++网络编程的核心知识,帮助你掌握TCP服务器与客户端的实现。通过学习,你将能够:

  1. 理解网络编程的基本概念,掌握TCP/IP协议的核心要点
  2. 学会使用套接字编程,实现简单的TCP服务器与客户端
  3. 理解网络编程中的错误处理,提高程序的健壮性
  4. 学会使用Boost.Asio库,简化网络编程的复杂流程
  5. 培养网络编程思维,设计高效且稳定的网络应用

二、网络编程基础

2.1 TCP/IP协议简介

TCP/IP(传输控制协议/互联网协议)是互联网的核心协议,它提供了可靠的、面向连接的通信服务。TCP协议确保数据的可靠传输,通过三次握手建立连接,四次挥手关闭连接。

2.2 套接字编程基础

套接字(Socket)是网络编程的基础,它提供了进程间通信的接口。在C++中,可以使用<sys/socket.h>头文件提供的套接字API进行网络编程。

三、TCP服务器的实现

3.1 基本服务器结构

cpp 复制代码
#include <iostream>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>

const int PORT = 8080;
const int BUFFER_SIZE = 1024;

int main() {
    std::cout << "=== TCP服务器示例 ===" << std::endl;
    
    // 1. 创建套接字
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd < 0) {
        std::cerr << "套接字创建失败" << std::endl;
        return 1;
    }
    
    // 2. 设置套接字选项,允许地址复用
    int opt = 1;
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
        std::cerr << "设置套接字选项失败" << std::endl;
        close(server_fd);
        return 1;
    }
    
    // 3. 绑定地址和端口
    sockaddr_in address;
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);
    
    if (bind(server_fd, (struct sockaddr*)&address, sizeof(address)) < 0) {
        std::cerr << "绑定地址和端口失败" << std::endl;
        close(server_fd);
        return 1;
    }
    
    // 4. 监听连接
    if (listen(server_fd, 3) < 0) {
        std::cerr << "监听连接失败" << std::endl;
        close(server_fd);
        return 1;
    }
    std::cout << "服务器正在监听端口 " << PORT << "..." << std::endl;
    
    // 5. 接受连接
    int addrlen = sizeof(address);
    int new_socket;
    
    while (true) {
        std::cout << "等待客户端连接..." << std::endl;
        new_socket = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen);
        if (new_socket < 0) {
            std::cerr << "接受连接失败" << std::endl;
            continue;
        }
        
        char client_ip[INET_ADDRSTRLEN];
        inet_ntop(AF_INET, &address.sin_addr, client_ip, INET_ADDRSTRLEN);
        std::cout << "客户端 " << client_ip << " 已连接" << std::endl;
        
        // 6. 处理连接
        char buffer[BUFFER_SIZE] = {0};
        int valread = read(new_socket, buffer, BUFFER_SIZE);
        if (valread < 0) {
            std::cerr << "读取数据失败" << std::endl;
            close(new_socket);
            continue;
        }
        
        std::cout << "收到来自客户端的消息: " << buffer << std::endl;
        
        // 7. 发送响应
        std::string response = "服务器已收到消息: " + std::string(buffer);
        send(new_socket, response.c_str(), response.length(), 0);
        std::cout << "响应已发送" << std::endl;
        
        // 8. 关闭连接
        close(new_socket);
        std::cout << "客户端连接已关闭" << std::endl;
    }
    
    // 9. 关闭服务器套接字
    close(server_fd);
    
    return 0;
}

3.2 多线程服务器

cpp 复制代码
#include <iostream>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <thread>
#include <vector>

const int PORT = 8080;
const int BUFFER_SIZE = 1024;

void handleClient(int client_socket, const std::string& client_ip) {
    char buffer[BUFFER_SIZE] = {0};
    
    while (true) {
        int valread = read(client_socket, buffer, BUFFER_SIZE);
        if (valread < 0) {
            std::cerr << "客户端 " << client_ip << " 读取数据失败" << std::endl;
            break;
        } else if (valread == 0) {
            std::cout << "客户端 " << client_ip << " 已断开连接" << std::endl;
            break;
        }
        
        std::cout << "收到来自客户端 " << client_ip << " 的消息: " << buffer << std::endl;
        
        std::string response = "服务器已收到消息: " + std::string(buffer);
        send(client_socket, response.c_str(), response.length(), 0);
        std::cout << "响应已发送给客户端 " << client_ip << std::endl;
        
        memset(buffer, 0, BUFFER_SIZE);
    }
    
    close(client_socket);
}

int main() {
    std::cout << "=== 多线程TCP服务器示例 ===" << std::endl;
    
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd < 0) {
        std::cerr << "套接字创建失败" << std::endl;
        return 1;
    }
    
    int opt = 1;
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
        std::cerr << "设置套接字选项失败" << std::endl;
        close(server_fd);
        return 1;
    }
    
    sockaddr_in address;
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);
    
    if (bind(server_fd, (struct sockaddr*)&address, sizeof(address)) < 0) {
        std::cerr << "绑定地址和端口失败" << std::endl;
        close(server_fd);
        return 1;
    }
    
    if (listen(server_fd, 3) < 0) {
        std::cerr << "监听连接失败" << std::endl;
        close(server_fd);
        return 1;
    }
    std::cout << "服务器正在监听端口 " << PORT << "..." << std::endl;
    
    int addrlen = sizeof(address);
    int new_socket;
    std::vector<std::thread> client_threads;
    
    while (true) {
        std::cout << "等待客户端连接..." << std::endl;
        new_socket = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen);
        if (new_socket < 0) {
            std::cerr << "接受连接失败" << std::endl;
            continue;
        }
        
        char client_ip[INET_ADDRSTRLEN];
        inet_ntop(AF_INET, &address.sin_addr, client_ip, INET_ADDRSTRLEN);
        std::cout << "客户端 " << client_ip << " 已连接" << std::endl;
        
        client_threads.emplace_back(handleClient, new_socket, client_ip);
    }
    
    for (auto& thread : client_threads) {
        if (thread.joinable()) {
            thread.join();
        }
    }
    
    close(server_fd);
    
    return 0;
}

四、TCP客户端的实现

4.1 基本客户端结构

cpp 复制代码
#include <iostream>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <thread>

const int PORT = 8080;
const int BUFFER_SIZE = 1024;
const std::string SERVER_IP = "127.0.0.1";

void receiveMessages(int sock) {
    char buffer[BUFFER_SIZE] = {0};
    while (true) {
        int valread = read(sock, buffer, BUFFER_SIZE);
        if (valread < 0) {
            std::cerr << "读取服务器响应失败" << std::endl;
            break;
        } else if (valread == 0) {
            std::cout << "服务器已断开连接" << std::endl;
            break;
        }
        
        std::cout << "服务器响应: " << buffer << std::endl;
        memset(buffer, 0, BUFFER_SIZE);
    }
}

int main() {
    std::cout << "=== TCP客户端示例 ===" << std::endl;
    
    // 1. 创建套接字
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        std::cerr << "套接字创建失败" << std::endl;
        return 1;
    }
    
    // 2. 配置服务器地址
    sockaddr_in serv_addr;
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);
    
    if (inet_pton(AF_INET, SERVER_IP.c_str(), &serv_addr.sin_addr) <= 0) {
        std::cerr << "无效的服务器地址" << std::endl;
        close(sock);
        return 1;
    }
    
    // 3. 连接服务器
    if (connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
        std::cerr << "连接服务器失败" << std::endl;
        close(sock);
        return 1;
    }
    std::cout << "已连接到服务器 " << SERVER_IP << ":" << PORT << std::endl;
    
    // 4. 接收服务器消息的线程
    std::thread recv_thread(receiveMessages, sock);
    recv_thread.detach();
    
    // 5. 发送消息
    std::string message;
    while (true) {
        std::cout << "请输入消息(输入\"quit\"退出): ";
        std::getline(std::cin, message);
        
        if (message == "quit") {
            break;
        }
        
        send(sock, message.c_str(), message.length(), 0);
    }
    
    // 6. 关闭套接字
    close(sock);
    
    return 0;
}

五、Boost.Asio库的使用

5.1 Boost.Asio基础

Boost.Asio是一个跨平台的网络编程库,它简化了网络编程的复杂流程,提供了异步、同步、单线程和多线程的编程模型。

5.2 使用Boost.Asio实现TCP服务器

cpp 复制代码
#include <iostream>
#include <string>
#include <boost/asio.hpp>

using boost::asio::ip::tcp;
using namespace std;

const int PORT = 8080;

void handleClient(tcp::socket socket) {
    try {
        char buffer[1024];
        
        // 读取客户端消息
        size_t len = socket.read_some(boost::asio::buffer(buffer));
        string message(buffer, len);
        cout << "收到客户端消息: " << message << endl;
        
        // 发送响应
        string response = "服务器已收到消息: " + message;
        boost::asio::write(socket, boost::asio::buffer(response));
        cout << "响应已发送" << endl;
    } catch (const std::exception& e) {
        cerr << "处理客户端连接时出错: " << e.what() << endl;
    }
}

int main() {
    cout << "=== Boost.Asio TCP服务器示例 ===" << endl;
    
    try {
        boost::asio::io_service io_service;
        
        // 创建acceptor
        tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), PORT));
        cout << "服务器正在监听端口 " << PORT << "..." << endl;
        
        while (true) {
            // 接受连接
            tcp::socket socket(io_service);
            acceptor.accept(socket);
            
            // 处理连接
            thread t(handleClient, move(socket));
            t.detach();
        }
    } catch (const std::exception& e) {
        cerr << "服务器出错: " << e.what() << endl;
        return 1;
    }
    
    return 0;
}

5.3 使用Boost.Asio实现TCP客户端

cpp 复制代码
#include <iostream>
#include <string>
#include <boost/asio.hpp>

using boost::asio::ip::tcp;
using namespace std;

const int PORT = 8080;
const string SERVER_IP = "127.0.0.1";

void receiveMessages(tcp::socket& socket) {
    try {
        char buffer[1024];
        while (true) {
            size_t len = socket.read_some(boost::asio::buffer(buffer));
            string response(buffer, len);
            cout << "服务器响应: " << response << endl;
        }
    } catch (const std::exception& e) {
        cerr << "读取服务器响应时出错: " << e.what() << endl;
    }
}

int main() {
    cout << "=== Boost.Asio TCP客户端示例 ===" << endl;
    
    try {
        boost::asio::io_service io_service;
        
        // 解析服务器地址
        tcp::resolver resolver(io_service);
        tcp::resolver::query query(tcp::v4(), SERVER_IP, to_string(PORT));
        tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
        
        // 连接服务器
        tcp::socket socket(io_service);
        boost::asio::connect(socket, endpoint_iterator);
        cout << "已连接到服务器 " << SERVER_IP << ":" << PORT << endl;
        
        // 接收服务器消息的线程
        thread t(receiveMessages, ref(socket));
        t.detach();
        
        // 发送消息
        string message;
        while (true) {
            cout << "请输入消息(输入\"quit\"退出): ";
            getline(cin, message);
            
            if (message == "quit") {
                break;
            }
            
            boost::asio::write(socket, boost::asio::buffer(message));
        }
    } catch (const std::exception& e) {
        cerr << "客户端出错: " << e.what() << endl;
        return 1;
    }
    
    return 0;
}

六、综合案例:实现一个简单的聊天服务器

6.1 项目结构

复制代码
ChatServer/
├── include/
│   └── ChatServer.h
├── src/
│   ├── ChatServer.cpp
│   └── main.cpp
└── build/

6.2 核心代码

cpp 复制代码
// include/ChatServer.h
#ifndef CHATSERVER_H
#define CHATSERVER_H

#include <iostream>
#include <string>
#include <vector>
#include <thread>
#include <mutex>
#include <boost/asio.hpp>

using boost::asio::ip::tcp;
using namespace std;

class ChatServer {
private:
    boost::asio::io_service io_service;
    tcp::acceptor acceptor;
    vector<shared_ptr<tcp::socket>> clients;
    mutex clients_mutex;
    
    void acceptConnection();
    void handleClient(shared_ptr<tcp::socket> socket);
    void broadcastMessage(const string& message, const tcp::endpoint& sender_endpoint);
    
public:
    ChatServer(int port);
    void run();
};

#endif // CHATSERVER_H

// src/ChatServer.cpp
#include "ChatServer.h"

ChatServer::ChatServer(int port) : acceptor(io_service, tcp::endpoint(tcp::v4(), port)) {
}

void ChatServer::run() {
    cout << "聊天服务器正在运行..." << endl;
    acceptConnection();
    io_service.run();
}

void ChatServer::acceptConnection() {
    shared_ptr<tcp::socket> socket = make_shared<tcp::socket>(io_service);
    acceptor.async_accept(*socket, [this, socket](const boost::system::error_code& ec) {
        if (!ec) {
            cout << "客户端 " << socket->remote_endpoint() << " 已连接" << endl;
            
            {
                lock_guard<mutex> lock(clients_mutex);
                clients.push_back(socket);
            }
            
            handleClient(socket);
        } else {
            cerr << "接受连接失败: " << ec.message() << endl;
        }
        
        acceptConnection();
    });
}

void ChatServer::handleClient(shared_ptr<tcp::socket> socket) {
    auto endpoint = socket->remote_endpoint();
    char buffer[1024];
    
    socket->async_read_some(boost::asio::buffer(buffer), [this, socket, endpoint](const boost::system::error_code& ec, size_t bytes_transferred) {
        if (!ec) {
            string message(buffer, bytes_transferred);
            cout << "收到来自 " << endpoint << " 的消息: " << message << endl;
            broadcastMessage(message, endpoint);
            handleClient(socket);
        } else {
            cout << "客户端 " << endpoint << " 已断开连接" << endl;
            
            {
                lock_guard<mutex> lock(clients_mutex);
                clients.erase(remove(clients.begin(), clients.end(), socket), clients.end());
            }
        }
    });
}

void ChatServer::broadcastMessage(const string& message, const tcp::endpoint& sender_endpoint) {
    string full_message = sender_endpoint.address().to_string() + ":" + to_string(sender_endpoint.port()) + " 说: " + message;
    
    {
        lock_guard<mutex> lock(clients_mutex);
        for (auto socket : clients) {
            if (socket->remote_endpoint() != sender_endpoint) {
                boost::asio::async_write(*socket, boost::asio::buffer(full_message), [](const boost::system::error_code& ec, size_t /*bytes_transferred*/) {
                    if (ec) {
                        cerr << "发送消息失败: " << ec.message() << endl;
                    }
                });
            }
        }
    }
}

// src/main.cpp
#include <iostream>
#include "ChatServer.h"

int main() {
    std::cout << "=== 简单的聊天服务器 ===" << std::endl;
    
    try {
        const int PORT = 8080;
        ChatServer server(PORT);
        server.run();
    } catch (const std::exception& e) {
        std::cerr << "服务器出错: " << e.what() << std::endl;
        return 1;
    }
    
    return 0;
}

6.3 项目构建与运行

bash 复制代码
# 创建构建目录
mkdir -p build && cd build

# 配置CMake
cmake -DCMAKE_BUILD_TYPE=Release ..

# 编译项目
cmake --build . --config Release

# 运行服务器
./ChatServer

# 在另一个终端运行客户端
./ChatClient

七、总结与练习

7.1 本章总结

本章介绍了C++网络编程的核心知识,包括:

  1. 网络编程的基本概念与TCP/IP协议
  2. 套接字编程基础
  3. TCP服务器的实现
  4. 多线程服务器的实现
  5. TCP客户端的实现
  6. Boost.Asio库的使用
  7. 综合案例:实现一个简单的聊天服务器

7.2 练习题

  1. 写一个程序,使用套接字编程实现一个简单的HTTP服务器。
  2. 编写一个函数,使用Boost.Asio库实现一个简单的FTP客户端。
  3. 写一个程序,使用多线程实现一个高并发的网络服务器。
  4. 实现一个类,使用Boost.Asio库实现一个简单的邮件服务器。
  5. 写一个程序,使用网络编程实现一个简单的实时数据传输系统。

7.3 进阶挑战

  1. 研究如何使用C++的无锁数据结构优化网络服务器的性能。
  2. 学习如何使用C++的协程(C++20及以后)与网络编程结合。
  3. 研究如何使用C++的并发编程优化一个大型网络应用。
  4. 学习如何使用C++的加密库(如OpenSSL)实现安全的网络通信。
  5. 研究如何使用C++的网络编程实现一个高可用性的分布式系统。
相关推荐
星辰徐哥2 小时前
C语言网络编程:TCP/IP协议栈、套接字、服务器/客户端通信深度解析
c语言·网络·tcp/ip
算法-大模型备案 多米2 小时前
大模型备案实操指南:材料、流程与避坑要点
大数据·网络·人工智能·算法·文心一言
·心猿意码·2 小时前
C++ volatile 与 std::atomic 底层语义剖析
c++
Tanecious.2 小时前
蓝桥杯备赛:Day1-奖学金
c语言·c++·蓝桥杯
汉克老师2 小时前
GESP2025年6月认证C++三级( 第三部分编程题(1、奇偶校验)
c++·算法·gesp三级·gesp3级·按位操作
CoderCodingNo2 小时前
【GESP】C++八级考试大纲知识点梳理 (8) 算法优化技巧
开发语言·c++
十年编程老舅3 小时前
窥探内核心脏:深入解析 proc 虚拟文件系统
linux·服务器·数据库·c++·linux内核·文件系统·读写锁
可乐鸡翅好好吃3 小时前
Keil更改RAM地址
网络·单片机·嵌入式硬件
运维行者_3 小时前
通过 OpManager 集成 Firewall Analyzer 插件,释放统一网络管理与安全的强大能力
大数据·运维·服务器·网络·数据库·安全