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 OverloadsSub 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 IntegerOptional 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