基于C++和Qt封装一个简单的socket(TCP/IP)通信UI界面

        最近在学习TCP/IP和socket套接字的有关知识,了解了三次握手四次挥手,TCP协议等等一大堆知识,但纸上得来终觉浅。网络上C++代码实现socket通信的资料很多,方便学习,于是想到自己用Qt实现一个基础的具有网络通信收发功能的服务端UI软件。进入正题:

一、UI界面及功能介绍

        此处我们在Windows系统下编程,使用Qt5框架,利用按钮(pushButton)来执行初始化socket和点击发送信息,接收信息和发送信息的窗口则使用文本编辑框(textEdit)来实现。整个界面只有两个按钮和两个文本框,实现的功能十分简单,即作为TCP/IP通信中的服务端,等待客户端连接,并与客户端进行通信。整个UI界面如下:

软件流程:

        1.点击初始化按钮,调用bind和listen等函数初始化socket,并调用accept等待客户端连接;

        2. 调用accept同时,弹出对话框(messageBox)提醒正在等待客户端连接;

        3. 客户端连接到本服务端,accept完成,自动关闭对话框;

        4. 在发送窗口输入字符并点击发送进行传输,在接收窗口接收来自客户端的消息。

另外,服务端发送消息失败时考虑客户端已将socket连接断开,所以关闭连接用和当前通信用的socket,恢复初始态,可以进行下一次连接。

二、代码实现

        本部分注释写的比较多,此处不再对代码进行解释,需要注意的点有:

        首先,Qt的主线程为UI线程,所以如果将耗时较长的操作放在主线程,会影响UI界面的绘制和刷新,导致界面阻塞,软件无法正常使用。所以这里的等待客户端连接和接收消息均另外开了线程来运行(std::thread),当然大家也可以使用QThread进行多线程操作。

        其次,在accept阻塞之前发送自定义信号acceptStart(),令消息提示框弹出;连接完成后再次发送另一个自定义信号acceptFinish(),令消息提示框关闭。

        最后,若想实现socket重连功能,则必须先关闭(closesocket)已经失效(断连)的socket,然后再次初始化,否则无法连接。

Server.h

#pragma once

#include <QtWidgets/QMainWindow>
#include "ui_Server.h"
#include <errno.h>
#include <sys/types.h>
#include <thread>
#include <stdio.h>
#include <iostream>
#include<WinSock2.h>
#include<QMessageBox>

#pragma comment(lib,"ws2_32.lib")

class Server : public QMainWindow
{
    Q_OBJECT

public slots:
	bool init_server();
	void send_msg();

signals:
	void acceptStart();
	void acceptFinish();

public:
    Server(QWidget *parent = nullptr);
    ~Server();
	void recv_msg();
	//接收信息线程
	void recv_thread_function();
	//初始化socket线程
	void init_server_thread_function();

private:
    Ui::ServerClass ui;
	static const int BUF_SIZE = 0XFFFF;
	//接收线程和初始化线程
	std::thread *recv_thread, *init_thread;
	//用于连接和用于通信的socket
	int socket_fd, client_fd;
	//ip地址和端口
	std::string server_ip;
	unsigned short server_port;
	//服务端是否工作标志位
	bool isServerWork;
	//接收和发送缓冲区
	char recv_buf[BUF_SIZE];
	char send_buf[BUF_SIZE];
	//开始通信过程
	void start();
	//关闭socket
	void on_disconnected();
	//等待连接时弹出的对话框
	QMessageBox* dialogWait;
};

Server.cpp

#include "Server.h"

Server::Server(QWidget *parent)
    : QMainWindow(parent)
{
    ui.setupUi(this);
	//初始化用于连接的socket
	socket_fd = -1;
	server_ip = "127.0.0.1";
	server_port = 8888;
	isServerWork = true;
	dialogWait = new QMessageBox(this);
	dialogWait->setText(QStringLiteral("正在等待客户端连接..."));
	dialogWait->addButton(QMessageBox::Ok);
	connect(ui.pushButtonInit, &QPushButton::clicked, this, &Server::init_server);
	connect(ui.pushButtonSend, &QPushButton::clicked, this, &Server::send_msg);
	connect(this, &Server::acceptStart, this->dialogWait, &QMessageBox::show);
	connect(this, &Server::acceptFinish, this->dialogWait, &QMessageBox::accept);
}

Server::~Server()
{}

void Server::init_server_thread_function()
{
	struct sockaddr_in client;
	int len = sizeof(client);
	if (isServerWork)
	{
		emit acceptStart();//弹出等待对话框
		client_fd = accept(socket_fd, (struct sockaddr*)&client, &len);//此处阻塞直到客户端连接
		if (client_fd == -1)
		{
			perror("accept");
			exit(-1);
		}
		emit acceptFinish();//关闭等待对话框
		char *client_ip = inet_ntoa(client.sin_addr);
		ui.textEditRecv->append(QStringLiteral("连接成功,客户端IP为:"));
		ui.textEditRecv->append(client_ip);
		//服务端和客户端连接后可以开启接收线程
		recv_thread = new std::thread(std::bind(&Server::recv_thread_function, this));
	}
}

bool Server::init_server()
{
	//win系统下必须进行WSAStartup
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	wVersionRequested = MAKEWORD(1, 1);
	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0)
	{
		return S_FALSE;
	}
	std::string ip = server_ip;
	unsigned short port = server_port;
	//不进行WSAStartup,socket_fd无法创建成功
	socket_fd = socket(AF_INET, SOCK_STREAM, 0);
	if (socket_fd == -1)
	{
		perror("socket");
		exit(-1);
	}
	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	addr.sin_addr.s_addr = inet_addr(ip.c_str());

	int ret = bind(socket_fd, (struct sockaddr*)&addr, sizeof(addr));
	if (ret == -1)
	{
		perror("bind");
		exit(-1);
	}

	listen(socket_fd, 10);
	ui.textEditRecv->append(QStringLiteral("等待客户端连接..."));
	start();
	return true;
}

void Server::start()
{
	ui.pushButtonInit->setEnabled(false);
	//等待accept会阻塞UI,此处开线程进行处理
	init_thread = new std::thread(std::bind(&Server::init_server_thread_function, this));
}

void Server::send_msg()
{
	if (ui.textEditSend->document()->isEmpty())
		return;
	QString qstrToSend = ui.textEditSend->toPlainText();
	QByteArray ba = qstrToSend.toLatin1();
	char* buf = ba.data();
	memset(send_buf, 0, BUF_SIZE);
	memcpy(send_buf, buf, sizeof(buf));
	int ret = send(client_fd, send_buf, strlen(send_buf), 0);
	//若发送失败,考虑已经断线,可以关闭socket并进行下一步重连
	if (ret < 0)
	{
		on_disconnected();
		ui.textEditRecv->append(QStringLiteral("连接已断开,请重新连接!"));
		ui.pushButtonInit->setEnabled(true);
	}
		
}

void Server::recv_msg()
{
	int recv_size = recv(client_fd, recv_buf, sizeof(recv_buf), 0);
	if (recv_size <= 0)
		return;
	char recv_content[1024];
	memset(recv_content, 0, sizeof(recv_content));
	memcpy(&recv_content, recv_buf, sizeof(recv_content));
	ui.textEditRecv->append(recv_content);
}

void Server::on_disconnected()
{
	closesocket(socket_fd);
	closesocket(client_fd);
}

void Server::recv_thread_function()
{
	while (true)
	{
		recv_msg();
	}
}

三、软件效果

        

         此处使用的网络调试助手为野人家园的NetAssist软件,在此扮演客户端的角色。下载链接:http://www.cmsoft.cn/download/cmsoft/netassist.ziphttp://www.cmsoft.cn/download/cmsoft/netassist.zip

         该软件依然存在一些问题:如果服务端一直不发送消息给客户端,则无法自动实现关闭socket操作;无法发送中文等等,都是可以改进的方面。

猜你喜欢

转载自blog.csdn.net/m0_57315535/article/details/129998046