1 #Region "Microsoft.VisualBasic::188fcab53d5b0a41525c8ebd651472e1, Microsoft.VisualBasic.Core\Net\Tcp\Persistent\Socket\ServicesSocket.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 ServicesSocket
35     
36     '         Properties: Connections, IsShutdown, LocalPort, Running
37     
38     '         Constructor: (+2 OverloadsSub New
39     
40     '         Function: Run
41     
42     '         Sub: __acceptSocket, __initSocket, __initSocketThread, __runHost, AcceptCallback
43     '              Run, WaitForRunning
44     '         Delegate Sub
45     
46     '             Properties: AcceptCallbackHandleInvoke
47     
48     '             Sub: __socketCleanup, (+2 Overloads) Dispose, ForceCloseHandle
49     
50     
51     
52     ' /********************************************************************************/
53
54 #End Region
55
56 Imports System.Net.Sockets
57 Imports System.Runtime.CompilerServices
58 Imports System.Threading
59 Imports Microsoft.VisualBasic.ApplicationServices.Debugging.ExceptionExtensions
60 Imports Microsoft.VisualBasic.Language.Default
61 Imports Microsoft.VisualBasic.Net.Tcp.Persistent.Application.Protocols
62 Imports TcpEndPoint = System.Net.IPEndPoint
63 Imports TcpSocket = System.Net.Sockets.Socket
64
65 Namespace Net.Tcp.Persistent.Socket
66
67     ''' <summary>
68     ''' 
69     ''' </summary>
70     ''' <remarks>
71     ''' 一、TCP长连接
72     ''' 
73     ''' 正常情况下,一条TCP连接建立后,只要双不提出关闭请求并且不出现异常情况,这条连接是一直存在的,
74     ''' 操作系统不会自动去关闭它,甚至经过物理网络拓扑的改变之后仍然可以使用。
75     ''' 所以一条连接保持几天、几个月、几年或者更长时间都有可能,只要不出现异常情况或由用户(应用层)主动关闭。
76     ''' 在编程中, 往往需要建立一条TCP连接, 并且长时间处于连接状态。
77     ''' 所谓的TCP长连接并没有确切的时间限制, 而是说这条连接需要的时间比较长。
78     ''' 
79     ''' 二、TCP连接的正常中断
80     ''' 
81     ''' TCP连接在事务处理完毕之后, 由一方提出关闭连接请求, 双方通过四次握手(建立连接是三次握手, 
82     ''' 当然可以通过优化TCP / IP协议栈来减少握手的次数来提高性能, 但这样会形成不规范或者不优雅的通信)来正常关闭连接
83     ''' 
84     ''' 三、TCP连接的异常中断
85     ''' 
86     ''' 导致TCP连接异常中断的因素有: 物理连接被中断、操作系统down机、程序崩溃等等。
87     ''' </remarks>
88     Public Class ServicesSocket : Implements IDisposable
89
90 #Region "INTERNAL FIELDS"
91
92         Dim _threadEndAccept As Boolean = True
93         Dim _socketListener As TcpSocket
94
95         ''' <summary>
96         ''' Socket对象监听的端口号
97         ''' </summary>
98         ''' <remarks></remarks>
99         Protected _LocalPort As Integer
100         Protected _exceptionHandle As ExceptionHandler
101
102 #End Region
103
104         ''' <summary>
105         ''' The server services listening on this local port.(当前的这个服务器对象实例所监听的本地端口号)
106         ''' </summary>
107         ''' <value></value>
108         ''' <returns></returns>
109         ''' <remarks></remarks>
110         Public Overridable ReadOnly Property LocalPort As Integer
111             <MethodImpl(MethodImplOptions.AggressiveInlining)>
112             Get
113                 Return _LocalPort
114             End Get
115         End Property
116
117         Public ReadOnly Property IsShutdown As Boolean
118             <MethodImpl(MethodImplOptions.AggressiveInlining)>
119             Get
120                 Return disposedValue
121             End Get
122         End Property
123
124         Shared ReadOnly DefaultHandler As New DefaultValue(Of ExceptionHandler)(AddressOf VBDebugger.PrintException)
125
126         ''' <summary>
127         ''' 消息处理的方法接口: Public Delegate Function DataResponseHandler(str As String, RemotePort As IntegerAs String
128         ''' </summary>
129         ''' <param name="localPort">监听的本地端口号,假若需要进行端口映射的话,则可以在<see cref="Run"></see>方法之中设置映射的端口号</param>
130         ''' <remarks></remarks>
131         Sub New(Optional localPort% = 11000, Optional exHandler As ExceptionHandler = Nothing)
132             Me._LocalPort = localPort
133             Me._exceptionHandle = exHandler Or DefaultHandler
134         End Sub
135
136         <MethodImpl(MethodImplOptions.AggressiveInlining)>
137         Sub New(Optional exHandler As ExceptionHandler = Nothing)
138             Me._exceptionHandle = exHandler Or DefaultHandler
139         End Sub
140
141         ''' <summary>
142         ''' This server waits for a connection and then uses  asychronous operations to
143         ''' accept the connection, get data from the connected client,
144         ''' echo that data back to the connected client.
145         ''' It then disconnects from the client and waits for another client.(请注意,当服务器的代码运行到这里之后,代码将被阻塞在这里)
146         ''' </summary>
147         ''' <remarks></remarks>
148         Public Overridable Function Run() As Integer
149             ' Establish the local endpoint for the socket.
150             Call Run(New TcpEndPoint(System.Net.IPAddress.Any, _LocalPort))
151             Return 0
152         End Function
153
154         ''' <summary>
155         ''' This server waits for a connection and then uses  asychronous operations to
156         ''' accept the connection, get data from the connected client,
157         ''' echo that data back to the connected client.
158         ''' It then disconnects from the client and waits for another client.(请注意,当服务器的代码运行到这里之后,代码将被阻塞在这里)
159         ''' </summary>
160         ''' <remarks></remarks>
161         Public Overridable Sub Run(localEndPoint As TcpEndPoint)
162             _LocalPort = localEndPoint.Port
163
164             Try
165                 Call __initSocket(localEndPoint)
166             Catch ex As Exception
167                 Dim msg$ = "Error on initialize socket connection: local_EndPoint=" & localEndPoint.ToString
168                 ex = New Exception(msg, ex)
169                 Call _exceptionHandle(ex)
170                 Throw ex
171             End Try
172
173             Call __runHost()
174         End Sub
175
176         Private Sub __runHost()
177             _threadEndAccept = True
178             _Running = True
179
180             While Not Me.disposedValue
181                 Call Thread.Sleep(1)
182
183                 If _threadEndAccept Then
184                     Call __acceptSocket()
185                 End If
186             End While
187         End Sub
188
189         Private Sub __acceptSocket()
190             _threadEndAccept = False
191
192             Dim callback As New AsyncCallback(AddressOf AcceptCallback)
193             Call _socketListener.BeginAccept(callback, _socketListener)
194         End Sub
195
196         ''' <summary>
197         ''' Bind the socket to the local endpoint and listen for incoming connections.
198         ''' </summary>
199         ''' <param name="localEndPoint"></param>
200         Private Sub __initSocket(localEndPoint As TcpEndPoint)
201             ' Create a TCP/IP socket.
202             _socketListener = New TcpSocket(
203                 AddressFamily.InterNetwork,
204                 SocketType.Stream,
205                 ProtocolType.Tcp
206             )
207
208             Call _socketListener.Bind(localEndPoint)
209             Call _socketListener.ReceiveBufferSize.SetValue(4096)
210             Call _socketListener.SendBufferSize.SetValue(4096)
211             Call _socketListener.Listen(backlog:=100)
212         End Sub
213
214         Public ReadOnly Property Running As Boolean = False
215
216         Public Sub WaitForRunning()
217             Do While Not Running
218                 Call Thread.Sleep(10)
219             Loop
220         End Sub
221
222         ''' <summary>
223         ''' Get the socket that handles the client request.
224         ''' </summary>
225         ''' <param name="ar"></param>
226         Public Sub AcceptCallback(ar As IAsyncResult)
227             Dim listener As TcpSocket = DirectCast(ar.AsyncState, TcpSocket)
228             End the operation.
229             Dim handler As TcpSocket
230
231             Try
232                 handler = listener.EndAccept(ar)
233                 Call __initSocketThread(handler)
234             Catch ex As Exception
235                 _threadEndAccept = True
236                 Return
237             Finally
238                 _threadEndAccept = True
239             End Try
240         End Sub 'AcceptCallback
241
242         ''' <summary>
243         ''' Create the state object for the async receive.
244         ''' </summary>
245         ''' <param name="handler"></param>
246         Private Sub __initSocketThread(handler As TcpSocket)
247             Dim state As New StateObject With {
248                 .workSocket = handler
249             }
250             Dim socket As New WorkSocket(state) With {
251                 .ExceptionHandle = _exceptionHandle,
252                 .ForceCloseHandle = AddressOf Me.ForceCloseHandle
253             }
254
255             Try
256                 Call handler.BeginReceive(state.readBuffer, 0, StateObject.BufferSize, 0, New AsyncCallback(AddressOf socket.ReadCallback), state)
257                 Call AcceptCallbackHandleInvoke()(socket)
258                 Call _connections.Add(socket.GetHashCode, socket)
259                 Call Thread.Sleep(500)
260                 Call socket.PushMessage(ServicesProtocol.SendChannelHashCode(socket.GetHashCode))
261             Catch ex As Exception
262                 ' 远程强制关闭主机连接,则放弃这一条数据请求的线程
263                 Call ForceCloseHandle(socket)
264             End Try
265         End Sub
266
267         Protected _connections As New Dictionary(Of Integer, WorkSocket)
268
269         ''' <summary>
270         ''' 查找socket是通过<see cref="WorkSocket.GetHashCode()"/>函数来完成的
271         ''' </summary>
272         ''' <returns></returns>
273         Public ReadOnly Property Connections As WorkSocket()
274             Get
275                 Try
276                     Return _connections.Values.ToArray
277                 Catch ex As Exception
278                     Call ex.PrintException
279                     Return New WorkSocket() {}
280                 End Try
281             End Get
282         End Property
283
284         Public Delegate Sub AcceptCallbackHandle(socket As WorkSocket)
285
286         ''' <summary>
287         ''' 在这个方法之中添加新增加的socket长连接建立之后的处理代码
288         ''' 
289         ''' ```
290         ''' <see cref="System.Delegate"/> Sub(socket As <see cref="WorkSocket"/>)
291         ''' ```
292         ''' </summary>
293         ''' <returns></returns>
294         Public Property AcceptCallbackHandleInvoke As AcceptCallbackHandle
295
296         Protected Sub ForceCloseHandle(socket As WorkSocket)
297             Dim hash As Integer = socket.GetHashCode
298             Dim remoteName$ = socket.workSocket.RemoteEndPoint.ToString
299             Dim msg$ = $"Connection was force closed by [{remoteName}]!"
300
301             On Error Resume Next
302
303             Call msg.__INFO_ECHO
304             Call _connections.Remove(hash)
305             Call socket.Dispose()
306             Call __socketCleanup(hash)
307         End Sub
308
309         Protected Overridable Sub __socketCleanup(hash As Integer)
310
311         End Sub
312
313 #Region "IDisposable Support"
314
315         ''' <summary>
316         ''' 退出监听线程所需要的
317         ''' </summary>
318         ''' <remarks></remarks>
319         Private disposedValue As Boolean = False  To detect redundant calls
320
321         ' IDisposable
322         Protected Overridable Sub Dispose(disposing As Boolean)
323             If Not Me.disposedValue Then
324                 If disposing Then
325
326                     Call _socketListener.Dispose()
327                     Call _socketListener.Free()
328                     ' TODO: dispose managed state (managed objects).
329                 End If
330
331                 ' TODO: free unmanaged resources (unmanaged objects) and override Finalize() below.
332                 ' TODO: set large fields to null.
333             End If
334             Me.disposedValue = True
335         End Sub
336
337         ' TODO: override Finalize() only if Dispose(      disposing As Boolean) above has code to free unmanaged resources.
338         'Protected Overrides Sub Finalize()
339         '    ' Do not change this code.  Put cleanup code in Dispose(      disposing As Boolean) above.
340         '    Dispose(False)
341         '    MyBase.Finalize()
342         'End Sub
343
344         ' This code added by Visual Basic to correctly implement the disposable pattern.
345
346         ''' <summary>
347         ''' Stop the server socket listening threads.(终止服务器Socket监听线程)
348         ''' </summary>
349         ''' <remarks></remarks>
350         Public Sub Dispose() Implements IDisposable.Dispose
351             Do not change this code.  Put cleanup code in Dispose(disposing As Boolean) above.
352             Dispose(True)
353             GC.SuppressFinalize(Me)
354         End Sub
355 #End Region
356     End Class
357 End Namespace