1 #Region "Microsoft.VisualBasic::683a09100dd79078cf84027e8deddd7a, Microsoft.VisualBasic.Core\ApplicationServices\Tools\Network\Tcp\TcpSynchronizationServicesSocket.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 TcpSynchronizationServicesSocket
35     
36     '         Properties: IsShutdown, LocalPort, Responsehandler, Running
37     
38     '         Constructor: (+2 OverloadsSub New
39     
40     '         Function: BeginListen, IsServerInternalException, LoopbackEndPoint, (+2 Overloads) Run, ToString
41     
42     '         Sub: AcceptCallback, (+2 Overloads) Dispose, ForceCloseHandle, HandleRequest, ReadCallback
43     '              (+2 Overloads) Send, SendCallback, WaitForStart
44     
45     
46     ' /********************************************************************************/
47
48 #End Region
49
50 Imports System.Net
51 Imports System.Net.Sockets
52 Imports System.Reflection
53 Imports System.Text
54 Imports System.Threading
55 Imports Microsoft.VisualBasic.ComponentModel
56 Imports Microsoft.VisualBasic.Net.Abstract
57 Imports Microsoft.VisualBasic.Net.Http
58 Imports Microsoft.VisualBasic.Net.Protocols
59
60 Namespace Net
61
62     ''' <summary>
63     ''' Socket listening object which is running at the server side asynchronous able multiple threading.
64     ''' (运行于服务器端上面的Socket监听对象,多线程模型)
65     ''' </summary>
66     ''' <remarks></remarks>
67     Public Class TcpSynchronizationServicesSocket
68         Implements IDisposable
69         Implements ITaskDriver
70         Implements IServicesSocket
71
72 #Region "INTERNAL FIELDS"
73
74         Dim _threadEndAccept As Boolean = True
75         Dim __exceptionHandle As Abstract.ExceptionHandler
76         Dim _servicesSocket As Socket
77
78 #End Region
79
80         ''' <summary>
81         ''' The server services listening on this local port.(当前的这个服务器对象实例所监听的本地端口号)
82         ''' </summary>
83         ''' <value></value>
84         ''' <returns></returns>
85         ''' <remarks></remarks>
86         Public ReadOnly Property LocalPort As Integer Implements IServicesSocket.LocalPort
87
88         ''' <summary>
89         ''' This function pointer using for the data request handling of the data request from the client socket.   
90         ''' [Public Delegate Function DataResponseHandler(str As <see cref="System.String"/>, RemoteAddress As <see cref="System.Net.IPEndPoint"/>) As <see cref="System.String"/>]
91         ''' (这个函数指针用于处理来自于客户端的请求)
92         ''' </summary>
93         ''' <remarks></remarks>
94         Public Property Responsehandler As DataRequestHandler Implements IServicesSocket.Responsehandler
95
96         Public ReadOnly Property IsShutdown As Boolean Implements IServicesSocket.IsShutdown
97             Get
98                 Return disposedValue
99             End Get
100         End Property
101
102         ''' <summary>
103         ''' 消息处理的方法接口: Public Delegate Function DataResponseHandler(str As String, RemotePort As IntegerAs String
104         ''' </summary>
105         ''' <param name="LocalPort">监听的本地端口号,假若需要进行端口映射的话,则可以在<see cref="Run"></see>方法之中设置映射的端口号</param>
106         ''' <remarks></remarks>
107         Sub New(Optional LocalPort As Integer = 11000,
108                 Optional exHandler As Abstract.ExceptionHandler = Nothing)
109
110             Me._LocalPort = LocalPort
111             Me.__exceptionHandle = If(exHandler Is NothingAddressOf PrintException, exHandler)
112         End Sub
113
114         ''' <summary>
115         ''' 短连接socket服务端
116         ''' </summary>
117         ''' <param name="DataArrivalEventHandler"></param>
118         ''' <param name="LocalPort"></param>
119         ''' <param name="exHandler"></param>
120         Sub New(DataArrivalEventHandler As DataRequestHandler, LocalPort As IntegerOptional exHandler As Abstract.ExceptionHandler = Nothing)
121             Me.Responsehandler = DataArrivalEventHandler
122             Me.__exceptionHandle = If(exHandler Is NothingAddressOf PrintException, exHandler)
123             Me._LocalPort = LocalPort
124         End Sub
125
126         ''' <summary>
127         ''' 函数返回Socket的注销方法
128         ''' </summary>
129         ''' <param name="DataArrivalEventHandler">Public Delegate Function DataResponseHandler(str As String, RemotePort As IntegerAs String</param>
130         ''' <param name="LocalPort"></param>
131         ''' <param name="exHandler"></param>
132         ''' <returns></returns>
133         ''' <remarks></remarks>
134         Public Shared Function BeginListen(DataArrivalEventHandler As DataRequestHandler,
135                                            Optional LocalPort As Integer = 11000,
136                                            Optional exHandler As Abstract.ExceptionHandler = NothingAs Action
137             Dim Socket As New TcpSynchronizationServicesSocket(DataArrivalEventHandler, LocalPort, exHandler)
138             Call (Sub() Call Socket.Run()).BeginInvoke(Nothing, Nothing)
139             Return AddressOf Socket.Dispose
140         End Function
141
142         Public Function LoopbackEndPoint(Port As IntegerAs System.Net.IPEndPoint
143             Return New System.Net.IPEndPoint(System.Net.IPAddress.Loopback, Port)
144         End Function
145
146         Public Overrides Function ToString() As String
147             Return $"{GetIPAddress()}:{LocalPort}"
148         End Function
149
150         ''' <summary>
151         ''' This server waits for a connection and then uses  asychronous operations to
152         ''' accept the connection, get data from the connected client,
153         ''' echo that data back to the connected client.
154         ''' It then disconnects from the client and waits for another client.(请注意,当服务器的代码运行到这里之后,代码将被阻塞在这里)
155         ''' </summary>
156         ''' <remarks></remarks>
157         Public Function Run() As Integer Implements ITaskDriver.Run, IServicesSocket.Run
158
159             ' Establish the local endpoint for the socket.
160             Dim localEndPoint As System.Net.IPEndPoint =
161                 New System.Net.IPEndPoint(System.Net.IPAddress.Any, _LocalPort)
162             Return Run(localEndPoint)
163         End Function 'Main
164
165         ''' <summary>
166         ''' This server waits for a connection and then uses  asychronous operations to
167         ''' accept the connection, get data from the connected client,
168         ''' echo that data back to the connected client.
169         ''' It then disconnects from the client and waits for another client.(请注意,当服务器的代码运行到这里之后,代码将被阻塞在这里)
170         ''' </summary>
171         ''' <remarks></remarks>
172         Public Function Run(localEndPoint As System.Net.IPEndPoint) As Integer Implements IServicesSocket.Run
173
174             _LocalPort = localEndPoint.Port
175
176             ' Create a TCP/IP socket.
177             _servicesSocket = New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
178             '_InternalSocketListener.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, True)
179             ' Bind the socket to the local endpoint and listen for incoming connections.
180
181             Try
182                 Call _servicesSocket.Bind(localEndPoint)
183                 Call _servicesSocket.ReceiveBufferSize.SetValue(4096000)
184                 Call _servicesSocket.SendBufferSize.SetValue(4096000)
185                 Call _servicesSocket.Listen(backlog:=1000)
186             Catch ex As Exception
187                 Dim exMessage As String =
188                     "Exception on try initialize the socket connection local_EndPoint=" & localEndPoint.ToString &
189                     vbCrLf &
190                     vbCrLf &
191                     ex.ToString
192                 Call Me.__exceptionHandle(New Exception(exMessage, ex))
193                 Throw
194             Finally
195 #If DEBUG Then
196                 Call $"{MethodBase.GetCurrentMethod().GetFullName}  ==> {localEndPoint.ToString}".__DEBUG_ECHO
197 #End If
198             End Try
199 #Region ""
200             'If SelfMapping Then  '端口转发映射设置
201             '    Call Console.WriteLine("Self port mapping @wan_port={0} --->@lan_port", _LocalPort)
202             '    If Microsoft.VisualBasic.PortMapping.SetPortsMapping(_LocalPort, _LocalPort) = False Then
203             '        Call Console.WriteLine("Ports mapping is not successful!")
204             '    End If
205             'Else
206             '    If Not PortMapping < 100 Then
207             '        Call Console.WriteLine("Ports mapping wan_port={0}  ----->  lan_port={1}", PortMapping, LocalPort)
208             '        If False = SetPortsMapping(PortMapping, _LocalPort) Then
209             '            Call Console.WriteLine("Ports mapping is not successful!")
210             '        End If
211             '    End If
212             'End If
213 #End Region
214             _threadEndAccept = True
215             _Running = True
216
217             While Not Me.disposedValue
218
219                 If _threadEndAccept Then
220                     _threadEndAccept = False
221
222                     Dim callback As New AsyncCallback(AddressOf AcceptCallback)
223                     Try
224                         Call _servicesSocket.BeginAccept(callback, _servicesSocket)  ' Free 之后可能会出现空引用错误,则忽略掉这个错误,退出线程
225                     Catch ex As Exception
226                         Call App.LogException(ex)
227                     End Try
228                 End If
229
230                 Call Thread.Sleep(1)
231             End While
232
233             _Running = False
234
235             Return 0
236         End Function
237
238         Public ReadOnly Property Running As Boolean = False Implements IServicesSocket.IsRunning
239
240         Public Sub WaitForStart()
241             Do While Running = False
242                 Call Thread.Sleep(10)
243             Loop
244         End Sub
245
246         Public Sub AcceptCallback(ar As IAsyncResult)
247
248             Get the socket that handles the client request.
249             Dim listener As Socket = DirectCast(ar.AsyncState, Socket)
250
251             End the operation.
252             Dim handler As Socket
253
254             Try
255                 handler = listener.EndAccept(ar)
256             Catch ex As Exception
257                 _threadEndAccept = True
258                 Return
259             End Try
260
261             ' Create the state object for the async receive.
262             Dim state As StateObject = New StateObject With {.workSocket = handler}
263
264             Try
265                 Call handler.BeginReceive(state.readBuffer, 0, StateObject.BufferSize, 0, New AsyncCallback(AddressOf ReadCallback), state)
266             Catch ex As Exception
267                 ' 远程强制关闭主机连接,则放弃这一条数据请求的线程
268                 Call ForceCloseHandle(handler.RemoteEndPoint)
269             End Try
270
271             _threadEndAccept = True
272
273         End Sub 'AcceptCallback
274
275         Private Sub ForceCloseHandle(RemoteEndPoint As EndPoint)
276             Call $"Connection was force closed by {RemoteEndPoint.ToString}, services thread abort!".__DEBUG_ECHO
277         End Sub
278
279         Private Sub ReadCallback(ar As IAsyncResult)
280             ' Retrieve the state object and the handler socket
281             ' from the asynchronous state object.
282             Dim state As StateObject = DirectCast(ar.AsyncState, StateObject)
283             Dim handler As Socket = state.workSocket
284             ' Read data from the client socket.
285             Dim bytesRead As Integer
286
287             Try
288                 bytesRead = handler.EndReceive(ar)  '在这里可能发生远程客户端主机强制断开连接,由于已经被断开了,客户端已经放弃了这一次数据请求,所有在这里讲这个请求线程放弃
289             Catch ex As Exception
290                 Call ForceCloseHandle(handler.RemoteEndPoint)
291                 Return
292             End Try
293
294             If bytesRead > 0 Then  '有新的数据
295
296                 ' There  might be more data, so store the data received so far.
297                 state.ChunkBuffer.AddRange(state.readBuffer.Takes(bytesRead))
298                 ' Check for end-of-file tag. If it is not there, read
299                 ' more data.
300                 state.readBuffer = state.ChunkBuffer.ToArray
301
302                 Dim requestData As RequestStream = New RequestStream(state.readBuffer) '得到的是原始的请求数据
303
304                 If requestData.FullRead Then
305                     Call HandleRequest(handler, requestData)
306                 Else
307                     Try
308                         Not all data received. Get more.
309                         Call handler.BeginReceive(state.readBuffer, 0, StateObject.BufferSize, 0, New AsyncCallback(AddressOf ReadCallback), state)
310                     Catch ex As Exception
311                         Call ForceCloseHandle(handler.RemoteEndPoint)
312                         Return
313                     End Try
314                 End If
315             End If
316         End Sub 'ReadCallback
317
318         ''' <summary>
319         ''' All the data has been read from the client. Display it on the console.
320         ''' Echo the data back to the client.
321         ''' </summary>
322         ''' <param name="handler"></param>
323         ''' <param name="requestData"></param>
324         Private Sub HandleRequest(handler As Socket, requestData As RequestStream)
325             ' All the data has been read from the
326             ' client. Display it on the console.
327             ' Echo the data back to the client.
328
329             Dim remoteEP = DirectCast(handler.RemoteEndPoint, System.Net.IPEndPoint)
330
331             Try
332                 If requestData.IsPing Then
333                     requestData = NetResponse.RFC_OK
334                 Else
335                     requestData = Me.Responsehandler()(requestData.uid, requestData, remoteEP)
336                 End If
337                 Call Send(handler, requestData)
338             Catch ex As Exception
339                 Call __exceptionHandle(ex)
340                 '错误可能是内部处理请求的时候出错了,则将SERVER_INTERNAL_EXCEPTION结果返回给客户端
341                 Try
342                     Call Send(handler, NetResponse.RFC_INTERNAL_SERVER_ERROR)
343                 Catch ex2 As Exception '这里处理的是可能是强制断开连接的错误
344                     Call __exceptionHandle(ex2)
345                 End Try
346             End Try
347         End Sub
348
349         ''' <summary>
350         ''' Server reply the processing result of the request from the client.
351         ''' </summary>
352         ''' <param name="handler"></param>
353         ''' <param name="data"></param>
354         ''' <remarks></remarks>
355         Private Sub Send(handler As Socket, data As String)
356
357             ' Convert the string data to byte data using ASCII encoding.
358             Dim byteData As Byte() = Encoding.UTF8.GetBytes(data)
359             byteData = New RequestStream(0, 0, byteData).Serialize
360             ' Begin sending the data to the remote device.
361             Call handler.BeginSend(byteData, 0, byteData.Length, 0, New AsyncCallback(AddressOf SendCallback), handler)
362         End Sub 'Send
363
364         Private Sub Send(handler As Socket, data As RequestStream)
365             ' Convert the string data to byte data using ASCII encoding.
366             Dim byteData As Byte() = data.Serialize
367             ' Begin sending the data to the remote device.
368             Call handler.BeginSend(byteData, 0, byteData.Length, 0, New AsyncCallback(AddressOf SendCallback), handler)
369         End Sub
370
371         Private Sub SendCallback(ar As IAsyncResult)
372
373             ' Retrieve the socket from the state object.
374             Dim handler As Socket = DirectCast(ar.AsyncState, Socket)
375             ' Complete sending the data to the remote device.
376             Dim bytesSent As Integer = handler.EndSend(ar)
377             'Console.WriteLine("Sent {0} bytes to client.", bytesSent)
378             Call handler.Shutdown(SocketShutdown.Both)
379             Call handler.Close()
380         End Sub 'SendCallback
381
382         ''' <summary>
383         ''' SERVER_INTERNAL_EXCEPTION,Server encounter an internal exception during processing
384         ''' the data request from the remote device.
385         ''' (判断是否服务器在处理客户端的请求的时候,发生了内部错误)
386         ''' </summary>
387         ''' <param name="replyData"></param>
388         ''' <returns></returns>
389         ''' <remarks></remarks>
390         Public Shared Function IsServerInternalException(replyData As StringAs Boolean
391             Return String.Equals(replyData, NetResponse.RFC_INTERNAL_SERVER_ERROR.GetUTF8String)
392         End Function
393
394 #Region "IDisposable Support"
395
396         ''' <summary>
397         ''' 退出监听线程所需要的
398         ''' </summary>
399         ''' <remarks></remarks>
400         Private disposedValue As Boolean = False  To detect redundant calls
401
402         ' IDisposable
403         Protected Overridable Sub Dispose(disposing As Boolean)
404             If Not Me.disposedValue Then
405                 If disposing Then
406
407                     Call _servicesSocket.Dispose()
408                     Call _servicesSocket.Free()
409                     ' TODO: dispose managed state (managed objects).
410                 End If
411
412                 ' TODO: free unmanaged resources (unmanaged objects) and override Finalize() below.
413                 ' TODO: set large fields to null.
414             End If
415             Me.disposedValue = True
416         End Sub
417
418         ' TODO: override Finalize() only if Dispose(      disposing As Boolean) above has code to free unmanaged resources.
419         'Protected Overrides Sub Finalize()
420         '    ' Do not change this code.  Put cleanup code in Dispose(      disposing As Boolean) above.
421         '    Dispose(False)
422         '    MyBase.Finalize()
423         'End Sub
424
425         ' This code added by Visual Basic to correctly implement the disposable pattern.
426
427         ''' <summary>
428         ''' Stop the server socket listening threads.(终止服务器Socket监听线程)
429         ''' </summary>
430         ''' <remarks></remarks>
431         Public Sub Dispose() Implements IDisposable.Dispose
432             Do not change this code.  Put cleanup code in Dispose(disposing As Boolean) above.
433             Dispose(True)
434             GC.SuppressFinalize(Me)
435         End Sub
436 #End Region
437
438     End Class
439 End Namespace