Bài 46.630 phút đọc
send_all, recv_all và giao thức tiền tố độ dài
Sau bài này bạn sẽ làm được
- Viết send_all và recv_all xử lý đúng mọi trường hợp
- Xử lý EINTR và trả về từng phần
- Cài giao thức tiền tố độ dài kèm kiểm tra kích thước
- Giải thích vì sao gọi send một lần là sai
Hai hàm trong bài này ngắn, khoảng mười dòng mỗi hàm, và bạn sẽ chép lại chúng vào mọi dự án mạng bạn viết. Không có chúng thì mọi thứ ở trên đều sai một cách ngẫu nhiên.
#send_all
send_all, phiên bản đầy đủ
/* Gui DU n byte. Tra ve 0 thanh cong, -1 loi. */
static int send_all(Sock s, const void *dl, size_t n) {
const char *p = (const char *)dl;
size_t con = n;
while (con > 0) {
int g = send(s, p, (int)con, 0);
if (g > 0) { /* gui duoc mot phan */
p += g;
con -= (size_t)g;
continue;
}
if (g == 0) return -1; /* khong the xay ra voi n > 0,
nhung phong xa */
#if defined(_WIN32)
int e = WSAGetLastError();
if (e == WSAEINTR) continue; /* bi ngat, thu lai */
#else
if (errno == EINTR) continue; /* bi tin hieu ngat, thu lai */
#endif
return -1; /* loi that su */
}
return 0;
}#recv_all
recv_all, phiên bản đầy đủ
/* Doc DU n byte. Tra ve 0 thanh cong, -1 loi hoac ben kia dong. */
static int recv_all(Sock s, void *dl, size_t n) {
char *p = (char *)dl;
size_t con = n;
while (con > 0) {
int g = recv(s, p, (int)con, 0);
if (g > 0) {
p += g;
con -= (size_t)g;
continue;
}
if (g == 0) return -1; /* ben kia dong SACH giua chung */
#if defined(_WIN32)
if (WSAGetLastError() == WSAEINTR) continue;
#else
if (errno == EINTR) continue;
#endif
return -1;
}
return 0;
}#Giao thức tiền tố độ dài
Khung tin hoàn chỉnh
#include <stdint.h>
#define TIN_TOI_DA (1024u * 1024u) /* 1 MB */
/* Khung: [do dai 4 byte, thu tu mang][du lieu N byte] */
static int gui_khung(Sock s, const void *dl, uint32_t n) {
uint32_t d = htonl(n);
if (send_all(s, &d, 4) != 0) return -1;
if (n > 0 && send_all(s, dl, n) != 0) return -1;
return 0;
}
/* Cap phat bo dem va tra ve qua *ra. Nguoi goi PHAI free. */
static int nhan_khung(Sock s, char **ra, uint32_t *ra_n) {
uint32_t d;
if (recv_all(s, &d, 4) != 0) return -1;
d = ntohl(d);
if (d > TIN_TOI_DA) {
fprintf(stderr, "khung qua lon: %u\n", d);
return -1; /* dong ket noi */
}
char *dem = malloc((size_t)d + 1); /* +1 de them NUL */
if (dem == NULL) return -1;
if (d > 0 && recv_all(s, dem, d) != 0) { free(dem); return -1; }
dem[d] = '\0';
*ra = dem;
*ra_n = d;
return 0;
}#Kiểm chứng khắc nghiệt
Cách chắc chắn nhất để biết khung tin của bạn đúng là ép nó vào trường hợp xấu nhất: gửi từng byte một.
khung.c, phần send_all cố ý gửi một byte mỗi lần
static int send_all(SOCKET s, const void *dl, size_t n) {
const char *p = (const char *)dl;
size_t con = n;
while (con > 0) {
int g = send(s, p, (int)(con > 1 ? 1 : con), 0); /* CO Y 1 byte */
if (g <= 0) return -1;
p += g; con -= (size_t)g;
}
return 0;
}
static const char *TIN[] = {
"xin chao",
"day la thong diep thu hai dai hon",
"a",
"", /* khung RONG, truong hop bien */
"ket thuc"
};terminal
gcc -std=c11 -O2 -Wall -Wextra -D_WIN32_WINNT=0x0600 -D__USE_MINGW_ANSI_STDIO=1 khung.c -o khung.exe -lws2_32
./khung.exe
server: khung 1, 8 byte: "xin chao" dung server: khung 2, 33 byte: "day la thong diep thu hai dai hon" dung client: da gui 5 khung, tung byte mot server: khung 3, 1 byte: "a" dung server: khung 4, 0 byte: "" dung server: khung 5, 8 byte: "ket thuc" dung server: nhan du 5 khung
#Mở rộng giao thức
Thêm trường loại, để giao thức có nhiều loại thông điệp
/* Khung: [do dai 4][loai 1][du lieu N-1] */
enum Loai {
LOAI_TIN = 1, /* tin nhan van ban */
LOAI_TEN = 2, /* dat ten */
LOAI_LENH = 3, /* lenh dieu khien */
LOAI_PING = 4, /* giu ket noi song */
LOAI_PONG = 5,
};
typedef struct {
uint8_t loai;
uint32_t co;
char *du_lieu; /* co the NULL neu co == 0 */
} Khung;
static int gui_khung2(Sock s, uint8_t loai, const void *dl, uint32_t n) {
uint32_t d = htonl(n + 1); /* +1 cho byte loai */
if (send_all(s, &d, 4) != 0) return -1;
if (send_all(s, &loai, 1) != 0) return -1;
if (n > 0 && send_all(s, dl, n) != 0) return -1;
return 0;
}
static int nhan_khung2(Sock s, Khung *k) {
uint32_t d;
if (recv_all(s, &d, 4) != 0) return -1;
d = ntohl(d);
if (d < 1 || d > TIN_TOI_DA) return -1; /* phai co it nhat byte loai */
if (recv_all(s, &k->loai, 1) != 0) return -1;
k->co = d - 1;
k->du_lieu = malloc((size_t)k->co + 1);
if (k->du_lieu == NULL) return -1;
if (k->co > 0 && recv_all(s, k->du_lieu, k->co) != 0) {
free(k->du_lieu);
k->du_lieu = NULL;
return -1;
}
k->du_lieu[k->co] = '\0';
return 0;
}Giao thức mong manh
/* Gui struct nguyen khoi */
typedef struct {
int loai;
char ten[32];
double gia_tri;
} TinNhan;
TinNhan t = { 1, "an", 3.14 };
send(s, &t, sizeof t, 0);
/* Ba van de:
1. Dem cau truc khac nhau giua trinh bien dich
2. Thu tu byte khac nhau giua kien truc
3. sizeof(double) va dinh dang so thuc co the khac
Ma nay chi chay khi hai ben dung cung trinh bien dich,
cung co, cung tuy chon. */
Giao thức bền
/* Tuan tu hoa tung truong */
static int gui_tin(Sock s, int loai, const char *ten, double gia_tri) {
char goi[64];
size_t o = 0;
uint8_t l = (uint8_t)loai;
memcpy(goi + o, &l, 1); o += 1;
uint8_t ten_co = (uint8_t)strlen(ten);
memcpy(goi + o, &ten_co, 1); o += 1;
memcpy(goi + o, ten, ten_co); o += ten_co;
/* So thuc: gui duoi dang van ban, hoac dung IEEE754
da duoc chuan hoa. Cach don gian va an toan: */
char so[24];
int so_co = snprintf(so, sizeof so, "%.17g", gia_tri);
memcpy(goi + o, &so_co, 1); o += 1;
memcpy(goi + o, so, (size_t)so_co); o += (size_t)so_co;
return gui_khung(s, goi, (uint32_t)o);
}
Tự làm thử
- Cài
send_allvàrecv_allvào một tệpmang.hdùng chung. - Sửa
send_allđể gửi từng byte một và chứng minh khung tin vẫn đúng. - Thêm một thông điệp một triệu byte vào bộ kiểm thử và xác nhận nó qua được.
- Bỏ kiểm tra
d < 1đi rồi gửi một khung có độ dài 0, xem chuyện gì xảy ra. - Cài
recv_all2ba giá trị và phân biệt được đóng giữa khung với đóng giữa hai khung. - Đo xem
gui_khunghai lầnsend_allchậm hơngui_khung_1một lần bao nhiêu phần trăm với thông điệp mười byte.
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
sendvàrecvđều có thể xử lý ít hơn số byte bạn yêu cầu; luôn dùng vòng lặp.EINTRkhông phải lỗi, gọi lại.SIGPIPEtrên POSIX giết chương trình, phải bỏ qua nó.recv == 0nghĩa là đóng sạch, khác hẳnrecv < 0.- Khung tiền tố độ dài cần bốn quyết định: bốn byte,
htonl, giới hạn trên, và kiểm tra cận dưới. - Kiểm chứng bằng cách gửi từng byte một và bằng khung rỗng; đó là hai trường hợp lộ ra hầu hết lỗi.