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.