| 1 | #Region "Microsoft.VisualBasic::a3473a28b510eade8ad556ad3a6366ba, Microsoft.VisualBasic.Core\Net\Tcp\TcpRequest.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 TcpRequest |
| 35 | ' |
| 36 | ' Properties: LocalIPAddress |
| 37 | ' |
| 38 | ' Constructor: (+4 Overloads) Sub New |
| 39 | ' Function: LocalConnection, OperationTimeOut, (+2 Overloads) SendMessage, ToString |
| 40 | ' Delegate Function |
| 41 | ' |
| 42 | ' Function: (+4 Overloads) SendMessage |
| 43 | ' |
| 44 | ' Sub: __send, ConnectCallback, (+2 Overloads) Dispose, Receive, ReceiveCallback |
| 45 | ' SendCallback |
| 46 | ' |
| 47 | ' |
| 48 | ' |
| 49 | ' /********************************************************************************/ |
| 50 | |
| 51 | #End Region |
| 52 | |
| 53 | Imports System.Net |
| 54 | Imports System.Net.Sockets |
| 55 | Imports System.Runtime.CompilerServices |
| 56 | Imports System.Threading |
| 57 | Imports Microsoft.VisualBasic.ApplicationServices.Debugging.ExceptionExtensions |
| 58 | Imports Microsoft.VisualBasic.Language.Default |
| 59 | Imports Microsoft.VisualBasic.Net.Http |
| 60 | Imports Microsoft.VisualBasic.Net.Protocols |
| 61 | Imports TcpEndPoint = System.Net.IPEndPoint |
| 62 | |
| 63 | Namespace Net.Tcp |
| 64 | |
| 65 | ''' <summary> |
| 66 | ''' The server socket should returns some data string to this client or this client will stuck at the <see cref="SendMessage"></see> function. |
| 67 | ''' (服务器端<see cref="TcpServicesSocket"></see>必须要返回数据,否则本客户端会在<see cref="SendMessage |
| 68 | ''' "></see>函数位置一直处于等待的状态) |
| 69 | ''' </summary> |
| 70 | ''' <remarks></remarks> |
| 71 | Public Class TcpRequest : Implements IDisposable |
| 72 | |
| 73 | #Region "Internal Fields" |
| 74 | |
| 75 | ''' <summary> |
| 76 | ''' The port number for the remote device. |
| 77 | ''' </summary> |
| 78 | ''' <remarks></remarks> |
| 79 | Dim port As Integer |
| 80 | |
| 81 | ''' <summary> |
| 82 | ''' The response from the remote device. |
| 83 | ''' </summary> |
| 84 | ''' <remarks></remarks> |
| 85 | Dim response As Byte() |
| 86 | |
| 87 | ''' <summary> |
| 88 | ''' ' ManualResetEvent instances signal completion. |
| 89 | ''' </summary> |
| 90 | ''' <remarks></remarks> |
| 91 | Dim connectDone As ManualResetEvent |
| 92 | Dim sendDone As ManualResetEvent |
| 93 | Dim receiveDone As ManualResetEvent |
| 94 | Dim __exceptionHandler As ExceptionHandler |
| 95 | Dim remoteHost As String |
| 96 | |
| 97 | ''' <summary> |
| 98 | ''' Remote End Point |
| 99 | ''' </summary> |
| 100 | ''' <remarks></remarks> |
| 101 | Protected ReadOnly remoteEP As TcpEndPoint |
| 102 | #End Region |
| 103 | |
| 104 | ''' <summary> |
| 105 | ''' Gets the IP address of this local machine. |
| 106 | ''' (获取本机对象的IP地址,请注意这个属性获取得到的仅仅是本机在局域网内的ip地址,假若需要获取得到公网IP地址,还需要外部服务器的帮助才行) |
| 107 | ''' </summary> |
| 108 | ''' <value></value> |
| 109 | ''' <returns></returns> |
| 110 | ''' <remarks></remarks> |
| 111 | Public Shared ReadOnly Property LocalIPAddress As String |
| 112 | Get |
| 113 | #Disable Warning |
| 114 | Dim IP As System.Net.IPAddress = Dns.Resolve(Dns.GetHostName).AddressList(0) |
| 115 | Dim IPAddr As String = IP.ToString |
| 116 | #Enable Warning |
| 117 | Return IPAddr |
| 118 | End Get |
| 119 | End Property |
| 120 | |
| 121 | Public Overrides Function ToString() As String |
| 122 | Return $"Remote_connection={remoteHost}:{port}, local_host={LocalIPAddress}" |
| 123 | End Function |
| 124 | |
| 125 | Sub New(remoteDevice As TcpEndPoint, Optional exceptionHandler As ExceptionHandler = Nothing) |
| 126 | Call Me.New(remoteDevice.Address.ToString, remoteDevice.Port, exceptionHandler) |
| 127 | End Sub |
| 128 | |
| 129 | Sub New(remoteDevice As IPEndPoint, Optional exceptionHandler As ExceptionHandler = Nothing) |
| 130 | Call Me.New(remoteDevice.IPAddress, remoteDevice.Port, exceptionHandler) |
| 131 | End Sub |
| 132 | |
| 133 | Shared ReadOnly defaultHandler As New DefaultValue(Of ExceptionHandler)(AddressOf VBDebugger.PrintException) |
| 134 | |
| 135 | ''' <summary> |
| 136 | ''' |
| 137 | ''' </summary> |
| 138 | ''' <param name="client"> |
| 139 | ''' Copy the TCP client connection profile data from this object. |
| 140 | ''' (从本客户端对象之中复制出连接配置参数以进行初始化操作) |
| 141 | ''' </param> |
| 142 | ''' <param name="exceptionHandler"></param> |
| 143 | ''' <remarks></remarks> |
| 144 | Sub New(client As TcpRequest, Optional exceptionHandler As ExceptionHandler = Nothing) |
| 145 | remoteHost = client.remoteHost |
| 146 | port = client.port |
| 147 | __exceptionHandler = exceptionHandler Or defaultHandler |
| 148 | remoteEP = New TcpEndPoint(System.Net.IPAddress.Parse(remoteHost), port) |
| 149 | End Sub |
| 150 | |
| 151 | ''' <summary> |
| 152 | ''' |
| 153 | ''' </summary> |
| 154 | ''' <param name="remotePort"></param> |
| 155 | ''' <param name="exceptionHandler"> |
| 156 | ''' Public <see cref="System.Delegate"/> Sub ExceptionHandler(ex As <see cref="Exception"/>) |
| 157 | ''' </param> |
| 158 | ''' <remarks></remarks> |
| 159 | Sub New(hostName$, remotePort%, Optional exceptionHandler As ExceptionHandler = Nothing) |
| 160 | remoteHost = hostName |
| 161 | |
| 162 | If String.Equals(remoteHost, "localhost", StringComparison.OrdinalIgnoreCase) Then |
| 163 | remoteHost = LocalIPAddress |
| 164 | End If |
| 165 | |
| 166 | port = remotePort |
| 167 | __exceptionHandler = exceptionHandler Or defaultHandler |
| 168 | remoteEP = New TcpEndPoint(System.Net.IPAddress.Parse(remoteHost), port) |
| 169 | End Sub |
| 170 | |
| 171 | ''' <summary> |
| 172 | ''' 初始化一个在本机进行进程间通信的Socket对象 |
| 173 | ''' </summary> |
| 174 | ''' <param name="localPort"></param> |
| 175 | ''' <param name="exceptionHandler"></param> |
| 176 | ''' <returns></returns> |
| 177 | ''' <remarks></remarks> |
| 178 | ''' |
| 179 | <MethodImpl(MethodImplOptions.AggressiveInlining)> |
| 180 | Public Shared Function LocalConnection(localPort%, Optional exceptionHandler As ExceptionHandler = Nothing) As TcpRequest |
| 181 | Return New TcpRequest(LocalIPAddress, localPort, exceptionHandler) |
| 182 | End Function |
| 183 | |
| 184 | ''' <summary> |
| 185 | ''' 判断服务器所返回来的数据是否为操作超时 |
| 186 | ''' </summary> |
| 187 | ''' <param name="str"></param> |
| 188 | ''' <returns></returns> |
| 189 | ''' <remarks></remarks> |
| 190 | Public Shared Function OperationTimeOut(str As String) As Boolean |
| 191 | Return String.Equals(str, NetResponse.RFC_REQUEST_TIMEOUT.GetUTF8String) |
| 192 | End Function |
| 193 | |
| 194 | ''' <summary> |
| 195 | ''' Returns the server reply.(假若操作超时的话,则会返回<see cref="NetResponse.RFC_REQUEST_TIMEOUT"></see>) |
| 196 | ''' </summary> |
| 197 | ''' <param name="Message"></param> |
| 198 | ''' <param name="OperationTimeOut">操作超时的时间长度,默认为30秒</param> |
| 199 | ''' <returns></returns> |
| 200 | ''' <remarks></remarks> |
| 201 | Public Function SendMessage(Message As String, |
| 202 | Optional OperationTimeOut As Integer = 30 * 1000, |
| 203 | Optional OperationTimeoutHandler As Action = Nothing) As String |
| 204 | Dim request As New RequestStream(0, 0, Message) |
| 205 | Dim response = SendMessage(request, OperationTimeOut, OperationTimeoutHandler).GetUTF8String |
| 206 | Return response |
| 207 | End Function 'Main |
| 208 | |
| 209 | ''' <summary> |
| 210 | ''' Returns the server reply.(假若操作超时的话,则会返回<see cref="NetResponse.RFC_REQUEST_TIMEOUT"></see>, |
| 211 | ''' 请注意,假若目标服务器启用了ssl加密服务的话,假若这个请求是明文数据,则服务器会直接拒绝请求返回<see cref="HTTP_RFC.RFC_NO_CERT"/> 496错误代码, |
| 212 | ''' 所以调用前请确保参数<paramref name="Message"/>已经使用证书加密) |
| 213 | ''' </summary> |
| 214 | ''' <param name="Message"></param> |
| 215 | ''' <param name="timeOut">操作超时的时间长度,默认为30秒</param> |
| 216 | ''' <returns></returns> |
| 217 | ''' <remarks></remarks> |
| 218 | Public Function SendMessage(Message As RequestStream, |
| 219 | Optional timeout% = 30 * 1000, |
| 220 | Optional timeoutHandler As Action = Nothing) As RequestStream |
| 221 | |
| 222 | Dim response As RequestStream = Nothing |
| 223 | Dim bResult As Boolean = Parallel.OperationTimeOut( |
| 224 | AddressOf SendMessage, |
| 225 | [In]:=Message, |
| 226 | Out:=response, |
| 227 | TimeOut:=timeout / 1000) |
| 228 | |
| 229 | If bResult Then |
| 230 | If Not timeoutHandler Is Nothing Then Call timeoutHandler() '操作超时了 |
| 231 | |
| 232 | If Not connectDone Is Nothing Then Call connectDone.Set() ' ManualResetEvent instances signal completion. |
| 233 | If Not sendDone Is Nothing Then Call sendDone.Set() |
| 234 | If Not receiveDone Is Nothing Then Call receiveDone.Set() '中断服务器的连接 |
| 235 | |
| 236 | Dim ex As Exception = New Exception("[OPERATION_TIME_OUT] " & Message.GetUTF8String) |
| 237 | Dim ret As New RequestStream(0, HTTP_RFC.RFC_REQUEST_TIMEOUT, "HTTP/408 " & Me.ToString) |
| 238 | Call __exceptionHandler(New Exception(ret.GetUTF8String, ex)) |
| 239 | Return ret |
| 240 | Else |
| 241 | Return response |
| 242 | End If |
| 243 | End Function |
| 244 | |
| 245 | Public Delegate Function SendMessageInvoke(Message As String) As String |
| 246 | |
| 247 | Public Function SendMessage(Message As String, Callback As Action(Of String)) As IAsyncResult |
| 248 | Dim SendMessageClient As New TcpRequest(Me, exceptionHandler:=Me.__exceptionHandler) |
| 249 | Return (Sub() Call Callback(SendMessageClient.SendMessage(Message))).BeginInvoke(Nothing, Nothing) |
| 250 | End Function |
| 251 | |
| 252 | ''' <summary> |
| 253 | ''' This function returns the server reply for this request <paramref name="Message"></paramref>. |
| 254 | ''' </summary> |
| 255 | ''' <param name="Message">The client request to the server.</param> |
| 256 | ''' <returns></returns> |
| 257 | ''' <remarks></remarks> |
| 258 | Public Function SendMessage(Message As String) As String |
| 259 | Dim byteData As Byte() = System.Text.Encoding.UTF8.GetBytes(Message) |
| 260 | byteData = SendMessage(byteData) |
| 261 | Dim response As String = New RequestStream(byteData).GetUTF8String |
| 262 | Return response |
| 263 | End Function 'Main |
| 264 | |
| 265 | ''' <summary> |
| 266 | ''' Send a request message to the remote server. |
| 267 | ''' </summary> |
| 268 | ''' <param name="Message"></param> |
| 269 | ''' <returns></returns> |
| 270 | Public Function SendMessage(Message As RequestStream) As RequestStream |
| 271 | Dim byteData As Byte() = SendMessage(Message.Serialize) |
| 272 | |
| 273 | If RequestStream.IsAvaliableStream(byteData) Then |
| 274 | Return New RequestStream(byteData) |
| 275 | Else |
| 276 | Return New RequestStream(0, 0, byteData) |
| 277 | End If |
| 278 | End Function |
| 279 | |
| 280 | ''' <summary> |
| 281 | ''' 最底层的消息发送函数 |
| 282 | ''' </summary> |
| 283 | ''' <param name="Message"></param> |
| 284 | ''' <returns></returns> |
| 285 | Public Function SendMessage(Message As Byte()) As Byte() |
| 286 | If Not RequestStream.IsAvaliableStream(Message) Then |
| 287 | Message = New RequestStream(0, 0, Message).Serialize |
| 288 | End If |
| 289 | |
| 290 | connectDone = New ManualResetEvent(False) ' ManualResetEvent instances signal completion. |
| 291 | sendDone = New ManualResetEvent(False) |
| 292 | receiveDone = New ManualResetEvent(False) |
| 293 | response = Nothing |
| 294 | |
| 295 | ' Establish the remote endpoint for the socket. |
| 296 | ' For this example use local machine. |
| 297 | ' Create a TCP/IP socket. |
| 298 | Dim client As New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) |
| 299 | Call client.Bind(New System.Net.IPEndPoint(System.Net.IPAddress.Any, 0)) |
| 300 | ' Connect to the remote endpoint. |
| 301 | Call client.BeginConnect(remoteEP, New AsyncCallback(AddressOf ConnectCallback), client) |
| 302 | ' Wait for connect. |
| 303 | Call connectDone.WaitOne() |
| 304 | ' Send test data to the remote device. |
| 305 | Call __send(client, Message) |
| 306 | Call sendDone.WaitOne() |
| 307 | |
| 308 | ' Receive the response from the remote device. |
| 309 | Call Receive(client) |
| 310 | Call receiveDone.WaitOne() |
| 311 | |
| 312 | On Error Resume Next |
| 313 | |
| 314 | ' Release the socket. |
| 315 | Call client.Shutdown(SocketShutdown.Both) |
| 316 | Call client.Close() |
| 317 | |
| 318 | Return response |
| 319 | End Function |
| 320 | |
| 321 | Private Sub ConnectCallback(ar As IAsyncResult) |
| 322 | |
| 323 | ' Retrieve the socket from the state object. |
| 324 | Dim client As Socket = DirectCast(ar.AsyncState, Socket) |
| 325 | |
| 326 | ' Complete the connection. |
| 327 | Try |
| 328 | client.EndConnect(ar) |
| 329 | ' Signal that the connection has been made. |
| 330 | connectDone.Set() |
| 331 | Catch ex As Exception |
| 332 | Call __exceptionHandler(ex) |
| 333 | End Try |
| 334 | End Sub 'ConnectCallback |
| 335 | |
| 336 | ''' <summary> |
| 337 | ''' An exception of type '<see cref="SocketException"/>' occurred in System.dll but was not handled in user code |
| 338 | ''' Additional information: A request to send or receive data was disallowed because the socket is not connected and |
| 339 | ''' (when sending on a datagram socket using a sendto call) no address was supplied |
| 340 | ''' </summary> |
| 341 | ''' <param name="client"></param> |
| 342 | Private Sub Receive(client As Socket) |
| 343 | ' Create the state object. |
| 344 | Dim state As New StateObject With { |
| 345 | .workSocket = client |
| 346 | } |
| 347 | |
| 348 | ' Begin receiving the data from the remote device. |
| 349 | Try |
| 350 | Call client.BeginReceive(state.readBuffer, 0, StateObject.BufferSize, 0, New AsyncCallback(AddressOf ReceiveCallback), state) |
| 351 | Catch ex As Exception |
| 352 | Call Me.__exceptionHandler(ex) |
| 353 | End Try |
| 354 | End Sub 'Receive |
| 355 | |
| 356 | ''' <summary> |
| 357 | ''' Retrieve the state object and the client socket from the asynchronous state object. |
| 358 | ''' </summary> |
| 359 | ''' <param name="ar"></param> |
| 360 | Private Sub ReceiveCallback(ar As IAsyncResult) |
| 361 | Dim state As StateObject = DirectCast(ar.AsyncState, StateObject) |
| 362 | Dim client As Socket = state.workSocket |
| 363 | Dim bytesRead As Integer |
| 364 | |
| 365 | Try |
| 366 | ' Read data from the remote device. |
| 367 | bytesRead = client.EndReceive(ar) |
| 368 | Catch ex As Exception |
| 369 | Call __exceptionHandler(ex) |
| 370 | GoTo EX_EXIT |
| 371 | End Try |
| 372 | |
| 373 | If bytesRead > 0 Then |
| 374 | ' There might be more data, so store the data received so far. |
| 375 | state.ChunkBuffer.AddRange(state.readBuffer.Takes(bytesRead)) |
| 376 | ' Get the rest of the data. |
| 377 | client.BeginReceive(state.readBuffer, 0, StateObject.BufferSize, 0, New AsyncCallback(AddressOf ReceiveCallback), state) |
| 378 | Else |
| 379 | ' All the data has arrived; put it in response. |
| 380 | If state.ChunkBuffer.Count > 1 Then |
| 381 | response = state.ChunkBuffer.ToArray |
| 382 | Else |
| 383 | EX_EXIT: response = Nothing |
| 384 | End If |
| 385 | ' Signal that all bytes have been received. |
| 386 | Call receiveDone.Set() |
| 387 | End If |
| 388 | End Sub |
| 389 | |
| 390 | ''' <summary> |
| 391 | ''' ???? |
| 392 | ''' An exception of type 'System.Net.Sockets.SocketException' occurred in System.dll but was not handled in user code |
| 393 | ''' Additional information: A request to send or receive data was disallowed because the socket is not connected and |
| 394 | ''' (when sending on a datagram socket using a sendto call) no address was supplied |
| 395 | ''' </summary> |
| 396 | ''' <param name="client"></param> |
| 397 | ''' <param name="byteData"></param> |
| 398 | ''' <remarks></remarks> |
| 399 | Private Sub __send(client As Socket, byteData As Byte()) |
| 400 | |
| 401 | ' Begin sending the data to the remote device. |
| 402 | Try |
| 403 | Call client.BeginSend(byteData, 0, byteData.Length, 0, New AsyncCallback(AddressOf SendCallback), client) |
| 404 | Catch ex As Exception |
| 405 | Call Me.__exceptionHandler(ex) |
| 406 | End Try |
| 407 | End Sub 'Send |
| 408 | |
| 409 | Private Sub SendCallback(ar As IAsyncResult) |
| 410 | |
| 411 | ' Retrieve the socket from the state object. |
| 412 | Dim client As Socket = DirectCast(ar.AsyncState, Socket) |
| 413 | ' Complete sending the data to the remote device. |
| 414 | Dim bytesSent As Integer = client.EndSend(ar) |
| 415 | 'Console.WriteLine("Sent {0} bytes to server.", bytesSent) |
| 416 | ' Signal that all bytes have been sent. |
| 417 | sendDone.Set() |
| 418 | End Sub 'SendCallback |
| 419 | |
| 420 | #Region "IDisposable Support" |
| 421 | Private disposedValue As Boolean ' To detect redundant calls |
| 422 | |
| 423 | ' IDisposable |
| 424 | Protected Overridable Sub Dispose(disposing As Boolean) |
| 425 | If Not Me.disposedValue Then |
| 426 | If disposing Then |
| 427 | ' TODO: dispose managed state (managed objects). |
| 428 | Call connectDone.Set() ' ManualResetEvent instances signal completion. |
| 429 | Call sendDone.Set() |
| 430 | Call receiveDone.Set() '中断服务器的连接 |
| 431 | End If |
| 432 | |
| 433 | ' TODO: free unmanaged resources (unmanaged objects) and override Finalize() below. |
| 434 | ' TODO: set large fields to null. |
| 435 | End If |
| 436 | Me.disposedValue = True |
| 437 | End Sub |
| 438 | |
| 439 | ' TODO: override Finalize() only if Dispose( disposing As Boolean) above has code to free unmanaged resources. |
| 440 | 'Protected Overrides Sub Finalize() |
| 441 | ' ' Do not change this code. Put cleanup code in Dispose( disposing As Boolean) above. |
| 442 | ' Dispose(False) |
| 443 | ' MyBase.Finalize() |
| 444 | 'End Sub |
| 445 | |
| 446 | ' This code added by Visual Basic to correctly implement the disposable pattern. |
| 447 | Public Sub Dispose() Implements IDisposable.Dispose |
| 448 | ' Do not change this code. Put cleanup code in Dispose(disposing As Boolean) above. |
| 449 | Dispose(True) |
| 450 | GC.SuppressFinalize(Me) |
| 451 | End Sub |
| 452 | #End Region |
| 453 | |
| 454 | End Class 'AsynchronousClient |
| 455 | End Namespace |