Bỏ qua điều hướng, tới nội dung chính
Học C
Bài 60.528 phút đọc

Level 5: Máy chủ chat TCP

Sau bài này bạn sẽ làm được

  • Phục vụ trăm client đồng thời bằng ghép kênh vào ra
  • Dùng giao thức tiền tố độ dài xử lý đúng dòng TCP
  • Chống client ngắt đột ngột làm sập server
  • Chạy sạch dưới công cụ bộ nhớ sau kiểm thử tải

Máy chủ chat TCP là nơi Phần 16 và 17 gặp nhau: một luồng phục vụ hàng trăm client bằng epoll, một giao thức tiền tố độ dài xử lý đúng dòng TCP, và mã đủ vững để một client xấu tính không làm sập cả server.

#Mục tiêu

Client 1  --+
Client 2  --+-->  Server  -->  phat toi moi client
Client 3  --+
Chức năngKỹ thuậtBài
Trăm client đồng thờiepoll, một luồng46.9
Xử lý đúng dòng TCPGiao thức tiền tố độ dài46.5, 46.6
Nickname, phòng, chat riêngTrạng thái mỗi kết nối46.10
Client ngắt đột ngộtXử lý recv trả 0 và lỗi46.6
Chống spamGiới hạn tốc độ mỗi clientMới
Thoát sạchSIGINT đặt cờ, dọn ở luồng chính46.10

#Giao thức khung

Khung tiền tố độ dài, từ Bài 46.6
/* Moi thong diep: [do dai 4 byte, thu tu mang][loai 1 byte][than] */

#define TIN_TOI_DA (4096u)

enum {
    L_TEN   = 1,    /* dat nickname */
    L_TIN   = 2,    /* tin nhan */
    L_PHONG = 3,    /* vao phong */
    L_RIENG = 4,    /* chat rieng */
    L_DS    = 5,    /* danh sach online */
    L_HE    = 6,    /* thong bao he thong */
};

/* nhan_khung kiem tra CA can duoi lan can tren: */
static int nhan_khung(int fd, uint8_t *loai, char **ra, uint32_t *ra_n) {
    uint32_t d;
    if (recv_all(fd, &d, 4) != 0) return -1;
    d = ntohl(d);
    if (d < 1 || d > TIN_TOI_DA + 1) return -1;   /* bat buoc, Bai 59.2 */
    ...
}

#Kiến trúc một luồng

  1. Socket lắng nghe, đăng ký với epoll

    Đặt socket lắng nghe sang không chặn và đăng ký EPOLLIN. Khi nó sẵn sàng đọc nghĩa là có kết nối mới.
  2. Mỗi kết nối một cấu trúc trạng thái

    Nickname, phòng, bộ đệm tích luỹ vào, bộ đệm ra chưa gửi hết. Lưu con trỏ tới cấu trúc này trong epoll_data.ptr.
  3. Vòng lặp sự kiện

    epoll_wait trả về các fd sẵn sàng. Với mỗi cái: nếu là socket lắng nghe thì accept; nếu là client thì đọc vào bộ đệm tích luỹ và tách khung.
  4. Gửi không chặn

    Không gọi send_all trong vòng lặp vì nó chặn. Gửi một lần; nếu chưa hết, lưu phần còn lại và đăng ký EPOLLOUT. Xem Bài 46.9.

#Chống sập

Server mong manh
int n = recv(fd, buf, sizeof buf, 0); xu_ly(buf, n); /* n co the la 0 hoac -1 */ /* n == 0: client dong. n < 0: loi. Ca hai deu vao xu_ly voi du lieu rac. Va mot client gui 4 byte do dai roi im lang lam recv_all chan, treo ca server. */
Server vững
int n = recv(fd, buf, sizeof buf, 0); if (n == 0) { dong_ket_noi(fd); return; } /* client dong sach */ if (n < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) return; /* het du lieu */ if (errno == EINTR) return; dong_ket_noi(fd); return; /* loi that -> dong */ } them_vao_bo_dem(fd, buf, n); tach_moi_khung_day_du(fd); /* khong chan */

#Yêu cầu

Tự làm thử

  1. epoll hoặc bể luồng xử lý ít nhất một trăm client đồng thời.
  2. Giao thức tiền tố độ dài, xử lý đúng dòng TCP.
  3. Nickname, phòng chat, chat riêng, danh sách người online.
  4. Lệnh: /nick, /join, /leave, /list, /msg, /quit, /help.
  5. Client ngắt đột ngột không làm sập server.
  6. Giới hạn tốc độ chống spam.
  7. Ghi log có mốc thời gian.
  8. Thoát sạch bằng SIGINT.
  9. Kiểm thử tải một trăm client, mỗi client một nghìn tin.
  10. Valgrind sạch sau kiểm thử tải.

Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.

Tóm tắt

  • Đây là Bài 46.10 nâng quy mô: epoll thay select, thêm phòng, chat riêng, chống spam, kiểm thử tải.
  • Giao thức tiền tố độ dài là bắt buộc vì TCP là dòng byte; mỗi kết nối cần bộ đệm tích luỹ.
  • Không bao giờ làm việc chặn trong vòng lặp sự kiện; gửi không chặn và dùng EPOLLOUT cho phần dư.
  • Kiểm tra recv trả 0 và lỗi, giới hạn độ dài, đặt thời gian chờ, và bỏ qua SIGPIPE.
  • Xong khi một trăm client chạy đồng thời, client chết đột ngột không làm sập, và Valgrind sạch sau tải.