(tools, client, server) feat: Remove gRPC support, add TCP back and reorganized project
This commit is contained in:
@ -584,7 +584,7 @@ GameObject:
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
m_IsActive: 0
|
||||
--- !u!4 &1388451206
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -639,7 +639,7 @@ Transform:
|
||||
m_GameObject: {fileID: 2036983430}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0.17242, y: 0.05575, z: 0}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
@ -677,7 +677,7 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
isGlobal: 1
|
||||
--- !u!1 &2104915506
|
||||
--- !u!1 &2053271181
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
@ -685,37 +685,37 @@ GameObject:
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 2104915508}
|
||||
- component: {fileID: 2104915507}
|
||||
- component: {fileID: 2053271183}
|
||||
- component: {fileID: 2053271182}
|
||||
m_Layer: 0
|
||||
m_Name: GrpcClientTest
|
||||
m_Name: TcpClientTest
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &2104915507
|
||||
--- !u!114 &2053271182
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2104915506}
|
||||
m_GameObject: {fileID: 2053271181}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 98c1b145899d4d97bd981b177b5c45a9, type: 3}
|
||||
m_Script: {fileID: 11500000, guid: e722dcca5e226864b964cd80358a17f9, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
--- !u!4 &2104915508
|
||||
m_EditorClassIdentifier: Assembly-CSharp::TcpClientTest
|
||||
--- !u!4 &2053271183
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2104915506}
|
||||
m_GameObject: {fileID: 2053271181}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: -0}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
@ -727,6 +727,6 @@ SceneRoots:
|
||||
m_Roots:
|
||||
- {fileID: 1057087090}
|
||||
- {fileID: 613797070}
|
||||
- {fileID: 2104915508}
|
||||
- {fileID: 2053271183}
|
||||
- {fileID: 1388451206}
|
||||
- {fileID: 2036983432}
|
||||
|
@ -1,38 +0,0 @@
|
||||
using Grpc.Net.Client;
|
||||
using Grpc.Net.Client.Web;
|
||||
using Protocol;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using Utils;
|
||||
|
||||
namespace Network
|
||||
{
|
||||
public class GrpcClient : Singleton<GrpcClient>
|
||||
{
|
||||
// The address must adopt HTTP.
|
||||
private const string ServerAddress = "http://127.0.0.1:12345";
|
||||
|
||||
private readonly GrpcChannel _channel;
|
||||
private readonly GameService.GameServiceClient _game;
|
||||
|
||||
public GrpcClient()
|
||||
{
|
||||
var channelOptions = new GrpcChannelOptions
|
||||
{
|
||||
HttpHandler = new GrpcWebHandler(GrpcWebMode.GrpcWeb, new HttpClientHandler())
|
||||
};
|
||||
|
||||
_channel = GrpcChannel.ForAddress(ServerAddress, channelOptions);
|
||||
|
||||
_game = new GameService.GameServiceClient(_channel);
|
||||
|
||||
Application.quitting += () => _channel.ShutdownAsync().Wait();
|
||||
}
|
||||
|
||||
public async Task<LoginResponse> Login(string username, string password)
|
||||
{
|
||||
return await _game.LoginAsync(new LoginRequest { Username = username, Password = password });
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bd54dd152e0e4bbda802e9aa04078197
|
||||
timeCreated: 1752480707
|
70
Client/Assets/Scripts/Network/UnityTcpClient.cs
Normal file
70
Client/Assets/Scripts/Network/UnityTcpClient.cs
Normal file
@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using Utils;
|
||||
|
||||
namespace Network
|
||||
{
|
||||
public class UnityTcpClient : Singleton<UnityTcpClient>, IDisposable
|
||||
{
|
||||
private TcpClient _client;
|
||||
private bool _disposed;
|
||||
|
||||
public UnityTcpClient()
|
||||
{
|
||||
try
|
||||
{
|
||||
_client = new TcpClient();
|
||||
_client.Connect("127.0.0.1", 12345);
|
||||
Application.quitting += Dispose;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<byte[]> SendAndReceiveData(byte[] data)
|
||||
{
|
||||
try
|
||||
{
|
||||
await using var stream = _client.GetStream();
|
||||
|
||||
await stream.WriteAsync(data, 0, data.Length);
|
||||
|
||||
var buffer = new byte[1024];
|
||||
await stream.ReadAsync(buffer);
|
||||
return buffer;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
try
|
||||
{
|
||||
_client.Close();
|
||||
_client.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
return;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_client = null;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
2
Client/Assets/Scripts/Network/UnityTcpClient.cs.meta
Normal file
2
Client/Assets/Scripts/Network/UnityTcpClient.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 39f71cfa622a1194b812b7b3cc5b86c1
|
@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
@ -5,24 +6,62 @@ using Utils;
|
||||
|
||||
namespace Network
|
||||
{
|
||||
public class UnityUdpClient : Singleton<UnityUdpClient>
|
||||
public class UnityUdpClient : Singleton<UnityUdpClient>, IDisposable
|
||||
{
|
||||
private readonly UdpClient _client;
|
||||
private UdpClient _client;
|
||||
private bool _disposed;
|
||||
|
||||
public UnityUdpClient()
|
||||
{
|
||||
_client = new UdpClient();
|
||||
_client.Connect("127.0.0.1", 12345);
|
||||
|
||||
Application.quitting += () => _client.Close();
|
||||
try
|
||||
{
|
||||
_client = new UdpClient();
|
||||
_client.Connect("127.0.0.1", 12345);
|
||||
Application.quitting += Dispose;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<byte[]> SendAndReceiveData(byte[] data)
|
||||
{
|
||||
await _client.SendAsync(data, data.Length);
|
||||
try
|
||||
{
|
||||
await _client.SendAsync(data, data.Length);
|
||||
|
||||
var result = await _client.ReceiveAsync();
|
||||
return result.Buffer;
|
||||
var result = await _client.ReceiveAsync();
|
||||
return result.Buffer;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
try
|
||||
{
|
||||
_client.Close();
|
||||
_client.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
return;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_client = null;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,10 +31,7 @@ namespace Protocol {
|
||||
"ZRgBIAEoCRIQCghwYXNzd29yZBgCIAEoCSJKCg5TaWdudXBSZXNwb25zZRIn",
|
||||
"CgZyZXN1bHQYASABKA4yFy5wcm90b2NvbC5SZXF1ZXN0UmVzdWx0Eg8KB21l",
|
||||
"c3NhZ2UYAiABKAkqJgoNUmVxdWVzdFJlc3VsdBILCgdTdWNjZXNzEAASCAoE",
|
||||
"RmFpbBABMoQBCgtHYW1lU2VydmljZRI4CgVMb2dpbhIWLnByb3RvY29sLkxv",
|
||||
"Z2luUmVxdWVzdBoXLnByb3RvY29sLkxvZ2luUmVzcG9uc2USOwoGU2lnbnVw",
|
||||
"EhcucHJvdG9jb2wuU2lnbnVwUmVxdWVzdBoYLnByb3RvY29sLlNpZ251cFJl",
|
||||
"c3BvbnNlYgZwcm90bzM="));
|
||||
"RmFpbBABYgZwcm90bzM="));
|
||||
descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
|
||||
new pbr::FileDescriptor[] { },
|
||||
new pbr::GeneratedClrTypeInfo(new[] {typeof(global::Protocol.RequestResult), }, null, new pbr::GeneratedClrTypeInfo[] {
|
||||
|
@ -1,195 +0,0 @@
|
||||
// <auto-generated>
|
||||
// Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
// source: message.proto
|
||||
// </auto-generated>
|
||||
#pragma warning disable 0414, 1591, 8981, 0612
|
||||
#region Designer generated code
|
||||
|
||||
using grpc = global::Grpc.Core;
|
||||
|
||||
namespace Protocol {
|
||||
public static partial class GameService
|
||||
{
|
||||
static readonly string __ServiceName = "protocol.GameService";
|
||||
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
static void __Helper_SerializeMessage(global::Google.Protobuf.IMessage message, grpc::SerializationContext context)
|
||||
{
|
||||
#if !GRPC_DISABLE_PROTOBUF_BUFFER_SERIALIZATION
|
||||
if (message is global::Google.Protobuf.IBufferMessage)
|
||||
{
|
||||
context.SetPayloadLength(message.CalculateSize());
|
||||
global::Google.Protobuf.MessageExtensions.WriteTo(message, context.GetBufferWriter());
|
||||
context.Complete();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
context.Complete(global::Google.Protobuf.MessageExtensions.ToByteArray(message));
|
||||
}
|
||||
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
static class __Helper_MessageCache<T>
|
||||
{
|
||||
public static readonly bool IsBufferMessage = global::System.Reflection.IntrospectionExtensions.GetTypeInfo(typeof(global::Google.Protobuf.IBufferMessage)).IsAssignableFrom(typeof(T));
|
||||
}
|
||||
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
static T __Helper_DeserializeMessage<T>(grpc::DeserializationContext context, global::Google.Protobuf.MessageParser<T> parser) where T : global::Google.Protobuf.IMessage<T>
|
||||
{
|
||||
#if !GRPC_DISABLE_PROTOBUF_BUFFER_SERIALIZATION
|
||||
if (__Helper_MessageCache<T>.IsBufferMessage)
|
||||
{
|
||||
return parser.ParseFrom(context.PayloadAsReadOnlySequence());
|
||||
}
|
||||
#endif
|
||||
return parser.ParseFrom(context.PayloadAsNewBuffer());
|
||||
}
|
||||
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
static readonly grpc::Marshaller<global::Protocol.LoginRequest> __Marshaller_protocol_LoginRequest = grpc::Marshallers.Create(__Helper_SerializeMessage, context => __Helper_DeserializeMessage(context, global::Protocol.LoginRequest.Parser));
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
static readonly grpc::Marshaller<global::Protocol.LoginResponse> __Marshaller_protocol_LoginResponse = grpc::Marshallers.Create(__Helper_SerializeMessage, context => __Helper_DeserializeMessage(context, global::Protocol.LoginResponse.Parser));
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
static readonly grpc::Marshaller<global::Protocol.SignupRequest> __Marshaller_protocol_SignupRequest = grpc::Marshallers.Create(__Helper_SerializeMessage, context => __Helper_DeserializeMessage(context, global::Protocol.SignupRequest.Parser));
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
static readonly grpc::Marshaller<global::Protocol.SignupResponse> __Marshaller_protocol_SignupResponse = grpc::Marshallers.Create(__Helper_SerializeMessage, context => __Helper_DeserializeMessage(context, global::Protocol.SignupResponse.Parser));
|
||||
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
static readonly grpc::Method<global::Protocol.LoginRequest, global::Protocol.LoginResponse> __Method_Login = new grpc::Method<global::Protocol.LoginRequest, global::Protocol.LoginResponse>(
|
||||
grpc::MethodType.Unary,
|
||||
__ServiceName,
|
||||
"Login",
|
||||
__Marshaller_protocol_LoginRequest,
|
||||
__Marshaller_protocol_LoginResponse);
|
||||
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
static readonly grpc::Method<global::Protocol.SignupRequest, global::Protocol.SignupResponse> __Method_Signup = new grpc::Method<global::Protocol.SignupRequest, global::Protocol.SignupResponse>(
|
||||
grpc::MethodType.Unary,
|
||||
__ServiceName,
|
||||
"Signup",
|
||||
__Marshaller_protocol_SignupRequest,
|
||||
__Marshaller_protocol_SignupResponse);
|
||||
|
||||
/// <summary>Service descriptor</summary>
|
||||
public static global::Google.Protobuf.Reflection.ServiceDescriptor Descriptor
|
||||
{
|
||||
get { return global::Protocol.MessageReflection.Descriptor.Services[0]; }
|
||||
}
|
||||
|
||||
/// <summary>Base class for server-side implementations of GameService</summary>
|
||||
[grpc::BindServiceMethod(typeof(GameService), "BindService")]
|
||||
public abstract partial class GameServiceBase
|
||||
{
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
public virtual global::System.Threading.Tasks.Task<global::Protocol.LoginResponse> Login(global::Protocol.LoginRequest request, grpc::ServerCallContext context)
|
||||
{
|
||||
throw new grpc::RpcException(new grpc::Status(grpc::StatusCode.Unimplemented, ""));
|
||||
}
|
||||
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
public virtual global::System.Threading.Tasks.Task<global::Protocol.SignupResponse> Signup(global::Protocol.SignupRequest request, grpc::ServerCallContext context)
|
||||
{
|
||||
throw new grpc::RpcException(new grpc::Status(grpc::StatusCode.Unimplemented, ""));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>Client for GameService</summary>
|
||||
public partial class GameServiceClient : grpc::ClientBase<GameServiceClient>
|
||||
{
|
||||
/// <summary>Creates a new client for GameService</summary>
|
||||
/// <param name="channel">The channel to use to make remote calls.</param>
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
public GameServiceClient(grpc::ChannelBase channel) : base(channel)
|
||||
{
|
||||
}
|
||||
/// <summary>Creates a new client for GameService that uses a custom <c>CallInvoker</c>.</summary>
|
||||
/// <param name="callInvoker">The callInvoker to use to make remote calls.</param>
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
public GameServiceClient(grpc::CallInvoker callInvoker) : base(callInvoker)
|
||||
{
|
||||
}
|
||||
/// <summary>Protected parameterless constructor to allow creation of test doubles.</summary>
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
protected GameServiceClient() : base()
|
||||
{
|
||||
}
|
||||
/// <summary>Protected constructor to allow creation of configured clients.</summary>
|
||||
/// <param name="configuration">The client configuration.</param>
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
protected GameServiceClient(ClientBaseConfiguration configuration) : base(configuration)
|
||||
{
|
||||
}
|
||||
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
public virtual global::Protocol.LoginResponse Login(global::Protocol.LoginRequest request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken))
|
||||
{
|
||||
return Login(request, new grpc::CallOptions(headers, deadline, cancellationToken));
|
||||
}
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
public virtual global::Protocol.LoginResponse Login(global::Protocol.LoginRequest request, grpc::CallOptions options)
|
||||
{
|
||||
return CallInvoker.BlockingUnaryCall(__Method_Login, null, options, request);
|
||||
}
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
public virtual grpc::AsyncUnaryCall<global::Protocol.LoginResponse> LoginAsync(global::Protocol.LoginRequest request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken))
|
||||
{
|
||||
return LoginAsync(request, new grpc::CallOptions(headers, deadline, cancellationToken));
|
||||
}
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
public virtual grpc::AsyncUnaryCall<global::Protocol.LoginResponse> LoginAsync(global::Protocol.LoginRequest request, grpc::CallOptions options)
|
||||
{
|
||||
return CallInvoker.AsyncUnaryCall(__Method_Login, null, options, request);
|
||||
}
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
public virtual global::Protocol.SignupResponse Signup(global::Protocol.SignupRequest request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken))
|
||||
{
|
||||
return Signup(request, new grpc::CallOptions(headers, deadline, cancellationToken));
|
||||
}
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
public virtual global::Protocol.SignupResponse Signup(global::Protocol.SignupRequest request, grpc::CallOptions options)
|
||||
{
|
||||
return CallInvoker.BlockingUnaryCall(__Method_Signup, null, options, request);
|
||||
}
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
public virtual grpc::AsyncUnaryCall<global::Protocol.SignupResponse> SignupAsync(global::Protocol.SignupRequest request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken))
|
||||
{
|
||||
return SignupAsync(request, new grpc::CallOptions(headers, deadline, cancellationToken));
|
||||
}
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
public virtual grpc::AsyncUnaryCall<global::Protocol.SignupResponse> SignupAsync(global::Protocol.SignupRequest request, grpc::CallOptions options)
|
||||
{
|
||||
return CallInvoker.AsyncUnaryCall(__Method_Signup, null, options, request);
|
||||
}
|
||||
/// <summary>Creates a new instance of client from given <c>ClientBaseConfiguration</c>.</summary>
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
protected override GameServiceClient NewInstance(ClientBaseConfiguration configuration)
|
||||
{
|
||||
return new GameServiceClient(configuration);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Creates service definition that can be registered with a server</summary>
|
||||
/// <param name="serviceImpl">An object implementing the server-side handling logic.</param>
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
public static grpc::ServerServiceDefinition BindService(GameServiceBase serviceImpl)
|
||||
{
|
||||
return grpc::ServerServiceDefinition.CreateBuilder()
|
||||
.AddMethod(__Method_Login, serviceImpl.Login)
|
||||
.AddMethod(__Method_Signup, serviceImpl.Signup).Build();
|
||||
}
|
||||
|
||||
/// <summary>Register service method with a service binder with or without implementation. Useful when customizing the service binding logic.
|
||||
/// Note: this method is part of an experimental API that can change or be removed without any prior notice.</summary>
|
||||
/// <param name="serviceBinder">Service methods will be bound by calling <c>AddMethod</c> on this object.</param>
|
||||
/// <param name="serviceImpl">An object implementing the server-side handling logic.</param>
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
public static void BindService(grpc::ServiceBinderBase serviceBinder, GameServiceBase serviceImpl)
|
||||
{
|
||||
serviceBinder.AddMethod(__Method_Login, serviceImpl == null ? null : new grpc::UnaryServerMethod<global::Protocol.LoginRequest, global::Protocol.LoginResponse>(serviceImpl.Login));
|
||||
serviceBinder.AddMethod(__Method_Signup, serviceImpl == null ? null : new grpc::UnaryServerMethod<global::Protocol.SignupRequest, global::Protocol.SignupResponse>(serviceImpl.Signup));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
#endregion
|
@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f2f787a1d94a6ae48b5586f040f0cf1b
|
@ -1,14 +0,0 @@
|
||||
using Network;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class GrpcClientTest : MonoBehaviour
|
||||
{
|
||||
private async void Start()
|
||||
{
|
||||
var loginResult = await GrpcClient.Instance.Login("野兽先辈", "114514");
|
||||
Debug.Log($"Received login result: {loginResult.Result}");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 98c1b145899d4d97bd981b177b5c45a9
|
||||
timeCreated: 1752478138
|
19
Client/Assets/Scripts/Test/TcpClientTest.cs
Normal file
19
Client/Assets/Scripts/Test/TcpClientTest.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using Network;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class TcpClientTest : MonoBehaviour
|
||||
{
|
||||
private async void Start()
|
||||
{
|
||||
var sendBytes = Encoding.UTF8.GetBytes("This is a test string sent via TCP.");
|
||||
|
||||
var receivedBytes = await UnityTcpClient.Instance.SendAndReceiveData(sendBytes);
|
||||
var receivedString = Encoding.UTF8.GetString(receivedBytes);
|
||||
|
||||
Debug.Log($"Received string: {receivedString}");
|
||||
}
|
||||
}
|
||||
}
|
2
Client/Assets/Scripts/Test/TcpClientTest.cs.meta
Normal file
2
Client/Assets/Scripts/Test/TcpClientTest.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e722dcca5e226864b964cd80358a17f9
|
@ -8,7 +8,7 @@ namespace Test
|
||||
{
|
||||
private async void Start()
|
||||
{
|
||||
var sendBytes = Encoding.UTF8.GetBytes("Test 汉语 and English simultaneously!");
|
||||
var sendBytes = Encoding.UTF8.GetBytes("This is a test string sent via UDP.");
|
||||
|
||||
var receivedBytes = await UnityUdpClient.Instance.SendAndReceiveData(sendBytes);
|
||||
var receivedString = Encoding.UTF8.GetString(receivedBytes);
|
||||
|
89
Server/Cargo.lock
generated
89
Server/Cargo.lock
generated
@ -710,8 +710,6 @@ dependencies = [
|
||||
"prettyplease",
|
||||
"prost",
|
||||
"prost-types",
|
||||
"pulldown-cmark",
|
||||
"pulldown-cmark-to-cmark",
|
||||
"regex",
|
||||
"syn",
|
||||
"tempfile",
|
||||
@ -739,26 +737,6 @@ dependencies = [
|
||||
"prost",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pulldown-cmark"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e8bbe1a966bd2f362681a44f6edce3c2310ac21e4d5067a6e7ec396297a6ea0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"memchr",
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pulldown-cmark-to-cmark"
|
||||
version = "21.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5b6a0769a491a08b31ea5c62494a8f144ee0987d86d670a8af4df1e1b7cde75"
|
||||
dependencies = [
|
||||
"pulldown-cmark",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
@ -871,11 +849,9 @@ dependencies = [
|
||||
"colored",
|
||||
"log",
|
||||
"prost",
|
||||
"prost-build",
|
||||
"tokio",
|
||||
"tonic",
|
||||
"tonic-prost",
|
||||
"tonic-prost-build",
|
||||
"tonic-web",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1029,63 +1005,6 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tonic-build"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49e323d8bba3be30833707e36d046deabf10a35ae8ad3cae576943ea8933e25d"
|
||||
dependencies = [
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tonic-prost"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9c511b9a96d40cb12b7d5d00464446acf3b9105fd3ce25437cfe41c92b1c87d"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"prost",
|
||||
"tonic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tonic-prost-build"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ef298fcd01b15e135440c4b8c974460ceca4e6a5af7f1c933b08e4d2875efa1"
|
||||
dependencies = [
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"prost-build",
|
||||
"prost-types",
|
||||
"quote",
|
||||
"syn",
|
||||
"tempfile",
|
||||
"tonic-build",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tonic-web"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd70b30990a5e47d404c5b2223c9cc194603ab400d2ee4248099533181e7b747"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bytes",
|
||||
"http",
|
||||
"http-body",
|
||||
"pin-project",
|
||||
"tokio-stream",
|
||||
"tonic",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.5.2"
|
||||
@ -1154,12 +1073,6 @@ version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
|
@ -12,8 +12,6 @@ log = "0.4"
|
||||
prost = "0.14"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tonic = "0.14"
|
||||
tonic-prost = "0.14"
|
||||
tonic-web = "0.14"
|
||||
|
||||
[build-dependencies]
|
||||
tonic-prost-build = "0.14"
|
||||
prost-build = "0.14"
|
||||
|
@ -5,6 +5,11 @@ fn main() -> io::Result<()> {
|
||||
unsafe {
|
||||
env::set_var("PROTOC", "../Tools/ProtoBuf/bin/protoc.exe");
|
||||
}
|
||||
tonic_prost_build::compile_protos("../Tools/ProtoBuf/proto/message.proto")?;
|
||||
|
||||
prost_build::compile_protos(
|
||||
&["../Tools/ProtoBuf/proto/message.proto"],
|
||||
&["../Tools/ProtoBuf/proto"],
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
use std::io::Write;
|
||||
|
||||
pub(crate) fn run() {
|
||||
use crate::servers::tcp_server::TCP_SERVER;
|
||||
use crate::servers::udp_server::UDP_SERVER;
|
||||
|
||||
pub(crate) async fn run() {
|
||||
let stdin = std::io::stdin();
|
||||
|
||||
loop {
|
||||
@ -14,7 +17,12 @@ pub(crate) fn run() {
|
||||
.expect("Failed to read from standard input!");
|
||||
|
||||
match input.trim() {
|
||||
"exit" => break,
|
||||
"exit" => {
|
||||
TCP_SERVER.lock().await.stop().await;
|
||||
UDP_SERVER.lock().await.stop().await;
|
||||
|
||||
break;
|
||||
}
|
||||
_ => println!("Usage: <command>"),
|
||||
}
|
||||
}
|
||||
|
@ -1,28 +0,0 @@
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use tokio::task;
|
||||
use tonic::transport::Server;
|
||||
use tonic_web::GrpcWebLayer;
|
||||
|
||||
use crate::protocol::game_service_server::GameServiceServer;
|
||||
use crate::services::game_service::GameServiceImpl;
|
||||
|
||||
pub(crate) struct GrpcServer;
|
||||
|
||||
impl GrpcServer {
|
||||
pub(crate) async fn init() {
|
||||
let addr = SocketAddr::new([127, 0, 0, 1].into(), 12345);
|
||||
|
||||
let game_service = GameServiceServer::new(GameServiceImpl);
|
||||
|
||||
task::spawn(async move {
|
||||
Server::builder()
|
||||
.accept_http1(true)
|
||||
.layer(GrpcWebLayer::new())
|
||||
.add_service(game_service)
|
||||
.serve(addr)
|
||||
.await
|
||||
.unwrap_or_else(|e| log::error!("Failed to build server: {e}"));
|
||||
});
|
||||
}
|
||||
}
|
@ -1,13 +1,12 @@
|
||||
mod command_helper;
|
||||
mod grpc_server;
|
||||
mod protocol;
|
||||
mod server_logger;
|
||||
mod servers;
|
||||
mod services;
|
||||
mod udp_server;
|
||||
|
||||
use grpc_server::GrpcServer;
|
||||
use server_logger::ServerLogger;
|
||||
use udp_server::UdpServer;
|
||||
use servers::tcp_server::TCP_SERVER;
|
||||
use servers::udp_server::UDP_SERVER;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
@ -15,10 +14,10 @@ async fn main() {
|
||||
|
||||
log::info!("Starting server...");
|
||||
|
||||
GrpcServer::init().await;
|
||||
UdpServer::init();
|
||||
TCP_SERVER.lock().await.start().await;
|
||||
UDP_SERVER.lock().await.start().await;
|
||||
|
||||
log::info!("Server successfully started!");
|
||||
|
||||
command_helper::run();
|
||||
command_helper::run().await;
|
||||
}
|
||||
|
4
Server/src/servers.rs
Normal file
4
Server/src/servers.rs
Normal file
@ -0,0 +1,4 @@
|
||||
pub(crate) mod tcp_server;
|
||||
pub(crate) mod udp_server;
|
||||
|
||||
const SERVER_ADDR: &str = "127.0.0.1:12345";
|
120
Server/src/servers/tcp_server.rs
Normal file
120
Server/src/servers/tcp_server.rs
Normal file
@ -0,0 +1,120 @@
|
||||
use std::collections::HashMap;
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio::sync::{Mutex, mpsc};
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
use super::SERVER_ADDR;
|
||||
|
||||
pub(crate) static TCP_SERVER: LazyLock<Mutex<TcpServer>> =
|
||||
LazyLock::new(|| Mutex::new(TcpServer::new()));
|
||||
|
||||
pub(crate) struct TcpServer {
|
||||
is_running: bool,
|
||||
clients: HashMap<SocketAddr, JoinHandle<()>>,
|
||||
shutdown_tx: Option<mpsc::Sender<()>>,
|
||||
}
|
||||
|
||||
impl TcpServer {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
is_running: false,
|
||||
clients: HashMap::new(),
|
||||
shutdown_tx: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn start(&mut self) {
|
||||
if self.is_running {
|
||||
log::warn!("TCP server is already running");
|
||||
return;
|
||||
}
|
||||
|
||||
match TcpListener::bind(SERVER_ADDR).await {
|
||||
Ok(listener) => {
|
||||
let (shutdown_tx, shutdown_rx) = mpsc::channel(1);
|
||||
self.is_running = true;
|
||||
self.shutdown_tx = Some(shutdown_tx);
|
||||
|
||||
tokio::spawn(async move {
|
||||
Self::listen_to_clients(listener, shutdown_rx).await;
|
||||
});
|
||||
|
||||
log::info!("TCP Server started on {}", SERVER_ADDR);
|
||||
}
|
||||
Err(e) => log::error!("Failed to bind to address: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn stop(&mut self) {
|
||||
if !self.is_running {
|
||||
return;
|
||||
}
|
||||
|
||||
self.is_running = false;
|
||||
|
||||
if let Some(shutdown_tx) = self.shutdown_tx.take() {
|
||||
_ = shutdown_tx.send(()).await;
|
||||
}
|
||||
|
||||
for (addr, connection) in self.clients.drain() {
|
||||
log::info!("Closing connection to {}", addr);
|
||||
connection.abort();
|
||||
}
|
||||
}
|
||||
|
||||
async fn listen_to_clients(listener: TcpListener, mut shutdown_rx: mpsc::Receiver<()>) {
|
||||
loop {
|
||||
tokio::select! {
|
||||
result = listener.accept() => {
|
||||
match result {
|
||||
Ok((socket, addr)) => {
|
||||
log::info!("New client connected: {addr}");
|
||||
|
||||
let task_handle = tokio::spawn(async move {
|
||||
if let Err(e) = Self::handle_client(socket, addr).await {
|
||||
log::error!("Client {addr} error: {e}");
|
||||
}
|
||||
log::info!("Client {addr} disconnected");
|
||||
});
|
||||
|
||||
let mut server = TCP_SERVER.lock().await;
|
||||
server.clients.insert(addr, task_handle);
|
||||
}
|
||||
Err(e) => log::error!("Couldn't get client: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
_ = shutdown_rx.recv() => {
|
||||
log::info!("TCP Server shutting down");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_client(mut socket: TcpStream, addr: SocketAddr) -> io::Result<()> {
|
||||
let mut buffer = [0; 1024];
|
||||
|
||||
loop {
|
||||
let len = socket.read(&mut buffer).await?;
|
||||
if len == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
log::debug!("Received {} bytes from {}", len, addr);
|
||||
|
||||
// TODO: Deserialize data
|
||||
socket.write_all(&buffer[..len]).await?;
|
||||
}
|
||||
|
||||
let mut server = TCP_SERVER.lock().await;
|
||||
server.clients.remove(&addr);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
68
Server/src/servers/udp_server.rs
Normal file
68
Server/src/servers/udp_server.rs
Normal file
@ -0,0 +1,68 @@
|
||||
use std::io;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use tokio::net::UdpSocket;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use super::SERVER_ADDR;
|
||||
|
||||
pub(crate) static UDP_SERVER: LazyLock<Mutex<UdpServer>> =
|
||||
LazyLock::new(|| Mutex::new(UdpServer::new()));
|
||||
|
||||
pub(crate) struct UdpServer {
|
||||
is_running: bool,
|
||||
}
|
||||
|
||||
impl UdpServer {
|
||||
fn new() -> Self {
|
||||
Self { is_running: false }
|
||||
}
|
||||
|
||||
pub(crate) async fn start(&mut self) {
|
||||
if self.is_running {
|
||||
log::warn!("UDP server is already running");
|
||||
return;
|
||||
}
|
||||
|
||||
match UdpSocket::bind(SERVER_ADDR).await {
|
||||
Ok(socket) => {
|
||||
self.is_running = true;
|
||||
|
||||
tokio::spawn(async move {
|
||||
Self::handle_client(&socket)
|
||||
.await
|
||||
.unwrap_or_else(|e| log::error!("Failed to process data: {e}"))
|
||||
});
|
||||
|
||||
log::info!("UDP Server started on {}", SERVER_ADDR);
|
||||
}
|
||||
Err(e) => log::error!("Failed to bind to address: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn stop(&mut self) {
|
||||
if !self.is_running {
|
||||
return;
|
||||
}
|
||||
|
||||
self.is_running = false;
|
||||
}
|
||||
|
||||
async fn handle_client(socket: &UdpSocket) -> io::Result<()> {
|
||||
loop {
|
||||
let mut buffer = [0; 1024];
|
||||
let (len, addr) = socket.recv_from(&mut buffer).await?;
|
||||
if len == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
log::info!("Received message from client {addr}");
|
||||
|
||||
// TODO: Deserialize data
|
||||
let buffer = &buffer[..len];
|
||||
socket.send_to(buffer, addr).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -1,33 +1 @@
|
||||
use tonic::{Request, Response, Status};
|
||||
|
||||
use crate::protocol::game_service_server::GameService;
|
||||
use crate::protocol::{LoginRequest, LoginResponse, RequestResult, SignupRequest, SignupResponse};
|
||||
|
||||
pub(crate) struct GameServiceImpl;
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl GameService for GameServiceImpl {
|
||||
async fn login(
|
||||
&self,
|
||||
request: Request<LoginRequest>,
|
||||
) -> Result<Response<LoginResponse>, Status> {
|
||||
log::info!("User {} logged in!", request.get_ref().username);
|
||||
|
||||
Ok(Response::new(LoginResponse {
|
||||
result: RequestResult::Success.into(),
|
||||
message: "".into(),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn signup(
|
||||
&self,
|
||||
request: Request<SignupRequest>,
|
||||
) -> Result<Response<SignupResponse>, Status> {
|
||||
log::info!("User {} signed up!", request.get_ref().username);
|
||||
|
||||
Ok(Response::new(SignupResponse {
|
||||
result: RequestResult::Success.into(),
|
||||
message: "".into(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
@ -1,27 +0,0 @@
|
||||
use std::net::UdpSocket;
|
||||
|
||||
use tokio::task;
|
||||
|
||||
pub(crate) struct UdpServer;
|
||||
|
||||
impl UdpServer {
|
||||
pub(crate) fn init() {
|
||||
match UdpSocket::bind("127.0.0.1:12345") {
|
||||
Ok(socket) => {
|
||||
task::spawn(async move {
|
||||
loop {
|
||||
let mut buf = [0; 1500];
|
||||
let (amt, src) = socket.recv_from(&mut buf).unwrap();
|
||||
|
||||
log::info!("Received message from client {src}");
|
||||
|
||||
// TODO: Process received data in an independent method.
|
||||
let buf = &buf[..amt];
|
||||
socket.send_to(buf, src).unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
Err(e) => log::error!("Failed to bind to address: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
@ -1,35 +1,30 @@
|
||||
from subprocess import Popen
|
||||
from os import path
|
||||
import subprocess
|
||||
import os
|
||||
import shutil
|
||||
|
||||
|
||||
protoc_dir = "./bin/protoc.exe"
|
||||
grpc_cs_plugin_dir = "./bin/grpc_csharp_plugin.exe"
|
||||
|
||||
proto_dir = "./proto"
|
||||
message_dir = proto_dir + "/message.proto"
|
||||
message_dir = f"{proto_dir}/message.proto"
|
||||
|
||||
gen_dir = "./gen"
|
||||
gen_code_dst_dir = "../../Client/Assets/Scripts/Protocol"
|
||||
|
||||
|
||||
def GenerateProtocol():
|
||||
if not path.exists(gen_dir):
|
||||
if not os.path.exists(gen_dir):
|
||||
os.makedirs(gen_dir)
|
||||
|
||||
result = Popen(
|
||||
gen_code = subprocess.Popen(
|
||||
[
|
||||
protoc_dir,
|
||||
f"--proto_path={proto_dir}",
|
||||
f"--csharp_out={gen_dir}",
|
||||
f"--grpc_out={gen_dir}",
|
||||
f"--plugin=protoc-gen-grpc={grpc_cs_plugin_dir}",
|
||||
message_dir,
|
||||
]
|
||||
)
|
||||
|
||||
result.wait()
|
||||
gen_code.wait()
|
||||
|
||||
|
||||
def MoveGeneratedCode():
|
||||
|
@ -2,15 +2,6 @@ syntax = "proto3";
|
||||
|
||||
package protocol;
|
||||
|
||||
// Define services
|
||||
|
||||
service GameService {
|
||||
rpc Login(LoginRequest) returns (LoginResponse);
|
||||
rpc Signup(SignupRequest) returns (SignupResponse);
|
||||
}
|
||||
|
||||
// Define messages
|
||||
|
||||
enum RequestResult {
|
||||
Success = 0;
|
||||
Fail = 1;
|
||||
|
Reference in New Issue
Block a user