Qt开发的即时通讯软件(基于TCP) C/S设计模式

项目要求是要达到即时通讯,群聊和私聊都可以,可以发文件,登陆,注册。可以理解为超级简易的QQ(好像有点皮了,我怎么敢和QQ比呢,哈哈)。

预览

项目有两个部分,客户端和服务器,先看结果吧。

登录界面
Qt开发的即时通讯软件(基于TCP) C/S设计模式_第1张图片

注册界面
Qt开发的即时通讯软件(基于TCP) C/S设计模式_第2张图片

找回密码
Qt开发的即时通讯软件(基于TCP) C/S设计模式_第3张图片

聊天室(双击用户列表里面的用户就可以进入私人聊天界面)
Qt开发的即时通讯软件(基于TCP) C/S设计模式_第4张图片

两个聊天界面(可以自由切换背景)
Qt开发的即时通讯软件(基于TCP) C/S设计模式_第5张图片

发送文件界面
Qt开发的即时通讯软件(基于TCP) C/S设计模式_第6张图片

客户端界面(这个就看看用户的在线情况,顺便监听聊天室的情况)
Qt开发的即时通讯软件(基于TCP) C/S设计模式_第7张图片

项目解析

服务器:

一,数据库:

1.连接数据库:

这个是基础了吧,不会的自行百度

bool MySql::initsql() { //初始化并建立一个数据库连接
	//连接数据库
	QSqlDatabase db = QSqlDatabase::addDatabase("QODBC");
	db.setHostName("localhost");
	db.setDatabaseName("qq");
	db.setUserName("root");
	db.setPort(3306);
	db.setPassword("123456789");

	if (db.open()) {
		qDebug() << "数据库连接成功,666666!" << endl;
	} else {
		qDebug() << "数据库连接失败,伞兵!" << endl;
		QMessageBox::warning(NULL, "error", "数据库连接失败");
		return 0;
	}
}
2.登陆:

也就是把数据传到服务器中,再和数据库里面的数据进行比较,再将信息反馈回去,简单

int MySql::Login(QString Name, QString Password) {
	QString str1, str2;
	query = new QSqlQuery;
	query->exec(QString("select Password,status from user where userName='%1'").arg(Name));
	while (query->next()) {
		str1 = query->value(0).toString(); //密码
		str2 = query->value(1).toString(); //在线状态
	}
	qDebug() << str1 << str2 << endl;
//    qDebug()<<"数据库连接成功,666666!"<
	if (str2 == NULL)  return 3; //用户不存在
	else if (str2 == '1')  return 2; //用户已经在线
	else if ((str2 == '0') && (str1 == Password)) return 1; //用户名和密码都正确
	else if ((str2 == '0') && (str1 != Password)) return 0; //密码错误
}
3.注册:

原理同上

bool MySql::Regist(QString Name, QString Password, QString Mail) {
	char status = '0', Mail2 = '0', Mail3 = '0';
	QString str = QString("select * from user where userName='%1'").arg(Name);
	query = new QSqlQuery;
	query->exec(str);
	query->last();
	int record = query->at() + 1;
	if (record != 0)
		return false;
	str = QString("insert into user value('%1','%2','%3','%4','%5','%6')").arg(Name).arg(status).arg(Password).arg(Mail).arg(Mail2).arg(Mail3);
	bool ret = query->exec(str);//数据模型重新查询数据,更新tableView显示
	qDebug() << "到达数据库注册!" << str << endl;
	return ret;
}
4.找回密码:

同上

int MySql::findpwd(QString Name, QString Mail) { //找回密码
	QString str1, str2, str3;
	query = new QSqlQuery;
	query->exec(QString("select Mail from user where userName='%1'").arg(Name));
	while (query->next()) {
		str1 = query->value(0).toString(); //邮件
	}
	if ((str1 == Mail) /*&& (str2 == question) && (str3 == answer)*/) return 3; //信息正确
	else if (str1 == NULL)   return 4; //用户不存在
	else  return 2;  //邮件错误
}

二,数据处理:

1.读取数据并处理:

接收从客户端传输过来的数据,通过前缀将它们分类,处理后发送回客户端或者服务器,这段代码可以说是服务器的重中之重,处理各种数据。

void Server::ReadMSG() { //读取来自客户端的数据
	qDebug() << "读取客户端数据" << endl;
	QString data = socket->readAll();   //读取客户端发送的数据
	qDebug() << data;
	QStringList list = data.split("#");
	bool ret = 0;
	int temp = 0;
	QString sendData = list[0];
	QString password, str1, str2, str3, str4;
	if (list[0] == "a") { //注册
		qDebug() << "到达注册" << endl;
		ret = checkSignUp(list[1], list[2], list[3]);
		if (ret)
			sendData = sendData + "#true";
		else
			sendData = sendData + "#false";
		socket->write(sendData.toLatin1());
	} else if (list[0] == "b") { //登录
		temp = checkLogIn(list[1], list[2]);
		if (temp == 1) {
			query = new QSqlQuery;
			query->exec(QString("update user set status='%1' where userName='%2'").arg(1).arg(list[1])); //设置数据库中的status为1
			sendData = sendData + "#" + list[1] + "#true";
		} else if (temp == 0)  sendData = sendData + "#false" + "#0";
		else if (temp == 2)  sendData = sendData + "#false" + "#2";
		else if (temp == 3)  sendData = sendData + "#false" + "#3";
		socket->write(sendData.toUtf8());

	} else if (list[0] == "c") { //找回密码
		temp = findpwd(list[1], list[2]);
		if (temp == 0) sendData = sendData + "#false" + "#0";
		else if (temp == 1) sendData = sendData + "#false" + "#1";
		else if (temp == 2) sendData = sendData + "#false" + "#2";
		else if (temp == 4) sendData = sendData + "#false" + "#4";
		else {
			QString str = QString("select Password from user where userName='%1'").arg(list[1]);
			query = new QSqlQuery;
			query->exec(str);
			while (query->next()) {
				password = query->value(0).toString();
			}
			sendData = sendData + "#true" + "#" + password;
		}
		socket->write(sendData.toUtf8());
	} else if (list[0] == "d") { //退出登录
		query = new QSqlQuery;
		query->exec(QString("update user set status='%1' where userName='%2'").arg(0).arg(list[1])); //设置数据库中的status为0

	} else if (list[0] == "e") { //返回用户列表
//        query->exec(QString("update user set status='%1' where userName='%2'").arg(1).arg(list[1])); //设置数据库中的status为1
		query = new QSqlQuery;
		query->exec(QString("update user set CIP='%1' , CPort='%2' where userName='%3'").arg(list[2]).arg(list[3]).arg(list[1]));
		qDebug() << list[2] << list[3] << list[1] << "6666666666666666666666666" << endl;
		query = new QSqlQuery;
		query->exec("select userName from user");
		QString str6 = NULL;

		while (query->next()) {
			str1 = query->value(0).toString();
			str6 = str6 + "#" + str1;
		}
		query = new QSqlQuery;
		query->exec(QString("select name1,MSG from msg where name2='%1'").arg(list[1]));
		while (query->next()) {
			str3 = query->value(0).toString();
			str4 = query->value(1).toString();
		}
		if (str4 != NULL) {
			str3 = str3 + ":" + str4;
		}
		sendData = sendData + str6 + "#" + "end" + "#" + str3;
		socket->write(sendData.toUtf8());
	} else if (list[0] == "f") { //群聊消息
		qDebug() << "群聊";
		//获取系统时间
		QDateTime time = QDateTime::currentDateTime();
		QString Time = time.toString("yyyy-MM-dd ddd hh:mm:ss");
		ui->textBrowser->append(Time);
		ui->textBrowser->append(list[1] + ":" + list[2]);
		sendData = sendData + "#" + list[1] + "#" + list[2];
		query = new QSqlQuery;
		int str5;
		qDebug() << "群聊      1";
		query->exec(QString("select CIP, CPort from user where status = '%1'").arg(1));
		while (query->next()) {
			qDebug() << "群聊      2";
			QString str1 = query->value(0).toString();
			str5 = query->value(1).toInt();
			socket = new QTcpSocket();
			socket->abort();   //取消已有的连接
			socket->connectToHost(str1, str5); //连接到客户端
			socket->waitForConnected();

			socket->write(sendData.toUtf8());
		}
	} else if (list[0] == "g") { //私聊
		qDebug() << "到达服务器的私聊";
		QString Status;
		query = new QSqlQuery;
		query->exec(QString("select status from user where userName='%1'").arg(list[1]));
		while (query->next()) {
			Status = query->value(0).toString();
		}
		if (Status == "1") {
			query->exec(QString("select CIP, CPort from user where userName = '%1'").arg(list[1]));
			while (query->next()) {
				str1 = query->value(0).toString();
				str2 = query->value(1).toString();
			}
			sendData = sendData + "#" + list[1] + "#" + "1" + "#" + str1 + "#" + str2;
		} else {
			sendData = sendData + "#" + list[1] + "#" + "0";
		}
		socket->write(sendData.toUtf8());
	} else if (list[0] == "s") { //获取信息以发送文件

		query->exec(QString("select CIP, CPort from user where userName = '%1'").arg(list[1]));
		while (query->next()) {
			str1 = query->value(0).toString();
			str2 = query->value(1).toString();
		}
		sendData = sendData + "#" + list[1] + "#" + "1" + "#" + str1 + "#" + str2;
		socket->write(sendData.toUtf8());
	} else if (list[0] == "i") { //离线消息
		query = new QSqlQuery;
		query->exec(QString("insert into msg (name1, name2, MSG) values('%1','%2','%3')").arg(list[1]).arg(list[2]).arg(list[3]));
	} else if (list[0] == "y") { //获取信息以发送私聊信息

		query->exec(QString("select CIP, CPort from user where userName = '%1'").arg(list[1]));
		while (query->next()) {
			str1 = query->value(0).toString();
			str2 = query->value(1).toString();
		}
		sendData = sendData + "#" + list[1] + "#" + "1" + "#" + str1 + "#" + str2;
		socket->write(sendData.toUtf8());
	} else
		return;
}

2.跳转数据库:

接受数据处理的调配,跳转到数据库处理

int Server::checkLogIn(QString Name, QString Password) {//检查登陆
	MySql *sql = new MySql();
	int ret = sql->Login(Name, Password);
	return ret;
}

bool Server::checkSignUp(QString Name, QString Password, QString Mail) {//检查注册
	MySql *sql = new MySql();
	bool ret = sql->Regist(Name, Password, Mail);
	return ret;
}

int Server::findpwd(QString Name, QString Mail) {//找回密码
	MySql *sql = new MySql();
	int temp = sql->findpwd(Name, Mail);
	return temp;
}

客户端:

1.登陆:

开始的源头,登陆完成之后跳转到主界面,注意,这里有一个全局IP和全局端口(port),除了文件传输和私人聊天是要重新连接的,其他的都是通过这个去连接数据库的

#include "login.h"
#include "ui_login.h"
#include "register.h"
#include "findpsd.h"
#include "mainwindow.h"
#include 
#include 


Login::Login(QWidget *parent) :
	QDialog(parent),
	ui(new Ui::Login) {
	ui->setupUi(this);
	qDebug() << IP << endl;
	QMovie *movie = new QMovie("F:/1.gif");
	socket = new QTcpSocket(this);
	connect(socket, SIGNAL(readyRead()), this, SLOT(readMsg()));
	connect(socket, SIGNAL(error(QAbstractSocket::SocketError)),
	        this, SLOT(displayError(QAbstractSocket::SocketError)));  //发生错误时执行displayError函数
}

QString Login::IP = "192.168.80.66";
//192.168.80.66
int Login::Port = 6666;

Login::~Login() {
	delete this->socket;
	delete ui;
}



void Login::readMsg() {//读取信息
	QString data = socket->readAll();
	QStringList list = data.split("#");
	qDebug() << data;
	if (list[0] == "b" && list[2] == "true") {
		Mainwindow *w = new Mainwindow;
		w->setWindowTitle(list[1]);
		w->show();
	} else if (list[0] == "b" && list[1] == "false" && list[2] == '0')
		QMessageBox::information(this, "信息提示", "登录失败,密码错误!", QMessageBox::Ok);
	else if (list[0] == "b" && list[1] == "false" && list[2] == '2')
		QMessageBox::information(this, "信息提示", "用户已在线,拒绝重复登录!", QMessageBox::Ok);
	else if (list[0] == "b" && list[1] == "false" && list[2] == '3')
		QMessageBox::information(this, "信息提示", "用户不存在,请先注册!", QMessageBox::Ok);
	else
		return;
}


void Login::on_pushButton_Register_clicked() {
	Register *m = new Register;
	m->setWindowTitle("Regist");
	m->show(); //当点击注册账户时,弹出注册窗口
}

void Login::on_pushButton_Forget_clicked() {
	FindPsd *n = new FindPsd;
	n->setWindowTitle("FindPsd");
	n->show();//当点击忘记密码时,显示找回密码界面
}

void Login::on_pushButton_Login_clicked() {
	socket->abort();   //取消已有的连接
	socket->connectToHost(IP, Port); //连接服务器
	qDebug() << IP << endl;

	if (!socket->waitForConnected(30000)) {
		qDebug() << "Connection failed!";
		QMessageBox::information(this, tr("错误"), tr("服务器未开启,请先开启服务器!"));
		ui->lineEdit_Name->clear();  //清空账号栏
		ui->lineEdit_Password->clear();  //清空密码栏
		ui->lineEdit_Name->setFocus();  //将鼠标重新定位到账号栏
		return;
	}

	qDebug() << "Connect successfully!666666!";
	QString userName = ui->lineEdit_Name->text();
	QString passward = ui->lineEdit_Password->text();
	QString bs = "b";
	QString data = bs + "#" + userName + "#" + passward;
	socket->write(data.toUtf8());
	if (userName == NULL) { // 输入为空的提示
		QMessageBox::information(this, tr("错误"), tr("账号不能为空!"));
		ui->lineEdit_Name->setFocus();  //将鼠标重新定位到账号栏
	} else if (passward == NULL) {
		QMessageBox::information(this, tr("错误"), tr("密码不能为空!"));
		ui->lineEdit_Name->setFocus();
	}
	ui->lineEdit_Name->clear();  //清空账号栏
	ui->lineEdit_Password->clear();  //清空密码栏
	ui->lineEdit_Name->setFocus();  //将鼠标重新定位到账号栏
}

2.聊天室:

这个可以理解为是主界面,很多处理都是在这里实现的,这里有发送也有接收,还可以打开P2P的聊天界面。具体的还得看代码

#include "mainwindow.h"
#include 
#include "ui_mainwindow.h"
#include 
#include "sendfile.h"
#include "p2p.h"

Mainwindow::Mainwindow(QWidget *parent) :
	QDialog(parent),
	ui(new Ui::Mainwindow) {
	ui->setupUi(this);
	socket = new QTcpSocket();
	server = new QTcpServer();
	CIP = getIP();
	QTime current_time = QTime::currentTime();
	int second = current_time.second(); //系统当前秒数
	qsrand(second);
	Cport = QString::number(qrand() % 1024 + 6000);
	CPort = Cport.toInt();//通过即时的秒来确定Port
	server->listen(QHostAddress::Any, CPort); //监听本机的某个端口
	qDebug() << CPort;
	connect(server, SIGNAL(newConnection()), this, SLOT(Connect())); //接受来自其他客户端的信号并连
	connect(socket, SIGNAL(readyRead()), this, SLOT(Read_Data())); //接受来自服务器的数据
	connect(socket, SIGNAL(error(QAbstractSocket::SocketError)),
	        this, SLOT(displayError(QAbstractSocket::SocketError)));  //发生错误时执行displayError函数
}
int Mainwindow::CPort = CPort;

Mainwindow::~Mainwindow() {
	delete ui;
	delete this->socket;
}

void Mainwindow::Connect() { //连接客户端
	socket = server->nextPendingConnection();
	connect(socket, SIGNAL(readyRead()), this, SLOT(Read_Data())); //获取来自服务器的数据
}


QString Mainwindow::getIP() {
	QString localHostName = QHostInfo::localHostName();//本机用户名
	QString IP;
	QHostInfo info = QHostInfo::fromName(localHostName);
	foreach (QHostAddress address, info.addresses()) {
		if (address.protocol() == QAbstractSocket::IPv4Protocol) {
//             if (address.toString().contains("192.168.")){
//                 continue;
//             }
			IP = address.toString();//本机ip地址
			return IP;
		}
	}
	return NULL;
}

void Mainwindow::closeEvent(QCloseEvent *event) {//在关闭的时候自动退出登陆
	QString title = windowTitle();
	socket->abort();   //取消已有的连接
	socket->connectToHost(IP, port); //连接服务器
	socket->waitForConnected();
	QString ds = "d";
	QString data = ds + "#" + title;
	socket->write(data.toUtf8());
	close();
}

void Mainwindow::Read_Data() {
	qDebug() << "到达读取数据";
	QString data = socket->readAll();   //读取服务器发送的数据
	QStringList list = data.split("#");
	QString title = windowTitle();
	qDebug() << data;
	if (list[0] == "e") { //加载用户列表
		ui->listWidget->clear();
		for (int i = 1; i < list.length(); i++) {
			if (list[i] != "end") {
				QString str = list[i];
				QListWidgetItem *item = new QListWidgetItem;
				item->setText(str);
				ui->listWidget->addItem(item);
			} else ui->textBrowser->append(list[++i]);
		}
	} else if (list[0] == "f") { //群发消息功能实现
		qDebug() << "群聊      回到客户端";
		if (list[1] != title) {
			QDateTime time = QDateTime::currentDateTime();
			QString Time = time.toString("yyyy-MM-dd ddd hh:mm:ss ");
			ui->textBrowser->append(Time);
			ui->textBrowser->append(list[1] + " : " + list[2]);
		}
	} else if (list[0] == "g") { //私聊
		qDebug() << "私聊      客户端";
		if (list[2] == "1") {
			P2P *p2 = new P2P();
			p2->setWindowTitle(Name);
			p2->show();
			socket->abort();   //取消已有的连接
			YIP = list[3];
			cport = list[4].toInt();
			socket->connectToHost(YIP, cport); //连接到客户端B
			socket->waitForConnected();//等待连接
			p = 1;
			QString ms = "z";
			QString data = ms + "#" + title ;
			socket->write(data.toUtf8());
		} else if (list[2] == "0") {
			p = 2;
			QMessageBox::information(this, "信息提示", "该用户不在线,您在输入框中直接输入就可以向他发送离线消息!", QMessageBox::Ok);
		}
	} else if (list[0] == 'z') {//开启P2P
		qDebug() << "到达开启P2P";
		P2P *p3 = new P2P();
		p3->setWindowTitle(list[1]);
		p3->show();
	} else return;
}

void Mainwindow::on_listWidget_itemDoubleClicked(QListWidgetItem *item) {//双击开启P2P
	qDebug() << "双击666";
	Name = item->text();
	socket->abort();
	//连接服务器
	socket->connectToHost(IP, port);
	socket->waitForConnected();
	QString gs = "g";
	QString data = gs + "#" + Name;
	socket->write(data.toUtf8());
}

//void Mainwindow::Connect_for_userlist() {//得到用户列表,点击左上角,稍微偏右一些,多点几次就点到了
//    qDebug() << this->windowTitle();
//    QString title = windowTitle();
//    socket->abort();   //取消已有的连接
//    //连接服务器
//    socket->connectToHost(IP, port);
//    //等待连接成功
//    if (!socket->waitForConnected(30000)) {
//        qDebug() << "Connection failed!";
//        return;
//    } else {
//        qDebug() << "Connect successfully!";
//    }

//    QString es = "e";
//    QString data = es + "#" + title + "#" + CIP + "#" + Cport;
//    socket->write(data.toUtf8());
//}

void Mainwindow::on_pushButton_Send_clicked() {
	QString title = windowTitle();
	QString fs;
	QString msg = ui->textEdit->toPlainText();

	if (p != 2) { //群聊
		socket->abort();   //取消已有的连接
		//连接服务器
		socket->connectToHost(IP, port);
		//等待连接成功
		if (!socket->waitForConnected(30000)) {
			qDebug() << "Connection failed!";
			return;
		} else
			qDebug() << "Connect successfully!     连接服务器";
		fs = "f"; //群聊
		QString data = fs + "#" + title + "#" + msg;
		socket->write(data.toUtf8());
	} else { //离线消息
		socket->abort();  //取消已有的连接
		//连接服务器
		socket->connectToHost(IP, port);
		//等待连接成功
		if (!socket->waitForConnected(30000)) {
			qDebug() << "Connection failed!";
			return;
		} else
			qDebug() << "Connect successfully!";
		fs = "i"; //离线消息
		QString data = fs + "#" + title + "#" + Name + "#" + msg;
		socket->write(data.toUtf8());
	}

	QDateTime time = QDateTime::currentDateTime();
	QString Time = time.toString("yyyy-MM-dd ddd hh:mm:ss ");
	ui->textBrowser->append(Time);
	ui->textBrowser->append(title + " : " + msg);
	ui->textEdit->clear();
}


void Mainwindow::on_pushButton_Connect_clicked() {//得到用户列表,点击左上角,稍微偏右一些,多点几次就点到了
	qDebug() << this->windowTitle();
	QString title = windowTitle();
	socket->abort();   //取消已有的连接
	//连接服务器
	socket->connectToHost(IP, port);
	//等待连接成功
	if (!socket->waitForConnected(30000)) {
		qDebug() << "Connection failed!";
		return;
	} else {
		qDebug() << "Connect successfully!";
	}

	QString es = "e";
	QString data = es + "#" + title + "#" + CIP + "#" + Cport;
	socket->write(data.toUtf8());
}

3.P2P私聊:

通过之前登陆成功后连接是存储在数据库的客户端IP和Port去连接另外一个客户端,实现P2P交流。port是聊天室界面的port + 1。还可以打开文件传输,原理和P2P私聊差不多,重新建立连接,不过用的是UDP。

#include "p2p.h"
#include "ui_p2p.h"
#include "sendfile.h"

P2P::P2P(QWidget *parent) :
	QDialog(parent),
	ui(new Ui::P2P) {
	qDebug() << "打开P2P";
	ui->setupUi(this);

	map.load(":/icon/0C89F7B185A1EA66CE5B1148061C5622.jpg");
	map = map.scaled(QSize(221, 381));
	ui->label_pic->setPixmap(map);

	socket = new QTcpSocket();
	server = new QTcpServer();
	server->listen(QHostAddress::Any, CPort);
	qDebug() << CPort;
	connect(server, SIGNAL(newConnection()), this, SLOT(Connect())); //接受来自其他客户端的信号并连
	connect(socket, SIGNAL(readyRead()), this, SLOT(Read_Data())); //接受来自服务器的数据
}

P2P::~P2P() {
	delete ui;
}

void P2P::Port(int number) {
	this->CPort = number;
}

void P2P::Connect() { //连接客户端
	qDebug() << "连接客户端";
	socket = server->nextPendingConnection();
	connect(socket, SIGNAL(readyRead()), this, SLOT(Read_Data())); //获取来自服务器的数据
}

void P2P::Read_Data() {
	QString data = socket->readAll();   //读取服务器发送的数据
	QStringList list = data.split("#");
	QString title = windowTitle();
	qDebug() << data << "到达P2P的读数据";
	if (list[0] == "h") { //点对点消息功能实现
		qDebug() << "发消息           P2P";
		QDateTime time = QDateTime::currentDateTime();
		QString Time = time.toString("yyyy-MM-dd ddd hh:mm:ss ");
		ui->textBrowser_show->append(Time);
		ui->textBrowser_show->append(list[1] + " : " + list[2]);
	} else if (list[0] == "s") { //发送文件获取ip和端口

		YIP = list[3];
		cport = (list[4]).toInt();
		socket->abort();   //取消已有的连接
		socket->connectToHost(YIP, cport + 1); //连接到客户端B
		socket->waitForConnected();
		QString ms = "m";
		QString data = ms + "#" + title;
		socket->write(data.toUtf8());
	} else if (list[0] == "y") { //获取ip和端口

		YIP = list[3];
		cport = (list[4]).toInt();
		qDebug() << cport << "  " << YIP;
		socket->abort();   //取消已有的连接
		socket->connectToHost(YIP, cport + 1); //连接到客户端B
		socket->waitForConnected();
		socket->write("66666666");
		qDebug() << "success!'''''''''''''";
	} else if (list[0] == "m") { //发文件
		qDebug() << "发文件";
		Sendfile *m1  = new  Sendfile();
		m1->show();
		m1->setWindowTitle(title);
	}
}

void P2P::on_pushButton_Send_clicked() {
	QString title = windowTitle();
	QString fs;
	QString msg = ui->textEdit_write->toPlainText();
	//点对点私聊
	fs = "h"; //私聊
	QString data = fs + "#" + title + "#" + msg;
	socket->write(data.toUtf8());
	qDebug() << data;
	QDateTime time = QDateTime::currentDateTime();
	QString Time = time.toString("yyyy-MM-dd ddd hh:mm:ss ");
	ui->textBrowser_show->append(Time);
	ui->textBrowser_show->append(title + " : " + msg);
	ui->textEdit_write->clear();
}

void P2P::on_pushButton_Sendfile_clicked() {
	QString title = windowTitle();
	socket->abort();

	socket->connectToHost(IP, port); //连接到服务器
	socket->waitForConnected();
	qDebug() << "1";
	QString ss = "s#";
	QString data = ss + title;
	socket->write(data.toUtf8());
	Sendfile *m1  = new  Sendfile();
	qDebug() << "2";
	m1->show();
	qDebug() << "3";
	m1->setWindowTitle(title);
}


void P2P::on_pushButton_Connect_clicked() {
	qDebug() << this->windowTitle();
	socket->abort();   //取消已有的连接
	//连接服务器
	socket->connectToHost(IP, port);
	//等待连接成功
	if (!socket->waitForConnected(30000)) {
		qDebug() << "Connection failed!";
		return;
	} else {
		qDebug() << "Connect successfully!   P2P!" << windowTitle();
		QString name = windowTitle();
		QString ss = "y#";
		QString data = ss + name;
		socket->write(data.toUtf8());
	}
}

void P2P::on_comboBox_currentIndexChanged(int num) {//换图片
	qDebug() << num;
	if (num == 0) {
		map.load(":/icon/0C89F7B185A1EA66CE5B1148061C5622.jpg");
		map = map.scaled(QSize(221, 381));
		ui->label_pic->setPixmap(map);
	}
	if (num == 1) {
		map.load(":/icon/1C0A56ED7965651A3C7252C7C8BC4CCD.jpg");
		map = map.scaled(QSize(221, 381));
		ui->label_pic->setPixmap(map);
	}
	if (num == 2) {
		map.load(":/icon/2B589D8C0959A43101DA9A3A21CB713A.jpg");
		map = map.scaled(QSize(221, 381));
		ui->label_pic->setPixmap(map);
	}
	if (num == 3) {
		map.load(":/icon/3E9ABF37028B166DE4B5DCF1AAE1D14F.jpg");
		map = map.scaled(QSize(221, 381));
		ui->label_pic->setPixmap(map);
	}
	if (num == 4) {
		map.load(":/icon/3FAD45AFADED34F9854AA7289533884F.jpg");
		map = map.scaled(QSize(221, 381));
		ui->label_pic->setPixmap(map);
	}
	if (num == 5) {
		map.load(":/icon/468B317D6178792F1728B38937B1D143.jpg");
		map = map.scaled(QSize(221, 381));
		ui->label_pic->setPixmap(map);
	}
	if (num == 6) {
		map.load(":/icon/48C4A5DDA8AAADB312C726576B13B22E.jpg");
		map = map.scaled(QSize(221, 381));
		ui->label_pic->setPixmap(map);
	}
	if (num == 7) {
		map.load(":/icon/723E6423CC8B247CE0D903A5D4688A55.jpg");
		map = map.scaled(QSize(221, 381));
		ui->label_pic->setPixmap(map);
	}
	if (num == 8) {
		map.load(":/icon/865CE62027A75117E225A3104DEBF9F1.jpg");
		map = map.scaled(QSize(221, 381));
		ui->label_pic->setPixmap(map);
	}
	if (num == 9) {
		map.load(":/icon/AEEC264012EB9C71B036E637F0678EFA.jpg");
		map = map.scaled(QSize(221, 381));
		ui->label_pic->setPixmap(map);
	}
	if (num == 10) {
		map.load(":/icon/D3034F351F85E131D25BCDAD3A36EA8C.jpg");
		map = map.scaled(QSize(221, 381));
		ui->label_pic->setPixmap(map);
	}
	if (num == 11) {
		map.load(":/icon/D4B38D4B97344F4FAB55F710B43E5AF1.jpg");
		map = map.scaled(QSize(221, 381));
		ui->label_pic->setPixmap(map);
	}
	if (num == 12) {
		map.load(":/icon/D9C25CA4F04B83068A9E07548C2468B7.jpg");
		map = map.scaled(QSize(221, 381));
		ui->label_pic->setPixmap(map);
	}
}



4.文件传输:

文件传输的原理是在文件传输里面创建了两个端口,一个是服务端,一个是客户端。在我们打开文件传输的界面的时候,文件传输的客户端通过之前,就已经写在头文件里面的IP地址,去连接另一个服务器。我们把两个用户称为A和B。若a单打开的文件不为空,则进入传输文件。不断读取文件名的大小和传输文件的数据。服务端则对传输过来的信息进行判别,如果是文件,那么就。在本地创建一个文件的缓冲区。如果是内容的话,则在缓冲区不断添加接收的内容。与客户端不断进行交互,当信息传输完毕的时候就结束传输。

#include "sendfile.h"
#include "ui_sendfile.h"
#include "mainwindow.h"
#define TIMEOUT (500)
//int flag=0;
//QString YouIP="172.20.10.4";
Sendfile::Sendfile(QWidget *parent) :
	QDialog(parent),
	ui(new Ui::Sendfile) {
	ui->setupUi(this);
    //设置窗体最大化和最小化
    Qt::WindowFlags windowFlag  = Qt::Dialog;
    windowFlag                  |= Qt::WindowMinimizeButtonHint;
    windowFlag                  |= Qt::WindowMaximizeButtonHint;
    windowFlag                  |= Qt::WindowCloseButtonHint;
    setWindowFlags(windowFlag);
	send_init();
	receive_init();
}


Sendfile::~Sendfile() {
	delete ui;
}

//文件传输--发送文件handletimeout()

void Sendfile::send_init() {
	send_loadSize = 50 * 1024;
	send_totalBytes = 0;
	send_bytesWritten = 0;
	send_bytesToWrite = 0;
	QString ip = YouIP;
	send_ip.setAddress(ip);
	send_udpSocket = new QUdpSocket(this);
	send_udpSocket->bind(4444, QUdpSocket::ShareAddress);

	m_pTimer = new QTimer(this);
	connect(m_pTimer, SIGNAL(timeout()), this, SLOT(handletimeout()));
	connect(send_udpSocket, SIGNAL(readyRead()), this, SLOT(send_readmessage()));
	ui->pushButton_Send->setEnabled(false);
}


void Sendfile::openFile() {
	send_fileName = QFileDialog::getOpenFileName(this);
	if (!send_fileName.isEmpty()) {
		ui->pushButton_Send->setEnabled(true);
	}
}

void Sendfile::startTransfer() {
	send_localFile = new QFile(send_fileName);
	if (!send_localFile->open(QFile::ReadOnly)) {
		qDebug() << "open file error!";
		return;
	}
	send_totalBytes = send_localFile->size();
	qDebug() << send_totalBytes;
	QDataStream sendOut(&send_outBlock, QIODevice::WriteOnly);
	sendOut.setVersion(QDataStream::Qt_5_6);
	QString currentFileName = send_fileName.right(send_fileName.size() - send_fileName.lastIndexOf('/') - 1);
	sendOut << qint64(0) << qint64(0) << currentFileName;
	send_totalBytes  += send_outBlock.size();
	sendOut.device()->seek(0);
	sendOut << send_totalBytes << qint64((send_outBlock.size() - sizeof(qint64) * 2));
	m_pTimer->start(TIMEOUT);
	send_bytesToWrite = send_totalBytes - send_udpSocket->writeDatagram(send_outBlock, send_outBlock.size(), send_ip, 7777);
	qDebug() << send_bytesToWrite;
	send_bytesWritten = send_outBlock.size();
	qDebug() << send_bytesWritten;
	ui->progressBar_send->setMaximum(send_totalBytes);
	ui->progressBar_send->setValue(send_bytesWritten);
	send_outBlock.resize(0);
}

void Sendfile::send() {
	if (!send_localFile->atEnd()) {
		line = send_localFile->read(qMin(send_bytesToWrite, send_loadSize));
		send_bytesToWrite -= send_udpSocket->writeDatagram(line, line.size(), send_ip, 7777);
		send_bytesWritten = send_bytesWritten + line.size();
		ui->progressBar_send->setMaximum(send_totalBytes);
		ui->progressBar_send->setValue(send_bytesWritten);
		qDebug() << send_bytesWritten;
	} else {
		send_udpSocket->writeDatagram("over", send_ip, 7777);
		m_pTimer->stop();
		ui->progressBar_send->close();
		send_udpSocket->close();
		send_localFile->close();
	}
	return;
}

void Sendfile::send_readmessage() {
	while (send_udpSocket->hasPendingDatagrams()) {
		QByteArray res = "false";
		res.resize(send_udpSocket->pendingDatagramSize());
		send_udpSocket->readDatagram(res.data(), res.size());
		qDebug() << res;
		if (res == "true") {
			flag = 1;
		}

	}
}

void Sendfile::handletimeout() {
	qDebug() << flag;
	if (flag == 1) {
		send();
		flag = 0;
	} else {
		send_udpSocket->writeDatagram(line, line.size(), send_ip, 7777);
	}
}



//文件传输--接收文件
void Sendfile::receive_init() {
	receive_totalBytes = 0;
	receive_bytesReceived = 0;
	receive_fileNameSize = 0;
	QString ip = YouIP;
	receive_ip.setAddress(ip);
	receive_udpSocket = new QUdpSocket();
	receive_udpSocket->bind(7777, QUdpSocket::ShareAddress);
	connect(receive_udpSocket, SIGNAL(readyRead()), this, SLOT(processPendingDatagram()));
}

void Sendfile::processPendingDatagram() {
	while (receive_udpSocket->hasPendingDatagrams()) {
		receive_udpSocket->writeDatagram("true", receive_ip, 4444);
		receive_inBlock.resize(receive_udpSocket->pendingDatagramSize());
		qDebug() << receive_inBlock.size();
		receive_udpSocket->readDatagram(receive_inBlock.data(), receive_inBlock.size());
		if (receive_inBlock == "over") {
			qDebug() << receive_inBlock;
			receive_udpSocket->close();
			receive_localFile->close();
			ui->progressBar_receive->close();
		} else {
			updateServerProgress();
		}
	}
}

void Sendfile::updateServerProgress() {
	QDataStream in(&receive_inBlock, QIODevice::ReadOnly);
	in.setVersion(QDataStream::Qt_5_6);
	if (receive_bytesReceived <= sizeof(qint64) * 2) {
		if (((unsigned int)receive_inBlock.size() >= sizeof(qint64) * 2)
		        && (receive_fileNameSize == 0)) {
			in >> receive_totalBytes >> receive_fileNameSize;
			qDebug() << receive_totalBytes << receive_fileNameSize;
			ui->progressBar_receive->setMaximum(receive_totalBytes);
			ui->progressBar_receive->setValue(receive_bytesReceived);
			qDebug() << receive_bytesReceived;
		}
		if (((unsigned int)receive_inBlock.size() >= receive_fileNameSize)
		        && (receive_fileNameSize != 0)) {
			in >> receive_fileName;
			qDebug() << receive_fileName;
			qDebug() << receive_bytesReceived;
			receive_localFile = new QFile(receive_fileName);
			if (!receive_localFile->open(QFile::WriteOnly)) {
				qDebug() << "open file error!";
				return;
			}
		} else return;

	}
	if (receive_bytesReceived < receive_totalBytes) {
		receive_bytesReceived += receive_inBlock.size();
		receive_localFile->write(receive_inBlock);
		qDebug() << receive_localFile->size();
		receive_inBlock.resize(0);
	}
	qDebug() << receive_bytesReceived;
	ui->progressBar_receive->setMaximum(receive_totalBytes);
	ui->progressBar_receive->setValue(receive_bytesReceived);
}

void Sendfile::on_pushButton_Open_clicked() {
	openFile();
}

void Sendfile::on_pushButton_Send_clicked() {
	startTransfer();
}

补充:

TCP通信的建立

服务器和客户端都有相应的server和socket去接收和发送消息,我在登陆模块设置乐一个全局的IP地址和端口,就是为了方便客户端与服务器建立通信。然后发送到服务器的每一条信息都用相应的字母去标记他,以便服务器分类和处理数据,服务器在将数据处理完之后,带上之前的前缀和相关的信息,将数据发送回客户端。

客户端实例代码:

if (list[0] == "e") { //加载用户列表
		ui->listWidget->clear();
		for (int i = 1; i < list.length(); i++) {
			if (list[i] != "end") {
				QString str = list[i];
				QListWidgetItem *item = new QListWidgetItem;
				item->setText(str);
				ui->listWidget->addItem(item);
			} else ui->textBrowser->append(list[++i]);
		}
	} else if (list[0] == "f") { //群发消息功能实

服务器实例代码:

if (list[0] == "a") { //注册
		qDebug() << "到达注册" << endl;
		ret = checkSignUp(list[1], list[2], list[3]);
		if (ret)
			sendData = sendData + "#true";
		else
			sendData = sendData + "#false";
		socket->write(sendData.toLatin1());
	} else if (list[0] == "b") { //登录

项目的流程

仅供参考
Qt开发的即时通讯软件(基于TCP) C/S设计模式_第8张图片

数据库

两张表,一个是储存用户信息,一个是储存离线消息。
Qt开发的即时通讯软件(基于TCP) C/S设计模式_第9张图片
Qt开发的即时通讯软件(基于TCP) C/S设计模式_第10张图片

这个项目也是参考了其他的,我感觉通信是比较难的,在项目开始的那段时间一直在测试TCP通信,这里就不班门弄斧了,下面的这位博主写的很好,我当时也是看他的才对TCP有了比较好的理解。不过在写完这个项目之后感觉TCP也就那样,所以还是要多实践啊。

QT - 创建TCP Socket通信

下面是一些其他还不错的博客,对理解项目和发散学习有帮助:

QT学习——QSqlQuery基本操作
QT - 创建TCP Socket通信
C/S 和 P2P
QT5实现简单的TCP通信

项目的下载在下面的链接,欢迎评论区交流。
基于TCP的即时通信软件

你可能感兴趣的:(tcp,通信)