最近在学习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文章来源:https://www.toymoban.com/news/detail-721419.html
该软件依然存在一些问题:如果服务端一直不发送消息给客户端,则无法自动实现关闭socket操作;无法发送中文等等,都是可以改进的方面。文章来源地址https://www.toymoban.com/news/detail-721419.html
到了这里,关于基于C++和Qt封装一个简单的socket(TCP/IP)通信UI界面的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!