这里用Qt来简单设计实现一个场景,即:html
(1)两端:服务器QtServer和客户端QtClient服务器
(2)功能:服务端链接客户端,二者可以互相发送消息,传送文件,而且显示文件传送进度。网络
环境:VS20013 + Qt5.11.2 + Qt设计师架构
先看效果:app
客户端与服务器的基本概念不说了,关于TCP通讯的三次握手等等,在《计算机网络》里都有详细介绍。这里说下二者是如何创建起通讯链接的。socket
(1)IP地址:首先服务器和每个客户端都有一个地址,即IP地址。(底层的MAC地址,不关心,由于TCP通讯以及IP,是七层架构里面的网络层、传输层了,底层透明)。对于服务器来讲,客户端的数量及地址是未知的,除非创建了链接。可是对于客户端来讲,必须知道服务器的地址,由于二者之间的链接是由客户端主动发起的。tcp
(1)端口号:软件层面的端口号,指的是 “应用层的各类协议进程与运输实体进行层间交互的一种地址”。简而言之,每个TCP链接都是一个进程,操做系统须要为每一个进程分配一个协议端口(即每个客户端与服务端的链接,不是两台主机的链接,而是两个端口的链接)。但一台主机一般会有不少服务,不少进程,单靠一个IP地址不能标识某个具体的进程或者链接。因此用端口号来标识访问的目标服务器以及服务器的目标服务类型。端口号也有分类,但这不是本文的重点,详见教材。函数
(3)TCP链接:总的来讲,TCP的链接管理分为单个阶段:创建链接 -> 数据传送 -> 链接释放。在(2)里说到,每一个TCP链接的是具体IP地址的主机的两个端口,即TCP链接的两个端点由IP地址和端口号组成,这便是套接字(socket)的概念:套接字socket = IP + 端口号。ui
所以,咱们要经过经过套接字来创建服务端与客户端的通讯链接。
this
QTcpSocket:提供套接字
QTcpServer:提供基于TCP的服务端,看官方文档的解释以下:
This class makes it possible to accept incoming TCP connections. You can specify the port or have QTcpServer pick one automatically. You can listen on a specific address or on all the machine’s addresses.
这个解释里面提到两点:
(1)指定端口:即开通哪个端口用于创建TCP链接;
(2)监听:监听(1)中指定的端口是否有链接的请求。
(1)服务器:
(2)客户端:
客户端:
服务端:
咱们先要在工程文件中加入network
QT += core gui network
下面咱们来看看服务器程序步骤:
一、初始化 QTcpServer 对象
TCP_server = new QTcpServer();
二、启动服务器监听
TCP_server->listen(QHostAddress::Any,9988);//9988为端口号
三、链接 QTcpServer 对象的 newConnection 信号槽,当有客户端连接时,客户端会发送 newConnection 信号给服务器,触发槽函数接受连接(获得一个与客户端通讯的套接字 QTcpSocket)
connect(TCP_server, SIGNAL(newConnection()), this, SLOT(slot_newconnect())); // 在与 newConnection 信号链接的槽函数中,获取与相应客户端通讯的套接字 TCP_connectSocket = mServer->nextPendingConnection();
四、QTcpsocket 对象调用成员函数 write,发送数据给客户端
TCP_connectSocket->write(msg.toUtf8());
五、当客户端有数据发送来,QTcpSocket 对象就会发送 readyRead 信号,关联槽函数读取数据
connect(mSocket,SIGNAL(readyRead()),this,SLOT(slot_recvmessage()));
六、链接 QTcpsocket 对象的 disconnected 信号槽,当客户端对象调用成员函数 close,会触发 QTcpsocket 对象的 disconnected 信号,进而触发槽函数进行相应处理
connect(mSocket,SIGNAL(disconnected()),this,SLOT(slot_disconnect()));
TCP_Server.h
#ifndef TCP_SERVER_H #define TCP_SERVER_H #include <QWidget> #include <QTcpServer> #include <QTcpSocket> namespace Ui { class TCP_Server; } class TCP_Server : public QWidget { Q_OBJECT public: explicit TCP_Server(QWidget *parent = nullptr); ~TCP_Server(); private slots: void slot_newconnect(); //创建新链接的槽 void slot_sendmessage(); //发送消息的槽 void slot_recvmessage(); //接收消息的槽 void slot_disconnect(); //取消链接的槽 private: Ui::m_tcpServer *ui; QTcpServer *TCP_server; //QTcpServer服务器 QTcpSocket *TCP_connectSocket; //与客户端链接套接字 }; #endif // TCP_SERVER_H
TCP_Server.cpp
#include "tcp_server.h" #include "ui_tcp_server.h" #include <QMessageBox> #include <QDateTime> TCP_Server::TCP_Server(QWidget *parent) : QWidget(parent), ui(new Ui::TCP_Server) { ui->setupUi(this); //初始化 TCP_server = new QTcpServer(); TCP_connectSocket = nullptr; connect(ui->pushButton_send,SIGNAL(clicked()),this,SLOT(slot_sendmessage())); //调用listen函数监听同时绑定IP和端口号 if(TCP_server->listen(QHostAddress::LocalHost,10000)) //判断listen是否成功,成功则继续执行,链接新接收信号槽 { this->connect(TCP_server,SIGNAL(newConnection()),this,SLOT(slot_newconnect())); //将服务器的新链接信号链接到接收新链接的槽 } else { QMessageBox::critical(this,"错误","IP绑定错误,请关闭其它服务端或更改绑定端口号"); } } TCP_Server::~TCP_Server() { delete ui; } //创建新链接的槽 void TCP_Server::slot_newconnect() { if(TCP_server->hasPendingConnections()) //查询是否有新链接 { TCP_connectSocket = TCP_server->nextPendingConnection(); //获取与真实客户端相连的客户端套接字 ui->textBrowser->append("client login!"); //如有新链接,则提示 this->connect(TCP_connectSocket,SIGNAL(readyRead()),this,SLOT(slot_recvmessage())); //链接客户端的套接字的有新消息信号到接收消息的槽 this->connect(TCP_connectSocket,SIGNAL(disconnected()),this,SLOT(slot_disconnect())); //链接客户端的套接字取消链接信号到取消链接槽 } } //发送消息的槽 void TCP_Server::slot_sendmessage() { QString sendMessage = ui->lineEdit->text(); //获取单行文本框内要发送的内容 if(TCP_connectSocket != nullptr && !sendMessage.isEmpty()) //确保有客户端链接,而且发送内容不为空 { TCP_connectSocket->write(sendMessage.toLatin1()); //发送消息到客户端 QString localDispalyMessage = "send to client: " + sendMessage \ + QDateTime::currentDateTime().toString(" yyyy-M-dd hh:mm:ss") + tr("\n"); ui->textBrowser->append(localDispalyMessage); //将要发送的内容显示在listwidget } ui->lineEdit->clear(); } //接收消息的槽 void TCP_Server::slot_recvmessage() { if(TCP_connectSocket != nullptr) //与客户端链接的socket,不是nullptr,则说明有客户端存在 { QByteArray array = TCP_connectSocket->readAll(); //接收消息 QHostAddress clientaddr = TCP_connectSocket->peerAddress(); //得到IP int port = TCP_connectSocket->peerPort(); //得到端口号 QDateTime datetime = QDateTime::currentDateTime(); QString sendMessage = tr("recv from :") + clientaddr.toString() + tr(" : ") \ + QString::number(port) + tr(" ") + datetime.toString("yyyy-M-dd hh:mm:ss") + tr("\n"); sendMessage += array; ui->textBrowser->append(sendMessage); //将接收到的内容加入到listwidget } } //取消链接的槽 void TCP_Server::slot_disconnect() { if(TCP_connectSocket != nullptr) { ui->textBrowser->append("client logout!"); TCP_connectSocket->close(); //关闭客户端 TCP_connectSocket->deleteLater(); } }
下面咱们来看看客户端程序步骤:
一、初始化 QTcpSocket 对象
TCP_server = new QTcpSocket();
二、QTcpSocket 调用 connectToHost(QHostAddress("IP"), 端口号),链接服务器IP和端口号
TCP_sendMesSocket->connectToHost("127.0.0.1",10000);
三、QTcpsocket 对象调用成员函数 write,发送数据给服务器
//取发送信息编辑框内容 QString msg = ui->sendEdit->toPlainText(); TCP_sendMesSocket->write(msg.toUtf8());//转编码
四、链接QTcpsocket 对象的 connected() 信号槽,当客服端成功链接到服务器后触发 connected() 信号
connect(TCP_sendMesSocket,SIGNAL(connected()),this,SLOT(slot_connected()));
五、链接QTcpsocket 对象的 readyread() 信号槽,当客户端接收到服务端发来数据时触发 readyread() 信号
connect(TCP_sendMesSocket,SIGNAL(readyRead()),this,SLOT(slot_recvmessage()));
六、链接 QTcpsocket 对象的 disconnected 信号槽,当客户端对象调用成员函数 close,会触发 QTcpsocket 对象的 disconnected 信号,进而触发槽函数进行相应处理
connect(TCP_sendMesSocket, SIGNAL(disconnected()), this, SLOT(slot_disconnect()));
TCP_Client.h
#ifndef TCP_CLIENT_H #define TCP_CLIENT_H #include <QWidget> #include <QTcpSocket> namespace Ui { class TCP_Client; } class TCP_Client : public QWidget { Q_OBJECT public: explicit TCP_Client(QWidget *parent = nullptr); ~TCP_Client(); //与按钮交互,故函数都设置为槽函数 private slots: void slot_connected(); //处理成功链接到服务器的槽 void slot_sendmessage(); //发送消息到服务器的槽 void slot_recvmessage(); //接收来自服务器的消息的槽 void slot_disconnect(); //取消与服务器链接的槽 private: Ui::TCP_Client *ui; bool isconnetion; //判断是否链接到服务器的标志位 QTcpSocket *TCP_sendMesSocket; //发送消息套接字 }; #endif // TCP_CLIENT_H
TCP_Client.cpp
#include "tcp_client.h" #include "ui_tcp_client.h" #include <QHostAddress> #include <QDateTime> #include <QMessageBox> TCP_Client::TCP_Client(QWidget *parent) : QWidget(parent), ui(new Ui::TCP_Client) { ui->setupUi(this); /*** 初始化TCP ***/ this->setWindowTitle("TCP客户端"); this->isconnetion = false; //初始化sendMesSocket this->TCP_sendMesSocket = new QTcpSocket(); //终止以前的链接,重置套接字 TCP_sendMesSocket->abort(); //给定IP和端口号,链接服务器 this->TCP_sendMesSocket->connectToHost("127.0.0.1",10000); //QHostAddress::LocalHost等于127.0.0.1,因此二者均可以互相替换 //成功链接服务器的connected()信号链接到slot_connected() (注意:不是connect()信号) connect(TCP_sendMesSocket,SIGNAL(connected()),this,SLOT(slot_connected())); //发送按钮的clicked()信号链接到slot_sendmessage() connect(ui->pushButton_send,SIGNAL(clicked()),this,SLOT(slot_sendmessage())); //有新数据到达时的readyread()信号链接到slot_recvmessage() connect(TCP_sendMesSocket,SIGNAL(readyRead()),this,SLOT(slot_recvmessage())); //与服务器断开链接的disconnected()信号链接到slot_disconnect() connect(TCP_sendMesSocket,SIGNAL(disconnected()),this,SLOT(slot_disconnect())); } TCP_Client::~TCP_Client() { delete ui; } //处理成功链接到服务器的槽 void TCP_Client::slot_connected() { this->isconnetion = true; ui->textBrowser->append(tr("与服务器链接成功:") + QDateTime::currentDateTime().toString("yyyy-M-dd hh:mm:ss")); } //发送消息到服务器的槽 void TCP_Client::slot_sendmessage() { if(this->isconnetion) { QString sendMessage = ui->lineEdit->text(); //从单行文本框得到要发送消息 if(!sendMessage.isEmpty()) { //发送消息到服务器 this->TCP_sendMesSocket->write(sendMessage.toLatin1()); //本地显示发送的消息 QString localDispalyMessage = tr("send to server: ") + sendMessage \ + QDateTime::currentDateTime().toString(" yyyy-M-dd hh:mm:ss") + tr("\n"); ui->textBrowser->append(localDispalyMessage); } else QMessageBox::warning(this,"错误","消息不能为空!",QMessageBox::Ok); } else QMessageBox::warning(this,"错误","未链接到服务器!",QMessageBox::Ok); ui->lineEdit->clear(); } //接收来自服务器的消息的槽 void TCP_Client::slot_recvmessage() { //接收来自服务器的消息 QByteArray byteArray = this->TCP_sendMesSocket->readAll(); QString recvMessage = tr("recv from server: ") + byteArray + QDateTime::currentDateTime().toString(" yyyy-M-dd hh:mm:ss") + tr("\n"); ui->textBrowser->append(recvMessage); } //取消与服务器链接的槽 void TCP_Client::slot_disconnect() { QMessageBox::warning(this,"警告","与服务器的链接中断",QMessageBox::Ok); //关闭并随后删除socket TCP_sendMesSocket->close(); TCP_sendMesSocket->deleteLater(); }
参考: