一、项目要求
先不写吧,等写完了再来补充
二、设计思路
后面补上,今天很晚了,就做一个代码记录就好了,都在纸上后面会加进来的
三、第一天项目日记
1、今天总结:
今天编写了半个服务器,
主要实现的功能
1)能够使用Tcp协议创建一个服务器,端口自己定义,点击按钮开开启服务器
2)当有服务器连接上来时,能够显示连接的用户,这里为随机的id号
3)当客户端断开时刷新列表,只显示在线的id,显示在线客户端数量
4)客户端发送数据后消息内容广播发送所有客户端
测试:随便用什么网络工具都可以,在这里只需要充当客户端就好了。
2、服务器界面
说明:
开始运行之后,可以输入端口号,点击开始服务之后,按钮变灰,port不能输入,点击关闭服务之后退出,在线人数实时显示在线的客户端的数量,登录日志用来查看登录记录,暂时不能使用。
有客户端连接之后显示连接id,断开之后实时刷新。
3、代码结构
说明:tcpsocket 是继承自QTcpSocket的类
tcpsocket 是继承自QTcpServer的类
mymessage是自定义的发消息的类
1、mymessage.h
这个没什么好说的,就是自己封装一个消息而已
#ifndef MYMESSAGE_H
#define MYMESSAGE_H
#include <QString>
enum MsgId{
MSG_CLITEN_CONNECT = 0, //连接消息
MSG_READ_BYTES, //读取接收到的消息
MSG_CLIENT_CLOSE, //客户端关闭的消息
};
class MyMessage
{
enum MsgId msgid;
QString msgbuf;
int length;
public:
MyMessage();
MyMessage(MsgId msgid,QString msgbuf,int length);
void setmsgid(MsgId msgid);
void setmsgbuf(QString msgbuf);
void setlength(int length);
int getmsgid();
QString getmsgbuf();
int getlength();
};
#endif // MYMESSAGE_H
2、mymessage.cpp
一些消息函数的实现
#include "mymessage.h"
MyMessage::MyMessage()
{
}
MyMessage::MyMessage(MsgId msgid, QString msgbuf, int length)
{
this->msgid = msgid;
this->msgbuf = msgbuf;
this->length = length;
}
void MyMessage::setmsgid(MsgId msgid)
{
this->msgid = msgid;
}
void MyMessage::setmsgbuf(QString msgbuf)
{
this->msgbuf = msgbuf;
}
void MyMessage::setlength(int length)
{
this->length = length;
}
int MyMessage::getmsgid()
{
return msgid;
}
QString MyMessage::getmsgbuf()
{
return msgbuf;
}
int MyMessage::getlength()
{
return length;
}
3、tcpsocket.h
继承自QTcpSocket 然后对部分函数进行了重写
#ifndef TCPSOCKET_H
#define TCPSOCKET_H
#include <QWidget>
#include <QTcpSocket>
#include <QHostAddress>
#include "mymessage.h"
#include <QDebug>
class TcpSocket : public QTcpSocket
{
Q_OBJECT
public:
TcpSocket(QObject* parent = 0);
signals:
void disconnected(int); //发送离线信号
void messageToServer(MyMessage); //给server发送消息
public slots:
void datadisconnected(); //用来处理离线信号
void dataReceived(); //用来处理接受到的客户端的消息
};
#endif // TCPSOCKET_H
4、tcpsocket.cpp
#include "tcpsocket.h"
TcpSocket::TcpSocket(QObject *parent):
QTcpSocket(parent)
{
//本来想要在这里发送连接的信号给服务器的,结果发现在他状态改变的时候还读取不到端口号和ip地址,所以就放在房间socket的地方去发送消息了
// connect(this,SIGNAL(stateChanged(QAbstractSocket::SocketState)),this,SLOT(dataconnected()));
//连接断线函数/读取函数和自定义的槽函数
connect(this,SIGNAL(disconnected()),this,SLOT(datadisconnected()));
connect(this,SIGNAL(readyRead()),this,SLOT(dataReceived()));
}
void TcpSocket::datadisconnected()
{
//断开之后会发送-1
emit disconnected(this->socketDescriptor());
}
void TcpSocket::dataReceived()
{
//处理督导的信号
while(this->bytesAvailable() > 0){
char buf[1024];
int length = bytesAvailable();
this->read(buf,length); //读取接收
QString msgbuf = buf;
//把它转换成消息发送给server
MyMessage message(MSG_READ_BYTES,buf,length);
emit messageToServer(message); //发射消息信号
}
}
5、tcpserver.h
#ifndef TCPSERVER_H
#define TCPSERVER_H
#include <QObject>
#include <QtNetwork>
#include <QTcpServer>
#include "tcpsocket.h"
class TcpServer : public QTcpServer
{
Q_OBJECT
public:
explicit TcpServer(QObject* parent = 0,int port = 0);
QList<TcpSocket*> tcpSocketList;//用来存储客户端指针的list容器
QList<int> descriptorList;//用来对照客户端的容器,删除的时候用
protected:
void incomingConnection(int socketDescriptor);//重写了连接函数
signals:
void msgToServer(MyMessage);//用来给main发送消息的信号
public slots:
void msgFromSocket(MyMessage);//用来处理从socket接收到信号的槽函数
void tcpDisconnected(int);//用来处理客户端断开的槽函数
};
#endif // TCPSERVER_H
6、tcpserver.cpp
#include "tcpserver.h"
TcpServer::TcpServer(QObject *parent, int port):
QTcpServer(parent)
{
listen(QHostAddress::AnyIPv4,port); //用来监听ipv4的客户端,port是传进来的
}
void TcpServer::incomingConnection(int socketDescriptor)
{
TcpSocket *tcpSocket = new TcpSocket(this);
tcpSocket->setSocketDescriptor(socketDescriptor);
//发消息给服务器界面
QString msgbuf = QString::number(socketDescriptor);
//QString msgbuf = tcpSocket->peerAddress().toString()+" "+QString::number(tcpSocket->peerPort());
MyMessage message(MSG_CLITEN_CONNECT,msgbuf,msgbuf.length());
//qDebug()<<msgbuf << " "<<socketDescriptor;
emit msgToServer(message);//发送连接消息
connect(tcpSocket, SIGNAL(messageToServer(MyMessage)),
this, SLOT(msgFromSocket(MyMessage)));
connect(tcpSocket, SIGNAL(disconnected(int)),
this, SLOT(tcpDisconnected(int)));
//把socket指针放到socketlist中
tcpSocketList.append(tcpSocket);
//把他的Descriptor放到另一个容器中
descriptorList.append(socketDescriptor);
}
void TcpServer::msgFromSocket(MyMessage message)
{
emit msgToServer(message);
//下面是向客户端回复相同的数据
for(int i = 0; i < tcpSocketList.count(); i++)
{
QTcpSocket *temp = tcpSocketList.at(i);
if(temp->write(message.getmsgbuf().toLatin1(), message.getlength()) != message.getlength())
{
continue;
}
}
}
void TcpServer::tcpDisconnected(int descriptor)
{
//断开之后 descriptor就会变成-1,所以不知道哪个断开了,只能自己判断
//所以用一个QList<int>来存储,到时候哪个变成-1了就说明哪个关掉了
for(int i = 0; i < tcpSocketList.count(); i++)
{
QTcpSocket *temp = tcpSocketList.at(i);
if(temp->socketDescriptor() == descriptor)
{
temp->destroyed();
tcpSocketList.removeAt(i);
break;
}
}
int close_descriptor = 0;
int j = 0;
//对比两个容器看看哪个客户端离线了
for(int i = 0; i < descriptorList.count(); i++){
close_descriptor = descriptorList.value(i);
for(j = 0; j < tcpSocketList.count(); j++){
QTcpSocket *temp = tcpSocketList.at(j);
if(close_descriptor == temp->socketDescriptor()){
break;
}
}
if(j == tcpSocketList.count()){
//qDebug()<<close_descriptor << "is close";
descriptorList.removeAt(i);
break;
}
}
//发送该客户端被关闭的消息
QString msgbuf = QString::number(close_descriptor);
MyMessage message(MSG_CLIENT_CLOSE,msgbuf,msgbuf.length());
emit msgToServer(message);
return;
}
7、mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "tcpserver.h"
#include <QtNetwork>
#include <QMessageBox>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
void deleteclientlist(int);//用来清除信号客户端列表
private:
Ui::MainWindow *ui;
QTcpServer *tcpServer;
int port;
int online_num;
public slots:
void RecvMsg(MyMessage);
private slots:
void on_pushclose_clicked();
void on_pushopen_clicked();
};
#endif // MAINWINDOW_H
8、mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "tcpserver.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
this->setWindowTitle("服务器");
ui->lineport->setText("8010");//默认端口号为8010
ui->label_onlinenum->setText("0");//默认连接数量肯定是0
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::deleteclientlist(int client_id)
{
int row = 0;
QString str = QString::number(client_id);
QString line;
while(row < ui->listusers->count()){
line=ui->listusers->item(row)->text();
if(str==line)
{
//qDebug()<<"删除成功";
ui->listusers->takeItem(row);
break;
}
row++;
}
}
void MainWindow::RecvMsg(MyMessage message)
{
//qDebug()<<message.getmsgbuf().left(message.getlength());
if(message.getmsgid() == MSG_CLITEN_CONNECT){
ui->listusers->addItem(message.getmsgbuf().left(message.getlength()));
ui->label_onlinenum->setText(QString::number(ui->listusers->count()));
}else if(message.getmsgid() == MSG_READ_BYTES){
//qDebug()<<message.getmsgbuf().left(message.getlength());
}else if(message.getmsgid() == MSG_CLIENT_CLOSE){
//收到关闭的消息之后删除那个客户端的id 消息内容就是客户端的id
this->deleteclientlist(message.getmsgbuf().toInt());
//更新在线人数的label
ui->label_onlinenum->setText(QString::number(ui->listusers->count()));
}
return;
}
void MainWindow::on_pushclose_clicked()
{
qApp->quit();
}
void MainWindow::on_pushopen_clicked()
{
if(ui->lineport->text().isEmpty()){
QMessageBox::warning(this, tr("warning!!"),
tr("please input you Port!"));
return;
}
port = ui->lineport->text().toInt();
tcpServer = new TcpServer(this, port);
QObject::connect(tcpServer,SIGNAL(msgToServer(MyMessage)),this,SLOT(RecvMsg(MyMessage)));
//开启服务后按钮不可点击
ui->pushopen->setEnabled(false);
ui->lineport->setEnabled(false);
return;
}
4、项目文件
5、效果展示
1)能够使用Tcp协议创建一个服务器,端口自己定义,点击按钮开开启服务器
2)当有服务器连接上来时,能够显示连接的用户,这里为随机的id号
3)当客户端断开时刷新列表,只显示在线的id,显示在线客户端数量
4)客户端发送数据后消息内容广播发送所有客户端
都已经实现了,效果如下
四、记录结束
结束语:路漫漫其修远兮,吾将上下而求索!!!