diff --git a/Tutorials/PvPGameServer/FastBinaryRead.cs b/Tutorials/PvPGameServer/FastBinaryRead.cs new file mode 100644 index 0000000..8c04f88 --- /dev/null +++ b/Tutorials/PvPGameServer/FastBinaryRead.cs @@ -0,0 +1,322 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace PvPGameServer +{ + public class FastBinaryRead + { + public static bool Boolean(byte[] bytes, int offset) + { + return (bytes[offset] == 0) ? false : true; + } + public static bool Boolean(ReadOnlySpan bytes, int offset) + { + return (bytes[offset] == 0) ? false : true; + } + + public static byte Byte(byte[] bytes, int offset) + { + return bytes[offset]; + } + public static byte Byte(ReadOnlySpan bytes, int offset) + { + return bytes[offset]; + } + + public static byte[] Bytes(byte[] bytes, int offset, int count) + { + var dest = new byte[count]; + Buffer.BlockCopy(bytes, offset, dest, 0, count); + return dest; + } + + public static sbyte SByte(byte[] bytes, int offset) + { + return (sbyte)bytes[offset]; + } + public static sbyte SByte(ReadOnlySpan bytes, int offset) + { + return (sbyte)bytes[offset]; + } + + public static unsafe float Single(byte[] bytes, int offset) + { + if (offset % 4 == 0) + { + fixed (byte* ptr = bytes) + { + return *(float*)(ptr + offset); + } + } + else + { + uint num = (uint)((int)bytes[offset] | (int)bytes[offset + 1] << 8 | (int)bytes[offset + 2] << 16 | (int)bytes[offset + 3] << 24); + return *(float*)(&num); + } + } + public static unsafe float Single(ReadOnlySpan bytes, int offset) + { + if (offset % 4 == 0) + { + fixed (byte* ptr = bytes) + { + return *(float*)(ptr + offset); + } + } + else + { + uint num = (uint)((int)bytes[offset] | (int)bytes[offset + 1] << 8 | (int)bytes[offset + 2] << 16 | (int)bytes[offset + 3] << 24); + return *(float*)(&num); + } + } + + public static unsafe double Double(byte[] bytes, int offset) + { + if (offset % 8 == 0) + { + fixed (byte* ptr = bytes) + { + return *(double*)(ptr + offset); + } + } + else + { + uint num = (uint)((int)bytes[offset] | (int)bytes[offset + 1] << 8 | (int)bytes[offset + 2] << 16 | (int)bytes[offset + 3] << 24); + ulong num2 = (ulong)((int)bytes[offset + 4] | (int)bytes[offset + 5] << 8 | (int)bytes[offset + 6] << 16 | (int)bytes[offset + 7] << 24) << 32 | (ulong)num; + return *(double*)(&num2); + } + } + public static unsafe double Double(ReadOnlySpan bytes, int offset) + { + if (offset % 8 == 0) + { + fixed (byte* ptr = bytes) + { + return *(double*)(ptr + offset); + } + } + else + { + uint num = (uint)((int)bytes[offset] | (int)bytes[offset + 1] << 8 | (int)bytes[offset + 2] << 16 | (int)bytes[offset + 3] << 24); + ulong num2 = (ulong)((int)bytes[offset + 4] | (int)bytes[offset + 5] << 8 | (int)bytes[offset + 6] << 16 | (int)bytes[offset + 7] << 24) << 32 | (ulong)num; + return *(double*)(&num2); + } + } + + public static unsafe short Int16(byte[] bytes, int offset) + { + fixed (byte* ptr = bytes) + { + return *(short*)(ptr + offset); + } + } + public static unsafe short Int16(ReadOnlySpan bytes, int offset) + { + fixed (byte* ptr = bytes) + { + return *(short*)(ptr + offset); + } + } + + public static unsafe int Int32(byte[] bytes, int offset) + { + fixed (byte* ptr = bytes) + { + return *(int*)(ptr + offset); + } + } + public static unsafe int Int32(ReadOnlySpan bytes, int offset) + { + fixed (byte* ptr = bytes) + { + return *(int*)(ptr + offset); + } + } + + public static unsafe long Int64(byte[] bytes, int offset) + { + fixed (byte* ptr = bytes) + { + return *(long*)(ptr + offset); + } + } + public static unsafe long Int64(ReadOnlySpan bytes, int offset) + { + fixed (byte* ptr = bytes) + { + return *(long*)(ptr + offset); + } + } + + public static unsafe ushort UInt16(byte[] bytes, int offset) + { + fixed (byte* ptr = bytes) + { + return *(ushort*)(ptr + offset); + } + } + public static unsafe ushort UInt16(ReadOnlySpan bytes, int offset) + { + fixed (byte* ptr = bytes) + { + return *(ushort*)(ptr + offset); + } + } + + public static unsafe uint UInt32(byte[] bytes, int offset) + { + fixed (byte* ptr = bytes) + { + return *(uint*)(ptr + offset); + } + } + public static unsafe uint UInt32(ReadOnlySpan bytes, int offset) + { + fixed (byte* ptr = bytes) + { + return *(uint*)(ptr + offset); + } + } + + public static unsafe ulong UInt64(byte[] bytes, int offset) + { + fixed (byte* ptr = bytes) + { + return *(ulong*)(ptr + offset); + } + } + public static unsafe ulong UInt64(ReadOnlySpan bytes, int offset) + { + fixed (byte* ptr = bytes) + { + return *(ulong*)(ptr + offset); + } + } + + public static char Char(byte[] bytes, int offset) + { + return (char)FastBinaryRead.UInt16(bytes, offset); + } + public static char Char(ReadOnlySpan bytes, int offset) + { + return (char)FastBinaryRead.UInt16(bytes, offset); + } + + public static string String(byte[] bytes, int offset, int count) + { + return StringEncoding.UTF8.GetString(bytes, offset, count); + } + + public static unsafe decimal Decimal(byte[] bytes, int offset) + { + fixed (byte* ptr = bytes) + { + return *(Decimal*)(ptr + offset); + } + } + public static unsafe decimal Decimal(ReadOnlySpan bytes, int offset) + { + fixed (byte* ptr = bytes) + { + return *(Decimal*)(ptr + offset); + } + } + + public static unsafe Guid Guid(byte[] bytes, int offset) + { + fixed (byte* ptr = bytes) + { + return *(Guid*)(ptr + offset); + } + } + public static unsafe Guid Guid(ReadOnlySpan bytes, int offset) + { + fixed (byte* ptr = bytes) + { + return *(Guid*)(ptr + offset); + } + } + + #region Timestamp/Duration + public static unsafe TimeSpan TimeSpan(ref byte[] bytes, int offset) + { + checked + { + fixed (byte* ptr = bytes) + { + var seconds = *(long*)(ptr + offset); + var nanos = *(int*)(ptr + offset + 8); + + if (!Duration.IsNormalized(seconds, nanos)) + { + throw new InvalidOperationException("Duration was not a valid normalized duration"); + } + long ticks = seconds * System.TimeSpan.TicksPerSecond + nanos / Duration.NanosecondsPerTick; + return System.TimeSpan.FromTicks(ticks); + } + } + } + + public static unsafe DateTime DateTime(ref byte[] bytes, int offset) + { + fixed (byte* ptr = bytes) + { + var seconds = *(long*)(ptr + offset); + var nanos = *(int*)(ptr + offset + 8); + + if (!Timestamp.IsNormalized(seconds, nanos)) + { + throw new InvalidOperationException(string.Format(@"Timestamp contains invalid values: Seconds={0}; Nanos={1}", seconds, nanos)); + } + return Timestamp.UnixEpoch.AddSeconds(seconds).AddTicks(nanos / Duration.NanosecondsPerTick); + } + } + + internal static class Timestamp + { + internal static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + internal const long BclSecondsAtUnixEpoch = 62135596800; + internal const long UnixSecondsAtBclMaxValue = 253402300799; + internal const long UnixSecondsAtBclMinValue = -BclSecondsAtUnixEpoch; + internal const int MaxNanos = Duration.NanosecondsPerSecond - 1; + + internal static bool IsNormalized(long seconds, int nanoseconds) + { + return nanoseconds >= 0 && + nanoseconds <= MaxNanos && + seconds >= UnixSecondsAtBclMinValue && + seconds <= UnixSecondsAtBclMaxValue; + } + } + + internal static class Duration + { + public const int NanosecondsPerSecond = 1000000000; + public const int NanosecondsPerTick = 100; + public const long MaxSeconds = 315576000000L; + public const long MinSeconds = -315576000000L; + internal const int MaxNanoseconds = NanosecondsPerSecond - 1; + internal const int MinNanoseconds = -NanosecondsPerSecond + 1; + + internal static bool IsNormalized(long seconds, int nanoseconds) + { + // Simple boundaries + if (seconds < MinSeconds || seconds > MaxSeconds || + nanoseconds < MinNanoseconds || nanoseconds > MaxNanoseconds) + { + return false; + } + // We only have a problem is one is strictly negative and the other is + // strictly positive. + return Math.Sign(seconds) * Math.Sign(nanoseconds) != -1; + } + } + #endregion + } + + internal static class StringEncoding + { + public static Encoding UTF8 = new UTF8Encoding(false); + } +} diff --git a/Tutorials/PvPGameServer/FastBinaryWrite.cs b/Tutorials/PvPGameServer/FastBinaryWrite.cs new file mode 100644 index 0000000..75a49a1 --- /dev/null +++ b/Tutorials/PvPGameServer/FastBinaryWrite.cs @@ -0,0 +1,262 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace PvPGameServer +{ + public class FastBinaryWrite + { + public static void Boolean(byte[] bytes, int offset, bool value) + { + bytes[offset] = (byte)(value ? 1 : 0); + } + + public static void BooleanTrueUnsafe(byte[] bytes, int offset) + { + bytes[offset] = (byte)(1); + } + + public static void BooleanFalseUnsafe(byte[] bytes, int offset) + { + bytes[offset] = (byte)(0); + } + + public static void Byte(byte[] bytes, int offset, byte value) + { + bytes[offset] = value; + } + + public static int Bytes(byte[] bytes, int offset, byte[] value) + { + Buffer.BlockCopy(value, 0, bytes, offset, value.Length); + return value.Length; + } + + public static int SByte(byte[] bytes, int offset, sbyte value) + { + bytes[offset] = (byte)value; + return 1; + } + + public static unsafe int Single(byte[] bytes, int offset, float value) + { + if (offset % 4 == 0) + { + fixed (byte* ptr = bytes) + { + *(float*)(ptr + offset) = value; + } + } + else + { + uint num = *(uint*)(&value); + bytes[offset] = (byte)num; + bytes[offset + 1] = (byte)(num >> 8); + bytes[offset + 2] = (byte)(num >> 16); + bytes[offset + 3] = (byte)(num >> 24); + } + + return 4; + } + + public static unsafe int Double(byte[] bytes, int offset, double value) + { + if (offset % 8 == 0) + { + fixed (byte* ptr = bytes) + { + *(double*)(ptr + offset) = value; + } + } + else + { + ulong num = (ulong)(*(long*)(&value)); + bytes[offset] = (byte)num; + bytes[offset + 1] = (byte)(num >> 8); + bytes[offset + 2] = (byte)(num >> 16); + bytes[offset + 3] = (byte)(num >> 24); + bytes[offset + 4] = (byte)(num >> 32); + bytes[offset + 5] = (byte)(num >> 40); + bytes[offset + 6] = (byte)(num >> 48); + bytes[offset + 7] = (byte)(num >> 56); + } + + return 8; + } + + public static unsafe int Int16(byte[] bytes, int offset, short value) + { + fixed (byte* ptr = bytes) + { + *(short*)(ptr + offset) = value; + } + + return 2; + } + + public static unsafe int Int32(byte[] bytes, int offset, int value) + { + fixed (byte* ptr = bytes) + { + *(int*)(ptr + offset) = value; + } + + return 4; + } + + public static unsafe void Int32Unsafe(byte[] bytes, int offset, int value) + { + fixed (byte* ptr = bytes) + { + *(int*)(ptr + offset) = value; + } + } + + public static unsafe int Int64(byte[] bytes, int offset, long value) + { + fixed (byte* ptr = bytes) + { + *(long*)(ptr + offset) = value; + } + + return 8; + } + + public static unsafe int UInt16(byte[] bytes, int offset, ushort value) + { + fixed (byte* ptr = bytes) + { + *(ushort*)(ptr + offset) = value; + } + + return 2; + } + + public static unsafe int UInt32(byte[] bytes, int offset, uint value) + { + fixed (byte* ptr = bytes) + { + *(uint*)(ptr + offset) = value; + } + + return 4; + } + + public static unsafe int UInt64(byte[] bytes, int offset, ulong value) + { + fixed (byte* ptr = bytes) + { + *(ulong*)(ptr + offset) = value; + } + + return 8; + } + + public static int Char(byte[] bytes, int offset, char value) + { + return UInt16(bytes, offset, (ushort)value); + } + + public static int String(byte[] bytes, int offset, string value) + { + return StringEncoding.UTF8.GetBytes(value, 0, value.Length, bytes, offset); + } + + public static unsafe int Decimal(byte[] bytes, int offset, decimal value) + { + fixed (byte* ptr = bytes) + { + *(Decimal*)(ptr + offset) = value; + } + + return 16; + } + + public static unsafe int Guid(byte[] bytes, int offset, Guid value) + { + fixed (byte* ptr = bytes) + { + *(Guid*)(ptr + offset) = value; + } + + return 16; + } + + #region Timestamp/Duration + public static unsafe int TimeSpan(ref byte[] bytes, int offset, TimeSpan timeSpan) + { + checked + { + long ticks = timeSpan.Ticks; + long seconds = ticks / System.TimeSpan.TicksPerSecond; + int nanos = (int)(ticks % System.TimeSpan.TicksPerSecond) * Duration.NanosecondsPerTick; + + fixed (byte* ptr = bytes) + { + *(long*)(ptr + offset) = seconds; + *(int*)(ptr + offset + 8) = nanos; + } + + return 12; + } + } + + public static unsafe int DateTime(ref byte[] bytes, int offset, DateTime dateTime) + { + dateTime = dateTime.ToUniversalTime(); + + // Do the arithmetic using DateTime.Ticks, which is always non-negative, making things simpler. + long secondsSinceBclEpoch = dateTime.Ticks / System.TimeSpan.TicksPerSecond; + int nanoseconds = (int)(dateTime.Ticks % System.TimeSpan.TicksPerSecond) * Duration.NanosecondsPerTick; + + fixed (byte* ptr = bytes) + { + *(long*)(ptr + offset) = (secondsSinceBclEpoch - Timestamp.BclSecondsAtUnixEpoch); + *(int*)(ptr + offset + 8) = nanoseconds; + } + + return 12; + } + + internal static class Timestamp + { + internal static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + internal const long BclSecondsAtUnixEpoch = 62135596800; + internal const long UnixSecondsAtBclMaxValue = 253402300799; + internal const long UnixSecondsAtBclMinValue = -BclSecondsAtUnixEpoch; + internal const int MaxNanos = Duration.NanosecondsPerSecond - 1; + + internal static bool IsNormalized(long seconds, int nanoseconds) + { + return nanoseconds >= 0 && + nanoseconds <= MaxNanos && + seconds >= UnixSecondsAtBclMinValue && + seconds <= UnixSecondsAtBclMaxValue; + } + } + + internal static class Duration + { + public const int NanosecondsPerSecond = 1000000000; + public const int NanosecondsPerTick = 100; + public const long MaxSeconds = 315576000000L; + public const long MinSeconds = -315576000000L; + internal const int MaxNanoseconds = NanosecondsPerSecond - 1; + internal const int MinNanoseconds = -NanosecondsPerSecond + 1; + + internal static bool IsNormalized(long seconds, int nanoseconds) + { + // Simple boundaries + if (seconds < MinSeconds || seconds > MaxSeconds || + nanoseconds < MinNanoseconds || nanoseconds > MaxNanoseconds) + { + return false; + } + // We only have a problem is one is strictly negative and the other is + // strictly positive. + return Math.Sign(seconds) * Math.Sign(nanoseconds) != -1; + } + } + #endregion + } +} diff --git a/Tutorials/PvPGameServer/MainServer.cs b/Tutorials/PvPGameServer/MainServer.cs index e1fdfd0..e48b3a9 100644 --- a/Tutorials/PvPGameServer/MainServer.cs +++ b/Tutorials/PvPGameServer/MainServer.cs @@ -16,10 +16,8 @@ namespace PvPGameServer { public class MainServer : AppServer, IHostedService { - public static SuperSocket.SocketBase.Logging.ILog MainLogger; - - Dictionary> HandlerMap = new Dictionary>(); - + public static ILog MainLogger; + PacketProcessor MainPacketProcessor = new PacketProcessor(); RoomManager RoomMgr = new RoomManager(); @@ -208,22 +206,7 @@ void OnPacketReceived(NetworkSession session, EFBinaryRequestInfo reqInfo) MainLogger.Debug(string.Format("세션 번호 {0} 받은 데이터 크기: {1}, ThreadId: {2}", session.SessionID, reqInfo.Body.Length, System.Threading.Thread.CurrentThread.ManagedThreadId)); reqInfo.SessionID = session.SessionID; - Distribute(reqInfo); - - - - //_logger.LogInformation($"세션 번호 {session.SessionID}, 받은 데이터 크기: {reqInfo.Body.Length}"); - - var PacketID = reqInfo.PacketID; - - if (HandlerMap.ContainsKey(PacketID)) - { - HandlerMap[PacketID](session, reqInfo); - } - else - { - AppLogger.LogInformation($"세션 번호 {session.SessionID}, 받은 데이터 크기: {reqInfo.Body.Length}"); - } + Distribute(reqInfo); } } diff --git a/Tutorials/PvPGameServer/PKHCommon.cs b/Tutorials/PvPGameServer/PKHCommon.cs index 59a566c..883cf95 100644 --- a/Tutorials/PvPGameServer/PKHCommon.cs +++ b/Tutorials/PvPGameServer/PKHCommon.cs @@ -82,7 +82,7 @@ public void RequestLogin(EFBinaryRequestInfo packetData) ResponseLoginToClient(errorCode, packetData.SessionID); - MainServer.MainLogger.Debug("로그인 요청 답변 보냄"); + MainServer.MainLogger.Debug($"로그인 결과. UserID:{reqData.UserID}, {errorCode}"); } catch(Exception ex) @@ -99,9 +99,9 @@ public void ResponseLoginToClient(ERROR_CODE errorCode, string sessionID) Result = (short)errorCode }; - var bodyData = MessagePackSerializer.Serialize(resLogin); - var sendData = PacketToBytes.Make(PACKETID.RES_LOGIN, bodyData); - + var sendData = MessagePackSerializer.Serialize(resLogin); + WriteHeaderInfo(PACKETID.RES_LOGIN, sendData); + NetSendFunc(sessionID, sendData); } diff --git a/Tutorials/PvPGameServer/PKHandler.cs b/Tutorials/PvPGameServer/PKHandler.cs index b7f86a3..f3ef52c 100644 --- a/Tutorials/PvPGameServer/PKHandler.cs +++ b/Tutorials/PvPGameServer/PKHandler.cs @@ -19,5 +19,13 @@ public void Init(UserManager userMgr) UserMgr = userMgr; } + public void WriteHeaderInfo(PACKETID packetId, byte[] packetData) + { + var header = new MsgPackPacketHeadInfo(); + header.TotalSize = (UInt16)packetData.Length; + header.Id = (UInt16)packetId; + header.Type = 0; + header.Write(packetData); + } } } diff --git a/Tutorials/PvPGameServer/PacketData.cs b/Tutorials/PvPGameServer/PacketData.cs index cb74d32..098cec1 100644 --- a/Tutorials/PvPGameServer/PacketData.cs +++ b/Tutorials/PvPGameServer/PacketData.cs @@ -9,13 +9,63 @@ namespace PvPGameServer { public class PacketDef { - public const Int16 PACKET_HEADER_SIZE = 5; + public const Int16 MSGPACK_PACKET_HEADER_SIZE = 8; + + public const Int16 PACKET_HEADER_SIZE = 8; public const int MAX_USER_ID_BYTE_LENGTH = 16; public const int MAX_USER_PW_BYTE_LENGTH = 16; public const int INVALID_ROOM_NUMBER = -1; } + public struct MsgPackPacketHeadInfo + { + const int PacketHeaderMsgPackStartPos = 3; + public const int HeadSize = 8; + + public UInt16 TotalSize; + public UInt16 Id; + public byte Type; + + public static UInt16 GetTotalSize(byte[] data, int startPos) + { + return FastBinaryRead.UInt16(data, startPos + PacketHeaderMsgPackStartPos); + } + + public static void WritePacketId(byte[] data, UInt16 packetId) + { + FastBinaryWrite.UInt16(data, PacketHeaderMsgPackStartPos + 2, packetId); + } + + public void Read(byte[] headerData) + { + var pos = PacketHeaderMsgPackStartPos; + + TotalSize = FastBinaryRead.UInt16(headerData, pos); + pos += 2; + + Id = FastBinaryRead.UInt16(headerData, pos); + pos += 2; + + Type = headerData[pos]; + pos += 1; + } + + public void Write(byte[] mqData) + { + var pos = PacketHeaderMsgPackStartPos; + + FastBinaryWrite.UInt16(mqData, pos, TotalSize); + pos += 2; + + FastBinaryWrite.UInt16(mqData, pos, Id); + pos += 2; + + mqData[pos] = Type; + pos += 1; + } + } + public class PacketToBytes { public static byte[] Make(PACKETID packetID, byte[] bodyData) @@ -55,24 +105,38 @@ public static Tuple ClientReceiveData(int recvLength, byte[] recvDa } } - // 로그인 요청 + [MessagePackObject] - public class PKTReqLogin + public class MsgPackPacketHead { [Key(0)] - public string UserID; + public Byte[] Head = new Byte[PacketDef.MSGPACK_PACKET_HEADER_SIZE]; + } + + + // 로그인 요청 + [MessagePackObject] + public class PKTReqLogin : MsgPackPacketHead + { [Key(1)] + public string UserID; + [Key(2)] public string AuthToken; } [MessagePackObject] - public class PKTResLogin + public class PKTResLogin : MsgPackPacketHead { - [Key(0)] + [Key(1)] public short Result; } + + + + + [MessagePackObject] public class PKNtfMustClose { diff --git a/Tutorials/PvPGameServer/PacketProcessor.cs b/Tutorials/PvPGameServer/PacketProcessor.cs index 627c967..4c2957d 100644 --- a/Tutorials/PvPGameServer/PacketProcessor.cs +++ b/Tutorials/PvPGameServer/PacketProcessor.cs @@ -86,9 +86,12 @@ void Process() { var packet = MsgBuffer.Receive(); - if (PacketHandlerMap.ContainsKey(packet.PacketID)) + var header = new MsgPackPacketHeadInfo(); + header.Read(packet.Data); + + if (PacketHandlerMap.ContainsKey(header.Id)) { - PacketHandlerMap[packet.PacketID](packet); + PacketHandlerMap[header.Id](packet); } else { diff --git a/Tutorials/PvPGameServer/PvPGameServer.csproj b/Tutorials/PvPGameServer/PvPGameServer.csproj index 2f1b7fe..4d3d3c4 100644 --- a/Tutorials/PvPGameServer/PvPGameServer.csproj +++ b/Tutorials/PvPGameServer/PvPGameServer.csproj @@ -7,10 +7,12 @@ ..\00_server_bins + true ..\00_server_bins + true diff --git a/Tutorials/PvPGameServer/ReceiveFilter.cs b/Tutorials/PvPGameServer/ReceiveFilter.cs index b6aff9b..623dccf 100644 --- a/Tutorials/PvPGameServer/ReceiveFilter.cs +++ b/Tutorials/PvPGameServer/ReceiveFilter.cs @@ -13,24 +13,23 @@ namespace PvPGameServer //TODO msgpack 일체형으로 예제 코드 추가 public class EFBinaryRequestInfo : BinaryRequestInfo { - public UInt16 TotalSize; - - public UInt16 PacketID; - public Byte Type; + //public UInt16 TotalSize; + //public UInt16 PacketID; + //public Byte Type; public string SessionID; - public byte[] Data { get; private set; } + public byte[] Data; public const int PACKET_HEADER_MSGPACK_START_POS = 3; public const int HEADERE_SIZE = 5 + PACKET_HEADER_MSGPACK_START_POS; - public EFBinaryRequestInfo(UInt16 totalSize, UInt16 packetID, Byte type, byte[] body) + /*public EFBinaryRequestInfo(UInt16 totalSize, UInt16 packetID, Byte type, byte[] body) : base(null, body) { this.TotalSize = totalSize; this.PacketID = packetID; this.Type = type; - } + }*/ public EFBinaryRequestInfo(byte[] packetData) : base(null, packetData) { diff --git a/Tutorials/PvPGameServer/ServerPacketData.cs b/Tutorials/PvPGameServer/ServerPacketData.cs index 98eaa11..d8af892 100644 --- a/Tutorials/PvPGameServer/ServerPacketData.cs +++ b/Tutorials/PvPGameServer/ServerPacketData.cs @@ -31,14 +31,15 @@ public void Assign(string sessionID, Int16 packetID, byte[] packetBodyData) public static EFBinaryRequestInfo MakeNTFInConnectOrDisConnectClientPacket(bool isConnect, string sessionID) { var packet = new EFBinaryRequestInfo(null); + packet.Data = new byte[MsgPackPacketHeadInfo.HeadSize]; if (isConnect) { - packet.PacketID = (Int32)PACKETID.NTF_IN_CONNECT_CLIENT; + MsgPackPacketHeadInfo.WritePacketId(packet.Data, (UInt16)PACKETID.NTF_IN_CONNECT_CLIENT); } else { - packet.PacketID = (Int32)PACKETID.NTF_IN_DISCONNECT_CLIENT; + MsgPackPacketHeadInfo.WritePacketId(packet.Data, (UInt16)PACKETID.NTF_IN_DISCONNECT_CLIENT); } packet.SessionID = sessionID; diff --git a/Tutorials/PvPGameServer_Client/App.config b/Tutorials/PvPGameServer_Client/App.config new file mode 100644 index 0000000..ed1ad6d --- /dev/null +++ b/Tutorials/PvPGameServer_Client/App.config @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/Tutorials/PvPGameServer_Client/ClientSimpleTcp.cs b/Tutorials/PvPGameServer_Client/ClientSimpleTcp.cs new file mode 100644 index 0000000..62fa8fe --- /dev/null +++ b/Tutorials/PvPGameServer_Client/ClientSimpleTcp.cs @@ -0,0 +1,93 @@ +using System; +using System.Net.Sockets; +using System.Net; + +namespace csharp_test_client +{ + public class ClientSimpleTcp + { + public Socket Sock = null; + public string LatestErrorMsg; + + + //소켓연결 + public bool Connect(string ip, int port) + { + try + { + IPAddress serverIP = IPAddress.Parse(ip); + int serverPort = port; + + Sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + Sock.Connect(new IPEndPoint(serverIP, serverPort)); + + if (Sock == null || Sock.Connected == false) + { + return false; + } + + return true; + } + catch (Exception ex) + { + LatestErrorMsg = ex.Message; + return false; + } + } + + public Tuple Receive() + { + + try + { + byte[] ReadBuffer = new byte[2048]; + var nRecv = Sock.Receive(ReadBuffer, 0, ReadBuffer.Length, SocketFlags.None); + + if (nRecv == 0) + { + return null; + } + + return Tuple.Create(nRecv,ReadBuffer); + } + catch (SocketException se) + { + LatestErrorMsg = se.Message; + } + + return null; + } + + //스트림에 쓰기 + public void Send(byte[] sendData) + { + try + { + if (Sock != null && Sock.Connected) //연결상태 유무 확인 + { + Sock.Send(sendData, 0, sendData.Length, SocketFlags.None); + } + else + { + LatestErrorMsg = "먼저 채팅서버에 접속하세요!"; + } + } + catch (SocketException se) + { + LatestErrorMsg = se.Message; + } + } + + //소켓과 스트림 닫기 + public void Close() + { + if (Sock != null && Sock.Connected) + { + //Sock.Shutdown(SocketShutdown.Both); + Sock.Close(); + } + } + + public bool IsConnected() { return (Sock != null && Sock.Connected) ? true : false; } + } +} diff --git a/Tutorials/PvPGameServer_Client/DevLog.cs b/Tutorials/PvPGameServer_Client/DevLog.cs new file mode 100644 index 0000000..4d45fbe --- /dev/null +++ b/Tutorials/PvPGameServer_Client/DevLog.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using System.Runtime.CompilerServices; +using System.Threading; + +namespace csharp_test_client +{ + public class DevLog + { + static System.Collections.Concurrent.ConcurrentQueue logMsgQueue = new System.Collections.Concurrent.ConcurrentQueue(); + + static Int64 출력가능_로그레벨 = (Int64)LOG_LEVEL.TRACE; + + + + static public void Init(LOG_LEVEL logLevel) + { + ChangeLogLevel(logLevel); + } + + static public void ChangeLogLevel(LOG_LEVEL logLevel) + { + Interlocked.Exchange(ref 출력가능_로그레벨, (int)logLevel); + } + + public static LOG_LEVEL CurrentLogLevel() + { + var curLogLevel = (LOG_LEVEL)Interlocked.Read(ref 출력가능_로그레벨); + return curLogLevel; + } + + static public void Write(string msg, LOG_LEVEL logLevel = LOG_LEVEL.TRACE, + [CallerFilePath] string fileName = "", + [CallerMemberName] string methodName = "", + [CallerLineNumber] int lineNumber = 0) + { + if (CurrentLogLevel() <= logLevel) + { + logMsgQueue.Enqueue(string.Format("{0}:{1}| {2}", DateTime.Now, methodName, msg)); + } + } + + static public bool GetLog(out string msg) + { + if (logMsgQueue.TryDequeue(out msg)) + { + return true; + } + + return false; + } + + } + + + public enum LOG_LEVEL + { + TRACE, + DEBUG, + INFO, + WARN, + ERROR, + DISABLE + } +} diff --git a/Tutorials/PvPGameServer_Client/FastBinaryRead.cs b/Tutorials/PvPGameServer_Client/FastBinaryRead.cs new file mode 100644 index 0000000..723cfea --- /dev/null +++ b/Tutorials/PvPGameServer_Client/FastBinaryRead.cs @@ -0,0 +1,322 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace csharp_test_client +{ + public class FastBinaryRead + { + public static bool Boolean(byte[] bytes, int offset) + { + return (bytes[offset] == 0) ? false : true; + } + public static bool Boolean(ReadOnlySpan bytes, int offset) + { + return (bytes[offset] == 0) ? false : true; + } + + public static byte Byte(byte[] bytes, int offset) + { + return bytes[offset]; + } + public static byte Byte(ReadOnlySpan bytes, int offset) + { + return bytes[offset]; + } + + public static byte[] Bytes(byte[] bytes, int offset, int count) + { + var dest = new byte[count]; + Buffer.BlockCopy(bytes, offset, dest, 0, count); + return dest; + } + + public static sbyte SByte(byte[] bytes, int offset) + { + return (sbyte)bytes[offset]; + } + public static sbyte SByte(ReadOnlySpan bytes, int offset) + { + return (sbyte)bytes[offset]; + } + + public static unsafe float Single(byte[] bytes, int offset) + { + if (offset % 4 == 0) + { + fixed (byte* ptr = bytes) + { + return *(float*)(ptr + offset); + } + } + else + { + uint num = (uint)((int)bytes[offset] | (int)bytes[offset + 1] << 8 | (int)bytes[offset + 2] << 16 | (int)bytes[offset + 3] << 24); + return *(float*)(&num); + } + } + public static unsafe float Single(ReadOnlySpan bytes, int offset) + { + if (offset % 4 == 0) + { + fixed (byte* ptr = bytes) + { + return *(float*)(ptr + offset); + } + } + else + { + uint num = (uint)((int)bytes[offset] | (int)bytes[offset + 1] << 8 | (int)bytes[offset + 2] << 16 | (int)bytes[offset + 3] << 24); + return *(float*)(&num); + } + } + + public static unsafe double Double(byte[] bytes, int offset) + { + if (offset % 8 == 0) + { + fixed (byte* ptr = bytes) + { + return *(double*)(ptr + offset); + } + } + else + { + uint num = (uint)((int)bytes[offset] | (int)bytes[offset + 1] << 8 | (int)bytes[offset + 2] << 16 | (int)bytes[offset + 3] << 24); + ulong num2 = (ulong)((int)bytes[offset + 4] | (int)bytes[offset + 5] << 8 | (int)bytes[offset + 6] << 16 | (int)bytes[offset + 7] << 24) << 32 | (ulong)num; + return *(double*)(&num2); + } + } + public static unsafe double Double(ReadOnlySpan bytes, int offset) + { + if (offset % 8 == 0) + { + fixed (byte* ptr = bytes) + { + return *(double*)(ptr + offset); + } + } + else + { + uint num = (uint)((int)bytes[offset] | (int)bytes[offset + 1] << 8 | (int)bytes[offset + 2] << 16 | (int)bytes[offset + 3] << 24); + ulong num2 = (ulong)((int)bytes[offset + 4] | (int)bytes[offset + 5] << 8 | (int)bytes[offset + 6] << 16 | (int)bytes[offset + 7] << 24) << 32 | (ulong)num; + return *(double*)(&num2); + } + } + + public static unsafe short Int16(byte[] bytes, int offset) + { + fixed (byte* ptr = bytes) + { + return *(short*)(ptr + offset); + } + } + public static unsafe short Int16(ReadOnlySpan bytes, int offset) + { + fixed (byte* ptr = bytes) + { + return *(short*)(ptr + offset); + } + } + + public static unsafe int Int32(byte[] bytes, int offset) + { + fixed (byte* ptr = bytes) + { + return *(int*)(ptr + offset); + } + } + public static unsafe int Int32(ReadOnlySpan bytes, int offset) + { + fixed (byte* ptr = bytes) + { + return *(int*)(ptr + offset); + } + } + + public static unsafe long Int64(byte[] bytes, int offset) + { + fixed (byte* ptr = bytes) + { + return *(long*)(ptr + offset); + } + } + public static unsafe long Int64(ReadOnlySpan bytes, int offset) + { + fixed (byte* ptr = bytes) + { + return *(long*)(ptr + offset); + } + } + + public static unsafe ushort UInt16(byte[] bytes, int offset) + { + fixed (byte* ptr = bytes) + { + return *(ushort*)(ptr + offset); + } + } + public static unsafe ushort UInt16(ReadOnlySpan bytes, int offset) + { + fixed (byte* ptr = bytes) + { + return *(ushort*)(ptr + offset); + } + } + + public static unsafe uint UInt32(byte[] bytes, int offset) + { + fixed (byte* ptr = bytes) + { + return *(uint*)(ptr + offset); + } + } + public static unsafe uint UInt32(ReadOnlySpan bytes, int offset) + { + fixed (byte* ptr = bytes) + { + return *(uint*)(ptr + offset); + } + } + + public static unsafe ulong UInt64(byte[] bytes, int offset) + { + fixed (byte* ptr = bytes) + { + return *(ulong*)(ptr + offset); + } + } + public static unsafe ulong UInt64(ReadOnlySpan bytes, int offset) + { + fixed (byte* ptr = bytes) + { + return *(ulong*)(ptr + offset); + } + } + + public static char Char(byte[] bytes, int offset) + { + return (char)FastBinaryRead.UInt16(bytes, offset); + } + public static char Char(ReadOnlySpan bytes, int offset) + { + return (char)FastBinaryRead.UInt16(bytes, offset); + } + + public static string String(byte[] bytes, int offset, int count) + { + return StringEncoding.UTF8.GetString(bytes, offset, count); + } + + public static unsafe decimal Decimal(byte[] bytes, int offset) + { + fixed (byte* ptr = bytes) + { + return *(Decimal*)(ptr + offset); + } + } + public static unsafe decimal Decimal(ReadOnlySpan bytes, int offset) + { + fixed (byte* ptr = bytes) + { + return *(Decimal*)(ptr + offset); + } + } + + public static unsafe Guid Guid(byte[] bytes, int offset) + { + fixed (byte* ptr = bytes) + { + return *(Guid*)(ptr + offset); + } + } + public static unsafe Guid Guid(ReadOnlySpan bytes, int offset) + { + fixed (byte* ptr = bytes) + { + return *(Guid*)(ptr + offset); + } + } + + #region Timestamp/Duration + public static unsafe TimeSpan TimeSpan(ref byte[] bytes, int offset) + { + checked + { + fixed (byte* ptr = bytes) + { + var seconds = *(long*)(ptr + offset); + var nanos = *(int*)(ptr + offset + 8); + + if (!Duration.IsNormalized(seconds, nanos)) + { + throw new InvalidOperationException("Duration was not a valid normalized duration"); + } + long ticks = seconds * System.TimeSpan.TicksPerSecond + nanos / Duration.NanosecondsPerTick; + return System.TimeSpan.FromTicks(ticks); + } + } + } + + public static unsafe DateTime DateTime(ref byte[] bytes, int offset) + { + fixed (byte* ptr = bytes) + { + var seconds = *(long*)(ptr + offset); + var nanos = *(int*)(ptr + offset + 8); + + if (!Timestamp.IsNormalized(seconds, nanos)) + { + throw new InvalidOperationException(string.Format(@"Timestamp contains invalid values: Seconds={0}; Nanos={1}", seconds, nanos)); + } + return Timestamp.UnixEpoch.AddSeconds(seconds).AddTicks(nanos / Duration.NanosecondsPerTick); + } + } + + internal static class Timestamp + { + internal static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + internal const long BclSecondsAtUnixEpoch = 62135596800; + internal const long UnixSecondsAtBclMaxValue = 253402300799; + internal const long UnixSecondsAtBclMinValue = -BclSecondsAtUnixEpoch; + internal const int MaxNanos = Duration.NanosecondsPerSecond - 1; + + internal static bool IsNormalized(long seconds, int nanoseconds) + { + return nanoseconds >= 0 && + nanoseconds <= MaxNanos && + seconds >= UnixSecondsAtBclMinValue && + seconds <= UnixSecondsAtBclMaxValue; + } + } + + internal static class Duration + { + public const int NanosecondsPerSecond = 1000000000; + public const int NanosecondsPerTick = 100; + public const long MaxSeconds = 315576000000L; + public const long MinSeconds = -315576000000L; + internal const int MaxNanoseconds = NanosecondsPerSecond - 1; + internal const int MinNanoseconds = -NanosecondsPerSecond + 1; + + internal static bool IsNormalized(long seconds, int nanoseconds) + { + // Simple boundaries + if (seconds < MinSeconds || seconds > MaxSeconds || + nanoseconds < MinNanoseconds || nanoseconds > MaxNanoseconds) + { + return false; + } + // We only have a problem is one is strictly negative and the other is + // strictly positive. + return Math.Sign(seconds) * Math.Sign(nanoseconds) != -1; + } + } + #endregion + } + + internal static class StringEncoding + { + public static Encoding UTF8 = new UTF8Encoding(false); + } +} diff --git a/Tutorials/PvPGameServer_Client/FastBinaryWrite.cs b/Tutorials/PvPGameServer_Client/FastBinaryWrite.cs new file mode 100644 index 0000000..d77925e --- /dev/null +++ b/Tutorials/PvPGameServer_Client/FastBinaryWrite.cs @@ -0,0 +1,262 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace csharp_test_client +{ + public class FastBinaryWrite + { + public static void Boolean(byte[] bytes, int offset, bool value) + { + bytes[offset] = (byte)(value ? 1 : 0); + } + + public static void BooleanTrueUnsafe(byte[] bytes, int offset) + { + bytes[offset] = (byte)(1); + } + + public static void BooleanFalseUnsafe(byte[] bytes, int offset) + { + bytes[offset] = (byte)(0); + } + + public static void Byte(byte[] bytes, int offset, byte value) + { + bytes[offset] = value; + } + + public static int Bytes(byte[] bytes, int offset, byte[] value) + { + Buffer.BlockCopy(value, 0, bytes, offset, value.Length); + return value.Length; + } + + public static int SByte(byte[] bytes, int offset, sbyte value) + { + bytes[offset] = (byte)value; + return 1; + } + + public static unsafe int Single(byte[] bytes, int offset, float value) + { + if (offset % 4 == 0) + { + fixed (byte* ptr = bytes) + { + *(float*)(ptr + offset) = value; + } + } + else + { + uint num = *(uint*)(&value); + bytes[offset] = (byte)num; + bytes[offset + 1] = (byte)(num >> 8); + bytes[offset + 2] = (byte)(num >> 16); + bytes[offset + 3] = (byte)(num >> 24); + } + + return 4; + } + + public static unsafe int Double(byte[] bytes, int offset, double value) + { + if (offset % 8 == 0) + { + fixed (byte* ptr = bytes) + { + *(double*)(ptr + offset) = value; + } + } + else + { + ulong num = (ulong)(*(long*)(&value)); + bytes[offset] = (byte)num; + bytes[offset + 1] = (byte)(num >> 8); + bytes[offset + 2] = (byte)(num >> 16); + bytes[offset + 3] = (byte)(num >> 24); + bytes[offset + 4] = (byte)(num >> 32); + bytes[offset + 5] = (byte)(num >> 40); + bytes[offset + 6] = (byte)(num >> 48); + bytes[offset + 7] = (byte)(num >> 56); + } + + return 8; + } + + public static unsafe int Int16(byte[] bytes, int offset, short value) + { + fixed (byte* ptr = bytes) + { + *(short*)(ptr + offset) = value; + } + + return 2; + } + + public static unsafe int Int32(byte[] bytes, int offset, int value) + { + fixed (byte* ptr = bytes) + { + *(int*)(ptr + offset) = value; + } + + return 4; + } + + public static unsafe void Int32Unsafe(byte[] bytes, int offset, int value) + { + fixed (byte* ptr = bytes) + { + *(int*)(ptr + offset) = value; + } + } + + public static unsafe int Int64(byte[] bytes, int offset, long value) + { + fixed (byte* ptr = bytes) + { + *(long*)(ptr + offset) = value; + } + + return 8; + } + + public static unsafe int UInt16(byte[] bytes, int offset, ushort value) + { + fixed (byte* ptr = bytes) + { + *(ushort*)(ptr + offset) = value; + } + + return 2; + } + + public static unsafe int UInt32(byte[] bytes, int offset, uint value) + { + fixed (byte* ptr = bytes) + { + *(uint*)(ptr + offset) = value; + } + + return 4; + } + + public static unsafe int UInt64(byte[] bytes, int offset, ulong value) + { + fixed (byte* ptr = bytes) + { + *(ulong*)(ptr + offset) = value; + } + + return 8; + } + + public static int Char(byte[] bytes, int offset, char value) + { + return UInt16(bytes, offset, (ushort)value); + } + + public static int String(byte[] bytes, int offset, string value) + { + return StringEncoding.UTF8.GetBytes(value, 0, value.Length, bytes, offset); + } + + public static unsafe int Decimal(byte[] bytes, int offset, decimal value) + { + fixed (byte* ptr = bytes) + { + *(Decimal*)(ptr + offset) = value; + } + + return 16; + } + + public static unsafe int Guid(byte[] bytes, int offset, Guid value) + { + fixed (byte* ptr = bytes) + { + *(Guid*)(ptr + offset) = value; + } + + return 16; + } + + #region Timestamp/Duration + public static unsafe int TimeSpan(ref byte[] bytes, int offset, TimeSpan timeSpan) + { + checked + { + long ticks = timeSpan.Ticks; + long seconds = ticks / System.TimeSpan.TicksPerSecond; + int nanos = (int)(ticks % System.TimeSpan.TicksPerSecond) * Duration.NanosecondsPerTick; + + fixed (byte* ptr = bytes) + { + *(long*)(ptr + offset) = seconds; + *(int*)(ptr + offset + 8) = nanos; + } + + return 12; + } + } + + public static unsafe int DateTime(ref byte[] bytes, int offset, DateTime dateTime) + { + dateTime = dateTime.ToUniversalTime(); + + // Do the arithmetic using DateTime.Ticks, which is always non-negative, making things simpler. + long secondsSinceBclEpoch = dateTime.Ticks / System.TimeSpan.TicksPerSecond; + int nanoseconds = (int)(dateTime.Ticks % System.TimeSpan.TicksPerSecond) * Duration.NanosecondsPerTick; + + fixed (byte* ptr = bytes) + { + *(long*)(ptr + offset) = (secondsSinceBclEpoch - Timestamp.BclSecondsAtUnixEpoch); + *(int*)(ptr + offset + 8) = nanoseconds; + } + + return 12; + } + + internal static class Timestamp + { + internal static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + internal const long BclSecondsAtUnixEpoch = 62135596800; + internal const long UnixSecondsAtBclMaxValue = 253402300799; + internal const long UnixSecondsAtBclMinValue = -BclSecondsAtUnixEpoch; + internal const int MaxNanos = Duration.NanosecondsPerSecond - 1; + + internal static bool IsNormalized(long seconds, int nanoseconds) + { + return nanoseconds >= 0 && + nanoseconds <= MaxNanos && + seconds >= UnixSecondsAtBclMinValue && + seconds <= UnixSecondsAtBclMaxValue; + } + } + + internal static class Duration + { + public const int NanosecondsPerSecond = 1000000000; + public const int NanosecondsPerTick = 100; + public const long MaxSeconds = 315576000000L; + public const long MinSeconds = -315576000000L; + internal const int MaxNanoseconds = NanosecondsPerSecond - 1; + internal const int MinNanoseconds = -NanosecondsPerSecond + 1; + + internal static bool IsNormalized(long seconds, int nanoseconds) + { + // Simple boundaries + if (seconds < MinSeconds || seconds > MaxSeconds || + nanoseconds < MinNanoseconds || nanoseconds > MaxNanoseconds) + { + return false; + } + // We only have a problem is one is strictly negative and the other is + // strictly positive. + return Math.Sign(seconds) * Math.Sign(nanoseconds) != -1; + } + } + #endregion + } +} diff --git a/Tutorials/PvPGameServer_Client/Packet.cs b/Tutorials/PvPGameServer_Client/Packet.cs new file mode 100644 index 0000000..28c5dca --- /dev/null +++ b/Tutorials/PvPGameServer_Client/Packet.cs @@ -0,0 +1,342 @@ +using MessagePack; //https://github.com/neuecc/MessagePack-CSharp + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace csharp_test_client +{ + struct PacketData + { + public Int16 DataSize; + public Int16 PacketID; + public SByte Type; + public byte[] BodyData; + } + + public struct MsgPackPacketHeadInfo + { + const int PacketHeaderMsgPackStartPos = 3; + public const int HeadSize = 8; + + public UInt16 TotalSize; + public UInt16 Id; + public byte Type; + + public static UInt16 GetTotalSize(byte[] data, int startPos) + { + return FastBinaryRead.UInt16(data, startPos + PacketHeaderMsgPackStartPos); + } + + public void Read(byte[] headerData) + { + var pos = PacketHeaderMsgPackStartPos; + + TotalSize = FastBinaryRead.UInt16(headerData, pos); + pos += 2; + + Id = FastBinaryRead.UInt16(headerData, pos); + pos += 2; + + Type = headerData[pos]; + pos += 1; + } + + public void Write(byte[] mqData) + { + var pos = PacketHeaderMsgPackStartPos; + + FastBinaryWrite.UInt16(mqData, pos, TotalSize); + pos += 2; + + FastBinaryWrite.UInt16(mqData, pos, Id); + pos += 2; + + mqData[pos] = Type; + pos += 1; + } + } + + + [MessagePackObject] + public class MsgPackPacketHead + { + [Key(0)] + public Byte[] Head = new Byte[MsgPackPacketHeadInfo.HeadSize]; + } + + + // 로그인 요청 + [MessagePackObject] + public class PKTReqLogin : MsgPackPacketHead + { + [Key(1)] + public string UserID; + [Key(2)] + public string AuthToken; + } + + [MessagePackObject] + public class PKTResLogin : MsgPackPacketHead + { + [Key(1)] + public short Result; + } + + + + + + + + + + + + + public class PacketDump + { + public static string Bytes(byte[] byteArr) + { + StringBuilder sb = new StringBuilder("["); + for (int i = 0; i < byteArr.Length; ++i) + { + sb.Append(byteArr[i] + " "); + } + sb.Append("]"); + return sb.ToString(); + } + } + + + public class ErrorNtfPacket + { + public ERROR_CODE Error; + + public bool FromBytes(byte[] bodyData) + { + Error = (ERROR_CODE)BitConverter.ToInt16(bodyData, 0); + return true; + } + } + + + public class LoginReqPacket + { + byte[] UserID = new byte[PacketDef.MAX_USER_ID_BYTE_LENGTH]; + byte[] UserPW = new byte[PacketDef.MAX_USER_PW_BYTE_LENGTH]; + + public void SetValue(string userID, string userPW) + { + Encoding.UTF8.GetBytes(userID).CopyTo(UserID, 0); + Encoding.UTF8.GetBytes(userPW).CopyTo(UserPW, 0); + } + + public byte[] ToBytes() + { + List dataSource = new List(); + dataSource.AddRange(UserID); + dataSource.AddRange(UserPW); + return dataSource.ToArray(); + } + } + + public class LoginResPacket + { + public Int16 Result; + + public bool FromBytes(byte[] bodyData) + { + Result = BitConverter.ToInt16(bodyData, 0); + return true; + } + } + + + public class RoomEnterReqPacket + { + int RoomNumber; + public void SetValue(int roomNumber) + { + RoomNumber = roomNumber; + } + + public byte[] ToBytes() + { + List dataSource = new List(); + dataSource.AddRange(BitConverter.GetBytes(RoomNumber)); + return dataSource.ToArray(); + } + } + + public class RoomEnterResPacket + { + public Int16 Result; + public Int64 RoomUserUniqueId; + + public bool FromBytes(byte[] bodyData) + { + Result = BitConverter.ToInt16(bodyData, 0); + RoomUserUniqueId = BitConverter.ToInt64(bodyData, 2); + return true; + } + } + + public class RoomUserListNtfPacket + { + public int UserCount = 0; + public List UserUniqueIdList = new List(); + public List UserIDList = new List(); + + public bool FromBytes(byte[] bodyData) + { + var readPos = 0; + var userCount = (SByte)bodyData[readPos]; + ++readPos; + + for (int i = 0; i < userCount; ++i) + { + var uniqeudId = BitConverter.ToInt64(bodyData, readPos); + readPos += 8; + + var idlen = (SByte)bodyData[readPos]; + ++readPos; + + var id = Encoding.UTF8.GetString(bodyData, readPos, idlen); + readPos += idlen; + + UserUniqueIdList.Add(uniqeudId); + UserIDList.Add(id); + } + + UserCount = userCount; + return true; + } + } + + public class RoomNewUserNtfPacket + { + public Int64 UserUniqueId; + public string UserID; + + public bool FromBytes(byte[] bodyData) + { + var readPos = 0; + + UserUniqueId = BitConverter.ToInt64(bodyData, readPos); + readPos += 8; + + var idlen = (SByte)bodyData[readPos]; + ++readPos; + + UserID = Encoding.UTF8.GetString(bodyData, readPos, idlen); + readPos += idlen; + + return true; + } + } + + + public class RoomChatReqPacket + { + Int16 MsgLen; + byte[] Msg;//= new byte[PacketDef.MAX_USER_ID_BYTE_LENGTH]; + + public void SetValue(string message) + { + Msg = Encoding.UTF8.GetBytes(message); + MsgLen = (Int16)Msg.Length; + } + + public byte[] ToBytes() + { + List dataSource = new List(); + dataSource.AddRange(BitConverter.GetBytes(MsgLen)); + dataSource.AddRange(Msg); + return dataSource.ToArray(); + } + } + + public class RoomChatResPacket + { + public Int16 Result; + + public bool FromBytes(byte[] bodyData) + { + Result = BitConverter.ToInt16(bodyData, 0); + return true; + } + } + + public class RoomChatNtfPacket + { + public Int64 UserUniqueId; + public string Message; + + public bool FromBytes(byte[] bodyData) + { + UserUniqueId = BitConverter.ToInt64(bodyData, 0); + + var msgLen = BitConverter.ToInt16(bodyData, 8); + byte[] messageTemp = new byte[msgLen]; + Buffer.BlockCopy(bodyData, 8 + 2, messageTemp, 0, msgLen); + Message = Encoding.UTF8.GetString(messageTemp); + return true; + } + } + + + public class RoomLeaveResPacket + { + public Int16 Result; + + public bool FromBytes(byte[] bodyData) + { + Result = BitConverter.ToInt16(bodyData, 0); + return true; + } + } + + public class RoomLeaveUserNtfPacket + { + public Int64 UserUniqueId; + + public bool FromBytes(byte[] bodyData) + { + UserUniqueId = BitConverter.ToInt64(bodyData, 0); + return true; + } + } + + + + public class RoomRelayNtfPacket + { + public Int64 UserUniqueId; + public byte[] RelayData; + + public bool FromBytes(byte[] bodyData) + { + UserUniqueId = BitConverter.ToInt64(bodyData, 0); + + var relayDataLen = bodyData.Length - 8; + RelayData = new byte[relayDataLen]; + Buffer.BlockCopy(bodyData, 8, RelayData, 0, relayDataLen); + return true; + } + } + + + public class PingRequest + { + public Int16 PingNum; + + public byte[] ToBytes() + { + return BitConverter.GetBytes(PingNum); + } + + } +} diff --git a/Tutorials/PvPGameServer_Client/PacketBufferManager.cs b/Tutorials/PvPGameServer_Client/PacketBufferManager.cs new file mode 100644 index 0000000..331e185 --- /dev/null +++ b/Tutorials/PvPGameServer_Client/PacketBufferManager.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace csharp_test_client +{ + class PacketBufferManager + { + int BufferSize = 0; + int ReadPos = 0; + int WritePos = 0; + + int HeaderSize = 0; + int MaxPacketSize = 0; + byte[] PacketData; + byte[] PacketDataTemp; + + public bool Init(int size, int headerSize, int maxPacketSize) + { + if (size < (maxPacketSize * 2) || size < 1 || headerSize < 1 || maxPacketSize < 1) + { + return false; + } + + BufferSize = size; + PacketData = new byte[size]; + PacketDataTemp = new byte[size]; + HeaderSize = headerSize; + MaxPacketSize = maxPacketSize; + + return true; + } + + public bool Write(byte[] data, int pos, int size) + { + if (data == null || (data.Length < (pos + size))) + { + return false; + } + + var remainBufferSize = BufferSize - WritePos; + + if (remainBufferSize < size) + { + return false; + } + + Buffer.BlockCopy(data, pos, PacketData, WritePos, size); + WritePos += size; + + if (NextFree() == false) + { + BufferRelocate(); + } + return true; + } + + public byte[] Read() + { + var enableReadSize = WritePos - ReadPos; + + if (enableReadSize < HeaderSize) + { + return null; + } + + var packetDataSize = MsgPackPacketHeadInfo.GetTotalSize(PacketData, ReadPos); + if (enableReadSize < packetDataSize) + { + return null; + } + + var packet = new byte[packetDataSize]; + Buffer.BlockCopy(PacketData, ReadPos, packet, 0, packetDataSize); + + ReadPos += packetDataSize; + + return packet; + } + + bool NextFree() + { + var enableWriteSize = BufferSize - WritePos; + + if (enableWriteSize < MaxPacketSize) + { + return false; + } + + return true; + } + + void BufferRelocate() + { + var enableReadSize = WritePos - ReadPos; + + Buffer.BlockCopy(PacketData, ReadPos, PacketDataTemp, 0, enableReadSize); + Buffer.BlockCopy(PacketDataTemp, 0, PacketData, 0, enableReadSize); + + ReadPos = 0; + WritePos = enableReadSize; + } + } +} diff --git a/Tutorials/PvPGameServer_Client/PacketDefine.cs b/Tutorials/PvPGameServer_Client/PacketDefine.cs new file mode 100644 index 0000000..17f8739 --- /dev/null +++ b/Tutorials/PvPGameServer_Client/PacketDefine.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace csharp_test_client +{ + class PacketDef + { + + //public const Int16 PACKET_HEADER_SIZE = 5; + public const int MAX_USER_ID_BYTE_LENGTH = 16; + public const int MAX_USER_PW_BYTE_LENGTH = 16; + } + + public enum PACKET_ID : ushort + { + PACKET_ID_ECHO = 101, + + // Ping(Heart-beat) + PACKET_ID_PING_REQ = 201, + PACKET_ID_PING_RES = 202, + + PACKET_ID_ERROR_NTF = 203, + + + REQ_LOGIN = 1002, + RES_LOGIN = 1003, + NTF_MUST_CLOSE = 1005, + + REQ_ROOM_ENTER = 1015, + RES_ROOM_ENTER = 1016, + NTF_ROOM_USER_LIST = 1017, + NTF_ROOM_NEW_USER = 1018, + + REQ_ROOM_LEAVE = 1021, + RES_ROOM_LEAVE = 1022, + NTF_ROOM_LEAVE_USER = 1023, + + REQ_ROOM_CHAT = 1026, + NTF_ROOM_CHAT = 1027, + + + REQ_ROOM_DEV_ALL_ROOM_START_GAME = 1091, + RES_ROOM_DEV_ALL_ROOM_START_GAME = 1092, + + REQ_ROOM_DEV_ALL_ROOM_END_GAME = 1093, + RES_ROOM_DEV_ALL_ROOM_END_GAME = 1094, + } + + + public enum ERROR_CODE : Int16 + { + ERROR_NONE = 0, + + + + ERROR_CODE_USER_MGR_INVALID_USER_UNIQUEID = 112, + + ERROR_CODE_PUBLIC_CHANNEL_IN_USER = 114, + + ERROR_CODE_PUBLIC_CHANNEL_INVALIDE_NUMBER = 115, + } +} diff --git a/Tutorials/PvPGameServer_Client/PacketProcessForm.cs b/Tutorials/PvPGameServer_Client/PacketProcessForm.cs new file mode 100644 index 0000000..c44b007 --- /dev/null +++ b/Tutorials/PvPGameServer_Client/PacketProcessForm.cs @@ -0,0 +1,169 @@ +using MessagePack; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace csharp_test_client +{ + public partial class mainForm + { + Dictionary> PacketFuncDic = new Dictionary>(); + + void SetPacketHandler() + { + PacketFuncDic.Add(PACKET_ID.PACKET_ID_ECHO, PacketProcess_Echo); + PacketFuncDic.Add(PACKET_ID.PACKET_ID_ERROR_NTF, PacketProcess_ErrorNotify); + PacketFuncDic.Add(PACKET_ID.RES_LOGIN, PacketProcess_LoginResponse); + PacketFuncDic.Add(PACKET_ID.RES_ROOM_ENTER, PacketProcess_RoomEnterResponse); + PacketFuncDic.Add(PACKET_ID.NTF_ROOM_USER_LIST, PacketProcess_RoomUserListNotify); + PacketFuncDic.Add(PACKET_ID.NTF_ROOM_NEW_USER, PacketProcess_RoomNewUserNotify); + PacketFuncDic.Add(PACKET_ID.RES_ROOM_LEAVE, PacketProcess_RoomLeaveResponse); + PacketFuncDic.Add(PACKET_ID.NTF_ROOM_LEAVE_USER, PacketProcess_RoomLeaveUserNotify); + PacketFuncDic.Add(PACKET_ID.NTF_ROOM_CHAT, PacketProcess_RoomChatNotify); + //PacketFuncDic.Add(PACKET_ID.PACKET_ID_ROOM_RELAY_NTF, PacketProcess_RoomRelayNotify); + } + + void PacketProcess(byte[] packet) + { + var header = new MsgPackPacketHeadInfo(); + header.Read(packet); + + var packetType = (PACKET_ID)header.Id; + //DevLog.Write("Packet Error: PacketID:{packet.PacketID.ToString()}, Error: {(ERROR_CODE)packet.Result}"); + //DevLog.Write("RawPacket: " + packet.PacketID.ToString() + ", " + PacketDump.Bytes(packet.BodyData)); + + if (PacketFuncDic.ContainsKey(packetType)) + { + PacketFuncDic[packetType](packet); + } + else + { + DevLog.Write("Unknown Packet Id: " + packetType); + } + } + + void PacketProcess_Echo(byte[] bodyData) + { + DevLog.Write($"Echo 받음: {bodyData.Length}"); + } + + void PacketProcess_ErrorNotify(byte[] bodyData) + { + var notifyPkt = new ErrorNtfPacket(); + notifyPkt.FromBytes(bodyData); + + DevLog.Write($"에러 통보 받음: {notifyPkt.Error}"); + } + + + void PacketProcess_LoginResponse(byte[] packetData) + { + var responsePkt = MessagePackSerializer.Deserialize(packetData); + + DevLog.Write($"로그인 결과: {(ERROR_CODE)responsePkt.Result}"); + } + + + void PacketProcess_RoomEnterResponse(byte[] bodyData) + { + var responsePkt = new RoomEnterResPacket(); + responsePkt.FromBytes(bodyData); + + DevLog.Write($"방 입장 결과: {(ERROR_CODE)responsePkt.Result}"); + } + + void PacketProcess_RoomUserListNotify(byte[] bodyData) + { + var notifyPkt = new RoomUserListNtfPacket(); + notifyPkt.FromBytes(bodyData); + + for (int i = 0; i < notifyPkt.UserCount; ++i) + { + AddRoomUserList(notifyPkt.UserUniqueIdList[i], notifyPkt.UserIDList[i]); + } + + DevLog.Write($"방의 기존 유저 리스트 받음"); + } + + void PacketProcess_RoomNewUserNotify(byte[] bodyData) + { + var notifyPkt = new RoomNewUserNtfPacket(); + notifyPkt.FromBytes(bodyData); + + AddRoomUserList(notifyPkt.UserUniqueId, notifyPkt.UserID); + + DevLog.Write($"방에 새로 들어온 유저 받음"); + } + + + void PacketProcess_RoomLeaveResponse(byte[] bodyData) + { + var responsePkt = new RoomLeaveResPacket(); + responsePkt.FromBytes(bodyData); + + DevLog.Write($"방 나가기 결과: {(ERROR_CODE)responsePkt.Result}"); + } + + void PacketProcess_RoomLeaveUserNotify(byte[] bodyData) + { + var notifyPkt = new RoomLeaveUserNtfPacket(); + notifyPkt.FromBytes(bodyData); + + RemoveRoomUserList(notifyPkt.UserUniqueId); + + DevLog.Write($"방에서 나간 유저 받음"); + } + + + void PacketProcess_RoomChatResponse(byte[] bodyData) + { + var responsePkt = new RoomChatResPacket(); + responsePkt.FromBytes(bodyData); + + var errorCode = (ERROR_CODE)responsePkt.Result; + var msg = $"방 채팅 요청 결과: {(ERROR_CODE)responsePkt.Result}"; + if (errorCode == ERROR_CODE.ERROR_NONE) + { + DevLog.Write(msg, LOG_LEVEL.ERROR); + } + else + { + AddRoomChatMessageList(0, msg); + } + } + + + void PacketProcess_RoomChatNotify(byte[] bodyData) + { + var responsePkt = new RoomChatNtfPacket(); + responsePkt.FromBytes(bodyData); + + AddRoomChatMessageList(responsePkt.UserUniqueId, responsePkt.Message); + } + + void AddRoomChatMessageList(Int64 userUniqueId, string msgssage) + { + var msg = $"{userUniqueId}: {msgssage}"; + + if (listBoxRoomChatMsg.Items.Count > 512) + { + listBoxRoomChatMsg.Items.Clear(); + } + + listBoxRoomChatMsg.Items.Add(msg); + listBoxRoomChatMsg.SelectedIndex = listBoxRoomChatMsg.Items.Count - 1; + } + + + void PacketProcess_RoomRelayNotify(byte[] bodyData) + { + var notifyPkt = new RoomRelayNtfPacket(); + notifyPkt.FromBytes(bodyData); + + var stringData = Encoding.UTF8.GetString(notifyPkt.RelayData); + DevLog.Write($"방에서 릴레이 받음. {notifyPkt.UserUniqueId} - {stringData}"); + } + } +} diff --git a/Tutorials/PvPGameServer_Client/Program.cs b/Tutorials/PvPGameServer_Client/Program.cs new file mode 100644 index 0000000..a98e647 --- /dev/null +++ b/Tutorials/PvPGameServer_Client/Program.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace csharp_test_client +{ + static class Program + { + /// + /// 해당 응용 프로그램의 주 진입점입니다. + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new mainForm()); + } + } +} diff --git a/Tutorials/PvPGameServer_Client/Properties/AssemblyInfo.cs b/Tutorials/PvPGameServer_Client/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..d3e425d --- /dev/null +++ b/Tutorials/PvPGameServer_Client/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// 어셈블리에 대한 일반 정보는 다음 특성 집합을 통해 +// 제어됩니다. 어셈블리와 관련된 정보를 수정하려면 +// 이러한 특성 값을 변경하세요. +[assembly: AssemblyTitle("csharp_test_client")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("csharp_test_client")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// ComVisible을 false로 설정하면 이 어셈블리의 형식이 COM 구성 요소에 +// 표시되지 않습니다. COM에서 이 어셈블리의 형식에 액세스하려면 +// 해당 형식에 대해 ComVisible 특성을 true로 설정하세요. +[assembly: ComVisible(false)] + +// 이 프로젝트가 COM에 노출되는 경우 다음 GUID는 typelib의 ID를 나타냅니다. +[assembly: Guid("9e4b5e72-4e76-4e22-90b0-e53275a99018")] + +// 어셈블리의 버전 정보는 다음 네 가지 값으로 구성됩니다. +// +// 주 버전 +// 부 버전 +// 빌드 번호 +// 수정 버전 +// +// 모든 값을 지정하거나 아래와 같이 '*'를 사용하여 빌드 번호 및 수정 번호가 자동으로 +// 지정되도록 할 수 있습니다. +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Tutorials/PvPGameServer_Client/Properties/Resources.Designer.cs b/Tutorials/PvPGameServer_Client/Properties/Resources.Designer.cs new file mode 100644 index 0000000..4d47f26 --- /dev/null +++ b/Tutorials/PvPGameServer_Client/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// 이 코드는 도구를 사용하여 생성되었습니다. +// 런타임 버전:4.0.30319.42000 +// +// 파일 내용을 변경하면 잘못된 동작이 발생할 수 있으며, 코드를 다시 생성하면 +// 이러한 변경 내용이 손실됩니다. +// +//------------------------------------------------------------------------------ + +namespace csharp_test_client.Properties { + using System; + + + /// + /// 지역화된 문자열 등을 찾기 위한 강력한 형식의 리소스 클래스입니다. + /// + // 이 클래스는 ResGen 또는 Visual Studio와 같은 도구를 통해 StronglyTypedResourceBuilder + // 클래스에서 자동으로 생성되었습니다. + // 멤버를 추가하거나 제거하려면 .ResX 파일을 편집한 다음 /str 옵션을 사용하여 ResGen을 + // 다시 실행하거나 VS 프로젝트를 다시 빌드하십시오. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// 이 클래스에서 사용하는 캐시된 ResourceManager 인스턴스를 반환합니다. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("csharp_test_client.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// 이 강력한 형식의 리소스 클래스를 사용하여 모든 리소스 조회에 대해 현재 스레드의 CurrentUICulture 속성을 + /// 재정의합니다. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/Tutorials/PvPGameServer_Client/Properties/Resources.resx b/Tutorials/PvPGameServer_Client/Properties/Resources.resx new file mode 100644 index 0000000..ffecec8 --- /dev/null +++ b/Tutorials/PvPGameServer_Client/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Tutorials/PvPGameServer_Client/Properties/Settings.Designer.cs b/Tutorials/PvPGameServer_Client/Properties/Settings.Designer.cs new file mode 100644 index 0000000..65920ff --- /dev/null +++ b/Tutorials/PvPGameServer_Client/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// 이 코드는 도구를 사용하여 생성되었습니다. +// 런타임 버전:4.0.30319.42000 +// +// 파일 내용을 변경하면 잘못된 동작이 발생할 수 있으며, 코드를 다시 생성하면 +// 이러한 변경 내용이 손실됩니다. +// +//------------------------------------------------------------------------------ + +namespace csharp_test_client.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.7.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/Tutorials/PvPGameServer_Client/Properties/Settings.settings b/Tutorials/PvPGameServer_Client/Properties/Settings.settings new file mode 100644 index 0000000..abf36c5 --- /dev/null +++ b/Tutorials/PvPGameServer_Client/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Tutorials/PvPGameServer_Client/csharp_test_client.csproj b/Tutorials/PvPGameServer_Client/csharp_test_client.csproj new file mode 100644 index 0000000..4a30307 --- /dev/null +++ b/Tutorials/PvPGameServer_Client/csharp_test_client.csproj @@ -0,0 +1,139 @@ + + + + + Debug + AnyCPU + {9E4B5E72-4E76-4E22-90B0-E53275A99018} + WinExe + Properties + csharp_test_client + csharp_test_client + v4.7.2 + 512 + true + + + + AnyCPU + true + full + false + ..\bin\ + DEBUG;TRACE + prompt + 4 + false + true + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + false + true + + + + packages\MessagePack.2.2.60\lib\netstandard2.0\MessagePack.dll + + + packages\MessagePack.Annotations.2.2.60\lib\netstandard2.0\MessagePack.Annotations.dll + + + packages\Microsoft.Bcl.AsyncInterfaces.1.0.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll + + + + packages\System.Buffers.4.4.0\lib\netstandard2.0\System.Buffers.dll + + + packages\System.Collections.Immutable.1.5.0\lib\netstandard2.0\System.Collections.Immutable.dll + + + + packages\System.Memory.4.5.3\lib\netstandard2.0\System.Memory.dll + + + + packages\System.Numerics.Vectors.4.4.0\lib\net46\System.Numerics.Vectors.dll + + + packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll + + + packages\System.Threading.Tasks.Extensions.4.5.3\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll + + + + + + + + + + + + + packages\Z.ExtensionMethods.2.1.1\lib\net45\Z.ExtensionMethods.dll + + + + + + + + + Form + + + mainForm.cs + + + + + + Form + + + + + mainForm.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + True + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + + \ No newline at end of file diff --git a/Tutorials/PvPGameServer_Client/csharp_test_client.sln b/Tutorials/PvPGameServer_Client/csharp_test_client.sln new file mode 100644 index 0000000..8a34e3c --- /dev/null +++ b/Tutorials/PvPGameServer_Client/csharp_test_client.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "csharp_test_client", "csharp_test_client.csproj", "{9E4B5E72-4E76-4E22-90B0-E53275A99018}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9E4B5E72-4E76-4E22-90B0-E53275A99018}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9E4B5E72-4E76-4E22-90B0-E53275A99018}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9E4B5E72-4E76-4E22-90B0-E53275A99018}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9E4B5E72-4E76-4E22-90B0-E53275A99018}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Tutorials/PvPGameServer_Client/mainForm.Designer.cs b/Tutorials/PvPGameServer_Client/mainForm.Designer.cs new file mode 100644 index 0000000..327b596 --- /dev/null +++ b/Tutorials/PvPGameServer_Client/mainForm.Designer.cs @@ -0,0 +1,431 @@ +namespace csharp_test_client +{ + partial class mainForm + { + /// + /// 필수 디자이너 변수입니다. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// 사용 중인 모든 리소스를 정리합니다. + /// + /// 관리되는 리소스를 삭제해야 하면 true이고, 그렇지 않으면 false입니다. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form 디자이너에서 생성한 코드 + + /// + /// 디자이너 지원에 필요한 메서드입니다. + /// 이 메서드의 내용을 코드 편집기로 수정하지 마세요. + /// + private void InitializeComponent() + { + this.btnDisconnect = new System.Windows.Forms.Button(); + this.btnConnect = new System.Windows.Forms.Button(); + this.groupBox5 = new System.Windows.Forms.GroupBox(); + this.textBoxPort = new System.Windows.Forms.TextBox(); + this.label10 = new System.Windows.Forms.Label(); + this.checkBoxLocalHostIP = new System.Windows.Forms.CheckBox(); + this.textBoxIP = new System.Windows.Forms.TextBox(); + this.label9 = new System.Windows.Forms.Label(); + this.button1 = new System.Windows.Forms.Button(); + this.textSendText = new System.Windows.Forms.TextBox(); + this.labelStatus = new System.Windows.Forms.Label(); + this.listBoxLog = new System.Windows.Forms.ListBox(); + this.label1 = new System.Windows.Forms.Label(); + this.textBoxUserID = new System.Windows.Forms.TextBox(); + this.textBoxUserPW = new System.Windows.Forms.TextBox(); + this.label2 = new System.Windows.Forms.Label(); + this.button2 = new System.Windows.Forms.Button(); + this.Room = new System.Windows.Forms.GroupBox(); + this.textBoxRelay = new System.Windows.Forms.TextBox(); + this.btnRoomRelay = new System.Windows.Forms.Button(); + this.btnRoomChat = new System.Windows.Forms.Button(); + this.textBoxRoomSendMsg = new System.Windows.Forms.TextBox(); + this.listBoxRoomChatMsg = new System.Windows.Forms.ListBox(); + this.label4 = new System.Windows.Forms.Label(); + this.listBoxRoomUserList = new System.Windows.Forms.ListBox(); + this.btn_RoomLeave = new System.Windows.Forms.Button(); + this.btn_RoomEnter = new System.Windows.Forms.Button(); + this.textBoxRoomNumber = new System.Windows.Forms.TextBox(); + this.label3 = new System.Windows.Forms.Label(); + this.groupBox5.SuspendLayout(); + this.Room.SuspendLayout(); + this.SuspendLayout(); + // + // btnDisconnect + // + this.btnDisconnect.Font = new System.Drawing.Font("맑은 고딕", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(129))); + this.btnDisconnect.Location = new System.Drawing.Point(481, 48); + this.btnDisconnect.Name = "btnDisconnect"; + this.btnDisconnect.Size = new System.Drawing.Size(100, 28); + this.btnDisconnect.TabIndex = 29; + this.btnDisconnect.Text = "접속 끊기"; + this.btnDisconnect.UseVisualStyleBackColor = true; + this.btnDisconnect.Click += new System.EventHandler(this.btnDisconnect_Click); + // + // btnConnect + // + this.btnConnect.Font = new System.Drawing.Font("맑은 고딕", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(129))); + this.btnConnect.Location = new System.Drawing.Point(480, 17); + this.btnConnect.Name = "btnConnect"; + this.btnConnect.Size = new System.Drawing.Size(101, 28); + this.btnConnect.TabIndex = 28; + this.btnConnect.Text = "접속하기"; + this.btnConnect.UseVisualStyleBackColor = true; + this.btnConnect.Click += new System.EventHandler(this.btnConnect_Click); + // + // groupBox5 + // + this.groupBox5.Controls.Add(this.textBoxPort); + this.groupBox5.Controls.Add(this.label10); + this.groupBox5.Controls.Add(this.checkBoxLocalHostIP); + this.groupBox5.Controls.Add(this.textBoxIP); + this.groupBox5.Controls.Add(this.label9); + this.groupBox5.Location = new System.Drawing.Point(14, 13); + this.groupBox5.Name = "groupBox5"; + this.groupBox5.Size = new System.Drawing.Size(461, 56); + this.groupBox5.TabIndex = 27; + this.groupBox5.TabStop = false; + this.groupBox5.Text = "Socket 더미 클라이언트 설정"; + // + // textBoxPort + // + this.textBoxPort.Location = new System.Drawing.Point(257, 22); + this.textBoxPort.MaxLength = 6; + this.textBoxPort.Name = "textBoxPort"; + this.textBoxPort.Size = new System.Drawing.Size(58, 22); + this.textBoxPort.TabIndex = 18; + this.textBoxPort.Text = "32452"; + this.textBoxPort.WordWrap = false; + // + // label10 + // + this.label10.AutoSize = true; + this.label10.Location = new System.Drawing.Point(186, 26); + this.label10.Name = "label10"; + this.label10.Size = new System.Drawing.Size(73, 14); + this.label10.TabIndex = 17; + this.label10.Text = "포트 번호:"; + // + // checkBoxLocalHostIP + // + this.checkBoxLocalHostIP.AutoSize = true; + this.checkBoxLocalHostIP.Checked = true; + this.checkBoxLocalHostIP.CheckState = System.Windows.Forms.CheckState.Checked; + this.checkBoxLocalHostIP.Location = new System.Drawing.Point(326, 26); + this.checkBoxLocalHostIP.Name = "checkBoxLocalHostIP"; + this.checkBoxLocalHostIP.Size = new System.Drawing.Size(119, 18); + this.checkBoxLocalHostIP.TabIndex = 15; + this.checkBoxLocalHostIP.Text = "localhost 사용"; + this.checkBoxLocalHostIP.UseVisualStyleBackColor = true; + // + // textBoxIP + // + this.textBoxIP.Location = new System.Drawing.Point(78, 21); + this.textBoxIP.MaxLength = 6; + this.textBoxIP.Name = "textBoxIP"; + this.textBoxIP.Size = new System.Drawing.Size(99, 22); + this.textBoxIP.TabIndex = 11; + this.textBoxIP.Text = "0.0.0.0"; + this.textBoxIP.WordWrap = false; + // + // label9 + // + this.label9.AutoSize = true; + this.label9.Location = new System.Drawing.Point(7, 25); + this.label9.Name = "label9"; + this.label9.Size = new System.Drawing.Size(73, 14); + this.label9.TabIndex = 10; + this.label9.Text = "서버 주소:"; + // + // button1 + // + this.button1.Font = new System.Drawing.Font("맑은 고딕", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(129))); + this.button1.Location = new System.Drawing.Point(365, 76); + this.button1.Name = "button1"; + this.button1.Size = new System.Drawing.Size(114, 28); + this.button1.TabIndex = 39; + this.button1.Text = "echo 보내기"; + this.button1.UseVisualStyleBackColor = true; + this.button1.Click += new System.EventHandler(this.button1_Click); + // + // textSendText + // + this.textSendText.Location = new System.Drawing.Point(14, 80); + this.textSendText.MaxLength = 32; + this.textSendText.Name = "textSendText"; + this.textSendText.Size = new System.Drawing.Size(343, 22); + this.textSendText.TabIndex = 38; + this.textSendText.Text = "test1"; + this.textSendText.WordWrap = false; + // + // labelStatus + // + this.labelStatus.AutoSize = true; + this.labelStatus.Location = new System.Drawing.Point(12, 623); + this.labelStatus.Name = "labelStatus"; + this.labelStatus.Size = new System.Drawing.Size(135, 14); + this.labelStatus.TabIndex = 40; + this.labelStatus.Text = "서버 접속 상태: ???"; + // + // listBoxLog + // + this.listBoxLog.FormattingEnabled = true; + this.listBoxLog.HorizontalScrollbar = true; + this.listBoxLog.Location = new System.Drawing.Point(12, 461); + this.listBoxLog.Name = "listBoxLog"; + this.listBoxLog.Size = new System.Drawing.Size(559, 147); + this.listBoxLog.TabIndex = 41; + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(12, 133); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(55, 14); + this.label1.TabIndex = 42; + this.label1.Text = "UserID:"; + // + // textBoxUserID + // + this.textBoxUserID.Location = new System.Drawing.Point(71, 129); + this.textBoxUserID.MaxLength = 6; + this.textBoxUserID.Name = "textBoxUserID"; + this.textBoxUserID.Size = new System.Drawing.Size(99, 22); + this.textBoxUserID.TabIndex = 43; + this.textBoxUserID.Text = "jacking75"; + this.textBoxUserID.WordWrap = false; + // + // textBoxUserPW + // + this.textBoxUserPW.Location = new System.Drawing.Point(252, 130); + this.textBoxUserPW.MaxLength = 6; + this.textBoxUserPW.Name = "textBoxUserPW"; + this.textBoxUserPW.Size = new System.Drawing.Size(99, 22); + this.textBoxUserPW.TabIndex = 45; + this.textBoxUserPW.Text = "jacking75"; + this.textBoxUserPW.WordWrap = false; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(180, 133); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(70, 14); + this.label2.TabIndex = 44; + this.label2.Text = "PassWD:"; + // + // button2 + // + this.button2.Font = new System.Drawing.Font("맑은 고딕", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(129))); + this.button2.Location = new System.Drawing.Point(361, 125); + this.button2.Name = "button2"; + this.button2.Size = new System.Drawing.Size(114, 28); + this.button2.TabIndex = 46; + this.button2.Text = "Login"; + this.button2.UseVisualStyleBackColor = true; + this.button2.Click += new System.EventHandler(this.button2_Click); + // + // Room + // + this.Room.Controls.Add(this.textBoxRelay); + this.Room.Controls.Add(this.btnRoomRelay); + this.Room.Controls.Add(this.btnRoomChat); + this.Room.Controls.Add(this.textBoxRoomSendMsg); + this.Room.Controls.Add(this.listBoxRoomChatMsg); + this.Room.Controls.Add(this.label4); + this.Room.Controls.Add(this.listBoxRoomUserList); + this.Room.Controls.Add(this.btn_RoomLeave); + this.Room.Controls.Add(this.btn_RoomEnter); + this.Room.Controls.Add(this.textBoxRoomNumber); + this.Room.Controls.Add(this.label3); + this.Room.Location = new System.Drawing.Point(15, 185); + this.Room.Name = "Room"; + this.Room.Size = new System.Drawing.Size(566, 270); + this.Room.TabIndex = 47; + this.Room.TabStop = false; + this.Room.Text = "Room"; + // + // textBoxRelay + // + this.textBoxRelay.Location = new System.Drawing.Point(350, 26); + this.textBoxRelay.MaxLength = 6; + this.textBoxRelay.Name = "textBoxRelay"; + this.textBoxRelay.Size = new System.Drawing.Size(124, 22); + this.textBoxRelay.TabIndex = 55; + this.textBoxRelay.Text = "test"; + this.textBoxRelay.WordWrap = false; + // + // btnRoomRelay + // + this.btnRoomRelay.Font = new System.Drawing.Font("맑은 고딕", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(129))); + this.btnRoomRelay.Location = new System.Drawing.Point(480, 22); + this.btnRoomRelay.Name = "btnRoomRelay"; + this.btnRoomRelay.Size = new System.Drawing.Size(76, 28); + this.btnRoomRelay.TabIndex = 54; + this.btnRoomRelay.Text = "Relay"; + this.btnRoomRelay.UseVisualStyleBackColor = true; + this.btnRoomRelay.Click += new System.EventHandler(this.btnRoomRelay_Click); + // + // btnRoomChat + // + this.btnRoomChat.Font = new System.Drawing.Font("맑은 고딕", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(129))); + this.btnRoomChat.Location = new System.Drawing.Point(499, 230); + this.btnRoomChat.Name = "btnRoomChat"; + this.btnRoomChat.Size = new System.Drawing.Size(57, 28); + this.btnRoomChat.TabIndex = 53; + this.btnRoomChat.Text = "chat"; + this.btnRoomChat.UseVisualStyleBackColor = true; + this.btnRoomChat.Click += new System.EventHandler(this.btnRoomChat_Click); + // + // textBoxRoomSendMsg + // + this.textBoxRoomSendMsg.Location = new System.Drawing.Point(15, 233); + this.textBoxRoomSendMsg.MaxLength = 32; + this.textBoxRoomSendMsg.Name = "textBoxRoomSendMsg"; + this.textBoxRoomSendMsg.Size = new System.Drawing.Size(478, 22); + this.textBoxRoomSendMsg.TabIndex = 52; + this.textBoxRoomSendMsg.Text = "test1"; + this.textBoxRoomSendMsg.WordWrap = false; + // + // listBoxRoomChatMsg + // + this.listBoxRoomChatMsg.FormattingEnabled = true; + this.listBoxRoomChatMsg.Location = new System.Drawing.Point(165, 70); + this.listBoxRoomChatMsg.Name = "listBoxRoomChatMsg"; + this.listBoxRoomChatMsg.Size = new System.Drawing.Size(391, 147); + this.listBoxRoomChatMsg.TabIndex = 51; + // + // label4 + // + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(12, 55); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(70, 14); + this.label4.TabIndex = 50; + this.label4.Text = "User List:"; + // + // listBoxRoomUserList + // + this.listBoxRoomUserList.FormattingEnabled = true; + this.listBoxRoomUserList.Location = new System.Drawing.Point(15, 71); + this.listBoxRoomUserList.Name = "listBoxRoomUserList"; + this.listBoxRoomUserList.Size = new System.Drawing.Size(140, 147); + this.listBoxRoomUserList.TabIndex = 49; + // + // btn_RoomLeave + // + this.btn_RoomLeave.Font = new System.Drawing.Font("맑은 고딕", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(129))); + this.btn_RoomLeave.Location = new System.Drawing.Point(247, 21); + this.btn_RoomLeave.Name = "btn_RoomLeave"; + this.btn_RoomLeave.Size = new System.Drawing.Size(76, 28); + this.btn_RoomLeave.TabIndex = 48; + this.btn_RoomLeave.Text = "Leave"; + this.btn_RoomLeave.UseVisualStyleBackColor = true; + this.btn_RoomLeave.Click += new System.EventHandler(this.btn_RoomLeave_Click); + // + // btn_RoomEnter + // + this.btn_RoomEnter.Font = new System.Drawing.Font("맑은 고딕", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(129))); + this.btn_RoomEnter.Location = new System.Drawing.Point(165, 20); + this.btn_RoomEnter.Name = "btn_RoomEnter"; + this.btn_RoomEnter.Size = new System.Drawing.Size(76, 28); + this.btn_RoomEnter.TabIndex = 47; + this.btn_RoomEnter.Text = "Enter"; + this.btn_RoomEnter.UseVisualStyleBackColor = true; + this.btn_RoomEnter.Click += new System.EventHandler(this.btn_RoomEnter_Click); + // + // textBoxRoomNumber + // + this.textBoxRoomNumber.Location = new System.Drawing.Point(112, 22); + this.textBoxRoomNumber.MaxLength = 6; + this.textBoxRoomNumber.Name = "textBoxRoomNumber"; + this.textBoxRoomNumber.Size = new System.Drawing.Size(43, 22); + this.textBoxRoomNumber.TabIndex = 44; + this.textBoxRoomNumber.Text = "0"; + this.textBoxRoomNumber.WordWrap = false; + // + // label3 + // + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(6, 27); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(104, 14); + this.label3.TabIndex = 43; + this.label3.Text = "Room Number:"; + // + // mainForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(599, 651); + this.Controls.Add(this.Room); + this.Controls.Add(this.button2); + this.Controls.Add(this.textBoxUserPW); + this.Controls.Add(this.label2); + this.Controls.Add(this.textBoxUserID); + this.Controls.Add(this.label1); + this.Controls.Add(this.labelStatus); + this.Controls.Add(this.listBoxLog); + this.Controls.Add(this.button1); + this.Controls.Add(this.textSendText); + this.Controls.Add(this.btnDisconnect); + this.Controls.Add(this.btnConnect); + this.Controls.Add(this.groupBox5); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Fixed3D; + this.Name = "mainForm"; + this.Text = "네트워크 테스트 클라이언트"; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.mainForm_FormClosing); + this.Load += new System.EventHandler(this.mainForm_Load); + this.groupBox5.ResumeLayout(false); + this.groupBox5.PerformLayout(); + this.Room.ResumeLayout(false); + this.Room.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Button btnDisconnect; + private System.Windows.Forms.Button btnConnect; + private System.Windows.Forms.GroupBox groupBox5; + private System.Windows.Forms.TextBox textBoxPort; + private System.Windows.Forms.Label label10; + private System.Windows.Forms.CheckBox checkBoxLocalHostIP; + private System.Windows.Forms.TextBox textBoxIP; + private System.Windows.Forms.Label label9; + private System.Windows.Forms.Button button1; + private System.Windows.Forms.TextBox textSendText; + private System.Windows.Forms.Label labelStatus; + private System.Windows.Forms.ListBox listBoxLog; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.TextBox textBoxUserID; + private System.Windows.Forms.TextBox textBoxUserPW; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Button button2; + private System.Windows.Forms.GroupBox Room; + private System.Windows.Forms.Button btn_RoomLeave; + private System.Windows.Forms.Button btn_RoomEnter; + private System.Windows.Forms.TextBox textBoxRoomNumber; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.Button btnRoomChat; + private System.Windows.Forms.TextBox textBoxRoomSendMsg; + private System.Windows.Forms.ListBox listBoxRoomChatMsg; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.ListBox listBoxRoomUserList; + private System.Windows.Forms.Button btnRoomRelay; + private System.Windows.Forms.TextBox textBoxRelay; + } +} + diff --git a/Tutorials/PvPGameServer_Client/mainForm.cs b/Tutorials/PvPGameServer_Client/mainForm.cs new file mode 100644 index 0000000..cafc85b --- /dev/null +++ b/Tutorials/PvPGameServer_Client/mainForm.cs @@ -0,0 +1,362 @@ +using MessagePack; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace csharp_test_client +{ + public partial class mainForm : Form + { + ClientSimpleTcp Network = new ClientSimpleTcp(); + + bool IsNetworkThreadRunning = false; + bool IsBackGroundProcessRunning = false; + + System.Threading.Thread NetworkReadThread = null; + System.Threading.Thread NetworkSendThread = null; + + PacketBufferManager PacketBuffer = new PacketBufferManager(); + Queue RecvPacketQueue = new Queue(); + Queue SendPacketQueue = new Queue(); + + System.Windows.Threading.DispatcherTimer dispatcherUITimer; + + + public mainForm() + { + InitializeComponent(); + } + + private void mainForm_Load(object sender, EventArgs e) + { + PacketBuffer.Init((8096 * 10), MsgPackPacketHeadInfo.HeadSize, 1024); + + IsNetworkThreadRunning = true; + NetworkReadThread = new System.Threading.Thread(this.NetworkReadProcess); + NetworkReadThread.Start(); + NetworkSendThread = new System.Threading.Thread(this.NetworkSendProcess); + NetworkSendThread.Start(); + + IsBackGroundProcessRunning = true; + dispatcherUITimer = new System.Windows.Threading.DispatcherTimer(); + dispatcherUITimer.Tick += new EventHandler(BackGroundProcess); + dispatcherUITimer.Interval = new TimeSpan(0, 0, 0, 0, 100); + dispatcherUITimer.Start(); + + btnDisconnect.Enabled = false; + + SetPacketHandler(); + DevLog.Write("프로그램 시작 !!!", LOG_LEVEL.INFO); + } + + private void mainForm_FormClosing(object sender, FormClosingEventArgs e) + { + IsNetworkThreadRunning = false; + IsBackGroundProcessRunning = false; + + Network.Close(); + } + + private void btnConnect_Click(object sender, EventArgs e) + { + string address = textBoxIP.Text; + + if (checkBoxLocalHostIP.Checked) + { + address = "127.0.0.1"; + } + + int port = Convert.ToInt32(textBoxPort.Text); + + if (Network.Connect(address, port)) + { + labelStatus.Text = string.Format("{0}. 서버에 접속 중", DateTime.Now); + btnConnect.Enabled = false; + btnDisconnect.Enabled = true; + + DevLog.Write($"서버에 접속 중", LOG_LEVEL.INFO); + } + else + { + labelStatus.Text = string.Format("{0}. 서버에 접속 실패", DateTime.Now); + } + } + + private void btnDisconnect_Click(object sender, EventArgs e) + { + SetDisconnectd(); + Network.Close(); + } + + private void button1_Click(object sender, EventArgs e) + { + if (string.IsNullOrEmpty(textSendText.Text)) + { + MessageBox.Show("보낼 텍스트를 입력하세요"); + return; + } + + var body = Encoding.UTF8.GetBytes(textSendText.Text); + + PostSendPacket(PACKET_ID.PACKET_ID_ECHO, body); + + DevLog.Write($"Echo 요청: {textSendText.Text}, {body.Length}"); + } + + + + void NetworkReadProcess() + { + while (IsNetworkThreadRunning) + { + if (Network.IsConnected() == false) + { + System.Threading.Thread.Sleep(1); + continue; + } + + var recvData = Network.Receive(); + + if (recvData != null) + { + PacketBuffer.Write(recvData.Item2, 0, recvData.Item1); + + while (true) + { + var data = PacketBuffer.Read(); + if (data == null) + { + break; + } + + lock (((System.Collections.ICollection)RecvPacketQueue).SyncRoot) + { + RecvPacketQueue.Enqueue(data); + } + } + //DevLog.Write($"받은 데이터: {recvData.Item2}", LOG_LEVEL.INFO); + } + else + { + Network.Close(); + SetDisconnectd(); + DevLog.Write("서버와 접속 종료 !!!", LOG_LEVEL.INFO); + } + } + } + + void NetworkSendProcess() + { + while (IsNetworkThreadRunning) + { + System.Threading.Thread.Sleep(1); + + if (Network.IsConnected() == false) + { + continue; + } + + lock (((System.Collections.ICollection)SendPacketQueue).SyncRoot) + { + if (SendPacketQueue.Count > 0) + { + var packet = SendPacketQueue.Dequeue(); + Network.Send(packet); + } + } + } + } + + + void BackGroundProcess(object sender, EventArgs e) + { + ProcessLog(); + + try + { + byte[] packet = null; + + lock (((System.Collections.ICollection)RecvPacketQueue).SyncRoot) + { + if (RecvPacketQueue.Count() > 0) + { + packet = RecvPacketQueue.Dequeue(); + } + } + + if (packet != null) + { + PacketProcess(packet); + } + } + catch (Exception ex) + { + MessageBox.Show(string.Format("ReadPacketQueueProcess. error:{0}", ex.Message)); + } + } + + private void ProcessLog() + { + // 너무 이 작업만 할 수 없으므로 일정 작업 이상을 하면 일단 패스한다. + int logWorkCount = 0; + + while (IsBackGroundProcessRunning) + { + System.Threading.Thread.Sleep(1); + + string msg; + + if (DevLog.GetLog(out msg)) + { + ++logWorkCount; + + if (listBoxLog.Items.Count > 512) + { + listBoxLog.Items.Clear(); + } + + listBoxLog.Items.Add(msg); + listBoxLog.SelectedIndex = listBoxLog.Items.Count - 1; + } + else + { + break; + } + + if (logWorkCount > 8) + { + break; + } + } + } + + + public void SetDisconnectd() + { + if (btnConnect.Enabled == false) + { + btnConnect.Enabled = true; + btnDisconnect.Enabled = false; + } + + SendPacketQueue.Clear(); + + listBoxRoomChatMsg.Items.Clear(); + listBoxRoomUserList.Items.Clear(); + + labelStatus.Text = "서버 접속이 끊어짐"; + } + + public void PostSendPacket(PACKET_ID packetID, byte[] packetData) + { + if (Network.IsConnected() == false) + { + DevLog.Write("서버 연결이 되어 있지 않습니다", LOG_LEVEL.ERROR); + return; + } + + var header = new MsgPackPacketHeadInfo(); + header.TotalSize = (UInt16)packetData.Length; + header.Id = (UInt16)packetID; + header.Type = 0; + header.Write(packetData); + + SendPacketQueue.Enqueue(packetData); + } + + void AddRoomUserList(Int64 userUniqueId, string userID) + { + var msg = $"{userUniqueId}: {userID}"; + listBoxRoomUserList.Items.Add(msg); + } + + void RemoveRoomUserList(Int64 userUniqueId) + { + object removeItem = null; + + foreach( var user in listBoxRoomUserList.Items) + { + var items = user.ToString().Split(":"); + if( items[0].ToInt64() == userUniqueId) + { + removeItem = user; + return; + } + } + + if (removeItem != null) + { + listBoxRoomUserList.Items.Remove(removeItem); + } + } + + static public string ToReadableByteArray(byte[] bytes) + { + return string.Join(", ", bytes); + } + + + // 로그인 요청 + private void button2_Click(object sender, EventArgs e) + { + var loginReq = new PKTReqLogin(); + loginReq.UserID = textBoxUserID.Text; + loginReq.AuthToken = textBoxUserPW.Text; + + var sendPacketData = MessagePackSerializer.Serialize(loginReq); + + PostSendPacket(PACKET_ID.REQ_LOGIN, sendPacketData); + DevLog.Write($"로그인 요청: {textBoxUserID.Text}, {textBoxUserPW.Text}"); + DevLog.Write($"로그인 요청: {ToReadableByteArray(sendPacketData)}"); + } + + private void btn_RoomEnter_Click(object sender, EventArgs e) + { + var requestPkt = new RoomEnterReqPacket(); + requestPkt.SetValue(textBoxRoomNumber.Text.ToInt32()); + + PostSendPacket(PACKET_ID.REQ_ROOM_ENTER, requestPkt.ToBytes()); + DevLog.Write($"방 입장 요청: {textBoxRoomNumber.Text} 번"); + } + + private void btn_RoomLeave_Click(object sender, EventArgs e) + { + PostSendPacket(PACKET_ID.REQ_ROOM_LEAVE, null); + DevLog.Write($"방 입장 요청: {textBoxRoomNumber.Text} 번"); + } + + private void btnRoomChat_Click(object sender, EventArgs e) + { + if(textBoxRoomSendMsg.Text.IsEmpty()) + { + MessageBox.Show("채팅 메시지를 입력하세요"); + return; + } + + var requestPkt = new RoomChatReqPacket(); + requestPkt.SetValue(textBoxRoomSendMsg.Text); + + PostSendPacket(PACKET_ID.REQ_ROOM_CHAT, requestPkt.ToBytes()); + DevLog.Write($"방 채팅 요청"); + } + + private void btnRoomRelay_Click(object sender, EventArgs e) + { + if( textBoxRelay.Text.IsEmpty()) + { + MessageBox.Show("릴레이 할 데이터가 없습니다"); + return; + } + + /*var bodyData = Encoding.UTF8.GetBytes(textBoxRelay.Text); + PostSendPacket(PACKET_ID.PACKET_ID_ROOM_RELAY_REQ, bodyData); + DevLog.Write($"방 릴레이 요청");*/ + } + } +} diff --git a/Tutorials/PvPGameServer_Client/mainForm.resx b/Tutorials/PvPGameServer_Client/mainForm.resx new file mode 100644 index 0000000..29dcb1b --- /dev/null +++ b/Tutorials/PvPGameServer_Client/mainForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Tutorials/PvPGameServer_Client/packages.config b/Tutorials/PvPGameServer_Client/packages.config new file mode 100644 index 0000000..ad29990 --- /dev/null +++ b/Tutorials/PvPGameServer_Client/packages.config @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file