这里用Qt来简单设计实现一个场景,即:

(1)两端:服务器QtServer和客户端QtClient

(2)功能:服务端连接客户端,两者能够互相发送消息,传送文件,并且显示文件传送进度。

环境:VS20013 + Qt5.11.2 + Qt设计师

先看效果:

一、基本概念

客户端与服务器的基本概念不说了,关于TCP通信的三次握手等等,在《计算机网络》里都有详细介绍。这里说下两者是如何建立起通信连接的。

(1)IP地址:首先服务器和每一个客户端都有一个地址,即IP地址。(底层的MAC地址,不关心,因为TCP通信以及IP,是七层架构里面的网络层、传输层了,底层透明)。对于服务器来说,客户端的数量及地址是未知的,除非建立了连接。但是对于客户端来说,必须知道服务器的地址,因为两者之间的连接是由客户端主动发起的。

(1)端口号:软件层面的端口号,指的是 “应用层的各种协议进程与运输实体进行层间交互的一种地址”。简而言之,每一个TCP连接都是一个进程,操作系统需要为每个进程分配一个协议端口(即每一个客户端与服务端的连接,不是两台主机的连接,而是两个端口的连接)。但一台主机通常会有很多服务,很多进程,单靠一个IP地址不能标识某个具体的进程或者连接。所以用端口号来标识访问的目标服务器以及服务器的目标服务类型。端口号也有分类,但这不是本文的重点,详见教材。

(3)TCP连接:总的来说,TCP的连接管理分为单个阶段:建立连接 -> 数据传送 -> 连接释放。在(2)里说到,每个TCP连接的是具体IP地址的主机的两个端口,即TCP连接的两个端点由IP地址和端口号组成,这即是套接字(socket)的概念:套接字socket = IP + 端口号。

因此,我们要通过通过套接字来建立服务端与客户端的通信连接。

二、Qt相关类

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)服务器:

  1. 创建并初始化 QTcpServer 对象;
  2. 启动服务器监听,通过调用成员函数 listen(QHostAddress::Any, 端口号);
  3. 连接 QTcpServer 对象的 newConnection 信号槽,当有客户端链接时,客户端会发送 newConnection 信号给服务器,触发槽函数接受链接(得到一个与客户端通信的套接字 QTcpSocket);
  4. QTcpsocket 对象调用成员函数 write,发送数据给客户端;
  5. 当客户端有数据发送来,QTcpSocket 对象就会发送 readyRead 信号,关联槽函数读取数据;
  6. 连接 QTcpsocket 对象的 disconnected 信号槽,当客户端对象调用成员函数 close,会触发 QTcpsocket 对象的 disconnected 信号,进而触发槽函数进行相应处理。

(2)客户端:

  1. 创建并初始化 QTcpSocket 对象;
  2. QTcpSocket 调用 connectToHost(QHostAddress("IP"), 端口号),连接服务器IP和端口号;
  3. QTcpsocket 对象调用成员函数 write,发送数据给服务器;
  4. 连接QTcpsocket 对象的 connected() 信号槽,当客服端成功连接到服务器后触发 connected() 信号;
  5. 连接QTcpsocket 对象的 readyread() 信号槽,当客户端接收到服务端发来数据时触发 readyread() 信号;
  6. 连接 QTcpsocket 对象的 disconnected 信号槽,当客户端对象调用成员函数 close,会触发 QTcpsocket 对象的 disconnected 信号,进而触发槽函数进行相应处理。

四、UI设计

客户端:

服务端:

五、服务端实现

我们先要在工程文件中加入network

QT       += core gui network

下面我们来看看服务器程序步骤:

1、初始化 QTcpServer 对象

TCP_server = new QTcpServer();

2、启动服务器监听

TCP_server->listen(QHostAddress::Any,9988);//9988为端口号

3、连接 QTcpServer 对象的 newConnection 信号槽,当有客户端链接时,客户端会发送 newConnection 信号给服务器,触发槽函数接受链接(得到一个与客户端通信的套接字 QTcpSocket)

connect(TCP_server, SIGNAL(newConnection()), this, SLOT(slot_newconnect()));

// 在与 newConnection 信号连接的槽函数中,获取与相应客户端通信的套接字
TCP_connectSocket = mServer->nextPendingConnection();

4、QTcpsocket 对象调用成员函数 write,发送数据给客户端

TCP_connectSocket->write(msg.toUtf8());

5、当客户端有数据发送来,QTcpSocket 对象就会发送 readyRead 信号,关联槽函数读取数据

connect(mSocket,SIGNAL(readyRead()),this,SLOT(slot_recvmessage()));

6、连接 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();
}
}

六、客户端实现

下面我们来看看客户端程序步骤:

1、初始化 QTcpSocket 对象

TCP_server = new QTcpSocket();

2、QTcpSocket 调用 connectToHost(QHostAddress("IP"), 端口号),连接服务器IP和端口号

TCP_sendMesSocket->connectToHost("127.0.0.1",10000);

3、QTcpsocket 对象调用成员函数 write,发送数据给服务器

//取发送信息编辑框内容
QString msg = ui->sendEdit->toPlainText();
TCP_sendMesSocket->write(msg.toUtf8());//转编码

4、连接QTcpsocket 对象的 connected() 信号槽,当客服端成功连接到服务器后触发 connected() 信号

connect(TCP_sendMesSocket,SIGNAL(connected()),this,SLOT(slot_connected()));

5、连接QTcpsocket 对象的 readyread() 信号槽,当客户端接收到服务端发来数据时触发 readyread() 信号

connect(TCP_sendMesSocket,SIGNAL(readyRead()),this,SLOT(slot_recvmessage()));

6、连接 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();
}

参考:

QT之TCP通信

Qt实现客户端与服务器消息发送的更多相关文章

  1. Qt实现客户端与服务器消息发送与文件传输

    Qt实现客户端与服务器消息发送与文件传输需要使用到 QTcpSocket:提供套接字QTcpServer:提供基于TCP的服务端,官方文档的解释如下: This class makes it poss ...

  2. C#socket客户端自己输入消息发送到服务端通信实现通信

    一,服务端代码 using System; using System.Collections.Generic; using System.Linq; using System.Text; using ...

  3. Unity中接收服务器消息并广播事件的实现

    最近接触的项目,是一个棋牌游戏,棋牌游戏需要在前端进行一些操作,然后向服务器发送数据包,在服务器接收到客户端的数据后,会在服务端进行一系列的判断之后,然后发送给客户端一个返回数据,客户端接收到这个返回 ...

  4. spark结合 Openfire服务器,发送聊天消息

    1.下载OpenFire服务器,进行安装,参考http://www.cnblogs.com/hoojo/archive/2012/05/17/2506769.html 2.程序运行客户端:下载客户端代 ...

  5. C语言Socket-单工通信(客户端向服务器发送数据)

    服务端(server) #include <stdio.h> #include <winsock2.h> #pragma comment(lib,"ws2_32.li ...

  6. 反向Ajax,实现服务器向客户端推送消息

    反向Ajax的基本概念是客户端不必从服务器获取信息,服务器会把相关信息直接推送到客户端.这样做的目的是解决Ajax传统Web模型所带来的一个限制:实时信息很难从技术上解决.原因是,客户端必须联系服务器 ...

  7. android客户端向服务器发送请求中文乱码的问

    android客户端向服务器发送请求的时候,并将参数保存到数据库时遇到了中文乱码的问题: 解决方法: url = "http://xxxx.com/Orders/saveorder.html ...

  8. 介绍一款chrom浏览器插件 DHC是一款使用chrome模拟REST客户端向服务器发送测试数据的谷歌浏览器插件

    先打个小广告哈 公司招java架构师,月薪25K以上,负责电商平台架构工作,工作地点在北京 1号线永安里站 附近,如有意向 请把简历发我邮箱jia6235@163.com 可以内部推荐. DHC是一款 ...

  9. qt 利用Qtimer 定时器实现定时消息发送

    为了实现给控制器按周期发送控制指令,利用qt中的Qtimer 实现消息的定时发送. 需要进行三步处理: 1.在mainwindow.h文件中对timerEvent进行声明 public virtual ...

随机推荐

  1. poj 1986tarjan模板题

    #include<iostream> #include<vector> using namespace std; const int N=40010; int pre[N];/ ...

  2. SPOJ 3267 D-query (可持久化线段树,区间重复元素个数)

    D-query Given a sequence of n numbers a1, a2, ..., an and a number of d-queries. A d-query is a pair ...

  3. 前端开发:JavaScript---DOM & BOM

    DOM:Document Object Model  文档对象类型 模态框案例 <!DOCTYPE html> <html lang="en"> <h ...

  4. redis持久化机制【十三】

    一.Redis提供了哪些持久化机制: redis的高性能是因为其所有数据都存在了内存中 ,为了使redis在重启之后数据仍然不丢失,需要将数据同步到硬盘中,这一过程就是持久化. redis支持两种方式 ...

  5. Codeforces Round #457 (Div. 2) B

    B. Jamie and Binary Sequence (changed after round) time limit per test 2 seconds memory limit per te ...

  6. winform总结6=>线程和委托的关系

    基础类: using System; using System.Collections.Generic; using System.Linq; using System.Text; using Sys ...

  7. 安卓常见错误Unable to execute dex: java.nio.BufferOverflowException. Check the Eclipse log for stack trace.

    Unable to execute dex: java.nio.BufferOverflowException. Check the Eclipse log for stack trace. 导入新的 ...

  8. 18.9.22 noip模拟赛

    此题为找规律.期望100 实际100 #include<cstdio> #include<cstring> #include<iostream> #include& ...

  9. 中高级前端应该必会,js实现事件委托代理、切换样式、元素获取相对于文档位置等

    1.介绍 随着组件开发大流行,现在三大框架已经基本占领了整个前端. 这时候,我们要是引入一个 jq 是不是先得你的项目非常臃肿,jq 也很不适合. 这个时候,你就需要来增加你 js 的功底. 2.各种 ...

  10. 请问这个「 (?<=<(\w+)>).*(?=<\/\1>) 」正则表达式是什么意思呢?

    问题:https://www.zhihu.com/question/26480812  (?<=<(\w+)>).*(?=<\/\1>) ---------------- ...