Работа с классами и коллекциями
Здесь я расскажу об одной своей довольно интересной проге, на которой хорошо исключительно хорошо видно - как именно надо работать с классами (обьектами) и как это надо делать в многопоточном режиме. Это один из классов огромной системы и здесь я выложу реальный текст этого класса, который вы можете расширять дальше в любом направлении.
Этот небольшой класс является ядром любого прокси сервера, за исключением того, что общается со внешним миром он просто по TCP/IP, а не через HTTP. Однако, если вы пороетесь у меня на сайте, то найдете еще некий фрагмент, который позволит превратить нижеследующую прогу в полноценный прокси-сервер...
Чтобы на заморачиватся здесь на вопросах маршализации в поток формы из потоков прокси, представьте что вся диагностика по Normal_Message - это просто Console.WriteLine. Тогда протокол работы этого прокси будет выглядеть вот так.
Итак, наконец, вот собственно моя прога во всей красе, в которой отлично видно четыре основные техники работы с потоками (а может у кого-то их и больше)...
00001: Imports System.Net 00002: Imports System.Net.Sockets 00003: Imports System.IO 00004: 00005: Public Class NET_Listener 00006: ' 00007: Dim Config As System.Collections.Specialized.NameValueCollection 00008: Dim ValidIP As New System.Text.RegularExpressions.Regex("[1-2]\d{1,2}\.[1-2]?\d{1,2}\.[1-2]?\d{1,2}\.[1-2]?\d{1,2}") 00009: ' 00010: Dim SQL_Thread As Threading.Thread 00011: Dim SQL_IP As System.Net.IPAddress 00012: Dim SQL_Port As Int16 00013: Dim SQL_Listener As System.Net.Sockets.TcpListener 00014: Dim SQL_OpenNewPort As Int32 00015: ' 00016: Dim SVA_Queue As New SVA_QUE() 'очередь на передачу сообщений от SQL к SVA 00017: Dim SVA_Threads As New ArrayList() 'коллекция дочерних потоков 00018: Dim SVA_ListenerClass As New ArrayList() 'Коллекция указателей на классы прослушки 00019: Dim Perfomance As Int16 'Интервал опроса очереди на передачу 00020: ' 00021: Public Sub New() 00022: MyBase.new() 00023: End Sub 00024: ' 00025: Protected Overrides Sub Finalize() 'нас грохают 00026: MyBase.Finalize() 00027: End Sub 00028: ' 00029: Public ReadOnly Property GetQueue() As String 00030: Get 00031: Dim One As SVA_Data 00032: Dim Str1 As String = "" 00033: For Each One In SVA_Queue 00034: Str1 &= One.Value & ", " 00035: Next 00036: GetQueue = Str1 00037: End Get 00038: End Property 00039: ' 00040: Public ReadOnly Property GetThread() As String 00041: Get 00042: Dim Thread As System.Threading.Thread 00043: Dim Str2 As String = "" 00044: For Each Thread In SVA_Threads 00045: Str2 &= Thread.Name & ", " 00046: Next 00047: GetThread = Str2 00048: End Get 00049: End Property 00050: ' 00051: Public Function SQL_Start() As String 00052: If SQL_Thread Is Nothing Then 'нам лишних потоков не надо 00053: Config = System.Configuration.ConfigurationSettings.AppSettings 00054: If Config.Count = 0 Then 00055: Call Error_Message( _ 00056: "Ошибка. Нет файла конфигурации " & vbCrLf & _ 00057: System.AppDomain.CurrentDomain.SetupInformation.ConfigurationFile & vbCrLf & _ 00058: "в каталоге " & vbCrLf & _ 00059: System.AppDomain.CurrentDomain.SetupInformation.ApplicationBase) 00060: Else 00061: If Len(Config("SQL_IP")) = 0 Or Len(Config("SQL_PORT")) = 0 Or Len(Config("Perfomance")) = 0 Or Len(Config("SQL_OpenNewPort")) = 0 Then 00062: Call Error_Message( _ 00063: "Ошибка. В файле конфигурации " & vbCrLf & _ 00064: System.AppDomain.CurrentDomain.SetupInformation.ConfigurationFile & vbCrLf & _ 00065: "в каталоге " & vbCrLf & _ 00066: System.AppDomain.CurrentDomain.SetupInformation.ApplicationBase & vbCrLf & _ 00067: "Нет ключей - ""SQL_IP"" или ""SQL_PORT"" или ""Perfomance"" или ""SQL_OpenNewPort"" ") 00068: Else 00069: If ValidIP.IsMatch(Config("SQL_IP")) And Val(Config("SQL_PORT")) < 65535 Then 00070: Try 00071: SQL_IP = IPAddress.Parse(Config("SQL_IP")) 00072: SQL_Port = Val(Config("SQL_PORT")) 00073: SQL_OpenNewPort = Val(Config("SQL_OpenNewPort")) 00074: Perfomance = Val(Config("Perfomance")) 00075: ' 00076: SQL_Thread = New Threading.Thread(AddressOf SQL_Process) 00077: SQL_Thread.Start() 00078: ' 00079: SQL_Start = Config("SQL_IP") & ":" & Config("SQL_PORT") 00080: ' 00081: Catch x As System.Exception 00082: Call Error_Message( _ 00083: "Не стартовал главный поток прослушки сообщений от SQL-сервера." & vbCrLf & _ 00084: "Одна из вероятных причин - ошибки в файле конфигурации " & vbCrLf & _ 00085: System.AppDomain.CurrentDomain.SetupInformation.ConfigurationFile & vbCrLf & _ 00086: "в каталоге " & vbCrLf & _ 00087: System.AppDomain.CurrentDomain.SetupInformation.ApplicationBase & vbCrLf & _ 00088: "Адреса и порты прослушки, заданные в конфигурации: " & _ 00089: Config("SQL_IP") & ":" & Config("SQL_PORT") & vbCrLf & _ 00090: "Другая возможная ошибка:" & vbCrLf & _ 00091: x.Message) 00092: End Try 00093: 00094: Else 00095: Call Error_Message( _ 00096: "Ошибка. В файле конфигурации " & vbCrLf & _ 00097: System.AppDomain.CurrentDomain.SetupInformation.ConfigurationFile & vbCrLf & _ 00098: "в каталоге " & vbCrLf & _ 00099: System.AppDomain.CurrentDomain.SetupInformation.ApplicationBase & vbCrLf & _ 00100: "Неверно задан один из ключей - ""SQL_IP"" , ""SQL_PORT"" ") 00101: End If 00102: End If 00103: End If 00104: End If 00105: End Function 00106: ' 00107: Public Sub Abort_SVA_Thread(ByVal ThreadName As String) 00108: Dim Thread As System.Threading.Thread, SVA_Class As SVA_Listener 00109: Dim Str2 As String = "", i As Integer 00110: For Each Thread In SVA_Threads 00111: If ThreadName = Thread.Name Then 00112: SVA_Class = SVA_ListenerClass.Item(i) 00113: SVA_ListenerClass.RemoveAt(i) 00114: SVA_Class.Abort() 00115: SVA_Class = Nothing 00116: SVA_Threads.RemoveAt(i) 00117: Thread.Abort() 00118: Thread = Nothing 00119: Call Normal_Message("Закрыт поток " & ThreadName) 00120: Exit Sub 00121: End If 00122: i += 1 00123: Next 00124: End Sub 00125: ' 00126: Public Sub SQL_Abort() 00127: SQL_Thread.Abort() 'главный поток 00128: SQL_Thread = Nothing 00129: End Sub 00130: ' 00131: Private Sub SQL_Process() 00132: Call Normal_Message("Старт потока прослушки SQL-сервера : " & Config("SQL_IP") & ":" & Config("SQL_PORT").ToString) 00133: Try 00134: NewSQL_Connect: SQL_Listener = New System.Net.Sockets.TcpListener(SQL_IP, SQL_Port) 00135: ' 00136: SQL_Listener.Start() 00137: ' 00138: Dim TCPClient As System.Net.Sockets.TcpClient = SQL_Listener.AcceptTcpClient() 00139: 'Здесь поток ЖДЕТ первого пакета из сети чтобы все проинициализировалось 00140: ' 00141: Dim NetStream As System.Net.Sockets.NetworkStream = TCPClient.GetStream 00142: ' 00143: Dim SQL_Reader As System.IO.BinaryReader = New System.IO.BinaryReader(NetStream) 00144: ' 00145: Dim Result As Int32 00146: ' 00147: Do While True 'Вычитываем все что дает сервер в этом коннекте 00148: ' 00149: Try 00150: Result = SQL_Reader.ReadInt32 00151: 'пока данных нет - поток в этом месте подвисает 00152: 'или падает по закрытию коннекта (завершении процедуры) 00153: ' 00154: Catch x As System.IO.EndOfStreamException 00155: ' 00156: TCPClient.Close() 'полное закрытие коннекта с SQL 00157: TCPClient = Nothing 00158: SQL_Listener.Stop() 00159: SQL_Listener = Nothing 00160: NetStream.Close() 00161: NetStream = Nothing 00162: SQL_Reader.Close() 00163: SQL_Reader = Nothing 00164: ' 00165: Call Normal_Message("Закрыт коннект с SQL-сервером") 00166: GoTo NewSQL_Connect 00167: End Try 00168: ' 00169: Call Normal_Message("От SQL-сервера получено - " & Result.ToString) 00170: ' 00171: Select Case Result 00172: Case SQL_OpenNewPort 'дальше по нашему протоколу SQL-сервер сразу дает новый номер порта, который надо слушат у SVA и номер юзера 00173: Dim NewPort As Int32 00174: Dim NewUser As Int32 00175: Dim NewByte1, NewByte2, NewByte3, NewByte4 As Int32 00176: Dim SVA_IP As System.Net.IPAddress, SVA_IP_string As String 00177: Try 00178: NewPort = SQL_Reader.ReadInt32 00179: NewUser = SQL_Reader.ReadInt32 00180: 'теперь побайтно принимаем айпишник 00181: NewByte1 = SQL_Reader.ReadInt32 00182: NewByte2 = SQL_Reader.ReadInt32 00183: NewByte3 = SQL_Reader.ReadInt32 00184: NewByte4 = SQL_Reader.ReadInt32 00185: ' 00186: SVA_IP_string = NewByte1.ToString & "." & NewByte2.ToString & "." & NewByte3.ToString & "." & NewByte4.ToString 00187: If ValidIP.IsMatch(SVA_IP_string) Then 00188: SVA_IP = IPAddress.Parse(SVA_IP_string) 00189: ' 00190: Dim NewListener As New SVA_Listener(SVA_IP, NewPort, NewUser, SVA_Queue, Perfomance) 00191: SVA_ListenerClass.Add(NewListener) 00192: Dim NewThread As Threading.Thread = New Threading.Thread(AddressOf NewListener.Start) 00193: NewThread.Name = "SVA_Listener. User:" & NewUser.ToString & " - " & SVA_IP.ToString & ":" & NewPort.ToString 00194: NewThread.Start() 00195: SVA_Threads.Add(NewThread) 00196: 00197: Else 00198: Throw New System.Exception("От SQL-сервера принят неверный адрес прослушки SVA") 00199: End If 00200: Catch x As System.Exception 00201: Call Error_Message( _ 00202: "Ошибка создания нового потока прослушки данных от клиентов." & vbCrLf & _ 00203: x.Message & vbCrLf & _ 00204: "Запрашивалось создание потока для прослушки от юзера " & NewUser.ToString & " На " & SVA_IP.ToString & ":" & NewPort.ToString) 00205: End Try 00206: ' 00207: Case Else 'иначе просто в очередь на передачу данных 00208: Dim NewData As SVA_Data = New SVA_Data(Result) 00209: SVA_Queue.Add(NewData) 00210: ' 00211: End Select 00212: ' 00213: Loop 00214: ' 00215: Catch x As System.Exception 00216: Dim One As SVA_Data 00217: Dim Str1 As String = "", Str2 As String = "" 00218: For Each One In SVA_Queue 00219: Str1 &= One.Value & "," 00220: Next 00221: Dim Thread As System.Threading.Thread 00222: For Each Thread In SVA_Threads 00223: Str2 &= Thread.Name & "," 00224: Next 00225: If TypeName(x) = "ThreadAbortException" Then 00226: Call Normal_Message( _ 00227: "Закрыт главный поток прослушки данных от SQL-сервера" & vbCrLf & _ 00228: "Содержимое очереди на передачу: " & Str1 & vbCrLf & _ 00229: "Потоки передачи: " & Str2) 00230: Else 00231: Call Error_Message( _ 00232: "Ошибка TCP/IP в главном потоке прослушки сообщений от SQL-сервера" & vbCrLf & _ 00233: x.Message & vbCrLf & _ 00234: "Содержимое очереди на передачу: " & Str1 & vbCrLf & _ 00235: "Потоки передачи: " & Str2) 00236: End If 00237: End Try 00238: End Sub 00239: End Class 00240: 00241: 'Этот класс - отдельный поток. После создания 00242: 'он периодически просматривает очередь 00243: 'на передачу в свой порт 00244: Public Class SVA_Listener 00245: ' 00246: Dim MyIP As System.Net.IPAddress 'IP - Куда передаем (где слушаем юзера) 00247: Dim MyPort As Int16 'Port - Куда передаем (где слушаем юзера) 00248: Dim MyUser As Int32 'Номер юзера на этом порту 00249: Dim MyInterval As Int16 'Интервал опроса очереди на передачу 00250: Dim MyQueue As SVA_QUE 'Очередь на передачу 00251: ' 'Средства передачи по TCP/IP 00252: Dim MyListener As System.Net.Sockets.TcpListener 00253: Dim MyStream As System.Net.Sockets.NetworkStream 00254: Dim MyTcpClient As System.Net.Sockets.TcpClient 00255: Dim MyWriter As System.IO.BinaryWriter 00256: ' 00257: Public Sub New(ByVal IP As System.Net.IPAddress, ByVal Port As Int16, ByVal User As Int32, ByVal Queue As SVA_QUE, ByVal Interval_Oprosa As Int16) 00258: MyUser = User 00259: MyInterval = Interval_Oprosa 00260: MyQueue = Queue 00261: MyPort = Port 00262: MyIP = IP 00263: MyListener = New System.Net.Sockets.TcpListener(IP, Port) 00264: Call Normal_Message( _ 00265: "Создан обьект передачи для юзера - " & MyUser & _ 00266: " На порту - " & MyIP.ToString & ":" & MyPort.ToString) 00267: End Sub 00268: ' 00269: Public Sub Abort() 00270: Call Normal_Message( _ 00271: "Закрыт обьект передачи для юзера - " & MyUser & _ 00272: " На порту - " & MyIP.ToString & ":" & MyPort.ToString) 00273: If Not (MyWriter Is Nothing) Then MyWriter.Close() 00274: If Not (MyStream Is Nothing) Then MyStream.Close() 00275: If Not (MyListener Is Nothing) Then MyListener.Stop() 00276: If Not (MyTcpClient Is Nothing) Then MyTcpClient.Close() 00277: MyWriter = Nothing 00278: MyStream = Nothing 00279: MyListener = Nothing 00280: MyTcpClient = Nothing 00281: 'System.Threading.Thread.CurrentThread.Abort() 00282: End Sub 00283: ' 00284: Public Sub Start() 00285: 'Начало бесконечного цикла передачи сообщений по установленному каналу 00286: Try 00287: MyListener.Start() 00288: MyTcpClient = MyListener.AcceptTcpClient 00289: 'здесь подвисает ??? 00290: MyStream = MyTcpClient.GetStream 00291: MyWriter = New System.IO.BinaryWriter(MyStream) 00292: ' 00293: Call Normal_Message( _ 00294: "Инициализирован обьект передачи для юзера - " & MyUser & _ 00295: " На порту - " & MyIP.ToString & ":" & MyPort.ToString) 00296: ' 00297: Catch x As System.Exception 00298: Call Error_Message( _ 00299: "Ошибка создания порта прослушки " & MyIP.ToString & ":" & MyPort.ToString & vbCrLf & _ 00300: "для юзера " & MyUser.ToString & vbCrLf & _ 00301: x.Message) 00302: 'И весь этот поток и класс глючный - его надо убить 00303: 'пока непонятно как 00304: Exit Sub 00305: End Try 00306: ' 00307: Do While True 00308: System.Threading.Thread.Sleep(MyInterval) 00309: Try 00310: If MyQueue.Present(MyUser) Then 00311: ' 00312: Call Normal_Message( _ 00313: "Найдены данные для передачи юзеру - " & MyUser & _ 00314: " На порту - " & MyIP.ToString & ":" & MyPort.ToString) 00315: ' 00316: MyWriter.Write(MyUser) 00317: MyQueue.Delete(MyUser) 00318: ' 00319: Call Normal_Message( _ 00320: "Закончена передача для юзера - " & MyUser & _ 00321: " На порту - " & MyIP.ToString & ":" & MyPort.ToString) 00322: ' 00323: End If 00324: 00325: Catch x As System.Exception 00326: Call Error_Message( _ 00327: "Ошибка передачи данных юзеру " & MyUser.ToString & "в порт " & MyIP.ToString & ":" & MyPort.ToString & vbCrLf & _ 00328: vbCrLf & _ 00329: x.Message) 00330: End Try 00331: Loop 00332: End Sub 00333: End Class 00334: 00335: 'В этот класc прячем данные для передачи 00336: Public Class SVA_Data 00337: Dim Data As Int32 00338: 00339: Public Sub New(ByVal Value As Int32) 00340: Data = Value 00341: End Sub 00342: 00343: Public ReadOnly Property Value() As Int32 00344: Get 00345: Value = Data 00346: End Get 00347: End Property 00348: End Class 00349: 00350: 'Этот класс - очередь на передачу из элементов SVA_Data 00351: Public Class SVA_QUE 00352: Inherits System.Collections.CollectionBase 'переопределяем 00353: 00354: Public Sub Add(ByVal Data As SVA_Data) 00355: Me.List.Add(Data) 00356: Call Normal_Message("В очередь передачи добавлены данные : " & Data.Value.ToString) 00357: End Sub 00358: 00359: Public Function Present(ByVal Value As Int32) As Boolean 00360: Dim One As SVA_Data 00361: For Each One In Me 00362: If One.Value = Value Then 00363: Present = True 00364: Exit Function 00365: End If 00366: Next 00367: Present = False 00368: End Function 00369: 00370: Public Sub Delete(ByVal value As Int32) 00371: Dim One As SVA_Data 00372: Dim i As Int32 00373: For i = 0 To Me.Count 00374: One = Me.List(i) 00375: 'удаляем первый - они все равно одинаковые 00376: If One.Value = value Then 00377: Me.RemoveAt(i) 00378: Call Normal_Message("Из очереди передачи удалены данные : " & One.Value.ToString) 00379: Exit Sub 00380: End If 00381: Next 00382: End Sub 00383: 00384: End Class
Итак, как вы видите, функционально эта прога состоит из класса NET_Listener (строки 1-240) экземпляр которого, создает при запуске Прокси внешее приложение.
Этот класс имеет несколько, методов, первым (естественно после конструктора NEW (строка 22), который вызывается автоматически для создания экземпляра этого класса), так вот первым пользовательское приложение вызывает метод SQL_Start в строке 51. Этот метод считывает конфигурацию этого класса:
прверяет ее на правильность (строка 69) по регулярному выражению (заданному в строке 8) и наконец в строке 76-77 стартует новый поток SQL_Process (строка 131), который в дальнейшем будет управлять всеми действиями этого прокси-сервера. После старта управление возвращается вызывающей проге, а созданный экземпляр NET_Listener'а дальше будет жить своей жизнью (в бесконечном цикле со строки 134 по строку 213), независимой от вызывающей проги и того, что мы уже вернули управление, тк жизнь экземпляра этого класса будет продолжатся во вновь созданном нами потоке, тк мы запустили в новый поток метод этого же класса.
Это, так сказать первый прием работы с классами и потоками. Иной способ создания потоков (не другой поток в существующем классе, а на каждый поток - отдельный класс) - рассмотрен в самом конце этой странички. Заметьте, что поскольку экземпляр сохранен, и у вызывающего класса ЕСТЬ ВЕРНАЯ ССЫЛКА на экземпляр этого класса, независимо от потока в этом классе, МОЖНО ВЫЗВАТЬ МЕТОДЫ класса NET_LISTENER, скажем так: PROXY.GetQueue или так PROXY.GetThread и достать этими методами маленькие хитрости, который этот класс прячет у себя...
Теперь рассмотрим внутренню жизнь этого класса, заключающуюся в бесконечном цикле в строках 134-213. Взглянув на текст проги, вы понимаете, что здесь все начинается в строке 150, где прога получает данные из сети. Здесь, как вы видите, как у Ильи Муромца, есть три основные дорожки.
Первая дорожка, SQL-сервер закрыл коннект, срабатывает фильтрованная ловушка в строке 154, коннект полностью закрывается и переход в строке 166 приводит к полному переоткрытию коннекта с SQL. Физически это означает что на SQL-сервере закончился SQL-BATCH, скажем такой:
exec SY_MG_MessageWrite 11, 11, N'COMPUTER', N'432432', 0, N'', N'', 3, 20, 1, 10, 0, 0, 1, 1, 0, 0, 0, 0
00010: ALTER procedure SY_MG_MessageWrite ...... ...... <b>фрагмент - все детали опущены</b> ...... 00073: -- 00074: -- Теперь выполняем ОПОВЕЩЕНИЕ 00075: -- 00076: 00077: Declare @Ret int,@Handler int 00078: exec @Ret= SY_MG_SEND 'Load',@Handler OUTPUT,default,default,default 00079: IF (@Ret>0 or @Handler=0) return 7 00080: 00081: Declare @ProxyIP varchar(15), @ProxyPort smallint 00082: Select top 1 @ProxyIP=[IP], @ProxyPort=[Port] from SY_MG_ProxyParm 00083: exec @Ret= SY_MG_SEND 'Start',@Handler,@ProxyIP,@ProxyPort,default 00084: IF @Ret>0 return 8 00085: 00086: exec @Ret= SY_MG_SEND 'Connect',@Handler,default,default,default 00087: IF @Ret>0 return 9 00088: 00089: exec @Ret= SY_MG_SEND 'Write',@Handler,default,default,@ID 00090: IF @Ret>0 return 10 00091: 00092: exec @Ret= SY_MG_SEND 'Abort',@Handler 00093: IF @Ret>0 return 11 00094: 00095: return(0)
00007: ALTER procedure SY_MG_SEND 00008: -- 00009: -- @Action - определяет ШЕСТЬ РАЗЛИЧНЫХ ОПЕРАЦИЙ выполняемых процедурой: 00010: -- 'Load' - загрузка драйвера (возвращается @Handler - указатель на драйвер) 00011: -- во всех остальных случаях @Handler обязательно задается для работы процедуры 00012: -- 'Hello' - выдача на сервере диагностического сообщения для проверки наличия драйвера 00013: -- 'Start' - инициализация загруженного экземпляра драйвера для работы в сети 00014: -- 'Connect'- коннект к клиенту (надо задавать еще @IP и @Port) 00015: -- 'Write' - передача данных (надо указать передаваемое число в @Data) 00016: -- 'Abort' - разрыв соединения и уничтожение экземпляра драйвера 00017: -- если все сработало правильно - код возврата процедуры 0 00018: -- 00019: @Action Varchar(7), 00020: @Handler int OUTPUT, 00021: @IP varchar(15) ='', 00022: @Port varchar(5) ='', 00023: @Data int =0 00024: as 00025: DECLARE @hr int 00026: DECLARE @src varchar(255), @desc varchar(255), @Return int 00027: If @Action='Load' BEGIN 00028: EXEC @hr = sp_OACreate 'SQLSENDER.SQL_Sender', @Handler OUT 00029: IF (@hr = 0) Return (0) else GOTO ERROR1 00030: END 00031: ELSE If @Action='Hello' BEGIN 00032: EXEC @hr = sp_OAMethod @Handler, 'Hello', @Return OUT,'Привет из SQL-сервера' 00033: IF (@hr = 0) Return (0) else GOTO ERROR1 00034: END 00035: ELSE If @Action='Start' BEGIN 00036: EXEC @hr = sp_OAMethod @Handler, 'Start',@Return OUT,@IP,@Port 00037: IF (@hr = 0) Return (0) else GOTO ERROR1 00038: END 00039: ELSE If @Action='Connect' BEGIN 00040: EXEC @hr = sp_OAMethod @Handler, 'Connect',@Return OUT 00041: IF (@hr = 0) Return (0) else GOTO ERROR1 00042: END 00043: ELSE If @Action='Write' BEGIN 00044: EXEC @hr = sp_OAMethod @Handler, 'Write',@Return OUT,@Data 00045: IF (@hr = 0) Return (0) else GOTO ERROR1 00046: END 00047: ELSE If @Action='Abort' BEGIN 00048: EXEC @hr = sp_OAMethod @Handler, 'Abort',@Return OUT 00049: IF (@hr = 0) Return (0) else GOTO ERROR1 00050: EXEC @hr = sp_OADestroy @Handler 00051: IF (@hr = 0) Return (0) else GOTO ERROR1 00052: END 00053: ERROR1: 00054: EXEC sp_OAGetErrorInfo @Handler, @src OUT, @desc OUT 00055: SELECT @Action,hr=convert(varbinary(4),@hr), Source=@src, Description=@desc 00056: RETURN @hr
Вторая дорожка начинается в строке 207, если принятое число не равно значению переменной SQL_OpenNewPort. Эдесь применяется основной метод работы с классами. Если вы понимаете, как это работает, и реально будете применять это в сових прогах - половина работы с классами у вас в кармане.
Так вот - сначала делается маленький класс строки 336-348. Этот класс имеет параметризованный конструктор в строке 339 и когда вы создаете в строке 208 экземпляр этого класса в стек намертво утапливается значение, которое мы задали при создании экземпляра этого класса.
А вот следующий класс - строки 351-384 ГОРАЗДО более интересный. Замечательная строка 352, объявляет, что этот класс наследует System.Collections.CollectionBase, а чтобы отойти от этого слова к простым русским словам - уточняет, дополняет, переопределяет общие методы работы с коллекциями, прописанные в System.Collections.CollectionBase. Причем позволяет добавить (строка 209) в коллекцию (очередь на передачу), не какой угодно мусор, а только вышерассмотренный класс SVA_Data, который прячет в себе данные на передачу. Еще эта коллекция умеет проверить наличие данных для передачи и удалить элемент SVA_Data, хранящий уже не нужные данные для передачи. Обратите внимание, как работает эта хитрая коллекция - поймете - вам десять плюсов...
Теперь рассмотрим третью дорожку, самую сложную в плане понимания мультипоточного и обьектого программирования. Она начинается в строке 177, в том случае, если принятое число равно переменной SQL_OpenNewPort. В этом случае отрабатывает некоторый протокол, формируемый некоторой процедурой, в процессе которого передаются данные, какой именно порт и для кого следует открыть на внешнем (или иногда на внутреннем) интерфейсе прокси-сервера. Эта профедура имеет вот такой вид - только смысловой фрагмент, детали опущены:
exec SY_MG_Connect 11, 761162163, '192.168.0.51', 6011
00016: ALTER procedure SY_MG_Connect 00017: @CurrentUser int, 00018: @EID int, 00019: @IP as varchar(15), 00020: @Port as smallint 00021: as 00022: declare @Row1 int,@SCOPE int 00023: 00024: --сначала фиксируем вход 00025: INSERT INTO [SY_MG_ConnectTab]([CurrentUser], [SVA_ID], [IP],[PORT], [StartTime]) 00026: VALUES( @CurrentUser, @EID, @IP, @PORT, getdate()) 00027: select @Row1=@@Rowcount 00028: select @SCOPE=SCOPE_IDENTITY() 00029: If @Row1<>1 return 1 00030: 00031: --теперь загружаем драйвер и передаем ПРОКСИ-СЕРВЕРУ команду на открытие нового порта 00032: Declare @Ret int,@Handler int 00033: exec @Ret= SY_MG_SEND 'Load',@Handler OUTPUT,default,default,default 00034: UPDATE [SY_MG_ConnectTab] SET [StateLoad]=@Ret where [ID]=@SCOPE 00035: IF (@Ret>0 or @Handler=0) return 2 00036: 00037: Declare @ProxyIP varchar(15), @ProxyPort smallint, @ProxyKey int 00038: Select top 1 @ProxyIP=[IP], @ProxyPort=[Port], @ProxyKey=[key] from SY_MG_ProxyParm 00039: exec @Ret= SY_MG_SEND 'Start',@Handler,@ProxyIP,@ProxyPort,default 00040: UPDATE [SY_MG_ConnectTab] SET [StateStart]=@Ret where [ID]=@SCOPE 00041: IF @Ret>0 return 3 00042: 00043: exec @Ret= SY_MG_SEND 'Connect',@Handler,default,default,default 00044: UPDATE [SY_MG_ConnectTab] SET [StateConnect]=@Ret where [ID]=@SCOPE 00045: IF @Ret>0 return 4 00046: 00047: exec @Ret= SY_MG_SEND 'Write',@Handler,default,default,@ProxyKey -- КЛЮЧ открыть новый порт 00048: UPDATE [SY_MG_ConnectTab] SET [StateWrite]=@Ret where [ID]=@SCOPE 00049: IF @Ret>0 return 5 00050: 00051: exec @Ret= SY_MG_SEND 'Write',@Handler,default,default,@Port -- Номер порта 00052: UPDATE [SY_MG_ConnectTab] SET [StateWrite]=@Ret where [ID]=@SCOPE 00053: IF @Ret>0 return 5 00054: 00055: exec @Ret= SY_MG_SEND 'Write',@Handler,default,default,@CurrentUser -- Кому 00056: UPDATE [SY_MG_ConnectTab] SET [StateWrite]=@Ret where [ID]=@SCOPE 00057: IF @Ret>0 return 5 00058: 00059: Declare @Byte1 int, @Byte2 int, @Byte3 int, @Byte4 int 00060: exec @Ret=SY_MG_ParseIP @IP, @Byte1 OUTPUT, @Byte2 OUTPUT, @Byte3 OUTPUT, @Byte4 OUTPUT 00061: 00062: exec @Ret= SY_MG_SEND 'Write',@Handler,default,default,@Byte1 -- Первый байт айпишника 00063: UPDATE [SY_MG_ConnectTab] SET [StateWrite]=@Ret where [ID]=@SCOPE 00064: IF @Ret>0 return 5 00065: 00066: exec @Ret= SY_MG_SEND 'Write',@Handler,default,default,@Byte2 -- Второй байт айпишника 00067: UPDATE [SY_MG_ConnectTab] SET [StateWrite]=@Ret where [ID]=@SCOPE 00068: IF @Ret>0 return 5 00069: 00070: exec @Ret= SY_MG_SEND 'Write',@Handler,default,default,@Byte3 -- Третий байт айпишника 00071: UPDATE [SY_MG_ConnectTab] SET [StateWrite]=@Ret 00072: IF @Ret>0 return 5 00073: 00074: exec @Ret= SY_MG_SEND 'Write',@Handler,default,default,@Byte4 -- Четвертый байт айпишника 00075: UPDATE [SY_MG_ConnectTab] SET [StateWrite]=@Ret 00076: IF @Ret>0 return 5 00077: 00078: exec @Ret= SY_MG_SEND 'Abort',@Handler 00079: UPDATE [SY_MG_ConnectTab] SET [StateAbort]=@Ret 00080: IF @Ret>0 return 5 00081: 00082: 00083: Return @SCOPE
Как видите процедура передает серию чисел, принимаемых в строках 178-184. Если все прошло нормально (иначе просто THROW) - в строке 190 создается экземпляр класса SVA_Listener, определенного в строках 241-333. Как видите в строке 257, этот класс параметризованный и проглатывает в себя при создании экземпляра все, что ему надо для работы. Строки 191, 193 и 195 здесь смыслового значения для организации этого класса не имеют, они выполняют учетные функции и позволяют увидеть потом на форме созданные задачи:
А вот строка 192 во всей этой проге - самая что ни на есть ключевая. Она создает НОВЫЙ ПОТОК ИЗ ДИНАМИЧЕСКИ СОЗДАННОГО НАМИ ЭКЗЕМПЛЯРА КЛАССА NewListener. Это принципиально иной прием, чем мы пользовались в строке 76, когда создавали новый поток из самого себя, в классе, в котором уже был один поток. Здесь получается что созданный нами каждый экземпляр фактически работает в однопоточном режиме, для чего все данные для его работы в начале его жизни в строке 257 упрятывались вовнуть него. Как видите ситуация с классом NET_Listener приниципиально иная - тот класс действительно исполняется многими потоками одновременно - один поток кружит по строкам 134-213. Другой поток запрашивает, скажем свойства GetQueue. Но как же я обошелся без SYNCLOCK. А вот так. Все данные развязаны между потоками через нашу хитрую коллекцию (строки 351-384), работающую с классом 336-348.
Здесь есть еще один момент. Как вы видите созданные нами экземпляры классов-потоков передачи устроены несложно. Они просто в строках 307-331 проверяют в бесконечном цикле очередь (нашу коллекцию) в строке 310 и при появлении данных на передачу по своему коннекту начинают передачу. Очередь проверяется с задержкой потока по таймеру в строке 308. На самом деле - это слишком простенько, но моего заказчика-плательщика это пока устроило. На самом деле более корректно было бы (поскольку адрес каждого экземпляра этого класса в строке 191 учтен), можно было бы останавливать поток на синхронизации после передачи данных, а потом после строки 209 сформировать делегат, который разбудит нужный поток и разбуженный поток начнет передачу. Но это более сложный и продвинутый вариант этой проги требующий виртуозной работы не только с классами и потоками, но с делегатами...
В сущности это лишь два основных приема мультипоточной работы. Третья техника - когда из существующего потока сначала создается новый поток, а текущий поток вешается по скажем FORM.SHOWDIALOG, рассмотрена здесь. Причем сделано это в модуле - фактически в заведомо созданном экземпляре класса.
Наконец, в этом же топике рассмотрена и четвертая техника работы с потоками, имеющая специальное название - маршализация в поток формы.
|