(NET) NET (2005 год)

Работа с классами и коллекциями

Здесь я расскажу об одной своей довольно интересной проге, на которой хорошо исключительно хорошо видно - как именно надо работать с классами (обьектами) и как это надо делать в многопоточном режиме. Это один из классов огромной системы и здесь я выложу реальный текст этого класса, который вы можете расширять дальше в любом направлении.

Этот небольшой класс является ядром любого прокси сервера, за исключением того, что общается со внешним миром он просто по 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, рассмотрена здесь. Причем сделано это в модуле - фактически в заведомо созданном экземпляре класса.

Наконец, в этом же топике рассмотрена и четвертая техника работы с потоками, имеющая специальное название - маршализация в поток формы.

Comments ( )
Link to this page: //www.vb-net.com/dotnet/tour16/index.htm
< THANKS ME>