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ăng | Kỹ thuật | Bài |
|---|---|---|
| Trăm client đồng thời | epoll, một luồng | 46.9 |
| Xử lý đúng dòng TCP | Giao thức tiền tố độ dài | 46.5, 46.6 |
| Nickname, phòng, chat riêng | Trạng thái mỗi kết nối | 46.10 |
| Client ngắt đột ngột | Xử lý recv trả 0 và lỗi | 46.6 |
| Chống spam | Giới hạn tốc độ mỗi client | Mới |
| Thoát sạch | SIGINT đặt cờ, dọn ở luồng chính | 46.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
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.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 trongepoll_data.ptr.Vòng lặp sự kiện
epoll_waittrả 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.Gửi không chặn
Không gọisend_alltrong 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ử
epollhoặc bể luồng xử lý ít nhất một trăm client đồng thời.- Giao thức tiền tố độ dài, xử lý đúng dòng TCP.
- Nickname, phòng chat, chat riêng, danh sách người online.
- Lệnh:
/nick,/join,/leave,/list,/msg,/quit,/help. - Client ngắt đột ngột không làm sập server.
- Giới hạn tốc độ chống spam.
- Ghi log có mốc thời gian.
- Thoát sạch bằng
SIGINT. - Kiểm thử tải một trăm client, mỗi client một nghìn tin.
- 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ô:
epollthayselect, 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
EPOLLOUTcho phần dư. - Kiểm tra
recvtrả 0 và lỗi, giới hạn độ dài, đặt thời gian chờ, và bỏ quaSIGPIPE. - 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.