Khắc Nam
Well-Known Member
1. Giới thiệu
Bài viết này minh họa xây dựng chương trình ChatRoom đơn giản nhằm mục đích tìm hiểu kỹ thuật lập trình mạng (socket) sử dụng các lớp thư viện Qt hỗ trợ, gồm:
- Lớp QTcpServer: hỗ trợ tạo một server, lắng nghe, quản lý và phục vụ các kết nối (gửi, nhận dữ liệu)
- Lớp QTcpSocket: tạo kết nối socket, quản lý các kết nối, gửi/nhận dữ liệu từ socket được thiết lập.
Ứng dụng gồm 2 phần:
- Chương trình ChatServer: Quản lý các client kết nối đến. Nhận các thông điệp và gửi broadcast lại các thông điệp này đến tất cả các client.
- Chương trình ChatClient: Kết nối đến Server, gửi các thông điệp đến server và nhận lại các thông điệp được server broadcast lại.
2. Xây dựng chương trình ChatServer
Mã nguồn chatserver.h
#ifndef CHATSERVER_H
#define CHATSERVER_H
#include <QWidget>
#include <QTcpServer>
#include <QList>
#include <QHash>
class QBuffer;
class QTcpSocket;
namespace Ui {
class ChatServer;
}
class ChatServer : public QWidget
{
Q_OBJECT
public:
explicit ChatServer(QWidget *parent = 0);
~ChatServer();
private:
QTcpServer *server;
//Danh sach chua cac connection de phuc vu client ket noi den
QList<QTcpSocket*> connections;
//Mang bo dem du lieu, moi phan tu tuong ung du lieu cua mot socket
QHash<QTcpSocket*, QBuffer*> buffers;
Ui::ChatServer *ui;
private slots: //Tao slots tu dong
void on_btnStop_clicked();
void on_btnStart_clicked();
private slots: //Tao bang cach viet ma
void addConnection(); //Xu ly khi co mot client ket noi den
void removeConnection(); //Xu ly khi co mot client ngat ket noi
void receiveMessage(); //Nhan thong diep tu cac client
};
#endif // CHATSERVER_H
Mã nguồn chatserver.cpp
#include "chatserver.h"
#include "ui_chatserver.h"
#include <QHostAddress>
#include <QTcpSocket>
#include <QBuffer>
static const int DEFAULT_PORT = 6789;
int port;
QHostAddress server_addr;
ChatServer::ChatServer(QWidget *parent) :
QWidget(parent),
ui(new Ui::ChatServer)
{
ui->setupUi(this);
server = new QTcpServer();
//Dang ky signal/slot cho su kien co client ket noi den
connect(server, SIGNAL(newConnection()), this, SLOT(addConnection()));
server_addr = QHostAddress::LocalHost; //Lay dia chi may host
//server_addr = QHostAddress::Any;
port = DEFAULT_PORT; //Cong mac dinh
ui->lineEditIP->setText(server_addr.toString()); //Hien dia chi nay len lineEdit
ui->lineEditIP->isEnabled();
ui->lineEditPort->setText(QString::number(port));
}
ChatServer::~ChatServer()
{
delete ui;
}
void ChatServer:
n_btnStart_clicked()
{
port = ui->lineEditPort->text().toInt();
//server bat dau lang nghe ket noi
bool b = server->listen(server_addr, port);
if(b)
{
qDebug("server started");
ui->btnStart->setEnabled(false);
ui->btnStop->setEnabled(true);
}
else
{
qDebug("server can't start! check IP or Port");
}
}
void ChatServer:
n_btnStop_clicked()
{
if(server->isListening()) //Neu server dang lang nghe
{
server->close(); //thi dung
ui->btnStart->setEnabled(true);
ui->btnStop->setEnabled(false);
}
}
/*******************************************************
Ham xu ly su kien khi co client ket noi den
Thuc hien:
- Tao socket phuc vu ket noi va them vao danh sach quan ly
- Tao bo dem du lieu tuong ung cho socket do va them vao danh sach quan ly
********************************************************/
void ChatServer::addConnection()
{
QTcpSocket* connection = server->nextPendingConnection();
connections.append(connection); //Them ket noi vao danh sach
QBuffer *buffer = new QBuffer(this); //Tao bo dem du lieu cho connection nay
buffer->open(QIODevice::ReadWrite);
buffers.insert(connection, buffer); //Luu vao danh sach
//Dang ky signal/slot cho moi connection khi co su kien disconnect hoac du lieu gui toi
connect(connection, SIGNAL(disconnected()), this, SLOT(removeConnection()));
connect(connection, SIGNAL(readyRead()), this, SLOT(receiveMessage()));
}
/*******************************************************
Ham xu ly su kien khi co client ngat ket noi
Thuc hien:
- Lay socket cua ket noi can ngat
- Lay bo dem du lieu dang quan ly
- Thuc hien ngat ket noi va giai phong bo dem du lieu
********************************************************/
void ChatServer::removeConnection()
{
//Lay socket gui su kien ngat ket noi
QTcpSocket* socket = static_cast<QTcpSocket*>(sender());
QBuffer *buffer = buffers.take(socket); //Lay bo dem du lieu cua ket noi nay (tu danh sach bo dem)
buffer->close(); //Ket thuc gui nhan
buffer->deleteLater();//giai phong bo dem
connections.removeAll(socket); //Xoa socket nay khoi danh sach ket noi server dang quan ly
socket->deleteLater(); //Giai phong socket
}
/*******************************************************
Ham xu ly su kien khi co du lieu san sang nhan tu mot socket
********************************************************/
void ChatServer::receiveMessage()
{
//Xac dinh ket noi nao co thong diep den
QTcpSocket* socket = static_cast<QTcpSocket*>(sender());
//Lay bo dem du lieu tuong ung voi socket (tu bang hash buffers)
QBuffer *buffer = buffers.value(socket);
//ghi toan bo du lieu cua socket nay vao bo dem (buffer) da xac dinh o tren
qint64 bytes = buffer->write(socket->readAll()); //bytes chua kich thuoc ghi thanh cong
//dich chuyen ve dau bo dem bang ham seek
buffer->seek(buffer->pos() - bytes);
//Vong lap doc tung dong (line) cua thong diep chua trong bo dem
while (buffer->canReadLine())
{
QByteArray line = buffer->readLine();//doc tung dong
//Gui broadcast den tat ca cac ket noi dang quan ly
foreach (QTcpSocket* connection, connections)
{
connection->write(line); //Gui bang cach ghi ra socket
}
}
}
3. Xây dựng chương trình ChatClient
- Thiết kế giao diện như hình minh họa
Sử dụng kỹ thuật layout:
- grid layout cho nhóm điều khiển ở trên cùng (labelServer, labelNick, lineEditServer, lineEditNick, spinBoxPort, btnConnect)
- horizontal layout cho nhóm điều khiển ở dưới cùng (labelMsg, lineEditMsg, btnSend)
- Sau đó, sử dụng vertical layout cho cả form (gồm 2 nhóm trên và điều khiển textEditChat ở giữa).
3.1. Mã nguồn chatclient.h
#ifndef CHATCLIENT_H
#define CHATCLIENT_H
#include <QWidget>
#include <QBuffer>
#include <QTcpSocket>
namespace Ui {
class ChatClient;
}
class ChatClient : public QWidget
{
Q_OBJECT
public:
explicit ChatClient(QWidget *parent = 0);
~ChatClient();
//Khai bao cac ham slot
private slots:
void setConnected();
void setDisconnected();
void toggleConnection();
void sendMessage();
void receiveMessage();
private:
Ui::ChatClient *ui;
QBuffer *buffer;
QTcpSocket *socket;
};
#endif // CHATCLIENT_H
3.2. Mã nguồn chatclient.cpp
#include "chatclient.h"
#include "ui_chatclient.h"
static const int DEFAULT_PORT = 6789;
ChatClient::ChatClient(QWidget *parent) :
QWidget(parent),
ui(new Ui::ChatClient)
{
ui->setupUi(this);
//Khoi tao cho cac dieu khien (widget)
ui->spinBoxPort->setRange(1000,32767);
ui->spinBoxPort->setValue(DEFAULT_PORT);
ui->lineEditServer->setText("localhost");
ui->lineEditNick->setText("hungpn");
//Khai bao socket va buffer du lieu
socket = new QTcpSocket(this);
buffer = new QBuffer(this);
buffer->open(QIODevice::ReadWrite);
//Dang ky signal/slot cho cac su kien
//Su kien click nut Connect
connect(ui->btnConnect, SIGNAL(clicked()), this, SLOT(toggleConnection()));
//Su kien go message va enter (gui di)
connect(ui->lineEditMsg, SIGNAL(returnPressed()), this, SLOT(sendMessage()));
//Hoac click nut Send (gui di)
connect(ui->btnSend, SIGNAL(clicked()), this, SLOT(sendMessage()));
//Su kien ket noi Socket thanh cong
connect(socket, SIGNAL(connected()), this, SLOT(setConnected()));
//Su kien ngat ket noi
connect(socket, SIGNAL(disconnected()), this, SLOT(setDisconnected()));
//Su kien san sang nhan du lieu
connect(socket, SIGNAL(readyRead()), this, SLOT(receiveMessage()));
//Ban dau chua ket noi
setDisconnected();
}
ChatClient::~ChatClient()
{
delete ui;
}
/*************************************************
Ham xu ly cac dieu khien khi ket noi thanh cong
**************************************************/
void ChatClient::setConnected()
{
ui->lineEditServer->setEnabled(false);
ui->spinBoxPort->setEnabled(false);
ui->lineEditNick->setEnabled(true);
ui->lineEditMsg->setEnabled(true);
ui->textEditChat->setEnabled(true);
ui->textEditChat->clear();
ui->btnSend->setEnabled(true);
ui->btnConnect->setText("Disconnect");
}
void ChatClient::setDisconnected()
{
ui->lineEditServer->setEnabled(true);
ui->spinBoxPort->setEnabled(true);
ui->lineEditNick->setEnabled(false);
ui->lineEditMsg->setEnabled(false);
ui->textEditChat->setEnabled(false);
ui->textEditChat->clear();
ui->btnSend->setEnabled(false);
ui->btnConnect->setText("Connect");
}
/*************************************************
Ham tat/bat ket noi den server
**************************************************/
void ChatClient::toggleConnection()
{
if(socket->state() == QAbstractSocket::UnconnectedState)
{
socket->connectToHost(ui->lineEditServer->text(), ui->spinBoxPort->value());
}
else
{
socket->disconnectFromHost();
}
}
void ChatClient::sendMessage()
{
// "<nick> message\n"
QString nick = ui->lineEditNick->text().toLatin1();
QString msg = ui->lineEditMsg->text().toLatin1();
//socket->write("<" + nick + "> " + msg + "\n");
socket->write("<" + ui->lineEditNick->text().toLatin1() + "> " + ui->lineEditMsg->text().toLatin1() + "\n");
ui->lineEditMsg->clear();
}
void ChatClient::receiveMessage()
{
// missing some checks for returns values for the sake of simplicity
qint64 bytes = buffer->write(socket->readAll());
// go back as many bytes as we just wrote so that it can be read
buffer->seek(buffer->pos() - bytes);
// read only full lines, line by line
while (buffer->canReadLine())
{
QString line = buffer->readLine();
ui->textEditChat->append(line.simplified());
}
}
4. Một số lớp thư viện Qt sử dụng trong ứng dụng
- Chương trình ChatServer có sử dụng một số lớp thư viện Qt quan trọng để hỗ trợ xử lý là:
· Lớp QList để quản lý một danh sách phần tử
· Lớp QBuffer cung cấp bộ đệm dữ liệu
· Lớp QHash quản lý một từ điển (dictionary) dựa trên bảng hash
4.1. Lớp QList:
QList<QTcpSocket*> connections;
Tạo ra một danh sách connections để quản lý các kết nối từ client đến. Một kết nối mới (là một socket) sẽ được thêm vào danh sách này khi có sự kiện
newConnection và sẽ được xóa khỏi danh sách khi có sự kiện ngắt kết nối (disconnected)
//Thêm một kết nối mới
QTcpSocket* connection = server->nextPendingConnection();
connections.append(connection); //Them ket noi vao danh sach
//Xóa một kết nối khỏi danh sách khi bị ngắt
QTcpSocket* socket = static_cast<QTcpSocket*>(sender());
//...
connections.removeAll(socket);
4.2. Lớp QHash
Lớp QHash cung cấp một từ điển dựa trên bảng hash.
Qhash<Key, T> là một lớp chứa cặp (key, value) và cung cấp cơ chế để truy cập nhanh vào value (giá trị) liên kết với một key (khóa)
Khai báo:
QHash<QTcpSocket*, QBuffer*> buffers;
Tạo ra một bảng hash có tên buffers để quản lý các cặp <socket, buffer>
Trong đó: socket là kết nối được tạo ra cho mỗi client kết nối đến server và buffer là bộ đệm (đối tượng lớp Qbuffer) để chứa dữ liệu gửi/nhận trên socket đó.
Câu lệnh:
buffers.insert(connection, buffer); //Luu vao danh sach
Sẽ chèn thêm một cặp <connection, buffer> vào bảng hash buffers để quản lý các kết nối và dữ liệu của nó.
Để lấy bộ đệm dữ liệu ứng với một kết nối (socket):
//Lay bo dem du lieu cua socket nay
QBuffer *buffer = buffers.value(socket);
4.3. Lớp QBuffer
Lớp này cung cấp cơ chế để giao tiếp với một mảng dữ liệu kiểu byte (QByteArray) sử dụng giao diện QIODevice
Trong hàm ChatServer::receiveMessage() xử lý sự kiện nhận thông điệp từ Client
//Xac dinh ket noi nao co thong diep den
QTcpSocket* socket = static_cast<QTcpSocket*>(sender());
//Lay bo dem du lieu tuong ung voi socket (tu bang hash buffers)
QBuffer *buffer = buffers.value(socket);
//ghi toan bo du lieu cua socket nay vao bo dem (buffer) da xac dinh o tren
qint64 bytes = buffer->write(socket->readAll()); //bytes chua kich thuoc ghithanh cong
//dich chuyen ve dau bo dem bang ham seek
buffer->seek(buffer->pos() - bytes);
//Vong lap doc tung dong (line) cua thong diep chua trong bo dem
while (buffer->canReadLine())
{
QByteArray line = buffer->readLine();//doc tung dong
//Gui broadcast den tat ca cac ket noi dang quan ly
foreach (QTcpSocket* connection, connections)
{
connection->write(line); //Gui bang cach ghi ra socket
}
}
Bài viết này minh họa xây dựng chương trình ChatRoom đơn giản nhằm mục đích tìm hiểu kỹ thuật lập trình mạng (socket) sử dụng các lớp thư viện Qt hỗ trợ, gồm:
- Lớp QTcpServer: hỗ trợ tạo một server, lắng nghe, quản lý và phục vụ các kết nối (gửi, nhận dữ liệu)
- Lớp QTcpSocket: tạo kết nối socket, quản lý các kết nối, gửi/nhận dữ liệu từ socket được thiết lập.
Ứng dụng gồm 2 phần:
- Chương trình ChatServer: Quản lý các client kết nối đến. Nhận các thông điệp và gửi broadcast lại các thông điệp này đến tất cả các client.

- Chương trình ChatClient: Kết nối đến Server, gửi các thông điệp đến server và nhận lại các thông điệp được server broadcast lại.


2. Xây dựng chương trình ChatServer
Mã nguồn chatserver.h
#ifndef CHATSERVER_H
#define CHATSERVER_H
#include <QWidget>
#include <QTcpServer>
#include <QList>
#include <QHash>
class QBuffer;
class QTcpSocket;
namespace Ui {
class ChatServer;
}
class ChatServer : public QWidget
{
Q_OBJECT
public:
explicit ChatServer(QWidget *parent = 0);
~ChatServer();
private:
QTcpServer *server;
//Danh sach chua cac connection de phuc vu client ket noi den
QList<QTcpSocket*> connections;
//Mang bo dem du lieu, moi phan tu tuong ung du lieu cua mot socket
QHash<QTcpSocket*, QBuffer*> buffers;
Ui::ChatServer *ui;
private slots: //Tao slots tu dong
void on_btnStop_clicked();
void on_btnStart_clicked();
private slots: //Tao bang cach viet ma
void addConnection(); //Xu ly khi co mot client ket noi den
void removeConnection(); //Xu ly khi co mot client ngat ket noi
void receiveMessage(); //Nhan thong diep tu cac client
};
#endif // CHATSERVER_H
Mã nguồn chatserver.cpp
#include "chatserver.h"
#include "ui_chatserver.h"
#include <QHostAddress>
#include <QTcpSocket>
#include <QBuffer>
static const int DEFAULT_PORT = 6789;
int port;
QHostAddress server_addr;
ChatServer::ChatServer(QWidget *parent) :
QWidget(parent),
ui(new Ui::ChatServer)
{
ui->setupUi(this);
server = new QTcpServer();
//Dang ky signal/slot cho su kien co client ket noi den
connect(server, SIGNAL(newConnection()), this, SLOT(addConnection()));
server_addr = QHostAddress::LocalHost; //Lay dia chi may host
//server_addr = QHostAddress::Any;
port = DEFAULT_PORT; //Cong mac dinh
ui->lineEditIP->setText(server_addr.toString()); //Hien dia chi nay len lineEdit
ui->lineEditIP->isEnabled();
ui->lineEditPort->setText(QString::number(port));
}
ChatServer::~ChatServer()
{
delete ui;
}
void ChatServer:
{
port = ui->lineEditPort->text().toInt();
//server bat dau lang nghe ket noi
bool b = server->listen(server_addr, port);
if(b)
{
qDebug("server started");
ui->btnStart->setEnabled(false);
ui->btnStop->setEnabled(true);
}
else
{
qDebug("server can't start! check IP or Port");
}
}
void ChatServer:
{
if(server->isListening()) //Neu server dang lang nghe
{
server->close(); //thi dung
ui->btnStart->setEnabled(true);
ui->btnStop->setEnabled(false);
}
}
/*******************************************************
Ham xu ly su kien khi co client ket noi den
Thuc hien:
- Tao socket phuc vu ket noi va them vao danh sach quan ly
- Tao bo dem du lieu tuong ung cho socket do va them vao danh sach quan ly
********************************************************/
void ChatServer::addConnection()
{
QTcpSocket* connection = server->nextPendingConnection();
connections.append(connection); //Them ket noi vao danh sach
QBuffer *buffer = new QBuffer(this); //Tao bo dem du lieu cho connection nay
buffer->open(QIODevice::ReadWrite);
buffers.insert(connection, buffer); //Luu vao danh sach
//Dang ky signal/slot cho moi connection khi co su kien disconnect hoac du lieu gui toi
connect(connection, SIGNAL(disconnected()), this, SLOT(removeConnection()));
connect(connection, SIGNAL(readyRead()), this, SLOT(receiveMessage()));
}
/*******************************************************
Ham xu ly su kien khi co client ngat ket noi
Thuc hien:
- Lay socket cua ket noi can ngat
- Lay bo dem du lieu dang quan ly
- Thuc hien ngat ket noi va giai phong bo dem du lieu
********************************************************/
void ChatServer::removeConnection()
{
//Lay socket gui su kien ngat ket noi
QTcpSocket* socket = static_cast<QTcpSocket*>(sender());
QBuffer *buffer = buffers.take(socket); //Lay bo dem du lieu cua ket noi nay (tu danh sach bo dem)
buffer->close(); //Ket thuc gui nhan
buffer->deleteLater();//giai phong bo dem
connections.removeAll(socket); //Xoa socket nay khoi danh sach ket noi server dang quan ly
socket->deleteLater(); //Giai phong socket
}
/*******************************************************
Ham xu ly su kien khi co du lieu san sang nhan tu mot socket
********************************************************/
void ChatServer::receiveMessage()
{
//Xac dinh ket noi nao co thong diep den
QTcpSocket* socket = static_cast<QTcpSocket*>(sender());
//Lay bo dem du lieu tuong ung voi socket (tu bang hash buffers)
QBuffer *buffer = buffers.value(socket);
//ghi toan bo du lieu cua socket nay vao bo dem (buffer) da xac dinh o tren
qint64 bytes = buffer->write(socket->readAll()); //bytes chua kich thuoc ghi thanh cong
//dich chuyen ve dau bo dem bang ham seek
buffer->seek(buffer->pos() - bytes);
//Vong lap doc tung dong (line) cua thong diep chua trong bo dem
while (buffer->canReadLine())
{
QByteArray line = buffer->readLine();//doc tung dong
//Gui broadcast den tat ca cac ket noi dang quan ly
foreach (QTcpSocket* connection, connections)
{
connection->write(line); //Gui bang cach ghi ra socket
}
}
}
3. Xây dựng chương trình ChatClient
- Thiết kế giao diện như hình minh họa

Sử dụng kỹ thuật layout:
- grid layout cho nhóm điều khiển ở trên cùng (labelServer, labelNick, lineEditServer, lineEditNick, spinBoxPort, btnConnect)
- horizontal layout cho nhóm điều khiển ở dưới cùng (labelMsg, lineEditMsg, btnSend)
- Sau đó, sử dụng vertical layout cho cả form (gồm 2 nhóm trên và điều khiển textEditChat ở giữa).
3.1. Mã nguồn chatclient.h
#ifndef CHATCLIENT_H
#define CHATCLIENT_H
#include <QWidget>
#include <QBuffer>
#include <QTcpSocket>
namespace Ui {
class ChatClient;
}
class ChatClient : public QWidget
{
Q_OBJECT
public:
explicit ChatClient(QWidget *parent = 0);
~ChatClient();
//Khai bao cac ham slot
private slots:
void setConnected();
void setDisconnected();
void toggleConnection();
void sendMessage();
void receiveMessage();
private:
Ui::ChatClient *ui;
QBuffer *buffer;
QTcpSocket *socket;
};
#endif // CHATCLIENT_H
3.2. Mã nguồn chatclient.cpp
#include "chatclient.h"
#include "ui_chatclient.h"
static const int DEFAULT_PORT = 6789;
ChatClient::ChatClient(QWidget *parent) :
QWidget(parent),
ui(new Ui::ChatClient)
{
ui->setupUi(this);
//Khoi tao cho cac dieu khien (widget)
ui->spinBoxPort->setRange(1000,32767);
ui->spinBoxPort->setValue(DEFAULT_PORT);
ui->lineEditServer->setText("localhost");
ui->lineEditNick->setText("hungpn");
//Khai bao socket va buffer du lieu
socket = new QTcpSocket(this);
buffer = new QBuffer(this);
buffer->open(QIODevice::ReadWrite);
//Dang ky signal/slot cho cac su kien
//Su kien click nut Connect
connect(ui->btnConnect, SIGNAL(clicked()), this, SLOT(toggleConnection()));
//Su kien go message va enter (gui di)
connect(ui->lineEditMsg, SIGNAL(returnPressed()), this, SLOT(sendMessage()));
//Hoac click nut Send (gui di)
connect(ui->btnSend, SIGNAL(clicked()), this, SLOT(sendMessage()));
//Su kien ket noi Socket thanh cong
connect(socket, SIGNAL(connected()), this, SLOT(setConnected()));
//Su kien ngat ket noi
connect(socket, SIGNAL(disconnected()), this, SLOT(setDisconnected()));
//Su kien san sang nhan du lieu
connect(socket, SIGNAL(readyRead()), this, SLOT(receiveMessage()));
//Ban dau chua ket noi
setDisconnected();
}
ChatClient::~ChatClient()
{
delete ui;
}
/*************************************************
Ham xu ly cac dieu khien khi ket noi thanh cong
**************************************************/
void ChatClient::setConnected()
{
ui->lineEditServer->setEnabled(false);
ui->spinBoxPort->setEnabled(false);
ui->lineEditNick->setEnabled(true);
ui->lineEditMsg->setEnabled(true);
ui->textEditChat->setEnabled(true);
ui->textEditChat->clear();
ui->btnSend->setEnabled(true);
ui->btnConnect->setText("Disconnect");
}
void ChatClient::setDisconnected()
{
ui->lineEditServer->setEnabled(true);
ui->spinBoxPort->setEnabled(true);
ui->lineEditNick->setEnabled(false);
ui->lineEditMsg->setEnabled(false);
ui->textEditChat->setEnabled(false);
ui->textEditChat->clear();
ui->btnSend->setEnabled(false);
ui->btnConnect->setText("Connect");
}
/*************************************************
Ham tat/bat ket noi den server
**************************************************/
void ChatClient::toggleConnection()
{
if(socket->state() == QAbstractSocket::UnconnectedState)
{
socket->connectToHost(ui->lineEditServer->text(), ui->spinBoxPort->value());
}
else
{
socket->disconnectFromHost();
}
}
void ChatClient::sendMessage()
{
// "<nick> message\n"
QString nick = ui->lineEditNick->text().toLatin1();
QString msg = ui->lineEditMsg->text().toLatin1();
//socket->write("<" + nick + "> " + msg + "\n");
socket->write("<" + ui->lineEditNick->text().toLatin1() + "> " + ui->lineEditMsg->text().toLatin1() + "\n");
ui->lineEditMsg->clear();
}
void ChatClient::receiveMessage()
{
// missing some checks for returns values for the sake of simplicity
qint64 bytes = buffer->write(socket->readAll());
// go back as many bytes as we just wrote so that it can be read
buffer->seek(buffer->pos() - bytes);
// read only full lines, line by line
while (buffer->canReadLine())
{
QString line = buffer->readLine();
ui->textEditChat->append(line.simplified());
}
}
4. Một số lớp thư viện Qt sử dụng trong ứng dụng
- Chương trình ChatServer có sử dụng một số lớp thư viện Qt quan trọng để hỗ trợ xử lý là:
· Lớp QList để quản lý một danh sách phần tử
· Lớp QBuffer cung cấp bộ đệm dữ liệu
· Lớp QHash quản lý một từ điển (dictionary) dựa trên bảng hash
4.1. Lớp QList:
QList<QTcpSocket*> connections;
Tạo ra một danh sách connections để quản lý các kết nối từ client đến. Một kết nối mới (là một socket) sẽ được thêm vào danh sách này khi có sự kiện
newConnection và sẽ được xóa khỏi danh sách khi có sự kiện ngắt kết nối (disconnected)
//Thêm một kết nối mới
QTcpSocket* connection = server->nextPendingConnection();
connections.append(connection); //Them ket noi vao danh sach
//Xóa một kết nối khỏi danh sách khi bị ngắt
QTcpSocket* socket = static_cast<QTcpSocket*>(sender());
//...
connections.removeAll(socket);
4.2. Lớp QHash
Lớp QHash cung cấp một từ điển dựa trên bảng hash.
Qhash<Key, T> là một lớp chứa cặp (key, value) và cung cấp cơ chế để truy cập nhanh vào value (giá trị) liên kết với một key (khóa)
Khai báo:
QHash<QTcpSocket*, QBuffer*> buffers;
Tạo ra một bảng hash có tên buffers để quản lý các cặp <socket, buffer>
Trong đó: socket là kết nối được tạo ra cho mỗi client kết nối đến server và buffer là bộ đệm (đối tượng lớp Qbuffer) để chứa dữ liệu gửi/nhận trên socket đó.
Câu lệnh:
buffers.insert(connection, buffer); //Luu vao danh sach
Sẽ chèn thêm một cặp <connection, buffer> vào bảng hash buffers để quản lý các kết nối và dữ liệu của nó.
Để lấy bộ đệm dữ liệu ứng với một kết nối (socket):
//Lay bo dem du lieu cua socket nay
QBuffer *buffer = buffers.value(socket);
4.3. Lớp QBuffer
Lớp này cung cấp cơ chế để giao tiếp với một mảng dữ liệu kiểu byte (QByteArray) sử dụng giao diện QIODevice
Trong hàm ChatServer::receiveMessage() xử lý sự kiện nhận thông điệp từ Client
//Xac dinh ket noi nao co thong diep den
QTcpSocket* socket = static_cast<QTcpSocket*>(sender());
//Lay bo dem du lieu tuong ung voi socket (tu bang hash buffers)
QBuffer *buffer = buffers.value(socket);
//ghi toan bo du lieu cua socket nay vao bo dem (buffer) da xac dinh o tren
qint64 bytes = buffer->write(socket->readAll()); //bytes chua kich thuoc ghithanh cong
//dich chuyen ve dau bo dem bang ham seek
buffer->seek(buffer->pos() - bytes);
//Vong lap doc tung dong (line) cua thong diep chua trong bo dem
while (buffer->canReadLine())
{
QByteArray line = buffer->readLine();//doc tung dong
//Gui broadcast den tat ca cac ket noi dang quan ly
foreach (QTcpSocket* connection, connections)
{
connection->write(line); //Gui bang cach ghi ra socket
}
}