Lập trình Socket TCP với C++ trên môi trường Linux

Lập trình mạng trên Linux thường dựa trên giao diện Socket API. Socket đóng vai trò là một điểm cuối (endpoint) để thiết lập giao tiếp giữa hai thực thể trên mạng. Đối với giao thức TCP (Transmission Control Protocol), quy trình giao tiếp diễn ra theo mô hình hướng kết nối, đảm bảo tính tin cậy của dữ liệu.

Quy trình thiết lập kết nối TCP

Mô hình Socket TCP bao gồm hai phía: Server (Máy chủ) và Client (Máy khách).
  • Phía Server: Thực hiện các bước: socket() -> bind() -> listen() -> accept() -> Giao tiếp (recv/send).
  • Phía Client: Thực hiện các bước: socket() -> connect() -> Giao tiếp (send/recv).

Xây dựng chương trình Server đơn giản

Dưới đây là mã nguồn của một Server cơ bản, có khả năng nhận dữ liệu từ Client và phản hồi lại (Echo Server).
#include <iostream>
#include <vector>
#include <string>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define LISTENING_PORT 8080
#define BACKLOG_SIZE 10
#define MAX_BUFFER 2048

int main() {
    int server_fd, active_conn;
    struct sockaddr_in server_addr, client_addr;
    socklen_t addr_len = sizeof(client_addr);
    char data_buffer[MAX_BUFFER];

    // 1. Tạo Socket: AF_INET (IPv4), SOCK_STREAM (TCP)
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == -1) {
        perror("Socket creation failed");
        return EXIT_FAILURE;
    }

    // 2. Thiết lập cấu hình địa chỉ server
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY; // Chấp nhận kết nối từ mọi giao diện mạng
    server_addr.sin_port = htons(LISTENING_PORT);

    // 3. Liên kết socket với địa chỉ và cổng
    if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("Bind failed");
        close(server_fd);
        return EXIT_FAILURE;
    }

    // 4. Chuyển socket sang trạng thái lắng nghe
    if (listen(server_fd, BACKLOG_SIZE) < 0) {
        perror("Listen failed");
        close(server_fd);
        return EXIT_FAILURE;
    }

    std::cout << "Server dang lang nghe tai cong " << LISTENING_PORT << "..." << std::endl;

    // 5. Chấp nhận kết nối từ Client
    active_conn = accept(server_fd, (struct sockaddr*)&client_addr, &addr_len);
    if (active_conn < 0) {
        perror("Accept failed");
        close(server_fd);
        return EXIT_FAILURE;
    }

    while (true) {
        std::memset(data_buffer, 0, MAX_BUFFER);
        ssize_t bytes_received = recv(active_conn, data_buffer, MAX_BUFFER, 0);

        if (bytes_received <= 0) {
            std::cout << "Client da ngat ket noi." << std::endl;
            break;
        }

        if (std::string(data_buffer) == "quit\n") {
            break;
        }

        std::cout << "Nhan duoc: " << data_buffer;
        
        // Phan hoi lai cho client
        send(active_conn, data_buffer, bytes_received, 0);
    }

    close(active_conn);
    close(server_fd);
    return 0;
}

Xây dựng chương trình Client

Client sẽ thực hiện kết nối tới địa chỉ IP và cổng mà Server đang mở để gửi thông điệp.
#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>

#define SERVER_PORT 8080
#define SERVER_IP "127.0.0.1"
#define TEMP_BUFFER 2048

int main() {
    int client_fd;
    struct sockaddr_in remote_server;
    char write_buf[TEMP_BUFFER];
    char read_buf[TEMP_BUFFER];

    // 1. Khoi tao socket
    client_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (client_fd < 0) {
        perror("Socket initialization error");
        return EXIT_FAILURE;
    }

    // 2. Cau hinh dia chi server dich
    remote_server.sin_family = AF_INET;
    remote_server.sin_port = htons(SERVER_PORT);
    
    if (inet_pton(AF_INET, SERVER_IP, &remote_server.sin_addr) <= 0) {
        perror("Invalid address or address not supported");
        return EXIT_FAILURE;
    }

    // 3. Thiet lap ket noi toi server
    if (connect(client_fd, (struct sockaddr*)&remote_server, sizeof(remote_server)) < 0) {
        perror("Connection to server failed");
        return EXIT_FAILURE;
    }

    std::cout << "Ket noi thanh cong den " << SERVER_IP << ". Nhap tin nhan (go 'quit' de thoat):" << std::endl;

    while (std::cin.getline(write_buf, TEMP_BUFFER)) {
        std::string input(write_buf);
        if (input == "quit") break;

        // Gui du lieu
        send(client_fd, write_buf, std::strlen(write_buf), 0);

        // Nhan phan hoi tu server
        std::memset(read_buf, 0, TEMP_BUFFER);
        int received_len = recv(client_fd, read_buf, TEMP_BUFFER, 0);
        
        if (received_len > 0) {
            std::cout << "Server phan hoi: " << read_buf << std::endl;
        }
    }

    close(client_fd);
    return 0;
}

Phân tích kỹ thuật

Trong các ví dụ trên, hàm socket() khởi tạo một descriptor tham chiếu đến tài nguyên mạng. Hàm bind() gán định danh (IP/Port) cho socket của server, trong khi listen() đặt server vào trạng thái chờ đợi các yêu cầu kết nối. Hàm accept() là một hàm chặn (blocking), nó sẽ dừng thực thi cho đến khi có một client kết nối tới. Dữ liệu được trao đổi qua send() và recv(). Trong thực tế, để xử lý nhiều client cùng lúc, người ta thường sử dụng đa luồng (multithreading), đa tiến trình (multiprocessing) hoặc các cơ chế I/O Multiplexing như select(), poll(), hoặc epoll() trên Linux để tối ưu hóa hiệu năng.

Thẻ: cpp linux socket-programming tcp-ip network-programming

Đăng vào ngày 29 tháng 10 lúc 06:52