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 Overloads) Sub 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 Integer, Optional 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 |