1 #Region "Microsoft.VisualBasic::769446369ecfa54198379a0e2a6dede1, Microsoft.VisualBasic.Core\ApplicationServices\Tools\Network\Tcp\AsynInvoke.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 AsynInvoke
35     
36     '         Properties: LocalIPAddress
37     
38     '         Constructor: (+4 OverloadsSub New
39     '         Function: LocalConnection, OperationTimeOut, SafelySendMessage, (+2 Overloads) SendMessage, ToString
40     '         Delegate Function
41     
42     '             Function: __decryptMessageCommon, (+6 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
54 Imports System.Net
55 Imports System.Net.Sockets
56 Imports System.Reflection
57 Imports System.Text
58 Imports System.Threading
59 Imports Microsoft.VisualBasic.Net.Abstract
60 Imports Microsoft.VisualBasic.Net.Http
61 Imports Microsoft.VisualBasic.Net.Protocols
62
63 Namespace Net
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="TcpSynchronizationServicesSocket"></see>必须要返回数据,否则本客户端会在<see cref="SendMessage
68     ''' "></see>函数位置一直处于等待的状态)
69     ''' </summary>
70     ''' <remarks></remarks>
71     Public Class AsynInvoke : Implements System.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 Abstract.ExceptionHandler
95         Dim remoteHost As String
96
97         ''' <summary>
98         ''' Remote End Point
99         ''' </summary>
100         ''' <remarks></remarks>
101         Protected ReadOnly remoteEP As System.Net.IPEndPoint
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 System.Net.IPEndPoint, Optional ExceptionHandler As Abstract.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 Abstract.ExceptionHandler = Nothing)
130             Call Me.New(remoteDevice.IPAddress, remoteDevice.Port, ExceptionHandler)
131         End Sub
132
133         ''' <summary>
134         '''
135         ''' </summary>
136         ''' <param name="Client">Copy the TCP client connection profile data from this object.(从本客户端对象之中复制出连接配置参数以进行初始化操作)</param>
137         ''' <param name="ExceptionHandler"></param>
138         ''' <remarks></remarks>
139         Sub New(Client As AsynInvoke, Optional ExceptionHandler As Abstract.ExceptionHandler = Nothing)
140             remoteHost = Client.remoteHost
141             port = Client.port
142             __exceptionHandler = If(ExceptionHandler Is NothingSub(ex As Exception) Call ex.PrintException, ExceptionHandler)
143             remoteEP = New System.Net.IPEndPoint(System.Net.IPAddress.Parse(remoteHost), port)
144         End Sub
145
146         ''' <summary>
147         '''
148         ''' </summary>
149         ''' <param name="remotePort"></param>
150         ''' <param name="ExceptionHandler">Public Delegate Sub ExceptionHandler(ex As Exception)</param>
151         ''' <remarks></remarks>
152         Sub New(hostName As String, remotePort As IntegerOptional ExceptionHandler As Abstract.ExceptionHandler = Nothing)
153             remoteHost = hostName
154
155             If String.Equals(remoteHost, "localhost"StringComparison.OrdinalIgnoreCase) Then
156                 remoteHost = LocalIPAddress
157             End If
158
159             port = remotePort
160             __exceptionHandler = If(ExceptionHandler Is NothingSub(ex As Exception) Call ex.PrintException, ExceptionHandler)
161             remoteEP = New System.Net.IPEndPoint(System.Net.IPAddress.Parse(remoteHost), port)
162         End Sub
163
164         ''' <summary>
165         ''' 初始化一个在本机进行进程间通信的Socket对象
166         ''' </summary>
167         ''' <param name="LocalPort"></param>
168         ''' <param name="ExceptionHandler"></param>
169         ''' <returns></returns>
170         ''' <remarks></remarks>
171         Public Shared Function LocalConnection(LocalPort As IntegerOptional ExceptionHandler As Abstract.ExceptionHandler = NothingAs AsynInvoke
172             Return New AsynInvoke(LocalIPAddress, LocalPort, ExceptionHandler)
173         End Function
174
175         ''' <summary>
176         ''' 判断服务器所返回来的数据是否为操作超时
177         ''' </summary>
178         ''' <param name="str"></param>
179         ''' <returns></returns>
180         ''' <remarks></remarks>
181         Public Shared Function OperationTimeOut(str As StringAs Boolean
182             Return String.Equals(str, NetResponse.RFC_REQUEST_TIMEOUT.GetUTF8String)
183         End Function
184
185         ''' <summary>
186         ''' Returns the server reply.(假若操作超时的话,则会返回<see cref="NetResponse.RFC_REQUEST_TIMEOUT"></see>)
187         ''' </summary>
188         ''' <param name="Message"></param>
189         ''' <param name="OperationTimeOut">操作超时的时间长度,默认为30秒</param>
190         ''' <returns></returns>
191         ''' <remarks></remarks>
192         Public Function SendMessage(Message As String,
193                                     Optional OperationTimeOut As Integer = 30 * 1000,
194                                     Optional OperationTimeoutHandler As Action = NothingAs String
195             Dim request As New RequestStream(0, 0, Message)
196             Dim response = SendMessage(request, OperationTimeOut, OperationTimeoutHandler).GetUTF8String
197             Return response
198         End Function 'Main
199
200         ''' <summary>
201         ''' Returns the server reply.(假若操作超时的话,则会返回<see cref="NetResponse.RFC_REQUEST_TIMEOUT"></see>,
202         ''' 请注意,假若目标服务器启用了ssl加密服务的话,假若这个请求是明文数据,则服务器会直接拒绝请求返回<see cref="HTTP_RFC.RFC_NO_CERT"/> 496错误代码,
203         ''' 所以调用前请确保参数<paramref name="Message"/>已经使用证书加密)
204         ''' </summary>
205         ''' <param name="Message"></param>
206         ''' <param name="timeOut">操作超时的时间长度,默认为30秒</param>
207         ''' <returns></returns>
208         ''' <remarks></remarks>
209         Public Function SendMessage(Message As RequestStream,
210                                     Optional timeout% = 30 * 1000,
211                                     Optional timeout_handler As Action = NothingAs RequestStream
212
213             Dim response As RequestStream = Nothing
214             Dim bResult As Boolean = Parallel.OperationTimeOut(
215                 AddressOf SendMessage,
216                 [In]:=Message,
217                 Out:=response,
218                 TimeOut:=timeout / 1000)
219
220             If bResult Then
221                 If Not timeout_handler Is Nothing Then Call timeout_handler() '操作超时了
222
223                 If Not connectDone Is Nothing Then Call connectDone.Set()  ' ManualResetEvent instances signal completion.
224                 If Not sendDone Is Nothing Then Call sendDone.Set()
225                 If Not receiveDone Is Nothing Then Call receiveDone.Set() '中断服务器的连接
226
227                 Dim ex As Exception = New Exception("[OPERATION_TIME_OUT] " & Message.GetUTF8String)
228                 Dim ret As New RequestStream(0, HTTP_RFC.RFC_REQUEST_TIMEOUT, "HTTP/408  " & Me.ToString)
229                 Call __exceptionHandler(New Exception(ret.GetUTF8String, ex))
230                 Return ret
231             Else
232                 Return response
233             End If
234         End Function
235
236         Public Function SafelySendMessage(Message As RequestStream,
237                                           CA As SSL.Certificate,
238                                           Optional OperationTimeOut As Integer = 30 * 1000,
239                                           Optional OperationTimeoutHandler As Action = NothingAs RequestStream
240
241             Message = CA.Encrypt(Message)
242             Message = SendMessage(Message, OperationTimeOut, OperationTimeoutHandler)
243
244             If Message.IsSSLProtocol OrElse Message.IsSSL_PublicToken Then
245                 Message = CA.Decrypt(Message)
246             Else
247                 Try
248                     Message.ChunkBuffer = CA.Decrypt(Message.ChunkBuffer)
249                 Catch ex As Exception
250                     Return Message
251                 End Try
252             End If
253
254             Return Message
255         End Function
256
257         Public Delegate Function SendMessageInvoke(Message As StringAs String
258
259         Public Function SendMessage(Message As StringCallback As Action(Of String)) As IAsyncResult
260             Dim SendMessageClient As New AsynInvoke(Me, ExceptionHandler:=Me.__exceptionHandler)
261             Return (Sub() Call Callback(SendMessageClient.SendMessage(Message))).BeginInvoke(Nothing, Nothing)
262         End Function
263
264         ''' <summary>
265         ''' This function returns the server reply for this request <paramref name="Message"></paramref>.
266         ''' </summary>
267         ''' <param name="Message">The client request to the server.</param>
268         ''' <returns></returns>
269         ''' <remarks></remarks>
270         Public Function SendMessage(Message As StringAs String
271             Dim byteData As Byte() = System.Text.Encoding.UTF8.GetBytes(Message)
272             byteData = SendMessage(byteData)
273             Dim response As String = New RequestStream(byteData).GetUTF8String
274             Return response
275         End Function 'Main
276
277         Public Function SendMessage(Message As String, CA As SSL.Certificate) As String
278             Dim request = New RequestStream(0, 0, Message)
279             Dim byteData = CA.Encrypt(request).Serialize
280             byteData = SendMessage(byteData)
281             Return __decryptMessageCommon(byteData, CA).GetUTF8String
282         End Function
283
284         ''' <summary>
285         ''' Send a request message to the remote server.
286         ''' </summary>
287         ''' <param name="Message"></param>
288         ''' <returns></returns>
289         Public Function SendMessage(Message As RequestStream) As RequestStream
290             Dim byteData As Byte() = Message.Serialize
291             byteData = SendMessage(byteData)
292             If RequestStream.IsAvaliableStream(byteData) Then
293                 Return New RequestStream(byteData)
294             Else
295                 Return New RequestStream(0, 0, byteData)
296             End If
297         End Function
298
299         Private Function __decryptMessageCommon(retData As Byte(), CA As SSL.Certificate) As RequestStream
300             If Not RequestStream.IsAvaliableStream(retData) Then Return New RequestStream(0, 0, retData)
301
302             Dim Message = New RequestStream(retData)
303
304             If Message.IsSSLProtocol OrElse Message.IsSSL_PublicToken Then
305                 Message = CA.Decrypt(Message)
306             Else
307                 Try
308                     Message.ChunkBuffer = CA.Decrypt(Message.ChunkBuffer)
309                 Catch ex As Exception
310                     Call App.LogException(ex, MethodBase.GetCurrentMethod.GetFullName)
311                 End Try
312             End If
313
314             Return Message
315         End Function
316
317         ''' <summary>
318         ''' 发送一段使用证书对象<paramref name="CA"/>进行数据加密操作的消息请求<paramref name="Message"/>
319         ''' </summary>
320         ''' <param name="Message"></param>
321         ''' <param name="CA"></param>
322         ''' <param name="isPublicToken"></param>
323         ''' <returns></returns>
324         Public Function SendMessage(Message As RequestStream, CA As SSL.Certificate, Optional isPublicToken As Boolean = FalseAs RequestStream
325             Dim byteData = If(isPublicToken, CA.PublicEncrypt(Message), CA.Encrypt(Message)).Serialize
326             byteData = SendMessage(byteData)
327             Return __decryptMessageCommon(byteData, CA)
328         End Function
329
330         ''' <summary>
331         ''' 最底层的消息发送函数
332         ''' </summary>
333         ''' <param name="Message"></param>
334         ''' <returns></returns>
335         Public Function SendMessage(Message As Byte()) As Byte()
336             If Not RequestStream.IsAvaliableStream(Message) Then
337                 Message = New RequestStream(0, 0, Message).Serialize
338             End If
339
340             connectDone = New ManualResetEvent(False) ' ManualResetEvent instances signal completion.
341             sendDone = New ManualResetEvent(False)
342             receiveDone = New ManualResetEvent(False)
343             response = Nothing
344
345             ' Establish the remote endpoint for the socket.
346             For this example use local machine.
347             ' Create a TCP/IP socket.
348             Dim client As New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
349             Call client.Bind(New System.Net.IPEndPoint(System.Net.IPAddress.Any, 0))
350             ' Connect to the remote endpoint.
351             Call client.BeginConnect(remoteEP, New AsyncCallback(AddressOf ConnectCallback), client)
352             ' Wait for connect.
353             Call connectDone.WaitOne()
354             ' Send test data to the remote device.
355             Call __send(client, Message)
356             Call sendDone.WaitOne()
357
358             ' Receive the response from the remote device.
359             Call Receive(client)
360             Call receiveDone.WaitOne()
361
362             On Error Resume Next
363
364             ' Release the socket.
365             Call client.Shutdown(SocketShutdown.Both)
366             Call client.Close()
367
368             Return response
369         End Function
370
371         Private Sub ConnectCallback(ar As IAsyncResult)
372
373             ' Retrieve the socket from the state object.
374             Dim client As Socket = DirectCast(ar.AsyncState, Socket)
375
376             ' Complete the connection.
377             Try
378                 client.EndConnect(ar)
379                 ' Signal that the connection has been made.
380                 connectDone.Set()
381             Catch ex As Exception
382                 Call __exceptionHandler(ex)
383             End Try
384         End Sub 'ConnectCallback
385
386         ''' <summary>
387         ''' An exception of type '<see cref="System.Net.Sockets.SocketException"/>' occurred in System.dll but was not handled in user code
388         ''' Additional information: A request to send or receive data was disallowed because the socket is not connected and
389         ''' (when sending on a datagram socket using a sendto call) no address was supplied
390         ''' </summary>
391         ''' <param name="client"></param>
392         Private Sub Receive(client As Socket)
393
394             ' Create the state object.
395             Dim state As New StateObject
396             state.workSocket = client
397             ' Begin receiving the data from the remote device.
398             Try
399                 Call client.BeginReceive(state.readBuffer, 0, StateObject.BufferSize, 0, New AsyncCallback(AddressOf ReceiveCallback), state)
400             Catch ex As Exception
401                 Call Me.__exceptionHandler(ex)
402             End Try
403         End Sub 'Receive
404
405         Private Sub ReceiveCallback(ar As IAsyncResult)
406
407             ' Retrieve the state object and the client socket
408             ' from the asynchronous state object.
409             Dim state As StateObject = DirectCast(ar.AsyncState, StateObject)
410             Dim client As Socket = state.workSocket
411             ' Read data from the remote device.
412
413             Dim bytesRead As Integer
414
415             Try
416                 bytesRead = client.EndReceive(ar)
417             Catch ex As Exception
418                 Call __exceptionHandler(ex)
419                 GoTo EX_EXIT
420             End Try
421
422             If bytesRead > 0 Then
423
424                 ' There might be more data, so store the data received so far.
425                 state.ChunkBuffer.AddRange(state.readBuffer.Takes(bytesRead))
426                 Get the rest of the data.
427                 client.BeginReceive(state.readBuffer, 0, StateObject.BufferSize, 0, New AsyncCallback(AddressOf ReceiveCallback), state)
428             Else
429                 ' All the data has arrived; put it in response.
430                 If state.ChunkBuffer.Count > 1 Then
431
432                     response = state.ChunkBuffer.ToArray
433                 Else
434 EX_EXIT:            response = Nothing
435                 End If
436                 ' Signal that all bytes have been received.
437                 Call receiveDone.Set()
438             End If
439         End Sub 'ReceiveCallback
440
441         ''' <summary>
442         ''' ????
443         ''' An exception of type 'System.Net.Sockets.SocketException' occurred in System.dll but was not handled in user code
444         ''' Additional information: A request to send or receive data was disallowed because the socket is not connected and
445         ''' (when sending on a datagram socket using a sendto call) no address was supplied
446         ''' </summary>
447         ''' <param name="client"></param>
448         ''' <param name="byteData"></param>
449         ''' <remarks></remarks>
450         Private Sub __send(client As Socket, byteData As Byte())
451
452             ' Begin sending the data to the remote device.
453             Try
454                 Call client.BeginSend(byteData, 0, byteData.Length, 0, New AsyncCallback(AddressOf SendCallback), client)
455             Catch ex As Exception
456                 Call Me.__exceptionHandler(ex)
457             End Try
458         End Sub 'Send
459
460         Private Sub SendCallback(ar As IAsyncResult)
461
462             ' Retrieve the socket from the state object.
463             Dim client As Socket = DirectCast(ar.AsyncState, Socket)
464             ' Complete sending the data to the remote device.
465             Dim bytesSent As Integer = client.EndSend(ar)
466             'Console.WriteLine("Sent {0} bytes to server.", bytesSent)
467             ' Signal that all bytes have been sent.
468             sendDone.Set()
469         End Sub 'SendCallback
470
471 #Region "IDisposable Support"
472         Private disposedValue As Boolean To detect redundant calls
473
474         ' IDisposable
475         Protected Overridable Sub Dispose(disposing As Boolean)
476             If Not Me.disposedValue Then
477                 If disposing Then
478                     ' TODO: dispose managed state (managed objects).
479                     Call connectDone.Set()  ' ManualResetEvent instances signal completion.
480                     Call sendDone.Set()
481                     Call receiveDone.Set() '中断服务器的连接
482                 End If
483
484                 ' TODO: free unmanaged resources (unmanaged objects) and override Finalize() below.
485                 ' TODO: set large fields to null.
486             End If
487             Me.disposedValue = True
488         End Sub
489
490         ' TODO: override Finalize() only if Dispose(      disposing As Boolean) above has code to free unmanaged resources.
491         'Protected Overrides Sub Finalize()
492         '    ' Do not change this code.  Put cleanup code in Dispose(      disposing As Boolean) above.
493         '    Dispose(False)
494         '    MyBase.Finalize()
495         'End Sub
496
497         ' This code added by Visual Basic to correctly implement the disposable pattern.
498         Public Sub Dispose() Implements IDisposable.Dispose
499             Do not change this code.  Put cleanup code in Dispose(disposing As Boolean) above.
500             Dispose(True)
501             GC.SuppressFinalize(Me)
502         End Sub
503 #End Region
504
505     End Class 'AsynchronousClient
506 End Namespace