• UE4 Sockets多线程TCP通信


    转自:https://blog.csdn.net/zilisen/article/details/75007447

    一、简介

    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] = { '' };
        inet_ntop(AF_INET, (void*)&serverAddr.sin_addr, IPdot, 16);
        printf("Welcome,the Host %s is running!Now Wating for someone comes in!
    ", 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] = { '' };
            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 
    
    ", 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,大功告成!博主运行结果如下: 

    可以看到,收发消息都成功了。

  • 相关阅读:
    java、asp.net 通用分页码函数
    SQL
    go build 参数
    alertmanager报错Failed to get final advertise address: No private IP address found, and explicit IP not provided"
    go语言三个点的用法
    CDH6.2安装
    python之链表
    jenkins触发构建后一直重复构建
    ansible远程执行寻找不到环境变量问题
    Python3之harbor sdk api
  • 原文地址:https://www.cnblogs.com/sevenyuan/p/8670575.html
Copyright © 2020-2023  润新知