Từ bước trước ta đã tạo một server để lắng nghe được 1 client duy nhất. Sau khi nhận được request là đóng luôn server.
Ở bước này ta sẽ thêm phản ứng lại với máy khách. Khi máy khách gửi “Ping” thì bạn sẽ phản hồi lại “Pong”.
Các bước để thiết lập kết nối server là
socket() -> bind() -> listen() -> accept()
Nghĩa là tạo socket; sau đó gắn socket vào IP; rồi bắt đầu lắng nghe các kết nối tới; cuối cùng chấp nhận kết nối.
Trước hết ta cần phải biết lệnh dùng để kết nối với máy khách như thế nào.
Hãy tưởng tượng có 1 khách hàng đến nhà hàng của bạn. Vị khách ấy sẽ được duyệt để vào cổng và ghi lại vào danh sách để tí nữa sẽ phục vụ. Trong C++, ta dùng accept() để đồng ý kết nối từ client.
accept():int accept(int sockfd, struct sockaddr *_Nullable restrict addr,
socklen_t *_Nullable restrict addrlen);
sockfd: bạn sẽ điền cái server (server_fd) vào.
addr: cấu trúc chứa địa chỉ các client.
addrlen: kích thước chỗ chứa client.
Ví dụ dùng hoàn chỉnh:
int client_fd = accept(server_fd, (struct sockaddr *)&client_addr,
(socklen_t *)&client_addr_len);
Như nhà hàng, mỗi khách hàng vô đều có đánh số để ta biết mà phục vụ, server cũng vậy.
Biến client_fd để lưu số của khách vừa mới truy cập để lát nữa ta phục vụ.
Ví dụ: client_fd = 4 sau khi accept, nếu có nhiều client nữa kết nối thì giá trị sẽ là 5, 6,… Ta sẽ xử lí cái số hiệu này sau.
Bạn có thể tìm hiểu thêm ở đây: accept(2) - Linux manual page
Sau khi accept() người dùng xong, ta sẽ phản hồi “Pong”.
Nhưng lưu ý, cách giải mã của Redis có cú pháp riêng nhưng ta sẽ nói sau. Ở bước này chỉ cần phản hồi “+Pong\r\n” là được.
Để làm việc này ta sẽ dùng lệnh send().
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
sockfd: socket khách cần gửi (client_fd)
*buf: con trỏ tới chuỗi cần gửi (ví dụ char* mes = “+PONG\r\n”;)
len: độ dài chuỗi
flags: kiểu gửi dữ liệu (các bạn nghiên cứu thêm)
| Flag | Ý nghĩa |
|---|---|
0 | mặc định (dùng nhiều nhất) |
MSG_DONTWAIT | non-blocking |
MSG_NOSIGNAL | tránh crash khi socket bị đóng |
MSG_OOB | gửi dữ liệu ưu tiên (rare) |
Ví dụ hoàn chỉnh:
const char *respond = "+PONG\r\n";
send(client_fd, respond, strlen(respond), 0);
Tham khảo thêm tại: send(2) - Linux manual page
#include <arpa/inet.h>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <netdb.h>
#include <string>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char **argv) {
// Flush after every std::cout / std::cerr
std::cout << std::unitbuf;
std::cerr << std::unitbuf;
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
std::cerr << "Failed to create server socket\n";
return 1;
}
// Since the tester restarts your program quite often, setting SO_REUSEADDR
// ensures that we don't run into 'Address already in use' errors
int reuse = 1;
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) <
0) {
std::cerr << "setsockopt failed\n";
return 1;
}
server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = ();
((server_fd, ( sockaddr *)&server_addr, (server_addr)) !=
) {
std::cerr << ;
;
}
connection_backlog = ;
((server_fd, connection_backlog) != ) {
std::cerr << ;
;
}
client_addr;
client_addr_len = (client_addr);
std::cout << ;
std::cout << ;
client_fd = (server_fd, ( sockaddr *)&client_addr,
( *)&client_addr_len);
std::cout << ;
*respond = ;
(client_fd, respond, (respond), );
(client_fd);
(server_fd);
;
}
Mình đã đánh dấu các đoạn code mình thêm vào bước này. Nếu bạn muốn tự tay gõ những dòng code cho riêng mình, bạn có thể tham khỏa nền tảng codecrafters.io, ở đây sẽ có hệ thống chấm tự động.
Vậy là chúng ta đã nhận và gửi phản hồi từ người dùng rồi đó. Nhưng hiện tại mới gửi nhận 1 client duy nhất là chương trình dừng rồi. Ở bài kế chúng ta sẽ tìm cách “Pong” cho nhiều clients hơn nha.
Loading comments...