Qt Tcp网络编程 Tcp通信 服务端编程
一、界面
1.效果
2.文件结构
3.相关部件
说明一下,我用的是Qt 5.15写的,还有就是这是Tcp服务端,功能方面应该看见界面就都知道了,就不再重复说了。
二、代码
代码也没啥好说的了,注释我都基本写上了,就是别忘了在pro文件里加上QT += network
1.TcpServer_zlh.pro
QT += core gui
QT += network
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++17
# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
main.cpp \
widget.cpp
HEADERS += \
widget.h
FORMS += \
widget.ui
# Default rules for deployment. qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target
2.widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QTcpServer>
#include <QTcpSocket>
#include <QNetworkInterface>
#include <QMessageBox>
#include <QDebug>
#include <QFileDialog>
#include <QFile>
#include <QFileDevice>
#include <QFileInfo>
#include <QDateTime>
#include <QDir>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
QTcpServer *tcpserver;
QList<QTcpSocket*> client_connections;
QTcpSocket *current_client;
void recv_display();
//void recv_open_file();
void recv_file();
void file_infomation();
void send_file_infomation();
void send_display();
void send_file();
private slots:
void on_clean_recv_clicked();
void on_clean_send_clicked();
void new_connection_slot();
void disconnect_slot();
void recv();
//void send();
void on_listen_clicked();
void on_send_clicked();
void on_file_box_stateChanged(int arg1);
void on_send_from_file_stateChanged(int arg1);
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
3.main.cpp
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.setWindowTitle("Tcp Server");
w.show();
return a.exec();
}
4.widget.cpp
#include "widget.h"
#include "ui_widget.h"
QString ip_port,ip_port_pre;
bool listen_flag = 0;
QByteArray buffer;
QString file_name;
QString send_file_name;
QFile recv_data;
QFile send_data;
bool file_open_flag=false;
bool send_file_open_flag = false;
QTcpSocket *client;
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
tcpserver = new QTcpServer(this);
ui->ip->setText(QNetworkInterface().allAddresses().at(1).toString());//获取本机IP地址,并设置IP
ui->port->setText("59001");//设置端口号
ui->send->setEnabled(false);//将发送按钮设置为不可点击
connect(tcpserver,SIGNAL(newConnection()),this,SLOT(new_connection_slot()));
}
Widget::~Widget()
{
delete ui;
}
//获取连接过来的客户端信息,并保存
void Widget::new_connection_slot()
{
current_client = tcpserver->nextPendingConnection();//获取连接过来的客户端信息
client_connections.append(current_client);//保存
//将客户端的信息添加到QcomboBox部件上
ui->connection->addItem(tr("%1:%2").arg(current_client->peerAddress().toString().split("::ffff:")[1]).arg(current_client->peerPort()));
ui->connection->setEditable(true);//将QcomboBox设置为可编辑状态
ui->connection->setCurrentText(tr("%1:%2").arg(client_connections[0]->peerAddress().toString().split("::ffff:")[1])\
.arg(client_connections[0]->peerPort()));//将客户端的信息在QcomboBox上显示出来
//已经有连接,所以将发送按钮设置为可点击状态
ui->send->setEnabled(true);
connect(current_client,SIGNAL(readyRead()),this,SLOT(recv()));
connect(current_client,SIGNAL(disconnected()),this,SLOT(disconnect_slot()));
}
//监听与取消监听
void Widget::on_listen_clicked()
{
//先判断目前是处于监听还是非监听状态
if(listen_flag)//非监听状态,断开所有的连接
{
int num = client_connections.length();//得到连接到此服务器的客户端数量
for(int i =0;i<num;i++)
{
QString ipport = tr("%1:%2").arg(client_connections[i]->peerAddress().toString().split("::ffff:")[1])\
.arg(client_connections[i]->peerPort());
ui->connection->removeItem(ui->connection->findText(ipport));//首先在QcomboBox部件上移除
client_connections[i] ->disconnectFromHost();//然后断开连接,并删除保存的客户端信息
}
listen_flag = 0;//是否监听标志位
ui->listen->setText("已停止监听");
ui->send->setEnabled(false);//将发送按钮设置为不可点击状态
tcpserver->close();//停止监听
ui->connection->setEditable(true);
ui->connection->setCurrentText("无连接");
}
else
{
//开始监听,第一个参数表示监听本机所有的网口,第二个参数是监听的端口。
listen_flag = tcpserver->listen(QHostAddress::Any,ui->port->text().toInt());
if(listen_flag ==0)
{
QMessageBox::information(this,"提示","开启监听错误,请重试");
}
else
{
ui->listen->setText("已开始监听");
}
}
}
//接收数据
void Widget::recv()
{
if(ui->file_box->isChecked()&&!file_name.isEmpty())
{
//打开选中的文件
recv_data.setFileName(file_name);
file_open_flag = recv_data.open(QFile::Append|QFile::ReadWrite);
}
for(int i = 0;i<client_connections.length();i++)
{
buffer = client_connections[i]->readAll();//获取某个客户端发来的信息
//如果信息为空,则跳过
if(buffer.isEmpty())
continue;
//获取当前客户端的IP和端口号
ip_port = tr("[%1:%2]:").arg(client_connections[i]->peerAddress().toString().split("::ffff:")[1])\
.arg(client_connections[i]->peerPort());
if(ip_port !=ip_port_pre)//如果这里的客户端和上次的不同,则显示客户端的信息,便于明白是那个客户端发送的信息
{
ui->recvwindow->append(ip_port);
}
//将发送信息到服务器的客户端信息在QcomboBox中显示出来
ui->connection->setEditable(true);
ui->connection->setCurrentText(tr("%1:%2").arg(client_connections[i]->peerAddress().toString().split("::ffff:")[1])\
.arg(client_connections[i]->peerPort()));
ui->send->setEnabled(true);
recv_display();//设置一下接收的格式
ip_port_pre = ip_port;//本次客户端成为上次的客户端了
}
}
//根据选择的条件设置数据的显示格式
void Widget::recv_display()
{
if(ui->file_box->isChecked())//选择了将接收的数据保存到文件
{
recv_file();
}
else
{
if(ui->change_line_box->isChecked())//选择了自动换行
{
if(ui->hex_box->isChecked())//选择了将数据以十六进制显示
{
//将数据转换为十六进制
QString hex(buffer.toHex().toUpper());
int len = hex.length()/2+1;
for(int i =0;i<len;i++)
{
hex.insert(2*i+i-1," ");
}
ui->recvwindow->append(hex);//将数据在界面中显示出来
}
else
{
ui->recvwindow->append(buffer);
}
}
else
{
if(ui->hex_box->isChecked())
{
//同上
QString hex(buffer.toHex().toUpper());
int len = hex.length()/2+1;
qDebug()<<len;
for(int i =0;i<len;i++)
{
hex.insert(2*i+i-1," ");
}
ui->recvwindow->insertPlainText(hex);
}
else
{
ui->recvwindow->insertPlainText(buffer);
}
}
}
}
//file_box状态改变了就调用
void Widget::on_file_box_stateChanged(int arg1)
{
//判断file_box是否处于勾选状态
if(ui->file_box->isChecked())//如果是
{
file_infomation();//选择文件,并获得文件的相关信息
}
//判断是否选择了文件
if(file_name.isEmpty())//如果打开了文件对话框,但是没有选自文件
{
//将file_box设置为非勾选状态
ui->file_box->setCheckState(Qt::Unchecked);
}
qDebug()<<arg1;//没用到,但是看见警告难受,就输出了一下
}
//选择文件,获取文件信息
void Widget::file_infomation()
{
//打开文件按对话框,选择文件
file_name = QFileDialog::getOpenFileName(
this,
tr("文件选择"),
QDir::currentPath(),
"Text Files(*.txt);;Word Files(*.doc,*.docx);;C++ Files(*.cpp);;C Files(*.c);;Python Files(*.py)"
);
//获取文件信息
QStringList file_info_list;
if(!file_name.isEmpty())
{
QFileInfo file_info(file_name);
file_info_list<<tr("文件路径: ")+file_info.absoluteFilePath();
file_info_list<<tr("文件名: ")+file_info.fileName();
file_info_list<<tr("文件类型: ")+file_info.suffix();
qint64 size = file_info.size();
size = size/1024;
file_info_list<<tr("文件大小: ")+QString::number(size)+" KB";
QDateTime time_info = file_info.birthTime();
file_info_list<<tr("创建日期: ") + time_info.toString("yyyy-MM-dd hh:mm:ss");
file_info_list<<tr("修改日期: ") + file_info.lastModified().toString("yyyy-MM-dd hh:mm:ss");
file_info_list<<tr("最后打开日期: ") + file_info.lastRead().toString("yyyy-MM-dd hh:mm:ss");
QString file_info_str = file_info_list.join('\n');
ui->recvwindow->append(file_info_str);
}
else
{
//获取当前时间
QDateTime current_time = QDateTime::currentDateTime();
QString time_str = current_time.toString("yyyy-MM-dd hh::mm::ss");
ui->recvwindow->append(time_str+" :未选择文件");//没选则文件的提示
}
}
//将接收的信息保存到文件
void Widget::recv_file()
{
//判断文件是否打开
if(file_open_flag)//如果是
{
QTextStream out(&recv_data);//使用文本流写文本文件
if(ui->change_line_box->isChecked())//选则了自动换行
{
if(ui->hex_box->isChecked())//选择了转换位十六进制
{
//转换位十六进制
QString hex(buffer.toHex().toUpper());
int len = hex.length()/2+1;
for(int i =0;i<len;i++)
{
hex.insert(2*i+i-1," ");
}
out<<hex<<"\n";//写入文件
//获取当前时间
QDateTime current_time = QDateTime::currentDateTime();
QString time_str = current_time.toString("yyyy-MM-dd hh::mm::ss");
ui->recvwindow->append(time_str+" :数据已写入文件");
recv_data.close();//关闭文件
}
else
{
//同上
out<<buffer<<"\n";
QDateTime current_time = QDateTime::currentDateTime();
QString time_str = current_time.toString("yyyy-MM-dd hh::mm::ss");
ui->recvwindow->append(time_str+" :数据已写入文件");
recv_data.close();
}
}
else
{
//同上
if(ui->hex_box->isChecked())
{
QString hex(buffer.toHex().toUpper());
int len = hex.length()/2+1;
for(int i =0;i<len;i++)
{
hex.insert(2*i+i-1," ");
}
out<<hex;
QDateTime current_time = QDateTime::currentDateTime();
QString time_str = current_time.toString("yyyy-MM-dd hh::mm::ss");
ui->recvwindow->append(time_str+" :数据已写入文件");
recv_data.close();
}
else
{
out<<buffer;
QDateTime current_time = QDateTime::currentDateTime();
QString time_str = current_time.toString("yyyy-MM-dd hh::mm::ss");
ui->recvwindow->append(time_str+" :数据已写入文件");
recv_data.close();
}
}
}
else
{
QDateTime current_time = QDateTime::currentDateTime();
QString time_str = current_time.toString("yyyy-MM-dd hh::mm::ss");
ui->recvwindow->append(time_str+" :文件未能打开");
}
}
//发送数据
void Widget::on_send_clicked()
{
QString client_ip;
int client_port;
if(ui->send_from_file->isChecked()&&!send_file_name.isEmpty())
{
//打开文件
send_data.setFileName(send_file_name);
send_file_open_flag = send_data.open(QFile::ReadWrite|QIODevice::Text);
}
//得到QcomboBox部件上当前显示的客户端的IP和端口号
client_ip = ui->connection->currentText().split(":")[0];
client_port = ui->connection->currentText().split(":")[1].toInt();
for(int i = 0;i<client_connections.length();i++)
{
//找到对应的客户端信息
if(client_connections[i]->peerAddress().toString().split("::ffff:")[1]==client_ip\
&&client_connections[i]->peerPort()==client_port)
{
client = client_connections[i];
send_display();//一些发送的设置
break;
}
}
}
void Widget::send_display()
{
//在接收数据中解释过了,不重复了
if(ui->send_from_file->isChecked())
{
send_file();
}
else
{
QString send_str=ui->sendwindow->toPlainText();
if(ui->hex_send_box->isChecked())
{
QByteArray bu = send_str.toLatin1();
QString hex(bu.toHex().toUpper());
int len = hex.length()/2+1;
qDebug()<<len;
for(int i =0;i<len;i++)
{
hex.insert(2*i+i-1," ");
}
client->write(hex.toLatin1());
}
else
{
client->write(send_str.toLatin1());
}
}
}
void Widget::send_file()
{
//同样在接收中基本都说过了
QTextStream in(&send_data);
if(send_file_open_flag)
{
if(ui->hex_send_box->isChecked())
{
while(!in.atEnd())
{
QString line = in.readLine();
QByteArray bu = line.toLatin1();
QString hex(bu.toHex().toUpper());
int len = hex.length()/2+1;
for(int i =0;i<len;i++)
{
hex.insert(2*i+i-1," ");
}
client->write(hex.toLatin1());
}
QDateTime current_time = QDateTime::currentDateTime();
QString time_str = current_time.toString("yyyy-MM-dd hh::mm::ss");
ui->sendwindow->append(time_str+" :文件发送成功");
send_data.close();
}
else
{
while(!in.atEnd())
{
QString line = in.readLine();
client->write(line.toLatin1());
}
QDateTime current_time = QDateTime::currentDateTime();
QString time_str = current_time.toString("yyyy-MM-dd hh::mm::ss");
ui->sendwindow->append(time_str+" :文件发送成功");
send_data.close();
}
}
else
{
QDateTime current_time = QDateTime::currentDateTime();
QString time_str = current_time.toString("yyyy-MM-dd hh::mm::ss");
ui->recvwindow->append(time_str+" :文件未能读取");
send_data.close();
}
}
void Widget::on_send_from_file_stateChanged(int arg1)
{
if(ui->send_from_file->isChecked())
{
send_file_infomation();
}
if(send_file_name.isEmpty())
{
ui->send_from_file->setCheckState(Qt::Unchecked);
}
qDebug()<<arg1;
}
void Widget::send_file_infomation()
{
send_file_name = QFileDialog::getOpenFileName(
this,
tr("文件选择"),
QDir::currentPath(),
"Text Files(*.txt);;Word Files(*.doc,*.docx);;C++ Files(*.cpp);;C Files(*.c);;Python Files(*.py)"
);
QStringList file_info_list;
if(!send_file_name.isEmpty())
{
QFileInfo file_info(send_file_name);
file_info_list<<tr("文件路径: ")+file_info.absoluteFilePath();
file_info_list<<tr("文件名: ")+file_info.fileName();
file_info_list<<tr("文件类型: ")+file_info.suffix();
qint64 size = file_info.size();
size = size/1024;
file_info_list<<tr("文件大小: ")+QString::number(size)+" KB";
QDateTime time_info = file_info.birthTime();
file_info_list<<tr("创建日期: ") + time_info.toString("yyyy-MM-dd hh:mm:ss");
file_info_list<<tr("修改日期: ") + file_info.lastModified().toString("yyyy-MM-dd hh:mm:ss");
file_info_list<<tr("最后打开日期: ") + file_info.lastRead().toString("yyyy-MM-dd hh:mm:ss");
QString file_info_str = file_info_list.join('\n');
ui->sendwindow->append(file_info_str);
}
else
{
QDateTime current_time = QDateTime::currentDateTime();
QString time_str = current_time.toString("yyyy-MM-dd hh::mm::ss");
ui->sendwindow->append(time_str+" :未选择文件");
}
}
//删除断开连接的客户端信息
void Widget::disconnect_slot()
{
int num = client_connections.length();
for(int i = 0;i<num;i++)
{
//找到断开连接的客户端信息
if(client_connections[i]->state() == QAbstractSocket::UnconnectedState)
{
QString ipport = tr("%1:%2").arg(client_connections[i]->peerAddress().toString().split("::ffff:")[1])\
.arg(client_connections[i]->peerPort());
ui->connection->removeItem(ui->connection->findText(ipport));
client_connections.removeAt(i);
}
}
if(client_connections.length()==0)//如果没有客户端连接了
{
ui->connection->setEditable(true);
ui->connection->setCurrentText("无连接");
}
}
void Widget::on_clean_recv_clicked()
{
ui->recvwindow->clear();
}
void Widget::on_clean_send_clicked()
{
ui->sendwindow->clear();
}
三、不足
一是想加边框划分一下区域,没有加上。二是窗口拉伸时部件不能一起适应变化。三是对中文数据的传输显示还没有处理好。不过,加油,共勉吧。
四、参考文章
https://blog.csdn.net/m0_73443478/article/details/128003603