又跳票了一段时间,终于有空回归到整题了。。。最近都在学习Ogre所以好久没有更文章了,哈哈。不过杰洛君回来了~
今天要讲的是一个服务端的实现方法~
实现一个带界面的服务端
我们先做一个有界面的服务端版本,然后再讲根据服务器具体的环境制作没有界面的版本~
新建一个项目为TestSrv的项目吧~
注意这里和弹幕程序不同,不需要用QMainWindow作为基类,只需要用QDialog作为基类就可以了~
在设置项目类信息中基类下拉框中选择QDialog吧~
界面的话就直接用拖动控件的方式就好~
用到的控件包括一个两个QLabel 三个QPushButton 一个QLineEdit 和一个QListWidget
用布局把它们包好就可以了~
服务端我们这样来处理。因为服务端是服务于多个客户端的,所以每个客户端的工作我们都用一个ClientJob类来包装起来。
新建一个ClientJob.h
#ifndef CLIENTJOBS_H
#define CLIENTJOBS_H
#include <QObject>
#include <QtNetwork>
class ClientJobs : public QObject
{
Q_OBJECT
public:
explicit ClientJobs(QObject *parent, QTcpSocket *client, QString strIPPort); //构造函数
//TCP连接
QTcpSocket *m_pClientSock; //保存客户端的socket连接,达到服务端与客户端长连接
QString m_strIPPort;//IP_Port 以字符串保存socket连接,用ip_端口号的形式保存
~ClientJobs(); //析构函数
signals:
void CallMainWindowDeleteClient(QString m_strIPPort); //自定义信号
public slots:
void SendBytes(QByteArray &data); //传输二进制字节,弹幕json
void LostConnection(); //断开连接时的收尾工作
};
#endif // CLIENTJOBS_H
这里我们用到了Socket(套接字) 使用TCP方式进行服务端与客户端之间的传输数据。所以用到了一个QTcpSocket *m_pClientSock;
。
杰洛君在这里使用了字符串来标识不同的连接,用IP_端口号的形式标识。
在断开连接时用自定义信号来传递要销毁的相应的ClientJob对象。在signals:先得函数声明就是自定义信号了,自定义信号只需要函数声明不需要实现。使用emit 关键字来发送就好。
数据都是用QByteArray的形式进行传输。
这么讲或许还不够清楚~看实现代码就好啦~
#include "clientjobs.h"
ClientJobs::ClientJobs(QObject *parent, QTcpSocket *client, QString strIPPort) :
QObject(parent)
{
m_pClientSock = client;
m_strIPPort = strIPPort;
//关联信号
connect(m_pClientSock, SIGNAL(disconnected()), this, SLOT(LostConnection()));
//可以关联接收数据信号readyRead(),这里不管
}
ClientJobs::~ClientJobs(){
qDebug()<<"ClientJobs bei xi gou"<<endl;
m_pClientSock->close();
}
void ClientJobs::SendBytes(QByteArray &data)
{
m_pClientSock->write(data); //把弹幕数据通过socket写到客户端
}
void ClientJobs::LostConnection()
{
//通知主窗口删除自己
emit CallMainWindowDeleteClient(m_strIPPort);
}
小C:这里信号发送出去后,没有对象响应呀?
杰洛君:诶,真的耶 ,别急嘛~ (~ ̄▽ ̄)→)) ̄▽ ̄)o
这个只是每个链接的一个简易封装。
对这些链接的管理。杰洛君用了DialogSrv类进行管理。这个类才是用到了刚才我们辛苦做的布局,创建我们的主界面~
#ifndef DIALOGSRV_H
#define DIALOGSRV_H
#include <QDialog>
#include <QtNetwork>
#include <QStringList>
#include <QList>
#include <QTimer>
#include <QDateTime>
#include "clientjobs.h"
namespace Ui {
class DialogSrv;
}
class DialogSrv : public QDialog
{
Q_OBJECT
public:
explicit DialogSrv(QWidget *parent = 0); //构造函数
~DialogSrv(); //析构函数
void sendDanmuData(QString ends,QString message); //发送弹幕
private slots:
void on_pushButtonListen_clicked(); //按钮函数 这种写法可以不用 connect 来连接信号和槽
//两个信号的槽函数
void onNewConnection(); //当有新的连接进来时
void onTomcatConnection(); //当有网页连接进来时
//
void DeleteOneClient(QString strIPAndPort); //删除一个客户端的长连接
void on_pushButtonStop_clicked(); //停止按钮被点击时的槽函数
void on_pushButtonSend_clicked(); //发送测试弹幕按钮的槽函数
void slotReadTcpData(); //读取Socket传来的弹幕数据
void sendServerDanmu(); //发送服务端弹幕代码
private:
Ui::DialogSrv *ui; //这个程序用到了界面文件
//server
QTcpServer *m_pTCPSrv; //监听客户端Socket
QTcpServer *t_cTCPSrv; //监听网页Socket;
//保存客户列表
QStringList m_listIPAndPorts; //IP_Port
QList<ClientJobs*> m_listClients;//客户端
QTcpSocket * tcsockclient; //处理新的Socket连接请求
QTimer * timer1; //定时器,在这里可以用来定时发弹幕
};
这个类中有些函数长得比较奇怪,可平时杰洛君命名方式不太一致。
比如:
- void on_pushButtonListen_clicked();
- void on_pushButtonStop_clicked();
- void on_pushButtonSend_clicked();
为什么要这样写函数呢?让我们看会上面杰洛君界面布局的时候3个按钮的名字,有没有发现什么呢?
使得中间的部分就是我们按钮的名字。这种写法&