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