├── .gitignore ├── LICENSE ├── README.md ├── build ├── .gitignore └── Network.sln ├── src ├── .gitignore ├── ClientConnector.cs ├── ClientNetwork.cs ├── Connector.cs ├── ConnectorBuffer.cs ├── HeartbeatManager.cs ├── Helper.cs ├── Log.cs ├── Message.cs ├── Network.csproj ├── Properties │ └── AssemblyInfo.cs ├── RC4.cs ├── ServerNetwork.cs └── SwapContainer.cs └── test ├── Network.Test.Client ├── .gitignore ├── App.config ├── Network.Test.Client.csproj ├── Program.cs └── Properties │ └── AssemblyInfo.cs └── Network.Test.Server ├── .gitignore ├── App.config ├── Network.Test.Server.csproj ├── Program.cs └── Properties └── AssemblyInfo.cs /.gitignore: -------------------------------------------------------------------------------- 1 | bin -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Network 2 | a socket wrapper written by csharp 3 | -------------------------------------------------------------------------------- /build/.gitignore: -------------------------------------------------------------------------------- 1 | .vs -------------------------------------------------------------------------------- /build/Network.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.23107.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Network", "..\src\Network.csproj", "{5E6106D6-D3C8-4728-AE20-637B346884C2}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Network.Test.Client", "..\test\Network.Test.Client\Network.Test.Client.csproj", "{14FF79B1-496D-4318-94F3-3F9FE0DCDBEA}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Network.Test.Server", "..\test\Network.Test.Server\Network.Test.Server.csproj", "{22306A9B-F19C-42AB-88FA-75926F6E1C99}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {5E6106D6-D3C8-4728-AE20-637B346884C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {5E6106D6-D3C8-4728-AE20-637B346884C2}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {5E6106D6-D3C8-4728-AE20-637B346884C2}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {5E6106D6-D3C8-4728-AE20-637B346884C2}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {14FF79B1-496D-4318-94F3-3F9FE0DCDBEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {14FF79B1-496D-4318-94F3-3F9FE0DCDBEA}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {14FF79B1-496D-4318-94F3-3F9FE0DCDBEA}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {14FF79B1-496D-4318-94F3-3F9FE0DCDBEA}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {22306A9B-F19C-42AB-88FA-75926F6E1C99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {22306A9B-F19C-42AB-88FA-75926F6E1C99}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {22306A9B-F19C-42AB-88FA-75926F6E1C99}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {22306A9B-F19C-42AB-88FA-75926F6E1C99}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | EndGlobal 35 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | obj/ 2 | bin/ 3 | ~* 4 | -------------------------------------------------------------------------------- /src/ClientConnector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Net.Sockets; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Network 10 | { 11 | public interface ISocketClient 12 | { 13 | 14 | } 15 | 16 | class ClientConnector 17 | { 18 | protected const int HeadLen = 4; 19 | 20 | // system socket 21 | private Socket sysSocket; 22 | 23 | private ConnectorBuffer receiveBuffer = new ConnectorBuffer(); 24 | private ConnectorBuffer sendBuffer = new ConnectorBuffer(); 25 | 26 | private readonly SwapContainer> msgQueue = new SwapContainer>(); 27 | 28 | protected RC4 rc4Read; 29 | protected RC4 rc4Write; 30 | 31 | public bool DefferedClose { get; protected set; } //如果出了异常等,会把这个值设true,然后在外层被断开;关闭服务器时也会设置 32 | public bool Connected { get; set; } 33 | 34 | public int Id { get; private set; } 35 | public string RemoteIp 36 | { 37 | get 38 | { 39 | if (sysSocket != null) 40 | { 41 | var ipEndPoint = sysSocket.RemoteEndPoint as IPEndPoint; 42 | if (ipEndPoint != null) 43 | { 44 | return ipEndPoint.Address.ToString(); 45 | } 46 | } 47 | return ""; 48 | } 49 | } 50 | public int RemotePort 51 | { 52 | get 53 | { 54 | if (sysSocket != null) 55 | { 56 | var ipEndPoint = sysSocket.RemoteEndPoint as IPEndPoint; 57 | if (ipEndPoint != null) 58 | { 59 | return ipEndPoint.Port; 60 | } 61 | } 62 | return 0; 63 | } 64 | } 65 | 66 | public ClientConnector(Socket s, int id) 67 | { 68 | sysSocket = s; 69 | Id = id; 70 | } 71 | 72 | public int Push(byte[] buffer, int size) 73 | { 74 | if (!Connected) 75 | { 76 | return -1; 77 | } 78 | 79 | if (rc4Write != null) //发送前做rc4加密 80 | { 81 | rc4Write.Encrypt(buffer, size); 82 | } 83 | 84 | var headData = new byte[HeadLen]; 85 | 86 | Helper.Int32ToByteArray(size, headData, 0); 87 | 88 | sendBuffer.PushData(headData, headData.Length); 89 | sendBuffer.PushData(buffer, buffer.Length); 90 | 91 | return size + HeadLen; 92 | } 93 | 94 | public void Send() 95 | { 96 | if (sendBuffer.Length <= 0)//没有任何数据 97 | { 98 | return; 99 | } 100 | 101 | try 102 | { 103 | //SysSocket.BeginSend(sendBuffer.ToArray(), 0, sendBuffer.Count, SocketFlags.None, null, null); 104 | int realSendLen = sysSocket.Send(sendBuffer.Buffer, sendBuffer.Start, sendBuffer.Length, SocketFlags.None); 105 | //LastTickSend += realSendLen; 106 | 107 | if (realSendLen == sendBuffer.Length) 108 | { 109 | //if (sendBuffer.Length >= 1024 * 4) 110 | //{ 111 | // if (endPoint < sendBuffer.Length / 4) //一直达不到25%使用率,会释放掉一般空间 112 | // { 113 | // shrinkHint++; 114 | // if (shrinkHint == 5) 115 | // { 116 | // sendBuffer = new byte[sendBuffer.Length / 4]; 117 | // shrinkHint = 0; 118 | // } 119 | // } 120 | // else 121 | // { 122 | // shrinkHint = 0; 123 | // } 124 | //} 125 | sendBuffer.Reset(); 126 | } 127 | else 128 | { 129 | sendBuffer.Pop(realSendLen); 130 | } 131 | } 132 | catch (SocketException e) 133 | { 134 | if (e.ErrorCode != 10035) //WSAEWOULDBLOCK,这个错误调试时会出现 135 | { 136 | Log.Debug("UXSocket.SendData error connectId={0} errorCode={1} msg={2}", Id, e.ErrorCode, e.Message); 137 | } 138 | 139 | if (UserId > 0 || e.ErrorCode == 10054 || e.ErrorCode == 10053 || e.ErrorCode == 10058)//UserId > 0主要是客户端,10054是远程强制关闭的情况 140 | { 141 | sendBuffer.Reset(); 142 | DefferedClose = true; 143 | } 144 | } 145 | catch (Exception e) 146 | { 147 | sendBuffer.Reset(); 148 | Log.Warn("UXSocket.SendData error={0}", e); 149 | DefferedClose = true; 150 | } 151 | } 152 | 153 | public void BeginReceive() 154 | { 155 | Receive(); 156 | } 157 | 158 | public void ProcessMessageQueue(ProcessMessage processMessage) 159 | { 160 | if (!Connected) 161 | { 162 | return; 163 | } 164 | if (msgQueue.Out.Count == 0) 165 | { 166 | // wold block for aaccquiring lock 167 | msgQueue.Swap(); 168 | } 169 | 170 | while (msgQueue.Out.Count > 0) 171 | { 172 | var msg = msgQueue.Out.Dequeue(); 173 | 174 | processMessage(this, msg); 175 | } 176 | } 177 | 178 | public void Close() 179 | { 180 | Connected = false; 181 | 182 | if (sysSocket != null) 183 | { 184 | sysSocket.Close(); 185 | sysSocket = null; 186 | } 187 | 188 | readBuffer = null; 189 | } 190 | 191 | private void Receive() 192 | { 193 | if (!Connected) 194 | { 195 | return; 196 | } 197 | try 198 | { 199 | if (receiveBuffer.EnsureFreeSpace(1)) 200 | { 201 | sysSocket.BeginReceive(receiveBuffer.Buffer, receiveBuffer.Start, receiveBuffer.Free, SocketFlags.None, OnReceivedCallback, this); 202 | } 203 | else 204 | { 205 | DefferedClose = true; 206 | } 207 | } 208 | catch (SocketException e) 209 | { 210 | Log.Debug("UXSocket.Receive error={0}", e.Message); 211 | } 212 | catch (Exception e) 213 | { 214 | Log.Warn("UXSocket.Receive error={0}", e); 215 | } 216 | } 217 | 218 | // io thread schedules this entrance 219 | private void OnReceivedCallback(IAsyncResult ar) 220 | { 221 | int bytesRead = 0; 222 | try 223 | { 224 | if (sysSocket != null) 225 | { 226 | bytesRead = sysSocket.EndReceive(ar); 227 | } 228 | } 229 | catch (ObjectDisposedException e) 230 | { 231 | Log.Debug("UXSocket.ReceiveCallback objectDisposedException connectId={0}", Id); 232 | DefferedClose = true; 233 | return; 234 | } 235 | catch (SocketException e) 236 | { 237 | Log.Debug("UXSocket.ReceiveCallback connectId={0} errorCode={1} errorMessage={2}", Id, e.ErrorCode, e.Message); 238 | if (/*UserId > 0 || */e.ErrorCode == 10054 || e.ErrorCode == 10053 || e.ErrorCode == 10058) //socket断开等严重错误,强制断开 239 | { 240 | DefferedClose = true; 241 | } 242 | return; 243 | } 244 | catch (Exception) 245 | { 246 | Log.Error("UXSocket.ReceiveCallback exception connectId={0}", Id);//这些错误是未知 247 | DefferedClose = true; 248 | return; 249 | } 250 | 251 | if (bytesRead == 0) 252 | { 253 | Log.Debug("UXSocket.ReceiveCallback read is 0 connectId={0} userId={1}", Id, UserId); 254 | //if (UserId > 0) //只会尝试断开外部连接 255 | //{ 256 | // WaitClose = true; 257 | //} 258 | //需要return,因为mono下receive会同步回调,导致卡死 259 | return; 260 | } 261 | 262 | receiveBuffer.Peek(bytesRead); 263 | //LastTickReceive += bytesRead; 264 | 265 | while (receiveBuffer.Length >= HeadLen) 266 | { 267 | int size = BitConverter.ToInt32(receiveBuffer.Buffer, receiveBuffer.Start);//todo 这里出过异常 gate 268 | 269 | if (size < 0) 270 | { 271 | Log.Warn("UXSocket.ReceiveCallback size={0} id={1} pkgBufferIndex={2} pkgBufferFinish={3} pkgBufferLen={4} bytesRead={5}", size, Id, readBufferEnd, readBufferBegin, readBuffer.Length, bytesRead); 272 | break; 273 | } 274 | 275 | if (receiveBuffer.Length >= size + HeadLen) 276 | { 277 | byte[] destBuffer = null; 278 | 279 | destBuffer = new byte[size]; 280 | Buffer.BlockCopy(receiveBuffer.Buffer, receiveBuffer.Start + HeadLen, destBuffer, 0, size); 281 | 282 | if (rc4Read != null) //做数据拆包前解密数据包 283 | { 284 | rc4Read.Encrypt(destBuffer, size); 285 | } 286 | 287 | receiveBuffer.Pop(size + HeadLen); 288 | 289 | //if (readBufferBegin >= readBuffer.Length / 2) //大于一半的buffer数据已经处理后把不用的数据清理一次 290 | //{ 291 | // readBufferEnd -= readBufferBegin; 292 | // Buffer.BlockCopy(readBuffer, readBufferBegin, readBuffer, 0, readBufferEnd); 293 | // readBufferBegin = 0; 294 | //} 295 | 296 | try 297 | { 298 | MessageQueueEnqueue(destBuffer, size); 299 | } 300 | catch (Exception e) 301 | { 302 | Log.Error(e); 303 | } 304 | } 305 | else 306 | { 307 | // 如果剩下的数据还没有形成完整的包,等待下次接受后继续处理 308 | break; 309 | } 310 | } 311 | Receive(); 312 | } 313 | 314 | private void MessageQueueEnqueue(byte[] buf, int len) 315 | { 316 | var msg = new Message(buf, len); 317 | lock (msgQueue.Lock) // 消息被压入队列,供主线程的 RefreshMessageQueue 处理 318 | { 319 | msgQueue.In.Enqueue(msg); 320 | } 321 | } 322 | } 323 | } 324 | -------------------------------------------------------------------------------- /src/ClientNetwork.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Sockets; 3 | 4 | namespace Network 5 | { 6 | // context for connect socket 7 | // manage just one connector, which means local client 8 | public class ClientNetwork 9 | { 10 | class ConnectAsyncResult 11 | { 12 | public Exception Ex; 13 | public Connector Conn; 14 | } 15 | 16 | public bool Connected { get { return connector != null && connector.Connected; } } 17 | 18 | public delegate void ClientNetworkConnectedHandler(ILocal local, Exception e); 19 | public delegate void ClientNetworkDisconnectedHandler(); 20 | public delegate void ClientNetworkMessageReceivedHandler(Message msg); 21 | 22 | // todo use event 23 | public ClientNetworkConnectedHandler ConnectorConnected; 24 | public ClientNetworkDisconnectedHandler ConnectorDisconnected; 25 | public ClientNetworkMessageReceivedHandler ConnectorMessageReceived; 26 | 27 | // compared to serverNetwork 28 | // clientNetwork hold one connector for connect socket only 29 | private Connector connector; 30 | 31 | private ConnectAsyncResult defferedConnected = null; 32 | 33 | // ip:port for host 34 | private readonly string hostIp; 35 | private readonly int hostPort; 36 | 37 | // system socket 38 | private readonly Socket sysSocket; 39 | 40 | public ClientNetwork(string ip, int port) 41 | { 42 | hostIp = ip; 43 | hostPort = port; 44 | 45 | sysSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) 46 | { 47 | SendTimeout = 500, 48 | ReceiveTimeout = 500, 49 | NoDelay = true 50 | }; 51 | } 52 | 53 | // block api 54 | public void Connect() 55 | { 56 | if (Connected) 57 | return; 58 | 59 | Log.Info("BeginReceive sysSocket.Connect(hostIp, hostPort);"); 60 | try 61 | { 62 | sysSocket.Connect(hostIp, hostPort); 63 | } 64 | catch (Exception e) 65 | { 66 | Log.Error("ClientNetwork BeginReceive throw exp:{0}", e); 67 | defferedConnected = new ConnectAsyncResult() 68 | { 69 | Ex = e, 70 | }; 71 | 72 | return; 73 | } 74 | 75 | defferedConnected = new ConnectAsyncResult() 76 | { 77 | Conn = new Connector(sysSocket, 0), 78 | }; 79 | } 80 | 81 | public void SendData(byte[] buffer) 82 | { 83 | if (connector == null || !connector.Connected) 84 | { 85 | return; 86 | } 87 | 88 | connector.Push(buffer, buffer.Length); 89 | } 90 | 91 | public void SendDatav(params byte[][] buffers) 92 | { 93 | if (connector == null || !connector.Connected) 94 | { 95 | return; 96 | } 97 | 98 | connector.Pushv(buffers); 99 | } 100 | 101 | public void Poll() 102 | { 103 | try 104 | { 105 | if (defferedConnected != null) 106 | { 107 | connector = defferedConnected.Conn; 108 | connector.BeginReceive(); 109 | 110 | // notify 111 | if (ConnectorConnected != null) 112 | { 113 | ConnectorConnected(defferedConnected.Conn, defferedConnected.Ex); 114 | } 115 | 116 | defferedConnected = null; 117 | } 118 | 119 | RefreshMessageQueue(); 120 | RefreshClient(); 121 | } 122 | catch (Exception e) 123 | { 124 | Log.Error(e); 125 | } 126 | } 127 | 128 | // user thread 129 | public void Close() 130 | { 131 | if (!Connected) 132 | { 133 | return; 134 | } 135 | 136 | connector.Close(); 137 | 138 | if (ConnectorDisconnected != null) 139 | { 140 | ConnectorDisconnected(); 141 | } 142 | } 143 | 144 | // client only 145 | // todo 146 | public void SetClientRc4Key(string key) 147 | { 148 | if (connector != null) 149 | { 150 | //connector.RC4Key = key; 151 | } 152 | } 153 | 154 | public void Dispose() 155 | { 156 | //OnRecvMessage = null; 157 | 158 | // disconnect 159 | Close(); 160 | } 161 | 162 | private void RefreshClient() 163 | { 164 | if (Connected) 165 | { 166 | if (!connector.DefferedClose) 167 | { 168 | connector.Send(); 169 | } 170 | 171 | // connector.Send() might cause a DefferedClose 172 | if (connector.DefferedClose) 173 | { 174 | Close(); 175 | } 176 | } 177 | } 178 | 179 | private void RefreshMessageQueue() 180 | { 181 | if (!Connected) 182 | { 183 | return; 184 | } 185 | 186 | connector.ProcessMessageQueue((c, msg) => 187 | { 188 | if (ConnectorMessageReceived != null) 189 | { 190 | ConnectorMessageReceived(msg); 191 | } 192 | }); 193 | } 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/Connector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net; 4 | using System.Net.Sockets; 5 | 6 | namespace Network 7 | { 8 | public interface IRemote 9 | { 10 | string RemoteIp { get; } 11 | int RemotePort { get; } 12 | int Id { get; } 13 | 14 | int Push(byte[] buffer, int len, int offset); 15 | int PushBegin(int len); 16 | int PushMore(byte[] buffer, int len, int offset); 17 | } 18 | 19 | public interface ILocal 20 | { 21 | string RemoteIp { get; } 22 | int RemotePort { get; } 23 | } 24 | 25 | internal class Connector : IRemote, ILocal 26 | { 27 | private const int HeadLen = 4; 28 | 29 | // system socket 30 | private Socket sysSocket; 31 | 32 | // todo change to bufferlist 33 | // todo not ensure thread-safe yet 34 | private ConnectorBuffer receiveBuffer = new ConnectorBuffer(); 35 | private ConnectorBuffer sendBuffer = new ConnectorBuffer(); 36 | 37 | private readonly SwapContainer> msgQueue = new SwapContainer>(); 38 | 39 | // todo not implemented yet 40 | private RC4 rc4Read; 41 | private RC4 rc4Write; 42 | 43 | // will be set to true when exception or ServerNetwork.Dispose, 44 | // after which Network will close this connection 45 | internal bool DefferedClose { get; private set; } 46 | internal bool Connected { get; set; } 47 | 48 | public int Id { get; private set; } 49 | 50 | public string RemoteIp 51 | { 52 | get 53 | { 54 | if (sysSocket != null) 55 | { 56 | var ipEndPoint = sysSocket.RemoteEndPoint as IPEndPoint; 57 | if (ipEndPoint != null) 58 | { 59 | return ipEndPoint.Address.ToString(); 60 | } 61 | } 62 | return ""; 63 | } 64 | } 65 | 66 | public int RemotePort 67 | { 68 | get 69 | { 70 | if (sysSocket != null) 71 | { 72 | var ipEndPoint = sysSocket.RemoteEndPoint as IPEndPoint; 73 | if (ipEndPoint != null) 74 | { 75 | return ipEndPoint.Port; 76 | } 77 | } 78 | return 0; 79 | } 80 | } 81 | 82 | public delegate void ConnectorMessageHandler(IRemote remote, Message msg); 83 | 84 | public Connector(Socket s, int id) 85 | { 86 | sysSocket = s; 87 | Id = id; 88 | Connected = true; 89 | } 90 | 91 | public int PushBegin(int len) 92 | { 93 | if (!Connected) 94 | { 95 | return -1; 96 | } 97 | 98 | var headData = new byte[HeadLen]; 99 | 100 | Helper.Int32ToByteArray(len, headData, 0); 101 | 102 | sendBuffer.PushData(headData, headData.Length); 103 | 104 | return HeadLen; 105 | } 106 | 107 | // todo in case inputs an ownerless buffer, sendBuffer should be converted to buffer list 108 | public int PushMore(byte[] buffer, int len, int offset = 0) 109 | { 110 | if (rc4Write != null) 111 | { 112 | rc4Write.Encrypt(buffer, len, offset); 113 | } 114 | 115 | sendBuffer.PushData(buffer, len, offset); 116 | 117 | return len; 118 | } 119 | 120 | public int Push(byte[] buffer, int len, int offset = 0) 121 | { 122 | if (!Connected) 123 | { 124 | return -1; 125 | } 126 | if (offset + len >= buffer.Length) 127 | { 128 | return -2; 129 | } 130 | 131 | //rc4 encryption 132 | if (rc4Write != null) 133 | { 134 | rc4Write.Encrypt(buffer, len, offset); 135 | } 136 | 137 | var headData = new byte[HeadLen]; 138 | 139 | Helper.Int32ToByteArray(len, headData, 0); 140 | 141 | sendBuffer.PushData(headData, headData.Length); 142 | sendBuffer.PushData(buffer, len, offset); 143 | 144 | return buffer.Length + HeadLen; 145 | } 146 | 147 | public int Pushv(params byte[][] buffers) 148 | { 149 | if (!Connected) 150 | { 151 | return -1; 152 | } 153 | 154 | int size = 0; 155 | foreach (var buffer in buffers) 156 | { 157 | size += buffer.Length; 158 | } 159 | 160 | // rc4 encryption 161 | if (rc4Write != null) 162 | { 163 | foreach (var buffer in buffers) 164 | { 165 | rc4Write.Encrypt(buffer, buffer.Length); 166 | } 167 | } 168 | 169 | var headData = new byte[HeadLen]; 170 | 171 | Helper.Int32ToByteArray(size, headData, 0); 172 | 173 | sendBuffer.PushData(headData, headData.Length); 174 | 175 | foreach (var buffer in buffers) 176 | { 177 | sendBuffer.PushData(buffer, buffer.Length); 178 | } 179 | 180 | return size + HeadLen; 181 | } 182 | 183 | public void Send() 184 | { 185 | // no data in sendBuffer 186 | // neednt to send data 187 | if (sendBuffer.Length <= 0) 188 | { 189 | return; 190 | } 191 | 192 | try 193 | { 194 | //SysSocket.BeginSend(sendBuffer.ToArray(), 0, sendBuffer.Count, SocketFlags.None, null, null); 195 | int realSendLen = sysSocket.Send(sendBuffer.Buffer, sendBuffer.Start, sendBuffer.Length, SocketFlags.None); 196 | 197 | // todo support for statistics here 198 | 199 | if (realSendLen == sendBuffer.Length) 200 | { 201 | // todo add shrink capablity to buffer 202 | sendBuffer.Reset(); 203 | } 204 | else 205 | { 206 | sendBuffer.Consume(realSendLen); 207 | } 208 | } 209 | catch (SocketException e) 210 | { 211 | // WSAEWOULDBLOCK 212 | // occurs when debug server group 213 | if (e.ErrorCode != 10035) 214 | { 215 | Log.Debug("Connector.SendData error connectId={0} errorCode={1} msg={2}", Id, e.ErrorCode, e.Message); 216 | } 217 | 218 | // todo specially, when the connector remote is frontend-client, any errors would cause DefferedClose 219 | // this can be done in gate 220 | if (e.ErrorCode == 10054 || e.ErrorCode == 10053 || e.ErrorCode == 10058) 221 | { 222 | // 10054 stands for a close of remote 223 | sendBuffer.Reset(); 224 | DefferedClose = true; 225 | } 226 | } 227 | catch (Exception e) 228 | { 229 | sendBuffer.Reset(); 230 | Log.Warn("Connector.SendData error={0}", e); 231 | DefferedClose = true; 232 | } 233 | } 234 | 235 | public void BeginReceive() 236 | { 237 | Receive(); 238 | } 239 | 240 | public void ProcessMessageQueue(ConnectorMessageHandler msgProcessor) 241 | { 242 | if (!Connected) 243 | { 244 | return; 245 | } 246 | 247 | if (msgQueue.Out.Count == 0) 248 | { 249 | // would block for accquiring lock 250 | msgQueue.Swap(); 251 | } 252 | 253 | while (msgQueue.Out.Count > 0) 254 | { 255 | var msg = msgQueue.Out.Dequeue(); 256 | 257 | msgProcessor(this, msg); 258 | } 259 | } 260 | 261 | public void Close() 262 | { 263 | Connected = false; 264 | 265 | if (sysSocket != null) 266 | { 267 | sysSocket.Close(); 268 | sysSocket = null; 269 | } 270 | 271 | receiveBuffer.Dispose(); 272 | sendBuffer.Dispose(); 273 | 274 | receiveBuffer = null; 275 | sendBuffer = null; 276 | } 277 | 278 | private void Receive() 279 | { 280 | if (!Connected) 281 | { 282 | return; 283 | } 284 | try 285 | { 286 | if (receiveBuffer.EnsureFreeSpace(1)) 287 | { 288 | sysSocket.BeginReceive(receiveBuffer.Buffer, receiveBuffer.Position, receiveBuffer.Free, SocketFlags.None, OnReceivedCallback, this); 289 | } 290 | else 291 | { 292 | DefferedClose = true; 293 | } 294 | } 295 | catch (SocketException e) 296 | { 297 | Log.Debug("Connector.Receive error={0}", e.Message); 298 | } 299 | catch (Exception e) 300 | { 301 | Log.Warn("Connector.Receive error={0}", e); 302 | } 303 | } 304 | 305 | // io thread schedules this entrance 306 | private void OnReceivedCallback(IAsyncResult ar) 307 | { 308 | int bytesRead = 0; 309 | try 310 | { 311 | if (sysSocket != null) 312 | { 313 | bytesRead = sysSocket.EndReceive(ar); 314 | } 315 | } 316 | catch (ObjectDisposedException e) 317 | { 318 | Log.Debug("Connector.ReceiveCallback objectDisposedException connectId={0}", Id); 319 | DefferedClose = true; 320 | return; 321 | } 322 | catch (SocketException e) 323 | { 324 | Log.Debug("Connector.ReceiveCallback connectId={0} errorCode={1} errorMessage={2}", Id, e.ErrorCode, e.Message); 325 | if (e.ErrorCode == 10054 || e.ErrorCode == 10053 || e.ErrorCode == 10058) 326 | { 327 | // todo specially, when the connector remote is frontend-client, any errors would cause DefferedClose 328 | // this can be done in gate 329 | DefferedClose = true; 330 | } 331 | return; 332 | } 333 | catch (Exception) 334 | { 335 | // unknown errors 336 | Log.Error("Connector.ReceiveCallback exception connectId={0}", Id); 337 | DefferedClose = true; 338 | return; 339 | } 340 | 341 | if (bytesRead == 0) 342 | { 343 | Log.Debug("Connector.ReceiveCallback read is 0 connectId={0}", Id); 344 | // todo specially, when the connector remote is frontend-client, any errors would cause DefferedClose 345 | // this can be done in gate 346 | 347 | // remember this return, or it will go to a infinity recursion 348 | return; 349 | } 350 | 351 | receiveBuffer.Produce(bytesRead); 352 | 353 | // todo support for statistics here 354 | 355 | while (receiveBuffer.Length >= HeadLen) 356 | { 357 | // todo a strange bug occurs here ever 358 | int size = BitConverter.ToInt32(receiveBuffer.Buffer, receiveBuffer.Start); 359 | 360 | if (size < 0) 361 | { 362 | Log.Warn("Connector.ReceiveCallback size={0} id={1} buffer={2} bytesRead={3}", size, Id, receiveBuffer, bytesRead); 363 | break; 364 | } 365 | 366 | if (receiveBuffer.Length >= size + HeadLen) 367 | { 368 | byte[] destBuffer = null; 369 | 370 | destBuffer = new byte[size]; 371 | Buffer.BlockCopy(receiveBuffer.Buffer, receiveBuffer.Start + HeadLen, destBuffer, 0, size); 372 | 373 | // decode 374 | if (rc4Read != null) 375 | { 376 | rc4Read.Encrypt(destBuffer, size); 377 | } 378 | 379 | // todo add shrink capablity to buffer 380 | receiveBuffer.Reset(); 381 | 382 | try 383 | { 384 | MessageQueueEnqueue(destBuffer); 385 | } 386 | catch (Exception e) 387 | { 388 | Log.Error(e); 389 | } 390 | } 391 | else 392 | { 393 | // remained data is still uncoming 394 | // wait for next callback, 395 | // since tcp is a stream-based protocol 396 | break; 397 | } 398 | } 399 | Receive(); 400 | } 401 | 402 | // io thread entrance 403 | private void MessageQueueEnqueue(byte[] buf) 404 | { 405 | var msg = new Message(buf); 406 | 407 | // msgQueue will be polled when user thread invoke 408 | lock (msgQueue.Lock) 409 | { 410 | msgQueue.In.Enqueue(msg); 411 | } 412 | } 413 | } 414 | } 415 | -------------------------------------------------------------------------------- /src/ConnectorBuffer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Network 4 | { 5 | class ConnectorBuffer : IDisposable 6 | { 7 | private byte[] bufferInner = new byte[1024]; 8 | 9 | private int position; 10 | private int begin; 11 | 12 | public byte[] Buffer 13 | { 14 | get { return bufferInner; } 15 | } 16 | public int Start 17 | { 18 | get { return begin; } 19 | } 20 | public int Position 21 | { 22 | get { return position; } 23 | } 24 | public int Length 25 | { 26 | get { return position - begin; } 27 | } 28 | public int Free 29 | { 30 | get { return bufferInner.Length - position; } 31 | } 32 | 33 | public void PushData(byte[] data, int size, int offset = 0) 34 | { 35 | CheckResize(size); 36 | 37 | System.Buffer.BlockCopy(data, offset, bufferInner, position, size); 38 | position += size; 39 | } 40 | 41 | public void Consume(int offset) 42 | { 43 | // todo check 44 | begin += offset; 45 | } 46 | 47 | public void Produce(int offset) 48 | { 49 | // todo check 50 | position += offset; 51 | } 52 | 53 | public void Reset() 54 | { 55 | position = 0; 56 | begin = 0; 57 | } 58 | 59 | public bool EnsureFreeSpace(int free) 60 | { 61 | CheckResize(free); 62 | 63 | return true; 64 | } 65 | 66 | void CheckResize(int size) 67 | { 68 | int newSize = bufferInner.Length; 69 | while (newSize - position < size) 70 | { 71 | // todo limit check 72 | newSize *= 2; 73 | } 74 | 75 | if (newSize > bufferInner.Length) 76 | { 77 | byte[] tmp = new byte[newSize]; 78 | if (position > 0) 79 | { 80 | if (position <= bufferInner.Length) 81 | { 82 | var buffLen = position - begin; 83 | 84 | System.Buffer.BlockCopy(bufferInner, begin, tmp, 0, buffLen); 85 | 86 | begin = 0; 87 | position = buffLen; 88 | } 89 | else 90 | { 91 | //Log.Error("AddData fail endPoint={0} sendBufferLen={1}", endPoint, sendBuffer.Length); 92 | } 93 | } 94 | bufferInner = tmp; 95 | } 96 | } 97 | 98 | public void Dispose() 99 | { 100 | Reset(); 101 | bufferInner = null; 102 | } 103 | 104 | public override string ToString() 105 | { 106 | return string.Format("{{bufferInner.Len:{0} position:{1} begin:{2}}}", bufferInner.Length, 107 | position, begin); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/HeartbeatManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Network 6 | { 7 | // TODO 8 | // reimplement 9 | class HeartbeatManager 10 | { 11 | public void SetHeartbeatTimeout(int timeout, int internalTimeout) 12 | { 13 | 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Helper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Network 4 | { 5 | public class Helper 6 | { 7 | // in place converter for int32 to byte[4], 8 | // not supported by BitConveter 9 | public static void Int32ToByteArray(int value, byte[] buf, int offset) 10 | { 11 | buf[offset + 3] = (byte)(value >> 24); 12 | buf[offset + 2] = (byte)(value >> 16); 13 | buf[offset + 1] = (byte)(value >> 8); 14 | buf[offset] = (byte)(value); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Log.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace Network 3 | { 4 | public static class LogHandlerRegister 5 | { 6 | public delegate void LogHandler(string msg); 7 | 8 | public static LogHandler InfoHandler; 9 | public static LogHandler DebugHandler; 10 | public static LogHandler WarnHandler; 11 | public static LogHandler ErrorHandler; 12 | } 13 | 14 | // internal log module 15 | // log handlers can be injected externally 16 | // todo not ensure thread-safe yet 17 | internal static class Log 18 | { 19 | enum LogLevel 20 | { 21 | Info, 22 | Debug, 23 | Warn, 24 | Error, 25 | } 26 | 27 | internal static void Info(string formater, params object[] args) 28 | { 29 | LogInternal(LogLevel.Info, string.Format(formater, args)); 30 | } 31 | internal static void Info(object msg) 32 | { 33 | LogInternal(LogLevel.Info, msg.ToString()); 34 | } 35 | internal static void Debug(string formater, params object[] args) 36 | { 37 | LogInternal(LogLevel.Debug, string.Format(formater, args)); 38 | } 39 | internal static void Debug(object msg) 40 | { 41 | LogInternal(LogLevel.Debug, msg.ToString()); 42 | } 43 | internal static void Warn(string formater, params object[] args) 44 | { 45 | LogInternal(LogLevel.Warn, string.Format(formater, args)); 46 | } 47 | internal static void Warn(object msg) 48 | { 49 | LogInternal(LogLevel.Warn, msg.ToString()); 50 | } 51 | internal static void Error(string formater, params object[] args) 52 | { 53 | LogInternal(LogLevel.Error, string.Format(formater, args)); 54 | } 55 | internal static void Error(object msg) 56 | { 57 | LogInternal(LogLevel.Error, msg.ToString()); 58 | } 59 | 60 | static void LogInternal(LogLevel level, string msg) 61 | { 62 | LogHandlerRegister.LogHandler handler = null; 63 | 64 | switch (level) 65 | { 66 | case LogLevel.Info: 67 | handler = LogHandlerRegister.InfoHandler; 68 | break; 69 | case LogLevel.Debug: 70 | handler = LogHandlerRegister.DebugHandler; 71 | break; 72 | case LogLevel.Warn: 73 | handler = LogHandlerRegister.WarnHandler; 74 | break; 75 | case LogLevel.Error: 76 | handler = LogHandlerRegister.ErrorHandler; 77 | break; 78 | } 79 | 80 | if (handler == null) 81 | { 82 | return; 83 | } 84 | 85 | handler(msg); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/Message.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace Network 3 | { 4 | public class Message 5 | { 6 | public byte[] Buffer = null; 7 | public Message(byte[] buf) 8 | { 9 | Buffer = buf; 10 | } 11 | 12 | public override string ToString() 13 | { 14 | return string.Format("len={0} buff={1}", Buffer!=null?Buffer.Length:-1, BytesToString(Buffer)); 15 | } 16 | 17 | public static string BytesToString(byte[] bytes) 18 | { 19 | if (bytes == null) 20 | { 21 | return ""; 22 | } 23 | string s = ""; 24 | for (int i = 0; i < bytes.Length; i++) 25 | { 26 | s += bytes[i]; 27 | if (i != bytes.Length - 1) 28 | { 29 | s += ","; 30 | } 31 | } 32 | return s; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Network.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {5E6106D6-D3C8-4728-AE20-637B346884C2} 8 | Library 9 | Properties 10 | Network 11 | Network 12 | v2.0 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | ..\bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 56 | -------------------------------------------------------------------------------- /src/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Network")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Network")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("5e6106d6-d3c8-4728-ae20-637b346884c2")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/RC4.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace Network 5 | { 6 | class RC4 7 | { 8 | private readonly Byte[] mBox; 9 | private static readonly Encoding Encode = Encoding.Default; 10 | private const int mBoxLength = 256; 11 | int x; 12 | int y; 13 | public RC4(string pass) 14 | { 15 | mBox = GetKey(Encode.GetBytes(pass)); 16 | x = 0; 17 | y = 0; 18 | } 19 | 20 | public void Encrypt(Byte[] data, int len, int offset = 0) 21 | { 22 | if (data == null) 23 | return; 24 | // encryption 25 | for (int i = offset; i < offset+len && i < data.Length; i++) 26 | { 27 | x = (x + 1) % mBoxLength; 28 | y = (y + mBox[x]) % mBoxLength; 29 | Byte temp = mBox[x]; 30 | mBox[x] = mBox[y]; 31 | mBox[y] = temp; 32 | Byte b = mBox[(mBox[x] + mBox[y]) % mBoxLength]; 33 | data[i] ^= b; 34 | } 35 | } 36 | 37 | // disorder the pass 38 | private static Byte[] GetKey(Byte[] pass) 39 | { 40 | Byte[] mBox = new Byte[mBoxLength]; 41 | 42 | for (int i = 0; i < mBoxLength; i++) 43 | { 44 | mBox[i] = (Byte)i; 45 | } 46 | int j = 0; 47 | for (int i = 0; i < mBoxLength; i++) 48 | { 49 | j = (j + mBox[i] + pass[i % pass.Length]) % mBoxLength; 50 | Byte temp = mBox[i]; 51 | mBox[i] = mBox[j]; 52 | mBox[j] = temp; 53 | } 54 | return mBox; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/ServerNetwork.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net; 4 | using System.Net.Sockets; 5 | using System.Threading; 6 | 7 | namespace Network 8 | { 9 | // context for socket listener 10 | // manage all clients accepted 11 | public class ServerNetwork 12 | { 13 | public int ClientCount { get { return clientConnectorsDict.Count; } } 14 | 15 | public delegate void ServerNetworkClientConnectedHandler(IRemote remote); 16 | public delegate void ServerNetworkClientDisconnectedHandler(IRemote remote); 17 | public delegate void ServerNetworkClientMessageReceivedHandler(IRemote remote, Message msg); 18 | 19 | // todo event 20 | public ServerNetworkClientConnectedHandler OnClientConnected; 21 | public ServerNetworkClientDisconnectedHandler OnClientDisconnected; 22 | public ServerNetworkClientMessageReceivedHandler OnClientMessageReceived; 23 | 24 | // io thread pushes while user thread pops 25 | private readonly Dictionary clientConnectorsDict = new Dictionary(); 26 | 27 | // io thread pushes while user thread pops 28 | private readonly SwapContainer> toAddClientConnectors = new SwapContainer>(); 29 | 30 | // io or user thread pushes while user thread pops 31 | // currently, only user thread pushes 32 | private readonly SwapContainer> toRemoveClientConnectors = new SwapContainer>(); 33 | 34 | // connectorId for next accepted client 35 | private int nextClientConnectorId = 1; 36 | 37 | //system socket 38 | private Socket listenSocket; 39 | 40 | public ServerNetwork(int port) 41 | { 42 | listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 43 | listenSocket.Bind(new IPEndPoint(IPAddress.Any, port)); 44 | listenSocket.Listen(10); 45 | } 46 | 47 | public void BeginAccept() 48 | { 49 | listenSocket.BeginAccept(OnAcceptedCallback, null); 50 | } 51 | 52 | public void Poll() 53 | { 54 | try 55 | { 56 | // todo heartbeat?? 57 | 58 | ProcessClientConnectorsMessageQueue(); 59 | RefreshClientList(); 60 | } 61 | catch (Exception e) 62 | { 63 | Log.Error(e); 64 | } 65 | } 66 | 67 | internal void CloseClient(Connector connector, NetworkCloseMode mode) 68 | { 69 | if (connector == null) 70 | { 71 | Log.Warn("ServerNetwork.CloseClient socket is null"); 72 | return; 73 | } 74 | 75 | lock (toRemoveClientConnectors.Lock) 76 | { 77 | toRemoveClientConnectors.In.Enqueue(connector); 78 | Log.Debug("ServerNetwork.CloseClient connectId={0}", mode, connector.Id); 79 | } 80 | } 81 | 82 | public void Dispose() 83 | { 84 | if (listenSocket != null) 85 | { 86 | listenSocket.Close(); 87 | } 88 | 89 | Log.Debug("ServerSocket.Dispose"); 90 | } 91 | 92 | // io thread schedules this entrance 93 | private void OnAcceptedCallback(IAsyncResult ar) 94 | { 95 | try 96 | { 97 | var clientSocket = listenSocket.EndAccept(ar); 98 | 99 | clientSocket.SendTimeout = 500; 100 | clientSocket.ReceiveTimeout = 500; 101 | clientSocket.NoDelay = true; 102 | 103 | var id = nextClientConnectorId; 104 | 105 | // to ensure atomic increment of nextClientConnectorId 106 | // todo handle wraparound 107 | // Interlocked.Exchange is not supported on specific platforms, unity iOS full-AOT 108 | Interlocked.Exchange(ref nextClientConnectorId, nextClientConnectorId + 1 < int.MaxValue ? nextClientConnectorId + 1 : 1); 109 | while (clientConnectorsDict.ContainsKey(nextClientConnectorId)) 110 | { 111 | Interlocked.Exchange(ref nextClientConnectorId, nextClientConnectorId + 1 < int.MaxValue ? nextClientConnectorId + 1 : 1); 112 | } 113 | 114 | var clientConnector = new Connector(clientSocket, id); 115 | 116 | lock (toAddClientConnectors.Lock) 117 | { 118 | toAddClientConnectors.In.Enqueue(clientConnector); 119 | } 120 | 121 | // continue to accept 122 | listenSocket.BeginAccept(OnAcceptedCallback, null); 123 | } 124 | catch (Exception) 125 | { 126 | //if (OnAccepted != null) OnAccepted(acceptedResult.SetValue(null, -1, null, e)); 127 | } 128 | } 129 | 130 | // user thread 131 | private void ProcessClientConnectorsMessageQueue() 132 | { 133 | foreach (var clientConnector in clientConnectorsDict.Values) 134 | { 135 | clientConnector.ProcessMessageQueue((c, msg) => 136 | { 137 | // notify upper layer, 138 | // that a new msg received 139 | if (OnClientMessageReceived != null) 140 | { 141 | OnClientMessageReceived(c, msg); 142 | } 143 | }); 144 | } 145 | } 146 | 147 | // user thread 148 | private void RefreshClientList() 149 | { 150 | // do accept client connectors 151 | if (toAddClientConnectors.Out.Count == 0) 152 | { 153 | // consume what io thread produces 154 | toAddClientConnectors.Swap(); 155 | 156 | foreach (var clientConnector in toAddClientConnectors.Out) 157 | { 158 | // todo race condition, when add conn to clientConnectorsDict 159 | if (clientConnectorsDict.ContainsKey(clientConnector.Id)) 160 | { 161 | Log.Warn("ServerNetwork.RefreshClientList connector exist id={0}", clientConnector.Id); 162 | return; 163 | } 164 | 165 | clientConnectorsDict.Add(clientConnector.Id, clientConnector); 166 | 167 | // notify upper layer, 168 | // that a new client connected 169 | if (OnClientConnected != null) 170 | { 171 | OnClientConnected(clientConnector); 172 | } 173 | 174 | // client.sysSocket.BeginReceive 175 | clientConnector.BeginReceive(); 176 | } 177 | 178 | toAddClientConnectors.Out.Clear(); 179 | } 180 | 181 | // do remove client connectors 182 | if (toRemoveClientConnectors.Out.Count == 0) 183 | { 184 | toRemoveClientConnectors.Swap(); 185 | foreach (var clientConnector in toRemoveClientConnectors.Out) 186 | { 187 | if (clientConnectorsDict.ContainsKey(clientConnector.Id)) 188 | { 189 | clientConnectorsDict.Remove(clientConnector.Id); 190 | clientConnector.Close(); 191 | if (OnClientDisconnected != null) 192 | { 193 | OnClientDisconnected(clientConnector); 194 | } 195 | } 196 | } 197 | 198 | toRemoveClientConnectors.Out.Clear(); 199 | } 200 | 201 | foreach (var client in clientConnectorsDict.Values) 202 | { 203 | client.Send(); 204 | if (client.DefferedClose) 205 | { 206 | // close any clients which failed sending data 207 | CloseClient(client, NetworkCloseMode.DefferedClose); 208 | } 209 | } 210 | } 211 | } 212 | 213 | public enum NetworkCloseMode 214 | { 215 | HeartbeatTimeout = 1, 216 | DefferedClose = 2, 217 | Dispose = 3, 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /src/SwapContainer.cs: -------------------------------------------------------------------------------- 1 | namespace Network 2 | { 3 | // swap message queue 4 | // a container, which retrieve item on one thread (io thread or user thread) 5 | // while pop item on the other thread () 6 | class SwapContainer 7 | where T:new() 8 | { 9 | public T In = new T(); 10 | public T Out = new T(); 11 | 12 | public readonly object Lock = new object(); 13 | 14 | // thraed-safe 15 | public void Swap() 16 | { 17 | // need accquiring lock first 18 | // swap in/out containers 19 | lock (Lock) 20 | { 21 | var temp = In; 22 | In = Out; 23 | Out = temp; 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/Network.Test.Client/.gitignore: -------------------------------------------------------------------------------- 1 | obj/ 2 | bin/ 3 | ~* 4 | -------------------------------------------------------------------------------- /test/Network.Test.Client/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /test/Network.Test.Client/Network.Test.Client.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {14FF79B1-496D-4318-94F3-3F9FE0DCDBEA} 8 | Exe 9 | Properties 10 | Network.Test.Client 11 | Network.Test.Client 12 | v4.5.2 13 | 512 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | ..\..\bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | {5e6106d6-d3c8-4728-ae20-637b346884c2} 55 | Network 56 | 57 | 58 | 59 | 66 | -------------------------------------------------------------------------------- /test/Network.Test.Client/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Network.Test.Client 8 | { 9 | class Program 10 | { 11 | static void Main(string[] args) 12 | { 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/Network.Test.Client/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // 有关程序集的一般信息由以下 6 | // 控制。更改这些特性值可修改 7 | // 与程序集关联的信息。 8 | [assembly: AssemblyTitle("Network.Test.Client")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Network.Test.Client")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | //将 ComVisible 设置为 false 将使此程序集中的类型 18 | //对 COM 组件不可见。 如果需要从 COM 访问此程序集中的类型, 19 | //请将此类型的 ComVisible 特性设置为 true。 20 | [assembly: ComVisible(false)] 21 | 22 | // 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID 23 | [assembly: Guid("14ff79b1-496d-4318-94f3-3f9fe0dcdbea")] 24 | 25 | // 程序集的版本信息由下列四个值组成: 26 | // 27 | // 主版本 28 | // 次版本 29 | // 生成号 30 | // 修订号 31 | // 32 | //可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值, 33 | // 方法是按如下所示使用“*”: : 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /test/Network.Test.Server/.gitignore: -------------------------------------------------------------------------------- 1 | obj/ 2 | bin/ 3 | ~* 4 | -------------------------------------------------------------------------------- /test/Network.Test.Server/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /test/Network.Test.Server/Network.Test.Server.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {22306A9B-F19C-42AB-88FA-75926F6E1C99} 8 | Exe 9 | Properties 10 | Network.Test.Server 11 | Network.Test.Server 12 | v4.5.2 13 | 512 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | ..\..\bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | {5e6106d6-d3c8-4728-ae20-637b346884c2} 55 | Network 56 | 57 | 58 | 59 | 66 | -------------------------------------------------------------------------------- /test/Network.Test.Server/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Network.Test.Server 8 | { 9 | class Program 10 | { 11 | static void Main(string[] args) 12 | { 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/Network.Test.Server/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // 有关程序集的一般信息由以下 6 | // 控制。更改这些特性值可修改 7 | // 与程序集关联的信息。 8 | [assembly: AssemblyTitle("Network.Test.Server")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Network.Test.Server")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | //将 ComVisible 设置为 false 将使此程序集中的类型 18 | //对 COM 组件不可见。 如果需要从 COM 访问此程序集中的类型, 19 | //请将此类型的 ComVisible 特性设置为 true。 20 | [assembly: ComVisible(false)] 21 | 22 | // 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID 23 | [assembly: Guid("22306a9b-f19c-42ab-88fa-75926f6e1c99")] 24 | 25 | // 程序集的版本信息由下列四个值组成: 26 | // 27 | // 主版本 28 | // 次版本 29 | // 生成号 30 | // 修订号 31 | // 32 | //可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值, 33 | // 方法是按如下所示使用“*”: : 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | --------------------------------------------------------------------------------