Basado en el paquete C++ y Qt, una interfaz UI de comunicación de socket simple (TCP/IP)

        Recientemente, estoy aprendiendo sobre TCP / IP y sockets. He aprendido mucho sobre el protocolo de enlace de tres vías y el protocolo de onda de cuatro vías, el protocolo TCP, etc., pero me siento superficial en el papel. Hay mucha información sobre el código C ++ para realizar la comunicación por socket en Internet, lo cual es conveniente para aprender, por lo que pensé en usar Qt para implementar un software de interfaz de usuario básico del lado del servidor con funciones de envío y recepción de comunicación de red. En el título:

1. Interfaz UI e introducción de funciones

        Aquí programamos bajo el sistema Windows , usamos el marco Qt5, usamos el botón (pushButton) para inicializar el socket y hacemos clic para enviar información, y la ventana para recibir información y enviar información se implementa usando un cuadro de edición de texto (textEdit). Solo hay dos botones y dos cuadros de texto en toda la interfaz, y las funciones realizadas son muy simples, es decir, como un servidor en comunicación TCP / IP, esperando que el cliente se conecte y comunicándose con el cliente. Toda la interfaz de usuario es la siguiente:

Proceso de software:

        1. Haga clic en el botón de inicialización, llame a funciones como vincular y escuchar para inicializar el socket, y llame a aceptar para esperar a que el cliente se conecte;

        2. Al llamar a aceptar, un cuadro de diálogo emergente (messageBox) recuerda que está esperando que el cliente se conecte;

        3.  El cliente se conecta al servidor, se completa la aceptación y el cuadro de diálogo se cierra automáticamente;

        4.  Ingrese caracteres en la ventana de envío y haga clic en Enviar para transmitir y recibir mensajes del cliente en la ventana de recepción.

Además, cuando el servidor no puede enviar un mensaje, se considera que el cliente ha desconectado la conexión del socket, por lo que el socket utilizado para la conexión y la comunicación actual se cierra, se restaura el estado inicial y se puede realizar la siguiente conexión.

2. Implementación del código

        Hay muchos comentarios en esta parte y el código no se explicará aquí. Los puntos que requieren atención son:

        En primer lugar, el hilo principal de Qt es el hilo de la interfaz de usuario, por lo que si realiza una operación que requiere mucho tiempo en el hilo principal, afectará el dibujo y la actualización de la interfaz de la interfaz de usuario, lo que provocará que la interfaz se bloquee y el El software no se puede utilizar normalmente. Por lo tanto, esperar la conexión del cliente y recibir mensajes aquí es abrir otro hilo para ejecutar ( std::thread ). Por supuesto, también puede usar QThread para operaciones de subprocesos múltiples.

        En segundo lugar, envíe la señal personalizada AcceptStart() antes del bloque de aceptación para que aparezca el cuadro de mensaje emergente; después de completar la conexión, envíe otra señal personalizada AcceptFinish() nuevamente para cerrar el cuadro de mensaje.

        Finalmente, si desea implementar la función de reconexión del socket, primero debe cerrar el socket no válido (desconectado) y luego inicializarlo nuevamente; de ​​lo contrario, no podrá conectarse.

Servidor.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;
};

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

3. Efecto del software

        

         El asistente de depuración de red utilizado aquí es el software NetAssist de Savage Homeland, que aquí desempeña el papel de cliente. Enlace de descarga: http://www.cmsoft.cn/download/cmsoft/netassist.zip http://www.cmsoft.cn/download/cmsoft/netassist.zip

         Todavía hay algunos problemas en este software: si el servidor no envía mensajes al cliente, el socket no se puede cerrar automáticamente, no se pueden enviar chinos, etc., lo cual se puede mejorar.

Supongo que te gusta

Origin blog.csdn.net/m0_57315535/article/details/129998046
Recomendado
Clasificación