首先搞明白什么是广播,在其他大佬的博文中在发送端,把发送的ip设置为255.255.255.255,就是广播地址,如果指定了局域网内的某一台的ip地址,就是单播。这个地方其他大佬的博客里没有说明白。因此有很多人踩坑了。
服务端代码:
#include "pch.h"
#include <iostream>
#include <stdio.h>
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib")
const int MAX_BUF_LEN = 255;
int main()
{
WSADATA socketData;
int err;
err = WSAStartup(MAKEWORD(2, 2), &socketData);
if (err != 0) {
return -1;
}
if (LOBYTE(socketData.wHighVersion) != 2 || HIBYTE(socketData.wVersion) != 2) {
WSACleanup();
return -1;
}
// 使用AF_INET,是IPv4网络协议的套接字类型
// SOCK_DGRAM 是UDP
SOCKET serverSocket=socket(AF_INET,SOCK_DGRAM,0);
// 打开广播
bool bOpt = true;
setsockopt(serverSocket, SOL_SOCKET, SO_BROADCAST, (char*)&bOpt, sizeof(bOpt));
// 设置发送的地址
SOCKADDR_IN serverAddr;
memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
// 套接字地址为广播地址
serverAddr.sin_addr.S_un.S_addr = htonl(INADDR_BROADCAST); // 等价于serverAddr.sin_addr.S_un.S_addr = inet_addr("255.255.255.255");
serverAddr.sin_port = htons(6789);
char buf[MAX_BUF_LEN];
memset(buf, 0, MAX_BUF_LEN);
for (int i = 0; i < 2; i++) {
sprintf_s(buf, "simplecloud94");
// 向广播地址发送消息
int sign=sendto(serverSocket, buf, strlen(buf),0,(SOCKADDR*)&serverAddr,sizeof(SOCKADDR_IN));
if (sign == SOCKET_ERROR) {
closesocket(serverSocket);
WSACleanup();
return -1;
}
printf("send: %s\n", buf);
Sleep(5000);
}
closesocket(serverSocket);
WSACleanup();
return 0;
if (serverSocket == INVALID_SOCKET) {
closesocket(serverSocket);
WSACleanup();
return -1;
}
return 0;
}
UE4 客户端代码
创建一个c++的ue4 项目,在Build.cs结尾的文件
引入
基于Actor创建一个Actor的子类,命名为RamaUDPReceiver.h(网上所有的udp的代码,几乎都来自官网的一个大佬的代码)
RamaUDPReceiver.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Runtime/Networking/Public/Networking.h"
#include "GameFramework/Actor.h"
#include "RamaUDPReceiver.generated.h"
UCLASS()
class TESTUDP_API ARamaUDPReceiver : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
ARamaUDPReceiver();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
public:
FSocket* ListenSocket;
FUdpSocketReceiver* UDPReceiver = nullptr;
UFUNCTION(BlueprintCallable, Category = "UDP")
void StartUDPReceiver(const FString& YourChosenSocketName, const FString& TheIP, const int32 ThePort, bool& success);
UFUNCTION(BlueprintPure, Category = "UDP")
void DataRecv(FString& str, bool& success);
FORCEINLINE void ScreenMsg(const FString& Msg)
{
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, *Msg);
}
FORCEINLINE void ScreenMsg(const FString& Msg, const float Value)
{
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, FString::Printf(TEXT("%s %f"), *Msg, Value));
}
FORCEINLINE void ScreenMsg(const FString& Msg, const FString& Msg2)
{
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, FString::Printf(TEXT("%s %s"), *Msg, *Msg2));
}
public:
/** Called whenever this actor is being removed from a level */
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
};
RamaUDPReceiver.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "RamaUDPReceiver.h"
// Called when the game starts or when spawned
void ARamaUDPReceiver::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void ARamaUDPReceiver::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
// Sets default values
ARamaUDPReceiver::ARamaUDPReceiver()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
//初始化ListenSocket
ListenSocket = NULL;
}
//结束时出发事件
void ARamaUDPReceiver::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
Super::EndPlay(EndPlayReason);
//UDPReceiver置空
delete UDPReceiver;
UDPReceiver = nullptr;
//Clear all sockets!
if (ListenSocket)
{
ListenSocket->Close();
ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(ListenSocket);
}
}
//初始化Receiver
void ARamaUDPReceiver::StartUDPReceiver(const FString & YourChosenSocketName, const FString & TheIP, const int32 ThePort, bool & success)
{
//TSharedRef<FInternetAddr> targetAddr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();
FIPv4Address Addr;
FIPv4Address::Parse(TheIP, Addr);
//使用指定的NetID和端口创建并初始化新的IPv4端点。
// Any: Defines the wild card endpoint, which is 0.0.0.0:0
FIPv4Endpoint Endpoint(FIPv4Address::Any, ThePort); //所有ip地址本地
//FUdpSocketBuilder: Implements a fluent builder for UDP sockets.
ListenSocket = FUdpSocketBuilder(*YourChosenSocketName)
.AsNonBlocking()//将套接字操作设置为非阻塞。 这个实例(用于方法链)。
.AsReusable()//使绑定的地址可以被其他套接字重用。 这个实例(用于方法链)。
.BoundToEndpoint(Endpoint)//设置将端口绑定到本地端点。 这个实例(用于方法链)。
.WithReceiveBufferSize(2 * 1024 * 1024)//设置接收数据大小
;
//BUFFER SIZE
int32 BufferSize = 2 * 1024 * 1024;
ListenSocket->SetSendBufferSize(BufferSize, BufferSize);
ListenSocket->SetReceiveBufferSize(BufferSize, BufferSize);
if (ListenSocket)
{
ScreenMsg("The receiver is initialized");
success = true;
}
else {
ScreenMsg("No socket");
success = false;
}
//return true;
}
void ARamaUDPReceiver::DataRecv(FString & str, bool & success)
{
if (!ListenSocket)
{
ScreenMsg("No sender socket");
success = false;
}
TSharedRef<FInternetAddr> targetAddr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();
TArray<uint8> ReceivedData;//定义一个接收器
uint32 Size;
//ListenSocket->HasPendingData(Size) 查询套接字以确定队列中是否有挂起的数据,如果套接字有数据,则为true,否则为false Size参数指示单个recv调用的管道上有多少数据
if (ListenSocket->HasPendingData(Size))
{
success = true;
str = "";
uint8 *Recv = new uint8[Size];
int32 BytesRead = 0;
//将数组调整到给定数量的元素。 新元素将被初始化。
ReceivedData.SetNumUninitialized(FMath::Min(Size, 65507u));
ListenSocket->RecvFrom(ReceivedData.GetData(), ReceivedData.Num(), BytesRead, *targetAddr);
char ansiiData[1024];
memcpy(ansiiData, ReceivedData.GetData(), BytesRead);//拷贝数据到接收器
ansiiData[BytesRead] = 0; //判断数据结束
FString debugData = ANSI_TO_TCHAR(ansiiData); //字符串转换
str = debugData;
// memset(ansiiData,0,1024);//清空
}
else
{
success = false;
}
}
在编辑器中创建一个基于RamaUDPReceiver的子类,命名为BP_UDPReceiver