项目介绍

本次项目是基于TCP通信的聊天室,用Qt框架制作软件界面,涉及Socket网络编程,TCP协议等知识(未来会增加更多功能,例如文件传输,人脸识别等)

开发时间

2022年 暑假

更新内容

  • 学习了计算机网络基础知识,例如OSI模型,TCP与UDP传输协议等
  • 了解Qt基本操作方式与开发流程
  • 了解Socket套接字网络编程基本原理与通信流程
  • 学习了Qt框架中提供套接字通信的类,熟悉了类的成员函数使用方法
  • 初步设计出Client-Server结构的通信程序

细节展示

通信原理

  • 本项目用的是TCP协议进行通信,它是一个面向连接的,安全的,流式传输协议,是一个传输层协议。

面向连接:是一个双向连接,通过三次握手完成,断开连接需要通过四次挥手完成。
安全:TCP 通信过程中,会对发送的每一数据包都会进行校验,如果发现数据丢失,会自动重传
流式传输:发送端和接收端处理数据的速度,数据的量都可以不一致

Socket套接字就是一套网络通信的接口,以便应用程序能简单地调用该接口通信,实现网络通信。网络通信的主体主要分为两部分:客户端和服务器端。在客户端和服务器通信的时候需要频繁提到三个概念:IP端口通信数据

原始TCP套接字通信流程 - 服务器端

  1. 创建用于监听的套接字

    socket();
  2. 把监听的文件描述符和本地的 IP 端口进行绑定

    bind();
  3. 设置监听,用于监听客户端的连接

    listen();
  4. 等待并接受客户端的连接请求,建立新的连接

    accept();
  5. 通信,读写操作

    // 接收数据
    read(); / recv();
    // 发送数据
    write(); / send();
  6. 断开连接,关闭套接字

    close();

在标准 C++ 没有提供专门用于套接字通信的类,所以只能使用操作系统提供的基于 C 的 API 函数。但在Qt框架中,提供了用于套接字通信的类:

QTcpServer:服务器类,用于监听客户端连接以及和客户端建立连接
QTcpSocket:通信的套接字类,客户端、服务器端都需要使用
这两个套接字通信类都属于网络模块 network

Qt套接字通信流程

服务器端

  1. 创建套接字服务器 QTcpServer 对象
  2. 通过 QTcpServer 对象设置监听
  3. 基于 QTcpServer::newConnection() 信号来检测是否有新的客户端连接
  4. 如果有新的客户端连接,则调用 QTcpSocket *QTcpServer::nextPendingConnection() 得到通信的套接字对象
  5. 使用通信的套接字对象 QTcpSocket 和客户端进行通信

程序图片

代码部分

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QHostAddress>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
setWindowTitle("聊天室 - 服务器端");
setWindowIcon(QIcon(":/Finder.ico"));
ui->port->setText("8899");

//创建监听服务器对象
msg_server = new QTcpServer(this);
connect(msg_server,&QTcpServer::newConnection,this,[=]()
{
msg_tcp = msg_server->nextPendingConnection();
status->setPixmap(QPixmap(":/connect.png").scaled(50,50));

//检测是否可以接收数据
connect(msg_tcp,&QTcpSocket::readyRead,this,[=]()
{
QByteArray data = msg_tcp->readAll();
ui->record->append("客户端说:"+ data);
});

//检测客户端是否断开连接
connect(msg_tcp,&QTcpSocket::disconnected,this,[=]()
{
msg_tcp->close();
//msg_tcp->deleteLater();
status->setPixmap(QPixmap(":/disconnect.png").scaled(50,50));
});

});

//状态栏初始化
status = new QLabel;
status->setPixmap(QPixmap(":/disconnect.png").scaled(50,50));
ui->statusbar->addWidget(new QLabel("连接状态:"));
ui->statusbar->addWidget(status);



}

MainWindow::~MainWindow()
{
delete ui;
}

//开启监听
void MainWindow::on_setListen_clicked()
{
unsigned short port = ui->port->text().toUShort();
msg_server->listen(QHostAddress::Any,port);
ui->setListen->setDisabled(true);
}

//发送信息
void MainWindow::on_sendMsg_clicked()
{
QString msg = ui->msg->toPlainText();
msg_tcp->write(msg.toUtf8());
ui->record->append("服务器说:"+ msg);

}

客户端

  1. 创建通信的套接字类 QTcpSocket 对象

  2. 使用服务器端绑定的 IP 和端口连接服务器 QAbstractSocket::connectToHost()

  3. 使用 QTcpSocket 对象和服务器进行通信

程序图片

代码部分

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QHostAddress>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
setWindowTitle("聊天室 - 客户端");
setWindowIcon(QIcon(":/Finder.ico"));
ui->port->setText("8899");
ui->ip->setText("127.0.0.1");
ui->disconnect->setDisabled(true);


//创建监听服务器对象
msg_tcp = new QTcpSocket(this);

connect(msg_tcp,&QTcpSocket::readyRead,this,[=]()
{
QByteArray data = msg_tcp->readAll();
ui->record->append("服务器说:"+ data);
});

connect(msg_tcp,&QTcpSocket::disconnected,this,[=]()
{
msg_tcp->close();
//msg_tcp->deleteLater();
status->setPixmap(QPixmap(":/disconnect.png").scaled(50,50));
ui->record->append("服务器已和客户端断开连接......");
ui->connect->setDisabled(false);
ui->disconnect->setDisabled(true);
});

connect(msg_tcp,&QTcpSocket::connected,this,[=]()
{
status->setPixmap(QPixmap(":/connect.png").scaled(50,50));
ui->record->append("已经成功连接到聊天室的服务器......");
ui->connect->setDisabled(true);
ui->disconnect->setDisabled(false);

});


//状态栏初始化
status = new QLabel;
status->setPixmap(QPixmap(":/disconnect.png").scaled(50,50));
ui->statusbar->addWidget(new QLabel("连接状态:"));
ui->statusbar->addWidget(status);
}

MainWindow::~MainWindow()
{
delete ui;
}

//发送信息
void MainWindow::on_sendMsg_clicked()
{
QString msg = ui->msg->toPlainText();
msg_tcp->write(msg.toUtf8());
ui->record->append("客户端说:"+ msg);

}

//连接服务器
void MainWindow::on_connect_clicked()
{
QString ip = ui->ip->text();
unsigned short port = ui->port->text().toUShort();
msg_tcp->connectToHost(QHostAddress(ip),port);

}

//断开连接
void MainWindow::on_disconnect_clicked()
{
msg_tcp->close();
ui->connect->setDisabled(false);
ui->disconnect->setDisabled(true);

}

程序测试