UE4 Sockets多线程TCP通信

一、简介

UE4引擎是提供了Sockets模块和Networking模块的,博主在研究此功能时也是参考的Sockets模块和Networking模块的源码,其中引擎为我们提供了一些实例类很有参考价值,比如Sockets模块中的MultichannelTcpReceiver.h和MultichannelTcpSender.h,Networking模块中的UdpSocketReceiver.h和UdpSocketSender.h。博主的例子就是研究参考了以上代码写出来的。

编译与运行环境

UnrealEngine 4.15 , Visual Studio 2015

实现功能介绍

博主的例子实现的是一个使用Socket多线程TCP通信的客户端。在主线程中发消息,子线程中收消息。当然也能类似的实现两个子线程分别收发消息。Socket相关函数都定义在了GameInstance中,以便我们能在不同场景都能调用。

二、代码实现

服务器代码

此处使用了一个有简单收发功能的服务器,用VS2015新建空项目即可:

//SocketTest.cpp: Defines the entry point for the console application

#include <stdio.h>
#include <string>
#include <iostream>
#include <WinSock2.h>
#include <WS2tcpip.h>
#pragma comment(lib, "WS2_32.lib")

using namespace std;

int main()
{
    WSADATA wsaData;
    SOCKET serverSock;
    SOCKADDR_IN serverAddr;
    SOCKET clientSock;
    SOCKADDR_IN clientAddr;


    cout << "Server Start!" << endl;
    //加载套接字库
    int err = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (0 != err)
    {
        cout << "WSAStartup failed!" << endl;
        return 1;
    }

    //构造监听SOCKET,流式SOCKET
    serverSock = socket(AF_INET, SOCK_STREAM, 0);
    //配置监听地址和端口
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(4399);  //本地监听端口:4399
    serverAddr.sin_addr.S_un.S_addr = INADDR_ANY;

    //绑定SOCKET
    int retVal = bind(serverSock, (SOCKADDR*)&serverAddr, sizeof(serverAddr));
    if (SOCKET_ERROR == retVal)
    {
        cout << "bind failed!" << endl;
        closesocket(serverSock);
        WSACleanup();
        return -1;
    }

    retVal = listen(serverSock, 5);
    if (SOCKET_ERROR == retVal)
    {
        cout << "listen failed!" << endl;
        closesocket(serverSock);
        WSACleanup();
        return -1;
    }

    char IPdot[20] = { '\0' };
    inet_ntop(AF_INET, (void*)&serverAddr.sin_addr, IPdot, 16);
    printf("Welcome,the Host %s is running!Now Wating for someone comes in!\n", IPdot);

    int addrClientlen = sizeof(clientAddr);
    //等待客户端连接
    clientSock = accept(serverSock, (SOCKADDR*)&clientAddr, &addrClientlen);
    while (1)
    {
        //发送数据
        char sendBuff[50];
        //sprintf_s(sendBuff, "welcome %s to here", inet_ntoa(clientAddr.sin_addr));
        char IPdotdec[20] = { '\0' };
        inet_ntop(AF_INET, (void*)&clientAddr.sin_addr, IPdotdec, 16);
        sprintf_s(sendBuff, "From Server: welcome %s to here", IPdotdec);
        send(clientSock, sendBuff, strlen(sendBuff) + 1, 0);

        //接收数据
        char recvBuff[50];
        recv(clientSock, recvBuff, 50, 0);
        printf(" %s \n\n", recvBuff);

        Sleep(1000);
    }
    //关闭套接字,关闭加载的套接字库
    closesocket(serverSock);
    WSACleanup();
    return 0;
}

模块添加

新建项目,在.Build.cs文件中添加Sockets模块和Networking模块:

// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.

using UnrealBuildTool;

public class SocketThread : ModuleRules
{
    public SocketThread(TargetInfo Target)
    {
        PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "HeadMountedDisplay","Sockets", "Networking" });
    }
}

Socket相关函数定义

STGameInstance.h
//this is the STGameInstance.h

#pragma once

#include "Engine/GameInstance.h"
#include "Networking.h"
#include "ReceiveThread.h"
#include "STGameInstance.generated.h"

UCLASS()
class SOCKETTHREAD_API USTGameInstance : public UGameInstance
{
    GENERATED_BODY()

public:
    //创建Socket并连接到服务器(主线程)
    UFUNCTION(BlueprintCallable, Category = "MySocket")
    bool SocketCreate(FString IPStr, int32 port);

    //发消息(主线程)
    UFUNCTION(BlueprintCallable, Category = "MySocket")
    bool SocketSend(FString message);

    //收消息(子线程)
    UFUNCTION(BlueprintCallable, Category = "MySocket")
    bool SocketReceive();

    UFUNCTION(BlueprintCallable, Category = "MySocket")
    bool ThreadEnd();

    FString StringFromBinaryArray(TArray<uint8> BinaryArray);

public:
    FSocket *Host;
    FIPv4Address ip;    
    FRunnableThread* m_RecvThread;
};
STGameInstance.cpp
//this is the STGameInstance.cpp

#include "SocketThread.h"
#include "STGameInstance.h"

bool USTGameInstance::SocketCreate(FString IPStr, int32 port)
{
    FIPv4Address::Parse(IPStr, ip);     //将传入的IPStr转为IPv4地址

    //创建一个addr存放ip地址和端口
    TSharedPtr<FInternetAddr> addr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();
    addr->SetIp(ip.Value);
    addr->SetPort(port);

    //创建客户端socket
    Host = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateSocket(NAME_Stream, TEXT("default"), false);

    //连接成功
    if (Host->Connect(*addr))
    {
        UE_LOG(LogTemp, Warning, TEXT("Connect Succeed!"));
        return true;
    }
    //连接失败
    else
    {
        UE_LOG(LogTemp, Warning, TEXT("Connect Failed!"));
        return false;
    }
}

bool USTGameInstance::SocketSend(FString message)
{
    TCHAR *seriallizedChar = message.GetCharArray().GetData();
    int32 size = FCString::Strlen(seriallizedChar) + 1;
    int32 sent = 0;

    if (Host->Send((uint8*)TCHAR_TO_UTF8(seriallizedChar), size, sent))
    {
        UE_LOG(LogTemp, Warning, TEXT("___Send Succeed!"));
        return true;
    }
    else
    {
        UE_LOG(LogTemp, Warning, TEXT("___Send Failed!"));
        return false;
    }
}

bool USTGameInstance::SocketReceive()
{
    m_RecvThread = FRunnableThread::Create(new FReceiveThread(Host), TEXT("RecvThread"));
    return true;
}

bool USTGameInstance::ThreadEnd()
{
    if (m_RecvThread != nullptr)
    {
        m_RecvThread->Kill(true);
        delete m_RecvThread;
    }
    return true;
}

FString USTGameInstance::StringFromBinaryArray(TArray<uint8> BinaryArray)
{
    return FString(ANSI_TO_TCHAR(reinterpret_cast<const char*>(BinaryArray.GetData())));
}

收消息线程

ReceiveThread.h
//this is the ReceiveThread.h
#pragma once

#include "ThreadingBase.h"
#include "Networking.h"

class SOCKETTHREAD_API FReceiveThread : public FRunnable
{
public:
    FReceiveThread(FSocket* client): m_Client(client)
    {}
    ~FReceiveThread()
    {
        stopping = true;
    }

    virtual bool Init() override
    {
        stopping = false;
        return true;
    }

    virtual uint32 Run() override
    {
        if (!m_Client)
        {
            return 0;
        }

        TArray<uint8> ReceiveData;
        uint8 element = 0;
        //接收数据包
        while (!stopping)   //线程计数器控制
        {
            ReceiveData.Init(element, 1024u);
            int32 read = 0;
            m_Client->Recv(ReceiveData.GetData(), ReceiveData.Num(), read);
            const FString ReceivedUE4String = FString(ANSI_TO_TCHAR(reinterpret_cast<const char*>(ReceiveData.GetData())));
            FString log = "Server:" + ReceivedUE4String;
            UE_LOG(LogTemp, Warning, TEXT("*** %s"), *log);

            FPlatformProcess::Sleep(0.01f);
        }


        return 1;
    }

    virtual void Stop() override
    {
        stopping = true;    //计数器-1
    }

private:
    FSocket* m_Client;  //客户端套接字
    bool stopping;      //循环控制
    FThreadSafeCounter m_StopTaskCounter;   //线程引用计数器
};

三、运行

在编辑器中,创建我们之前定义的GameInstance的蓝图类,并在Project Setting中设其为默认GameInstance。创建一个GameMode,在他的EventGraph中添加如下蓝图逻辑:
这里写图片描述
其中Succe是一个bool类型变量,Index是一个Int类型变量,不改默认值。
之后将这个GameMode绑定到当前场景中,编译运行服务器,然后Play,大功告成!博主运行结果如下:
这里写图片描述
可以看到,收发消息都成功了。

  • 13
    点赞
  • 69
    收藏
    觉得还不错? 一键收藏
  • 17
    评论
以下是Qt多线TCP服务端的实现示例: 1. 创建一个Qt控制台应用程序。 2. 在项目中添加一个新的类来实现TCP服务器。我们将称之为"TcpServer"。 3. 在TcpServer类的头文件中,添加以下内容: ``` #ifndef TCPSERVER_H #define TCPSERVER_H #include <QObject> #include <QTcpServer> #include <QTcpSocket> #include <QThread> class TcpServer : public QObject { Q_OBJECT public: explicit TcpServer(QObject *parent = nullptr); public slots: void newConnection(); //处理新连接 void readData(); //读取数据 void socketDisconnected(); //处理断开连接 private: QTcpServer *m_server = nullptr; //TCP服务器 QList<QTcpSocket*> m_sockets; //已连接的TCP套接字 }; #endif // TCPSERVER_H ``` 4. 在TcpServer类的源文件中,实现以下内容: ``` #include "tcpserver.h" TcpServer::TcpServer(QObject *parent) : QObject(parent) { m_server = new QTcpServer(this); connect(m_server, &QTcpServer::newConnection, this, &TcpServer::newConnection); if (!m_server->listen(QHostAddress::Any, 1234)) { qWarning() << "Could not start server"; } else { qDebug() << "Listening on port 1234..."; } } void TcpServer::newConnection() { QTcpSocket *socket = m_server->nextPendingConnection(); connect(socket, &QTcpSocket::readyRead, this, &TcpServer::readData); connect(socket, &QTcpSocket::disconnected, this, &TcpServer::socketDisconnected); m_sockets.append(socket); } void TcpServer::readData() { QTcpSocket *socket = qobject_cast<QTcpSocket*>(sender()); if (!socket) { return; } QByteArray data = socket->readAll(); qDebug() << "Received data:" << data; //将数据发送回客户端 socket->write(data); } void TcpServer::socketDisconnected() { QTcpSocket *socket = qobject_cast<QTcpSocket*>(sender()); if (!socket) { return; } m_sockets.removeOne(socket); socket->deleteLater(); } ``` 5. 在main.cpp文件中,实例化TcpServer类并将其移动到一个新的线程中: ``` #include <QCoreApplication> #include <QThread> #include "tcpserver.h" int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); //创建TcpServer对象 TcpServer *server = new TcpServer(); //创建新线程并将服务器对象移动到该线程 QThread *thread = new QThread(); server->moveToThread(thread); thread->start(); return a.exec(); } ``` 这样,我们就创建了一个基本的Qt多线TCP服务端。当客户端连接到服务器时,服务器将打印出"Listening on port 1234..."。当客户端发送数据时,服务器将打印出接收到的数据并将其发送回客户端。当客户端断开连接时,服务器将从已连接的套接字列表中删除该套接字。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值