QT中的网络编程(网络聊天)

这里主要说的是TCP通信

服务器简略设计

客户端和客户端之间是不能直接交互的,这也是服务器存在的意义.

响应和接收信息时都是异步通信的方式,产生相应信号

创建服务器连接

使用QTcpServer(QT中创建TCP服务器的类)创建服务器

设置监听服务器ip地址和端口

响应客户端连接

获取客户端通信的TCP套接字

保存客户端通信套接字

实时接收客户端发送的消息

接收客户端发送的信息

检查客户端是否有消息

有消息则读取 保存 显示

将消息发给所有在线的客户端

客户端简略设计

创建通信套接字

使用QTcpSocket创建套接字

和服务器建立连接

通过配置服务器ip和端口想服务器发送请求

连接成功,发送进入聊天提示信息

发送,接收聊天信息

输入聊天信息,发送到服务器

实时接收服务器发送的信息,并显示

TCP协议简介

TCP(传输控制协议),是一个可靠的,基于字节流的,面向连接的传输层协议.

在OSI定义的七层网络模型中,TCP属于第四层传输层所指定的功能,

在TCP/IP协议族中,位于IP层之上,应用层之下的中间层.

主机应用层之间经常需要可靠的,像管道一样的连接,但IP层不提供这样的流机制.

TCP协议符合这一要求,特别适合连续传输数据,同时能保证数据的安全性.

HTTP协议就是基于TCP协议实现的.

QTcpServer类简介

提供了TCP服务器,可以快速的建立TCP服务器,平且可以实时的响应客户端的连接请求.

QTcpServer::listen()可以指定服务器端口号(端口号0-65535,但是0-1024一般是系统使用的),没有指定QTcpServer会自动选择一个可用的端口号,该函数还会监听当前主机指定的IP地址(QHostAddress)(服务器主机上可能有多块网卡,例如笔记本上可以通过有线,无线连接(就好像手机可以双卡双待一样,设置一个IP就相当于设置一个电话号)),通常设置成QHostAddress::Any(对应0.0.0.0)这个地址可以监听所有地址.

设置监听后,每当检测到客户端发来的连接请求,都会发送信号newConnection(),可以自定义槽函数,调用nextPandingConnection()获取和客户端通信的套接字.

QTcpServer,创建TCP服务器

创建QTcpServer对象

QTcpServer tcp_server;

开启TCP服务器,监听所有地址,端口号

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

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);
    }
}

案例

服务器

添加网络模块

QT中的网络编程(网络聊天)_第1张图片

界面

QT中的网络编程(网络聊天)_第2张图片

 

server_chat.h

#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

server_chat.cpp

#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--;
        }
    }
}

 客户端

同样需要添加network

QT中的网络编程(网络聊天)_第3张图片

 界面

QT中的网络编程(网络聊天)_第4张图片

 client_chat.h

#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

client_chat.cpp

#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());
}

你可能感兴趣的:(QT,qt,网络,服务器,网络协议,tcp/ip)