【C++】socket套接字编程

IP 地址的意义就是标识公网内唯一一台主机。

  • 端口号是一个 2 字节,16 比特位的整数。
  • 一台主机中,一个端口号只能被一个进程所占用。

传输层协议(TCP 和 UDP)的数据段中也有两个端口号, 分别叫做源端口号和目的端口号.,它们描述 "数据是那个进程发送的, 要发给另外那个进程"。

Socket 网络通信

socket 通信的本质就是跨网络的进程间通信

TCP(传输控制协议)

TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议,具有以下特点:

  1. 传输层协议:TCP工作在OSI模型的传输层,负责在网络中的两个主机之间提供可靠的数据传输服务。
  2. 有连接:在数据传输开始之前,TCP需要建立一个连接,通过三次握手过程来确保双方准备好进行数据传输。
  3. 可靠传输:TCP通过确认和重传机制确保数据正确无误地从源传输到目的地。
  4. 面向字节流:TCP将数据视为字节流,不保留数据边界,这意味着发送的数据被看作是一连串的字节,TCP协议负责将这些字节顺序地、可靠地传输到目的地。

UDP(用户数据报协议)

UDP是一种无连接的、不可靠的、基于数据报的传输层通信协议,具有以下特点:

  1. 传输层协议:UDP同样工作在OSI模型的传输层,但它提供的是一种简单的消息传递服务。
  2. 无连接:UDP在数据传输前不需要建立连接,它直接发送数据,不保证数据的到达。
  3. 不可靠传输:UDP不保证数据的可靠传输,它不进行错误检测和修正,如果数据在传输过程中丢失或出错,UDP不会重传。
  4. 面向数据报:UDP将数据视为一系列独立的数据报,每个数据报都包含完整的信息,包括源端口号、目的端口号和数据负载。UDP发送和接收的数据单位是数据报。

数据报和字节流

  • 数据报:是一种独立的、包含完整信息的数据单元。在UDP中,每个数据报都是一个独立的网络消息,包含源和目的端口信息,以及数据负载。数据报的传输是无序的,且不保证到达。
  • 字节流:是一种连续的、无结构的字节序列。在TCP中,数据被看作是一连串的字节,不保留任何数据边界。TCP负责按顺序、可靠地将这些字节从发送方传输到接收方,确保数据的完整性和顺序。

网络字节序(常考)

我们知道,内存中的数据权值排列相对于内存地址的大小有大端和小端之分:

是以低地址的高位字节(大端)低位字节(小端)决定的

  • 大端存储:高权值数字存到内存的低地址位置上,低权值存到高地址上。
  • 小端存储:高权值数字存到内存的高地址位置上,低权值存到高地址上

如果发送端和接收端主机的存储字节序不同,则会造成发送的数据和识别出来的数据不一致的问题,如下图所示:

TCP/IP 协议规定,网络数据流应采用大端字节序,即低地址高权值:

如果发送主机是小端机, 就需要先进行数据转换;否则忽略,直接发送即可。

如果接受主机是小端机,则拿到数据后需要进行转换;否则忽略,直接读取即可。

Socket 编程

UDP 服务器代码(udp_server.cpp)
cpp 复制代码
#include <iostream>
#include <cstring>
#include <arpa/inet.h>

#define BUFSIZE 1024

class UdpServer {
public:
    UdpServer(uint16_t port) : port_(port), sockfd_(-1) {
        if ((sockfd_ = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
            std::cerr << "Socket creation error" << std::endl;
            exit(EXIT_FAILURE);
        }
        sockaddr_in server_addr;
        memset(&server_addr, 0, sizeof(server_addr));
        server_addr.sin_family = AF_INET;
        server_addr.sin_port = htons(port_);
        server_addr.sin_addr.s_addr = INADDR_ANY;
        if (bind(sockfd_, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
            std::cerr << "Bind error" << std::endl;
            exit(EXIT_FAILURE);
        }
    }

    void serve() {
        char buffer[BUFSIZE];
        sockaddr_in client_addr;
        socklen_t client_len = sizeof(client_addr);
        while (true) {
            memset(buffer, 0, BUFSIZE);
            int bytes_received = recvfrom(sockfd_, buffer, BUFSIZE, 0,
                                          (struct sockaddr*)&client_addr, &client_len);
            if (bytes_received < 0) {
                std::cerr << "Recvfrom error" << std::endl;
                continue;
            }
            std::cout << "Received message: " << buffer << std::endl;
            std::string response = "Echo: " + std::string(buffer);
            sendto(sockfd_, response.c_str(), response.size(), 0,
                   (struct sockaddr*)&client_addr, client_len);
        }
    }

    ~UdpServer() {
        if (sockfd_ != -1) {
            close(sockfd_);
        }
    }

private:
    uint16_t port_;
    int sockfd_;
};

int main(int argc, char* argv[]) {
    if (argc != 2) {
        std::cerr << "Usage: " << argv[0] << " <port>" << std::endl;
        return 1;
    }
    uint16_t port = static_cast<uint16_t>(std::atoi(argv[1]));
    UdpServer server(port);
    server.serve();
    return 0;
}
UDP 客户端代码(udp_client.cpp)
cpp 复制代码
#include <iostream>
#include <cstring>
#include <arpa/inet.h>

#define BUFSIZE 1024

class UdpClient {
public:
    UdpClient(const std::string& server_ip, uint16_t server_port)
        : server_ip_(server_ip), server_port_(server_port), sockfd_(-1) {
        if ((sockfd_ = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
            std::cerr << "Socket creation error" << std::endl;
            exit(EXIT_FAILURE);
        }
    }

    void send_receive() {
        sockaddr_in server_addr;
        memset(&server_addr, 0, sizeof(server_addr));
        server_addr.sin_family = AF_INET;
        server_addr.sin_port = htons(server_port_);
        server_addr.sin_addr.s_addr = inet_addr(server_ip_.c_str());

        std::string message = "Hello from UDP client";
        sendto(sockfd_, message.c_str(), message.size(), 0,
               (struct sockaddr*)&server_addr, sizeof(server_addr));

        char buffer[BUFSIZE];
        socklen_t server_len = sizeof(server_addr);
        int bytes_received = recvfrom(sockfd_, buffer, BUFSIZE, 0,
                                      (struct sockaddr*)&server_addr, &server_len);
        if (bytes_received < 0) {
            std::cerr << "Recvfrom error" << std::endl;
            return;
        }
        std::cout << "Received message: " << buffer << std::endl;
    }

    ~UdpClient() {
        if (sockfd_ != -1) {
            close(sockfd_);
        }
    }

private:
    std::string server_ip_;
    uint16_t server_port_;
    int sockfd_;
};

int main(int argc, char* argv[]) {
    if (argc != 4) {
        std::cerr << "Usage: " << argv[0] << " <server_ip> <server_port>" << std::endl;
        return 1;
    }
    std::string server_ip = argv[1];
    uint16_t server_port = static_cast<uint16_t>(std::atoi(argv[2]));
    UdpClient client(server_ip, server_port);
    client.send_receive();
    return 0;
}

TCP 客户端服务器代码

TCP 服务器代码(tcp_server.cpp)
cpp 复制代码
#include <iostream>
#include <cstring>
#include <arpa/inet.h>
#include <unistd.h>

#define BUFSIZE 1024

class TcpServer {
public:
    TcpServer(uint16_t port) : port_(port), listenfd_(-1) {
        if ((listenfd_ = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
            std::cerr << "Socket creation error" << std::endl;
            exit(EXIT_FAILURE);
        }
        sockaddr_in server_addr;
        memset(&server_addr, 0, sizeof(server_addr));
        server_addr.sin_family = AF_INET;
        server_addr.sin_port = htons(port_);
        server_addr.sin_addr.s_addr = INADDR_ANY;
        if (bind(listenfd_, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
            std::cerr << "Bind error" << std::endl;
            exit(EXIT_FAILURE);
        }
        if (listen(listenfd_, 5) < 0) {
            std::cerr << "Listen error" << std::endl;
            exit(EXIT_FAILURE);
        }
    }

    void serve() {
        sockaddr_in client_addr;
        socklen_t client_len = sizeof(client_addr);
        while (true) {
            int connfd = accept(listenfd_, (struct sockaddr*)&client_addr, &client_len);
            if (connfd < 0) {
                std::cerr << "Accept error" << std::endl;
                continue;
            }
            std::cout << "Connection established" << std::endl;
            char buffer[BUFSIZE];
            while (true) {
                memset(buffer, 0, BUFSIZE);
                int bytes_received = read(connfd, buffer, BUFSIZE);
                if (bytes_received < 0) {
                    std::cerr << "Read error" << std::endl;
                    break;
                }
                if (bytes_received == 0) {
                    std::cout << "Client disconnected" << std::endl;
                    break;
                }
                std::cout << "Received message: " << buffer << std::endl;
                write(connfd, buffer, bytes_received);
            }
            close(connfd);
        }
    }

    ~TcpServer() {
        if (listenfd_ != -1) {
            close(listenfd_);
        }
    }

private:
    uint16_t port_;
    int listenfd_;
};

int main(int argc, char* argv[]) {
    if (argc != 2) {
        std::cerr << "Usage: " << argv[0] << " <port>" << std::endl;
        return 1;
    }
    uint16_t port = static_cast<uint16_t>(std::atoi(argv[1]));
    TcpServer server(port);
    server.serve();
    return 0;
}
TCP 客户端代码(tcp_client.cpp)
cpp 复制代码
#include <iostream>
#include <cstring>
#include <arpa/inet.h>

#define BUFSIZE 1024

class TcpClient {
public:
    TcpClient(const std::string& server_ip, uint16_t server_port)
        : server_ip_(server_ip), server_port_(server_port), sockfd_(-1) {
        if ((sockfd_ = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
            std::cerr << "Socket creation error" << std::endl;
            exit(EXIT_FAILURE);
        }
    }

    void connect_and_communicate() {
        sockaddr_in server_addr;
        memset(&server_addr, 0, sizeof(server_addr));
        server_addr.sin_family = AF_INET;
        server_addr.sin_port = htons(server_port_);
        server_addr.sin_addr.s_addr = inet_addr(server_ip_.c_str());
        if (connect(sockfd_, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
            std::cerr << "Connect error" << std::endl;
            exit(EXIT_FAILURE);
        }
        std::cout << "Connected to the server" << std::endl;

        char buffer[BUFSIZE];
        while (true) {
            std::cout << "Enter message: ";
            std::cin.getline(buffer, BUFSIZE);
            if (write(sockfd_, buffer, std::strlen(buffer)) < 0) {
                std::cerr << "Write error" << std::endl;
                break;
            }
            memset(buffer, 0, BUFSIZE);
            if (read(sockfd_, buffer, BUFSIZE) <

多线程TCP服务器代码(threaded_tcp_server.cpp)

cpp 复制代码
#include <iostream>
#include <cstring>
#include <arpa/inet.h>
#include <unistd.h>
#include <thread>
#include <vector>

#define BUFSIZE 1024

// 处理每个客户端的函数
void handle_client(int client_fd, sockaddr_in client_addr) {
    char buffer[BUFSIZE];
    std::vector<char> reply(BUFSIZE);

    while (true) {
        memset(buffer, 0, BUFSIZE);
        int bytes_received = read(client_fd, buffer, BUFSIZE);
        if (bytes_received <= 0) {
            std::cout << "Client disconnected or read error" << std::endl;
            break;
        }

        std::cout << "Received message from " << inet_ntoa(client_addr.sin_addr) << ":" << ntohs(client_addr.sin_port) << ": " << buffer << std::endl;

        // Echo the message back to the client
        std::strcpy(reply.data(), buffer);
        write(client_fd, reply.data(), std::strlen(buffer));
    }

    close(client_fd);
}

// 线程函数,接受新的客户端连接
void accept_clients(int listenfd) {
    sockaddr_in client_addr;
    socklen_t client_len = sizeof(client_addr);

    while (true) {
        int client_fd = accept(listenfd, (struct sockaddr*)&client_addr, &client_len);
        if (client_fd < 0) {
            std::cerr << "Accept error" << std::endl;
            continue;
        }

        std::cout << "New client connected: " << inet_ntoa(client_addr.sin_addr) << ":" << ntohs(client_addr.sin_port) << std::endl;

        // 创建新线程处理客户端
        std::thread client_thread(handle_client, client_fd, client_addr);
        client_thread.detach(); // 分离线程,让它独立运行
    }
}

class ThreadedTcpServer {
public:
    ThreadedTcpServer(uint16_t port) : port_(port), listenfd_(-1) {
        if ((listenfd_ = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
            std::cerr << "Socket creation error" << std::endl;
            exit(EXIT_FAILURE);
        }
        sockaddr_in server_addr;
        memset(&server_addr, 0, sizeof(server_addr));
        server_addr.sin_family = AF_INET;
        server_addr.sin_port = htons(port_);
        server_addr.sin_addr.s_addr = INADDR_ANY;
        if (bind(listenfd_, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
            std::cerr << "Bind error" << std::endl;
            exit(EXIT_FAILURE);
        }
        if (listen(listenfd_, 5) < 0) {
            std::cerr << "Listen error" << std::endl;
            exit(EXIT_FAILURE);
        }
    }

    void serve() {
        accept_clients(listenfd_);
    }

    ~ThreadedTcpServer() {
        if (listenfd_ != -1) {
            close(listenfd_);
        }
    }

private:
    uint16_t port_;
    int listenfd_;
};

int main(int argc, char* argv[]) {
    if (argc != 2) {
        std::cerr << "Usage: " << argv[0] << " <port>" << std::endl;
        return 1;
    }
    uint16_t port = static_cast<uint16_t>(std::atoi(argv[1]));
    ThreadedTcpServer server(port);
    server.serve();
    return 0;
}
相关推荐
青花瓷24 分钟前
C++__XCode工程中Debug版本库向Release版本库的切换
c++·xcode
音徽编程25 分钟前
Rust异步运行时框架tokio保姆级教程
开发语言·网络·rust
dsywws1 小时前
Linux学习笔记之vim入门
linux·笔记·学习
捕鲸叉2 小时前
MVC(Model-View-Controller)模式概述
开发语言·c++·设计模式
23zhgjx-NanKon2 小时前
华为eNSP:QinQ
网络·安全·华为
23zhgjx-NanKon2 小时前
华为eNSP:mux-vlan
网络·安全·华为
点点滴滴的记录2 小时前
RPC核心实现原理
网络·网络协议·rpc
Dola_Pan3 小时前
C++算法和竞赛:哈希算法、动态规划DP算法、贪心算法、博弈算法
c++·算法·哈希算法
free3 小时前
netstat中sendq/recvq用于排查发送端发送数据的问题
服务器