实现一个基于 Qt 的客户端程序和一个使用 WinSock2 进行通信的基于 C++ 的服务器程序

1 篇文章 0 订阅

客户端程序负责与服务器建立连接,并通过发送和接收数据与服务器进行通信。服务器程序负责监听客户端的连接请求,并创建新的线程来处理每个客户端的收发包操作。客户端发送的数据经过服务器处理

网络访问接口,实现的功能是:服务器一直监听一个端口,一旦有客户端连接请求,便建立连接,并向客户端发送一个字符串,然后客户端接收该字符串并显示出来。

客户端程序:

  1. Client 类:定义了一个基于 QMainWindow 的客户端主窗口类。

  2. Client::Client(QWidget *parent) 构造函数:在构造函数中,创建了一个 QTcpSocket 对象 m_client,并尝试连接到服务器的 IP 地址和端口号("127.0.0.1:6666")。如果连接成功,会打印 "conn ok" 并将 m_client 对象的 readyRead() 信号与 myRead() 槽函数连接起来。

  3. Client::~Client() 析构函数:释放了通过 new 创建的 ui 对象。

  4. Client::on_sendButton_clicked() 槽函数:在点击发送按钮时触发,它会将字符串 "蚂蚁ok" 转换为本地编码的 QByteArray 类型,并通过 m_client 对象的 write() 方法向服务器发送数据。

  5. Client::myRead() 槽函数:在通过 m_client 对象接收到服务器发送的数据时触发,将接收到的数据转换为 QByteArray 类型,并使用 QString::fromLocal8Bit() 转换为本地编码字符串。然后将转换后的字符串追加到 QTextBrowser 部件中以在界面上显示收到的内容。

在qt中新建一个Qt Widgets Application,名为:Client (客户端)

ui界面添加一个编辑框,一个按钮,一个文本编辑框作为文本显示

client.h
#ifndef CLIENT_H
#define CLIENT_H

#include <QMainWindow>
#include <QTcpSocket>

namespace Ui {
    class Client;
}

class Client : public QMainWindow
{
    Q_OBJECT

public:
    explicit Client(QWidget *parent = nullptr);
    ~Client();

private slots:
    void on_sendButton_clicked();  // 发送按钮点击事件的槽函数
    void myRead();  // 读取数据的槽函数

private:
    Ui::Client *ui;  // UI界面对象指针
    QTcpSocket *m_client;  // TCP套接字对象指针
};

#endif // CLIENT_H

client.cpp

#include "client.h"
#include "ui_client.h"
#include <QDebug>

Client::Client(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::Client)
{
    ui->setupUi(this);

    m_client = new QTcpSocket(this);  // 创建TCP套接字对象
    m_client->connectToHost("127.0.0.1", 6666);  // 连接到服务器的IP地址和端口号
    if (m_client->waitForConnected()) {  // 等待连接成功
        qDebug() << "conn ok";
        connect(m_client, SIGNAL(readyRead()), this, SLOT(myRead()));  // 将readyRead()信号连接到myRead()槽函数,以接收数据
    } else {
        qDebug() << "conn fail" << m_client->errorString();  // 输出连接失败信息
    }
}

Client::~Client()
{
    delete ui;  // 释放UI界面对象的内存
}

void Client::on_sendButton_clicked()
{
    QString buffer = "蚂蚁ok";  // 待发送的字符串
    qDebug() << m_client->write(buffer.toLocal8Bit());  // 发送字符串数据到服务器
}

void Client::myRead()
{
    QByteArray buffer = m_client->readAll();  // 读取所有接收到的数据
    qDebug() << "buffer: " << buffer;
    qDebug() << "buffer.data: " << buffer.data();
    qDebug() << "中文内容:" << QString::fromLocal8Bit(buffer);

    ui->textBrowser->append(QString::fromLocal8Bit(buffer));  // 将接收到的内容追加到文本浏览器中显示
}

vs服务端

服务器程序:

  1. ServerSocket 类:定义一个服务器套接字类,实现了基于 WinSock2 的 socket 编程。

  2. ServerSocket::ServerSocket() 构造函数:在构造函数中初始化 WinSock2 环境,并创建服务器套接字。

  3. ServerSocket::~ServerSocket() 析构函数:关闭服务器套接字并清理网络环境。

  4. ServerSocket::initSocket() 方法:初始化服务器套接字,并绑定到指定的 IP 地址和端口号。如果初始化和绑定成功,还会创建一个线程来处理接受新连接的请求(accept)。

  5. ServerSocket::acceptThread() 方法:处理接受新连接(accept)的请求。当有新的客户端连接时,会创建一个新的线程来处理该客户端的收发包操作。

  6. ServerSocket::recvAndSendThread(SOCKET client) 方法:处理客户端的收发包操作。在一个循环中,接收客户端的数据,然后将接收到的数据发送回客户端。当收包出错或连接断开时,关闭连接。

  7. ServerSocket::heartCheckThread() 方法:心跳检测函数(尚未实现),可以在其中向所有连接的客户端发送心跳包并检测客户端的存活状态。

  8. main() 函数:在 main() 中创建了一个 ServerSocket 对象,并通过不断输入数据的方式来停止服务器。

新建一个c++空项目

ServerSocket.h

#pragma once
#include <iostream>
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib")
#include <thread>
using namespace std;

class ServerSocket
{
public:
    ServerSocket();
    virtual ~ServerSocket();
    bool initSocket();
    void acceptThread(); //线程函数,用于接受新的连接
    void recvAndSendThread(SOCKET client); //长连接收发包线程函数,处理收发包的操作
    void heartCheckThread(); //心跳检测线程函数,用于发送心跳包给连接的客户端
private:
    SOCKET m_server; //服务端套接字
};

ServerSocket.cpp

ServerSocket::ServerSocket()
{
    if (initSocket()) //调用socket初始化
    {
        cout << "init socket ok" << endl;
    }
    else {
        cout << "int socket file" << endl;
    }
}

ServerSocket::~ServerSocket() {
    closesocket(m_server); //关闭服务端的套接字
    WSACleanup(); //关闭网络环境
}

bool ServerSocket::initSocket() {
    WSAData wsa;
    WSAStartup(MAKEWORD(2, 2), &wsa); //启动网络环境

    m_server = socket(AF_INET, SOCK_STREAM, 0); //创建TCP套接字
    SOCKADDR_IN addr;
    addr.sin_family = AF_INET; //支持IPv4
    addr.sin_port = htons(6666); //设置端口号,使用大端字节序
    addr.sin_addr.S_un.S_addr = 0; //绑定到任意可用的IP地址

    int ret_bind = bind(m_server, (sockaddr*)&addr, sizeof(addr)); //将套接字绑定到指定的地址和端口
    if (ret_bind) {
        cout << "bind fail" << endl;
        return false;
    }

    int ret_listen = listen(m_server, 5); //开始监听连接请求,设置最大连接数为5
    if (ret_listen) {
        cout << "listen fail:" << WSAGetLastError() << endl;
        return false;
    }

    // 初始化成功,正常

    // 启动accept函数的线程
    thread accept_thread(&ServerSocket::acceptThread, this);
    accept_thread.detach(); //分离线程,防止异常结束

    // 启动心跳检测线程
    thread hear_check(&ServerSocket::heartCheckThread, this);
    hear_check.detach();

    return true;
}

void ServerSocket::recvAndSendThread(SOCKET client)
{
    //读取和发送数据
    char buffer[1024] = { 0 };
    while (true) {
        int bytesReceived = recv(client, buffer, sizeof(buffer), 0); //接收客户端发送的数据
        cout << "bytesReceived:" << bytesReceived << endl;
        if (bytesReceived <= 0) {
            cout << "client 收包异常:" << WSAGetLastError() << endl;
            //处理接收错误或连接断开
            break;
        }

        //收包正常
        cout << "buffer:" << buffer << endl;

        send(client, buffer, bytesReceived, 0); //将接收到的数据发送回客户端

        //处理收到的数据
        假设会发相同的数据
        //int bytesSent = send(client, buffer, bytesReceived, 0);
        //if (bytesSent <= 0) {
        //	//处理错误数据
        //	break;
        //}
    }
    //关闭连接
    closesocket(client);
}

void ServerSocket::heartCheckThread() {
    //处理心跳检测逻辑
    // 模拟心跳检测
    //while (true) {
    // // 发送心跳包给所有连接的客户端
    // // 例如,使用 send() 函数向客户端发送心跳包数据

    //	// 等待一段时间
    //	std::this_thread::sleep_for(std::chrono::seconds(5));
    //}
}

void ServerSocket::acceptThread() {
    while (true) {
        // 接受新的连接
        SOCKADDR_IN clientAddr;
        int len = sizeof(clientAddr);
        SOCKET client = accept(m_server, (sockaddr*)&clientAddr, &len);
        cout << "socket error:" << WSAGetLastError() << endl;
        if (client == SOCKET_ERROR) {
            // 处理连接错误
            cout << "socket close:" << WSAGetLastError() << endl;
            break;
        }
        else if (client == 0) {
            cout << "socket close:" << WSAGetLastError() << endl;
            break;
        }

        // 创建一个新的线程处理收发包
        std::thread recvAndSend(&ServerSocket::recvAndSendThread, this, client);
        recvAndSend.detach();
    }
}

cpp的main函数端

#include "ServerSocket.h"
#include <string>
#include <thread>
int main() {
    ServerSocket server;
    // 初始化Socket


    std::string str;
    while (std::cin >> str) {
        if (str == "88") {
            break;
        }
    }

    return 0;
}

总体来说,这段代码实现了一个使用 Qt 创建的客户端与使用 WinSock2 实现的服务器之间的通信。客户端通过点击按钮将数据发送到服务器,并从服务器接收数据并显示在界面上。服务器接受客户端的连接,并为每个连接创建一个线程以处理收发包的操作。

延展:另一个需求:在这基础上新传递一个结构体,包含多个成员变量

struct Data

{ int type;

int len;

char data[128];

};

内容”蚂蚁真棒“

type=100

len 为整个结构体的大小

想办法c->s:能够解开并输出内容

再回到C:s->c:

也要能解包,将内容追加显示到文本游览器

client.h
#ifndef CLIENT_H
#define CLIENT_H

#include <QMainWindow>
#include  <QTcpSocket>

namespace Ui {
class Client;
}
struct Data
{ int type;
int len;
char data[128];
};
class Client : public QMainWindow
{
    Q_OBJECT

public:
    explicit Client(QWidget *parent = nullptr);
    ~Client();

private slots:
    void on_sendButton_clicked();
    void myRead();  //收包槽函数


private:
    Ui::Client *ui;
    QTcpSocket *m_client;
};

#endif // CLIENT_H

client.cpp

#include "client.h"
#include "ui_client.h"
#include <QDebug>

Client::Client(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::Client)
{
    ui->setupUi(this);

    m_client =new QTcpSocket(this);
    m_client->connectToHost("127.0.0.1",6666);
    if(m_client->waitForConnected()){
        qDebug()<<"conn ok";
        connect(m_client,SIGNAL(readyRead()),this,SLOT(myRead()));
    }else{
        qDebug()<<"conn fail"<<m_client->errorString();
    }
}

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

void Client::on_sendButton_clicked()
{
//    QString buffter="蚂蚁ok";
//    qDebug()<<m_client->write(buffter.toLocal8Bit());
    Data data;
    strcpy_s(data.data, ui->lineEdit->text().toLocal8Bit().data());
    data.type=100;
    data.len=sizeof(Data);

    qint64 len=m_client->write((char*)&data,sizeof(data));
    qDebug()<<"len: "<<len;
}

void Client::myRead()
{
//   QByteArray buffer= m_client->readAll();
//   qDebug()<<"buffter:"<<buffer;
//   qDebug()<<"buffter.data:"<<buffer.data();
//   qDebug()<<"中文内容:"<<QString::fromLocal8Bit(buffer);

//    //点击发包的按钮将收到的内容追加显示到该文本游览器中
//   ui->textBrowser->append(QString::fromLocal8Bit(buffer));
    Data data;
    qint64 len = m_client->read((char*)&data, sizeof(data));
    qDebug()<<"len: "<<len;
    qDebug() << "type: " << data.type;
    qDebug() << "data: " << data.len;
    qDebug()<<"data: "<<QString::fromLocal8Bit(data.data);
    //将收到的内容追加显示在文本游览器中
    QString receiveData=QString("type: %1,len: %2").arg(data.type).arg(data.len);
    ui->textBrowser->append(receiveData);
    ui->textBrowser->append(QString::fromLocal8Bit(data.data));
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值