Ở bước trước, server của chúng ta đã có thể kết nối và phản hồi được với 1 client, sau đó chương trình đóng.
Ở bước này, chúng ta sẽ nâng cấp nó lên khiến nó có thể giữ kết nối và trả lời nhiều lần với 1 client.
Trước hết ta phải nhận được tin nhắn từ client đã. Trong C++, lệnh recv()để nhận request từ client.
recv()ssize_t recv(int sockfd, void *buf, size_t len, int flags);
sockfd: client đang kết nối (socket)
*buf: nơi chứa dữ liệu nhận được (char* - mảng kí tự)
len: độ lớn dữ liệu (độ lớn cái mảng char* chứa dữ liệu)
flags: kiểu nhận dữ liệu (các bạn nghiên cứu thêm)
| Flag | Ý nghĩa |
|---|---|
0 | mặc định |
MSG_DONTWAIT | non-blocking |
MSG_PEEK | xem trước (không lấy ra khỏi buffer) |
MSG_WAITALL | chờ đủ dữ liệu |
Ví dụ hoàn chỉnh:
int size_of_buffer = recv(client_fd, buffer, sizeof(buffer), 0);
Ta tạo biến size_of_buffer để chứa độ dài request từ người dùng, sau đó dữ liệu sẽ được đưa vào mảng char* buffer.
Giá trị size_of_buffer nhận được có ý nghĩa như sau:
| Giá trị | Ý nghĩa |
|---|---|
> 0 | số byte nhận được |
0 | client đã đóng kết nối |
-1 | lỗi |
Lưu ý
\0’ ở cuối nên khi dùng std::cout << buffer; có thể dẫn đến xuất hiện kí tự rác lạTham khảo thêm tại: https://man7.org/linux/man-pages/man2/recv.2.html
Sau khi dùng recv() để lấy dữ liệu và lưu giá trị trả về vào size_of_buffer, ta sẽ chia trường hợp để kiểm tra.
size_of_buffer = -1 thì báo lỗi.size_of_buffer = 0 thì client đã ngắt kết nối.size_of_buffer > 0 thì nó chứa số byte nhận được (tức là có dữ liệu người dùng gửi).#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 << ;
buffer[];
*response = ;
() {
size_of_buffer = (client_fd, buffer, (buffer), );
(size_of_buffer == ) {
std::cout << << std::endl;
;
}
(size_of_buffer == ) {
();
;
}
{
buffer[size_of_buffer] = ;
std::cout << buffer << std::endl;
(client_fd, response, (response), );
}
}
(client_fd);
(server_fd);
;
}
Mình thêm ‘\0’ ở cuối để kết thúc chuỗi.
Sau khi chạy chương trình, ta sẽ dùng redis-cli (bạn tìm hướng dẫn cài lên linux) để gửi request.
echo -e "PING\nPING" | redis-cli
Nếu dùng while(), server sẽ phản hồi “Pong” đủ 2 lần.
$ echo -e "PING\nPING" | redis-cli
PONG
PONG
Log sẽ có nội dung:
*2
$7
COMMAND
$4
DOCS
*1
$4
PING
*1
$4
PING
Client discoonedted!
while()Thực chất việc gửi dữ liệu qua mạng nó không hề đơn giản và suôn sẻ.
Khi gửi 1 request ví dụ “*1\r\n$4\r\nPING\r\n“ nhưng đôi lúc do yếu tố nào đó có thể bị tách thành “*1\r\n$4\r\” và “nPING\r\n".
Máy tính có 1 khu vực để chứa các gói tin máy nhận được (gọi là kernel buffer), lệnh recv() chỉ là lệnh lấy các phần máy đã nhận được thôi nên khi có nhiều user kết nối rồi gửi gói tin lớn sẽ bị phân mảnh. Lúc này cần xử lí sâu hơn.
Hiện tại ở bài này dùng while() để đọc dần thông tin có trong kernel buffer, vậy nếu không có while() thì chỉ đọc được “COMMAND DOCS” hay chỉ 1 lệnh “PING” hoặc tệ hơn vậy.
Cơ chế bên dưới TCP hay mạng máy tính thực sự vô cùng phức tạp nên ta cứ tìm hiểu từ từ trong quá trình làm dự án. Trước mắt thì chúng ta đã có thể phản hồi “Pong”, “Pong” với nhiều lần “Ping”, “Ping” từ người dùng rồi đó!
Ở bài tiếp theo ta sẽ tìm hiểu làm sao để có thể kết nối được với nhiều client nhé, cái này siêu khó luôn nha!
Loading comments...