| 1 | #Region "Microsoft.VisualBasic::ade6d7a7350fe60daabfb2490b524242, Microsoft.VisualBasic.Core\Net\Tcp\Persistent\Socket\TcpClient.vb" |
| 2 | |
| 3 | ' Author: |
| 4 | ' |
| 5 | ' asuka (amethyst.asuka@gcmodeller.org) |
| 6 | ' xie (genetics@smrucc.org) |
| 7 | ' xieguigang (xie.guigang@live.com) |
| 8 | ' |
| 9 | ' Copyright (c) 2018 GPL3 Licensed |
| 10 | ' |
| 11 | ' |
| 12 | ' GNU GENERAL PUBLIC LICENSE (GPL3) |
| 13 | ' |
| 14 | ' |
| 15 | ' This program is free software: you can redistribute it and/or modify |
| 16 | ' it under the terms of the GNU General Public License as published by |
| 17 | ' the Free Software Foundation, either version 3 of the License, or |
| 18 | ' (at your option) any later version. |
| 19 | ' |
| 20 | ' This program is distributed in the hope that it will be useful, |
| 21 | ' but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 22 | ' MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 23 | ' GNU General Public License for more details. |
| 24 | ' |
| 25 | ' You should have received a copy of the GNU General Public License |
| 26 | ' along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 27 | |
| 28 | |
| 29 | |
| 30 | ' /********************************************************************************/ |
| 31 | |
| 32 | ' Summaries: |
| 33 | |
| 34 | ' Class PersistentClient |
| 35 | ' |
| 36 | ' Properties: MessageHandler, MyLocalPort, MyServerHashCode, RemoteServerShutdown |
| 37 | ' |
| 38 | ' Constructor: (+3 Overloads) Sub New |
| 39 | ' |
| 40 | ' Function: readDataBuffer, Run, ToString |
| 41 | ' |
| 42 | ' Sub: __receive, __send, BeginConnect, ConnectCallback, (+2 Overloads) Dispose |
| 43 | ' Receive, ReceiveCallback, requestHandle, WaitForConnected, WaitForHash |
| 44 | ' waitReceive |
| 45 | ' Class StateObject |
| 46 | ' |
| 47 | ' |
| 48 | ' |
| 49 | ' |
| 50 | ' |
| 51 | ' |
| 52 | ' /********************************************************************************/ |
| 53 | |
| 54 | #End Region |
| 55 | |
| 56 | Imports System.Net.Sockets |
| 57 | Imports System.Reflection |
| 58 | Imports System.Text |
| 59 | Imports System.Threading |
| 60 | Imports Microsoft.VisualBasic.ApplicationServices.Debugging.ExceptionExtensions |
| 61 | Imports Microsoft.VisualBasic.Language.Default |
| 62 | Imports Microsoft.VisualBasic.Net.Abstract |
| 63 | Imports Microsoft.VisualBasic.Net.Protocols |
| 64 | Imports Microsoft.VisualBasic.Net.Tcp.Persistent.Application.Protocols |
| 65 | Imports Microsoft.VisualBasic.Parallel |
| 66 | Imports TcpEndPoint = System.Net.IPEndPoint |
| 67 | Imports TcpSocket = System.Net.Sockets.Socket |
| 68 | |
| 69 | Namespace Net.Tcp.Persistent.Socket |
| 70 | |
| 71 | ''' <summary> |
| 72 | ''' 请注意,这个对象是应用于客户端与服务器保持长连接所使用,并不会主动发送消息给服务器,而是被动的接受服务器的数据请求 |
| 73 | ''' </summary> |
| 74 | ''' <remarks></remarks> |
| 75 | Public Class PersistentClient : Implements IDisposable |
| 76 | |
| 77 | Protected _EndReceive As Boolean |
| 78 | Protected connectDone As ManualResetEvent |
| 79 | |
| 80 | #Region "IDisposable Support" |
| 81 | Protected disposedValue As Boolean ' To detect redundant calls |
| 82 | |
| 83 | #Region "Internal Fields" |
| 84 | |
| 85 | ''' <summary> |
| 86 | ''' The port number for the remote device. |
| 87 | ''' </summary> |
| 88 | ''' <remarks></remarks> |
| 89 | Protected port As Integer |
| 90 | |
| 91 | ''' <summary> |
| 92 | ''' Remote End Point |
| 93 | ''' </summary> |
| 94 | ''' <remarks></remarks> |
| 95 | Protected ReadOnly remoteEP As TcpEndPoint |
| 96 | Protected remoteHost As String |
| 97 | |
| 98 | Const LocalIPAddress As String = "127.0.0.1" |
| 99 | |
| 100 | Dim _ExceptionHandler As ExceptionHandler |
| 101 | |
| 102 | Shared ReadOnly defaultHandler As New DefaultValue(Of ExceptionHandler)(AddressOf VBDebugger.PrintException) |
| 103 | |
| 104 | Sub New(remoteDevice As TcpEndPoint, Optional ExceptionHandler As ExceptionHandler = Nothing) |
| 105 | Call Me.New(remoteDevice.Address.ToString, remoteDevice.Port, ExceptionHandler) |
| 106 | End Sub |
| 107 | |
| 108 | ''' <summary> |
| 109 | ''' |
| 110 | ''' </summary> |
| 111 | ''' <param name="client">Copy the TCP client connection profile data from this object.(从本客户端对象之中复制出连接配置参数以进行初始化操作)</param> |
| 112 | ''' <param name="ExceptionHandler"></param> |
| 113 | ''' <remarks></remarks> |
| 114 | Sub New(client As PersistentClient, Optional ExceptionHandler As ExceptionHandler = Nothing) |
| 115 | remoteHost = client.remoteHost |
| 116 | port = client.port |
| 117 | _ExceptionHandler = ExceptionHandler Or defaultHandler |
| 118 | remoteEP = New TcpEndPoint(System.Net.IPAddress.Parse(remoteHost), port) |
| 119 | End Sub |
| 120 | |
| 121 | ''' <summary> |
| 122 | ''' |
| 123 | ''' </summary> |
| 124 | ''' <param name="RemotePort"></param> |
| 125 | ''' <param name="ExceptionHandler">Public Delegate Sub ExceptionHandler(ex As Exception)</param> |
| 126 | ''' <remarks></remarks> |
| 127 | Sub New(HostName$, RemotePort As Integer, Optional ExceptionHandler As ExceptionHandler = Nothing) |
| 128 | remoteHost = HostName |
| 129 | |
| 130 | If String.Equals(remoteHost, "localhost", StringComparison.OrdinalIgnoreCase) Then |
| 131 | remoteHost = LocalIPAddress |
| 132 | End If |
| 133 | |
| 134 | port = RemotePort |
| 135 | _ExceptionHandler = ExceptionHandler Or defaultHandler |
| 136 | remoteEP = New TcpEndPoint(System.Net.IPAddress.Parse(remoteHost), port) |
| 137 | End Sub |
| 138 | |
| 139 | Public ReadOnly Property MyLocalPort As Integer |
| 140 | |
| 141 | ''' <summary> |
| 142 | ''' 本客户端socket在服务器上面的哈希句柄值 |
| 143 | ''' </summary> |
| 144 | ''' <returns></returns> |
| 145 | Public ReadOnly Property MyServerHashCode As Integer = 0 |
| 146 | |
| 147 | ''' <summary> |
| 148 | ''' 远程主机强制关闭连接之后触发这个动作 |
| 149 | ''' </summary> |
| 150 | ''' <returns></returns> |
| 151 | Public Property RemoteServerShutdown As MethodInvoker |
| 152 | ''' <summary> |
| 153 | ''' 将从服务器来的推送消息的处理过程赋值在这个属性之中 |
| 154 | ''' </summary> |
| 155 | ''' <returns></returns> |
| 156 | Public Property MessageHandler As ProcessMessagePush |
| 157 | |
| 158 | Public Sub BeginConnect() |
| 159 | Call Parallel.RunTask(AddressOf Run) |
| 160 | End Sub |
| 161 | |
| 162 | ''' <summary> |
| 163 | ''' 函数会像服务器上面的socket对象一样在这里发生阻塞 |
| 164 | ''' </summary> |
| 165 | ''' <remarks></remarks> |
| 166 | Public Function Run() As Integer |
| 167 | ' ManualResetEvent instances signal completion. |
| 168 | connectDone = New ManualResetEvent(False) |
| 169 | |
| 170 | ' Establish the remote endpoint for the socket. |
| 171 | ' For this example use local machine. |
| 172 | ' Create a TCP/IP socket. |
| 173 | Dim client As New TcpSocket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) |
| 174 | Call client.Bind(New TcpEndPoint(System.Net.IPAddress.Any, 0)) |
| 175 | ' Connect to the remote endpoint. |
| 176 | Call client.BeginConnect(remoteEP, New AsyncCallback(AddressOf ConnectCallback), client) |
| 177 | ' Wait for connect. |
| 178 | Call connectDone.WaitOne() |
| 179 | Call Console.WriteLine(client.LocalEndPoint.ToString) |
| 180 | |
| 181 | _MyLocalPort = DirectCast(client.LocalEndPoint, TcpEndPoint).Port |
| 182 | _EndReceive = True |
| 183 | |
| 184 | Dim state As New StateObject With { |
| 185 | .workSocket = client |
| 186 | } |
| 187 | |
| 188 | Do While Not Me.disposedValue |
| 189 | Call Thread.Sleep(1) |
| 190 | |
| 191 | If _EndReceive Then |
| 192 | _EndReceive = False |
| 193 | state.Stack = 0 |
| 194 | Call Receive(state) |
| 195 | End If |
| 196 | Loop |
| 197 | |
| 198 | Return 0 |
| 199 | End Function |
| 200 | |
| 201 | ' This code added by Visual Basic to correctly implement the disposable pattern. |
| 202 | Public Sub Dispose() Implements IDisposable.Dispose |
| 203 | ' Do not change this code. Put cleanup code in Dispose(disposing As Boolean) above. |
| 204 | Dispose(True) |
| 205 | GC.SuppressFinalize(Me) |
| 206 | End Sub |
| 207 | #End Region |
| 208 | |
| 209 | Public Overrides Function ToString() As String |
| 210 | Return $"Remote_connection={remoteHost}:{port}" |
| 211 | End Function |
| 212 | |
| 213 | Public Sub WaitForConnected() |
| 214 | Do While Me._MyLocalPort = 0 |
| 215 | Call Thread.Sleep(1) |
| 216 | Loop |
| 217 | End Sub |
| 218 | |
| 219 | Public Sub WaitForHash() |
| 220 | Do While MyServerHashCode = 0 |
| 221 | Call Thread.Sleep(1) |
| 222 | Loop |
| 223 | End Sub |
| 224 | |
| 225 | ''' <summary> |
| 226 | ''' Retrieve the socket from the state object. |
| 227 | ''' </summary> |
| 228 | ''' <param name="ar"></param> |
| 229 | Protected Sub ConnectCallback(ar As IAsyncResult) |
| 230 | Dim client As TcpSocket = CType(ar.AsyncState, TcpSocket) |
| 231 | |
| 232 | ' Complete the connection. |
| 233 | Try |
| 234 | client.EndConnect(ar) |
| 235 | ' Signal that the connection has been made. |
| 236 | Call connectDone.Set() |
| 237 | Catch ex As Exception |
| 238 | Call _ExceptionHandler(ex) |
| 239 | End Try |
| 240 | End Sub 'ConnectCallback |
| 241 | |
| 242 | ' IDisposable |
| 243 | Protected Overridable Sub Dispose(disposing As Boolean) |
| 244 | If Not Me.disposedValue Then |
| 245 | If disposing Then |
| 246 | ' TODO: dispose managed state (managed objects). |
| 247 | Call connectDone.Set() ' ManualResetEvent instances signal completion. |
| 248 | ' Call receiveDone.Set() '中断服务器的连接 |
| 249 | End If |
| 250 | |
| 251 | ' TODO: free unmanaged resources (unmanaged objects) and override Finalize() below. |
| 252 | ' TODO: set large fields to null. |
| 253 | End If |
| 254 | Me.disposedValue = True |
| 255 | End Sub |
| 256 | |
| 257 | ''' <summary> |
| 258 | ''' An exception of type '<see cref="SocketException"/>' occurred in System.dll but was not handled in user code |
| 259 | ''' Additional information: A request to send or receive data was disallowed because the socket is not connected and |
| 260 | ''' (when sending on a datagram socket using a sendto call) no address was supplied |
| 261 | ''' </summary> |
| 262 | ''' <param name="client"></param> |
| 263 | Protected Sub Receive(client As StateObject) |
| 264 | ' Begin receiving the data from the remote device. |
| 265 | Try |
| 266 | _EndReceive = False |
| 267 | Call __receive(client) |
| 268 | Catch ex As Exception ' 现有的连接被强制关闭 |
| 269 | Call Me._ExceptionHandler(ex) |
| 270 | |
| 271 | If Not Me.RemoteServerShutdown Is Nothing Then |
| 272 | Call RemoteServerShutdown()() |
| 273 | End If |
| 274 | |
| 275 | Try |
| 276 | Call client.workSocket.Shutdown(SocketShutdown.Both) |
| 277 | Catch exc As Exception |
| 278 | Call App.LogException(exc, MethodBase.GetCurrentMethod.GetFullName) |
| 279 | End Try |
| 280 | End Try |
| 281 | End Sub 'Receive |
| 282 | |
| 283 | Private Sub __receive(client As StateObject) |
| 284 | Dim ReceiveHandle = New AsyncCallback(AddressOf ReceiveCallback) |
| 285 | |
| 286 | Call client.workSocket.BeginReceive(client.readBuffer, 0, StateObject.BufferSize, 0, ReceiveHandle, client) |
| 287 | Call waitReceive() |
| 288 | |
| 289 | client.Stack += 1 |
| 290 | |
| 291 | If client.Stack > 1000 Then |
| 292 | _EndReceive = True |
| 293 | Return |
| 294 | Else |
| 295 | Call Thread.Sleep(1) |
| 296 | End If |
| 297 | |
| 298 | If Not Me.disposedValue Then |
| 299 | '还没有结束 |
| 300 | Call Receive(client) |
| 301 | End If |
| 302 | End Sub |
| 303 | |
| 304 | ''' <summary> |
| 305 | ''' ???? |
| 306 | ''' An exception of type 'System.Net.Sockets.SocketException' occurred in System.dll but was not handled in user code |
| 307 | ''' Additional information: A request to send or receive data was disallowed because the socket is not connected and |
| 308 | ''' (when sending on a datagram socket using a sendto call) no address was supplied |
| 309 | ''' </summary> |
| 310 | ''' <param name="client"></param> |
| 311 | ''' <param name="data"></param> |
| 312 | ''' <remarks></remarks> |
| 313 | Private Sub __send(client As TcpSocket, data As String) |
| 314 | ' Convert the string data to byte data using ASCII encoding. |
| 315 | Dim byteData As Byte() = Encoding.ASCII.GetBytes(data) |
| 316 | ' Begin sending the data to the remote device. |
| 317 | Try |
| 318 | Call client.Send(byteData, byteData.Length, SocketFlags.None) |
| 319 | Catch ex As Exception |
| 320 | Call Me._ExceptionHandler(ex) |
| 321 | Call App.LogException(ex, MethodBase.GetCurrentMethod.GetFullName) |
| 322 | End Try |
| 323 | End Sub 'Send |
| 324 | |
| 325 | ''' <summary> |
| 326 | ''' Read data from the remote device. |
| 327 | ''' </summary> |
| 328 | ''' <param name="state"></param> |
| 329 | ''' <param name="ar"></param> |
| 330 | ''' <returns></returns> |
| 331 | Private Function readDataBuffer(state As StateObject, ar As IAsyncResult) As Byte() |
| 332 | Dim client As TcpSocket = state.workSocket |
| 333 | Dim bytesRead As Integer |
| 334 | |
| 335 | Try |
| 336 | bytesRead = client.EndReceive(ar) |
| 337 | Catch ex As Exception |
| 338 | Call _ExceptionHandler(ex) |
| 339 | Call _EndReceive.SetValue(True) |
| 340 | Return Nothing |
| 341 | Finally |
| 342 | _EndReceive = True |
| 343 | End Try |
| 344 | |
| 345 | Dim bytesBuffer = state.readBuffer.Takes(bytesRead) |
| 346 | Return bytesBuffer |
| 347 | End Function |
| 348 | |
| 349 | ''' <summary> |
| 350 | ''' Retrieve the state object and the client socket from the asynchronous state object. |
| 351 | ''' </summary> |
| 352 | ''' <param name="ar"></param> |
| 353 | Private Sub ReceiveCallback(ar As IAsyncResult) |
| 354 | Dim state As StateObject = DirectCast(ar.AsyncState, StateObject) |
| 355 | Dim bytesBuffer = readDataBuffer(state, ar) |
| 356 | |
| 357 | If bytesBuffer.IsNullOrEmpty Then Return |
| 358 | |
| 359 | Call state.ChunkBuffer.AddRange(bytesBuffer) |
| 360 | |
| 361 | Dim TempBuffer = state.ChunkBuffer.ToArray |
| 362 | Dim request = New RequestStream(TempBuffer) |
| 363 | |
| 364 | If Not request.FullRead Then Return |
| 365 | |
| 366 | Call state.ChunkBuffer.Clear() |
| 367 | |
| 368 | If TempBuffer.Length > request.TotalBytes Then |
| 369 | TempBuffer = TempBuffer.Skip(request.TotalBytes).ToArray |
| 370 | ' 含有剩余的剪裁后的数据 |
| 371 | Call state.ChunkBuffer.AddRange(TempBuffer) |
| 372 | End If |
| 373 | |
| 374 | Try |
| 375 | Call requestHandle(request) |
| 376 | Catch ex As Exception |
| 377 | ' 客户端处理数据的时候发生了内部错误 |
| 378 | Call ex.PrintException |
| 379 | Call App.LogException(ex, MethodBase.GetCurrentMethod.GetFullName) |
| 380 | End Try |
| 381 | End Sub 'ReceiveCallback |
| 382 | |
| 383 | Private Sub requestHandle(request As RequestStream) |
| 384 | If ServicesProtocol.Protocols.ServerHash = request.Protocol Then |
| 385 | _MyServerHashCode = Scripting.CTypeDynamic(Of Integer)(request.GetUTF8String) |
| 386 | Else |
| 387 | Call RunTask(Sub() Call _MessageHandler(request)) |
| 388 | End If |
| 389 | End Sub |
| 390 | |
| 391 | Private Sub waitReceive() |
| 392 | Do While Not _EndReceive |
| 393 | Call Thread.Sleep(1) |
| 394 | Loop |
| 395 | End Sub |
| 396 | |
| 397 | Public Class StateObject : Inherits Tcp.StateObject |
| 398 | Public Stack As Integer |
| 399 | End Class |
| 400 | #End Region |
| 401 | |
| 402 | End Class 'AsynchronousClient |
| 403 | End Namespace |