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