这里主要说的是TCP通信
客户端和客户端之间是不能直接交互的,这也是服务器存在的意义.
响应和接收信息时都是异步通信的方式,产生相应信号
使用QTcpServer(QT中创建TCP服务器的类)创建服务器
设置监听服务器ip地址和端口
获取客户端通信的TCP套接字
保存客户端通信套接字
实时接收客户端发送的消息
检查客户端是否有消息
有消息则读取 保存 显示
将消息发给所有在线的客户端
使用QTcpSocket创建套接字
通过配置服务器ip和端口想服务器发送请求
连接成功,发送进入聊天提示信息
输入聊天信息,发送到服务器
实时接收服务器发送的信息,并显示
TCP(传输控制协议),是一个可靠的,基于字节流的,面向连接的传输层协议.
在OSI定义的七层网络模型中,TCP属于第四层传输层所指定的功能,
在TCP/IP协议族中,位于IP层之上,应用层之下的中间层.
主机应用层之间经常需要可靠的,像管道一样的连接,但IP层不提供这样的流机制.
TCP协议符合这一要求,特别适合连续传输数据,同时能保证数据的安全性.
HTTP协议就是基于TCP协议实现的.
提供了TCP服务器,可以快速的建立TCP服务器,平且可以实时的响应客户端的连接请求.
QTcpServer::listen()可以指定服务器端口号(端口号0-65535,但是0-1024一般是系统使用的),没有指定QTcpServer会自动选择一个可用的端口号,该函数还会监听当前主机指定的IP地址(QHostAddress)(服务器主机上可能有多块网卡,例如笔记本上可以通过有线,无线连接(就好像手机可以双卡双待一样,设置一个IP就相当于设置一个电话号)),通常设置成QHostAddress::Any(对应0.0.0.0)这个地址可以监听所有地址.
设置监听后,每当检测到客户端发来的连接请求,都会发送信号newConnection(),可以自定义槽函数,调用nextPandingConnection()获取和客户端通信的套接字.
QTcpServer tcp_server;
tcp_server.listen(QHostAddress::Any,port);
connect(&tcpserver,SLGNAL(newConnection()),this,SLOT(answerConnection()自定义槽函数));
void answerConnection()
{
//获取和客户端通信的套接字
QTcpServer* client_socket = tcp_server.nextPendingConnection();
//保存客户端套接字
client_list.append(client_socket);
//客户端向服务器发送消息时,触发readyread信号
connect(client_socket,SIGNAL,(readyread()),this,SLOT(answerReadyread()));
}
void answerReadyread()
{
//遍历检查哪个客户端有消息
for(int i = 0;i < client_list.size();i++)
{
//获取等待套接字字节数,没有消息返回0
if(client_list.at[i]->bytesAvailable())
{
QByteArray buf = client_list.at(i)->readALL();//读取保存到buf
ui->listWidget->addItem(buf);//显示到界面
sendMessage(buf);//发送给其他客户端
}
}
}
QTcpSocket tcp_socket;//创建和服务器通信的套接字
tcp_socket.connectToHost(server_ip,server_port);//向服务器发送连接请求
//向服务器发送连接时会发送信号connected
connect(&tcp_socket,SIGNAL(connected()),this,SLOT(answerConnected()));
//收到服务器转发消息时会发送readyRead()信号
connect(&tcp_socket,SIGNAL(readyRead()),this,SLOT(answerReadyRead()));
void answerReadyRead()
{
if(tcp_socket.bytesAvailable())
{
QByteArray buf = tcp_socket.readAll();
ui->listWidget->addItem(but);
}
}
#ifndef SERVERCHAT_H
#define SERVERCHAT_H
#include
#include //tcp服务器
#include //和客户端通信的套接字
#include
#include
#include
QT_BEGIN_NAMESPACE
namespace Ui { class ServerChat; }
QT_END_NAMESPACE
class ServerChat : public QDialog
{
Q_OBJECT
public:
ServerChat(QWidget *parent = nullptr);
~ServerChat();
private slots:
void on_pushButton_create_clicked();
//响应客户端连接请求的槽函数
void answerNewConnection();
//接收客户端消息的槽函数
void answerReadyRead();
//信息发送到其他客户端
void sendMessage(const QByteArray& buf);
//定期删除已离线的套接字
void answerTimeout();
private:
Ui::ServerChat *ui;
QTcpServer tcp_server;//服务器对象
quint16 port;//服务器端口 unsigned short别名
QList client_list;//容器保存和客户端通信的套接字
QTimer timer;
};
#endif // SERVERCHAT_H
#include "serverchat.h"
#include "ui_serverchat.h"
ServerChat::ServerChat(QWidget *parent)
: QDialog(parent)
, ui(new Ui::ServerChat)
{
ui->setupUi(this);
//异步通信的机制来实现,不需要阻塞等待请求
//当有客户端向服务器发送连接请求时会发送信号
connect(&tcp_server,SIGNAL(newConnection()),this,SLOT(answerNewConnection()));
//定时器到时,发送信号.我说我怎么写的这么别扭呢,成员没用m_开头...
connect(&this->timer,SIGNAL(timeout()),this,SLOT(answerTimeout()));
}
ServerChat::~ServerChat()
{
delete ui;
}
//创建服务器 点击按钮发送newConnection信号
void ServerChat::on_pushButton_create_clicked()
{
//获取服务器端口
this->port = ui->lineEdit_port->text().toShort();
//设置服务器IP和端口
if(tcp_server.listen(QHostAddress::Any,port))
{
qDebug() << "server creating a successful";
//创建成功禁用创建按钮和输入端口号按钮
ui->pushButton_create->setEnabled(false);
ui->lineEdit_port->setEnabled(false);
//开启定时器
this->timer.start(5000);
}
else
{
QMessageBox::critical(nullptr,"错误","服务器创建失败");
}
}
void ServerChat::answerNewConnection()
{
//获取和客户端通信的套接字
QTcpSocket* client_socket = tcp_server.nextPendingConnection();
//保存套接字
client_list.append(client_socket);
//当客户端向服务器发送信息时,会发送信号
connect(client_socket,SIGNAL(readyRead()),this,SLOT(answerReadyRead()));
}
void ServerChat::answerReadyRead()
{
//每个客户端都可以向服务器发送信息
//需要遍历查看是哪一个客户端向服务器发送的信息
for(int i = 0;i < client_list.size();i++)
{
//存在信息返回套接字消息的字节数,没有消息返回0
if(client_list.at(i)->bytesAvailable())
{
//读取保存信息
QByteArray buf = client_list.at(i)->readAll();
//显示到界面
ui->listWidget->addItem(buf);
ui->listWidget->scrollToBottom();//底部显示最新信息
//将信息发送给所有客户端
this->sendMessage(buf);
}
}
}
void ServerChat::sendMessage(const QByteArray &buf)
{
for(int i = 0;i < client_list.size();i++)
{
client_list.at(i)->write(buf);
}
}
void ServerChat::answerTimeout()
{
//遍历检查客户端套接字是否断开连接,如果断开连接删除
for(int i = 0;i < client_list.size();i++)
{
//QAbstractSocket::UnconnectedState 0 The socket is not connected.
//返回套接字状态,QAbstractSocket::UnconnectedState就是一个宏,表示已断开连接
if(client_list.at(i)->state() == QAbstractSocket::UnconnectedState)
{
//删除套接字
client_list.removeAt(i);
//删除会改变索引值,为了防止跳过数据
i--;
}
}
}
#ifndef CLIENTCHAT_H
#define CLIENTCHAT_H
#include
#include //服务器地址
#include //和服务器通信套接字
#include
#include
QT_BEGIN_NAMESPACE
namespace Ui { class ClientChat; }
QT_END_NAMESPACE
class ClientChat : public QDialog
{
Q_OBJECT
public:
ClientChat(QWidget *parent = nullptr);
~ClientChat();
private slots:
void on_pushButton_send_clicked();
void on_pushButton_connect_server_clicked();
//向服务器发送请求,成功发送connected信号
void answerConnected();//响应信号
//和服务器断开会发送DisConnected信号
void answerDisConnected();//响应信号
//服务器发送消息时会发送ReadyRead信号
void answerReadyRead();//响应信号
//网络出现异常时会产生Error信号
void answerError();
private:
Ui::ClientChat *ui;
bool falg;//标识客户端是否在线
QTcpSocket tcp_socket;//和服务器通信的套接字
QHostAddress server_ip;//服务器地址
quint16 server_port;//服务器端口
QString user_name;//聊天昵称
};
#endif // CLIENTCHAT_H
#include "clientchat.h"
#include "ui_clientchat.h"
ClientChat::ClientChat(QWidget *parent)
: QDialog(parent)
, ui(new Ui::ClientChat)
{
ui->setupUi(this);
this->falg = false;
//成功连接服务器执行槽函数
connect(&tcp_socket,SIGNAL(connected()),this,SLOT(answerConnected()));
//服务器断开执行槽函数
connect(&tcp_socket,SIGNAL(disconnected()),this,SLOT(answerDisConnected()));
//服务器发来消息执行槽函数
connect(&tcp_socket,SIGNAL(readyRead()),this,SLOT(answerReadyRead()));
//网络连接出现错误执行槽函数
connect(&tcp_socket,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(answerError()));
}
ClientChat::~ClientChat()
{
delete ui;
}
void ClientChat::on_pushButton_send_clicked()
{
//获取输入的聊天消息
QString str = ui->lineEdit_message->text();
//如果消息为空,不发送
if(str == "")
{
return;
}
str = user_name + ": "+ str;
//发送消息
this->tcp_socket.write(str.toUtf8());
//清空输入框
ui->lineEdit_message->clear();
}
void ClientChat::on_pushButton_connect_server_clicked()
{
//离线状态点击按钮连接,在线状态点击按钮断开
if(this->falg)
{
//向服务器发送离开信息
//toUtf8 是 qstring装为qbytearray
this->tcp_socket.write(QString(this->user_name + "离开聊天").toUtf8());
//断开服务器连接 发送DisConnected信号
this->tcp_socket.disconnectFromHost();
}
else
{
//获取IP,端口号,昵称
this->server_ip.setAddress(ui->lineEdit_serverIP->text());
this->server_port = ui->lineEdit_server_port->text().toShort();
this->user_name = ui->lineEdit_username->text();
//向服务器发送连接请求
//成功发送信号connected
//失败发送信号error
tcp_socket.connectToHost(this->server_ip,this->server_port);
}
}
void ClientChat::answerConnected()
{
//改变标识,改为在线
this->falg = true;
//发送信息按钮恢复可用状态
ui->pushButton_send->setEnabled(true);
//禁用配置信息
ui->lineEdit_serverIP->setEnabled(false);
ui->lineEdit_server_port->setEnabled(false);
ui->lineEdit_username->setEnabled(false);
//改变按钮文本
ui->pushButton_connect_server->setText("退出服务器");
//向服务器发送进入信息
//toUtf8 是 qstring装为qbytearray
this->tcp_socket.write(QString(this->user_name + "进入聊天").toUtf8());
}
void ClientChat::answerDisConnected()
{
//恢复初始状态
//改变标识,改为离线
this->falg = true;
//发送信息按钮恢复禁用状态
ui->pushButton_send->setEnabled(false);
//开启配置信息
ui->lineEdit_serverIP->setEnabled(true);
ui->lineEdit_server_port->setEnabled(true);
ui->lineEdit_username->setEnabled(true);
//改变文本
ui->pushButton_connect_server->setText("连接服务器");
}
void ClientChat::answerReadyRead()
{
//套接字存在返回字节数,不存在返回0
if(this->tcp_socket.bytesAvailable())
{
//显示信息
ui->listWidget->addItem(tcp_socket.readAll());
ui->listWidget->scrollToBottom();
}
}
void ClientChat::answerError()
{
//显示网络异常原因
QMessageBox::critical(this,"error",tcp_socket.errorString());
}