Bài viết này trình bày về việc phát triển một server sử dụng sockets trong C++. Mục tiêu là tạo một server mạnh mẽ hỗ trợ các chức năng như gửi nhận tin nhắn, thực thi命令, chuyển phát file lớn và giám sát trạng thái client.
Cấu trúc tổng quan
- Kỹ thuật chính: Sử dụng Qt để phát triển giao diện người dùng và xử lý sockets
- Các lớp chính:
- MainWindow: Lớp xử lý giao diện người dùng và tương tác người dùng
- ServerThread: Lớp xử lý kết nối và 통 tin từ client
- ClientThread: Lớp xử lý các giao dịch cụ thể với client
- ConnectionList: Lớp hiển thị trạng thái kết nối client
Thiết kế chi tiết
-
ServerThread:
-
Quản lý các kết nối mới
-
Tạo và quản lý các thread con cho mỗi client
-
Xử lý các tín hiệu từ người dùng (gửi tin nhắn, file, command)
-
ClientThread:
-
Xử lý các giao dịch cụ thể với client
-
Nhận và gửi dữ liệu
-
Thực thi các command từ server
-
ConnectionList:
-
Hiển thị danh sách client đang kết nối
-
Cập nhật trạng thái khi có thay đổi
Mã nguồn tái cấu trúc
MainWindow
#include "mainwindow.h"
#include "ui_mainwindow.h"
class MainWindow {
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_openfile_clicked();
void on_action_triggered();
void on_listen_clicked();
void on_disconnect_clicked();
void onServerClosed();
void on_sendmsg_2_clicked();
private:
Ui::MainWindow *ui;
QLabel *trang_thai_nghe;
QPushButton *listenBtn, *disconnectBtn, *sendMsgBtn, *sendCmdBtn, *.sendFileBtn;
ServerThread *server;
};
MainWindow::MainWindow(QWidget *parent) {
ui = new Ui::MainWindow;
ui->setupUi(this);
trang_thai_nghe = new QLabel;
trang_thai_nghe->setObjectName("listenstatus");
trang_thai_nghe->setText("Tắt nghe");
ui->statusbar->addPermanentWidget(trang_thai_nghe);
// Khởi tạo các nút và disable ban đầu
disconnectBtn->setEnabled(false);
sendMsgBtn->setEnabled(false);
sendCmdBtn->setEnabled(false);
sendFileBtn->setEnabled(false);
}
void MainWindow::on_openfile_clicked() {
QString fileName = QFileDialog::getOpenFileName(this, "Mở File", QDir::homePath());
if (!fileName.isEmpty()) {
ui->fileChosen->setText(fileName);
}
}
void MainWindow::on_action_triggered() {
if (trang_thai_nghe->text() == "Đang nghe") {
ConnectionList *listDlg = new ConnectionList(this);
listDlg->setList(server->clients);
listDlg->show();
} else {
QMessageBox::information(nullptr, "Thông báo", "Server chưa啟 động!");
}
}
void MainWindow::on_listen_clicked() {
server = new ServerThread(this);
server->port = ui->portInput->text().toInt();
server->maxConnections = ui->maxConnInput->text().toInt();
connect(ui->sendMsgBtn, &QPushButton::clicked, server, &ServerThread::sendMsg);
connect(ui->sendCmdBtn, &QPushButton::clicked, server, &ServerThread::sendCmd);
connect(ui->.sendFileBtn, &QPushButton::clicked, server, &ServerThread::sendFile);
connect(server, &ServerThread::serverClosed, this, &MainWindow::onServerClosed);
trang_thai_nghe->setText("Đang nghe");
server->start();
}
// Các hàm xử lý khác được bỏ qua để gọn gàng
ServerThread
#include "serverthread.h"
class ServerThread {
public:
ServerThread(MainWindow *parent);
~ServerThread();
private slots:
void handleNewConnection();
void onSendMsg();
void onSendCmd();
void onSendFile();
private:
QTcpServer *server;
MainWindow *parent;
QList<QTcpSocket*> clients;
QList<QThread*> threads;
};
void ServerThread::start() {
quint16 port = parent->ui->portInput->text().toInt();
quint16 maxConn = parent->ui->maxConnInput->text().toInt();
server->setMaxPendingConnections(maxConn);
connect(server, &QTcpServer::newConnection, this, &ServerThread::handleNewConnection);
server->listen(QHostAddress::Any, port);
}
void ServerThread::stop() {
server->close();
foreach (QTcpSocket *client in server->findChildren<QTcpSocket*>()) {
client->close();
}
foreach (QThread *thread in threads) {
thread->exit(0);
}
emit serverClosed();
}
ClientThread
#include "clientthread.h"
class ClientThread {
public:
ClientThread(QTcpSocket *client);
~ClientThread();
private slots:
void recvMsg();
void sendMsg();
void sendCmd();
void sendFile();
private:
QTcpSocket *client;
MainWindow *parent;
};
void ClientThread::recvMsg() {
QByteArray data = client->readAll();
QString ip = client->peerAddress().toString();
QString message = QString("[%1 gửi] %2").arg(ip).arg(data);
parent->ui->recvMsg->append(message);
}
void ClientThread::sendMsg() {
QString data = parent->ui->msgInput->toPlainText();
QByteArray byteData = ("MSG:" + data).toUtf8();
client->write(byteData);
}
// Các hàm sendCmd và sendFile tương tự được bỏ qua để gọn gàng
Các vấn đề gặp phải
- Quản lý thread: Phải手动回收 các thread và socket sau khi kết thúc
- An toàn thread: Truy cập UI từ thread con không an toàn
- Tính năng chưa hoàn thiện: Chưa xử lý đầy đủ các trường hợp lỗi và chưa tối ưu cho file lớn