Alibek Bolatov
alibek@mail.ru
version 1.0
В данной статье приводится пример написания приложения "клиент-сервер" с использованием компонента ActiveX WinSock.
Для начала, рассмотрим схему будущей системы "клиент-сервер".
Чем шифровать и чем вычислять хэши - дело второстепенное, можно вообще отказаться от криптографии. Но в данном примере будет использовать хэши MD5 (ссылка 6) и шифрование по алгоритму "Энигма" (ссылка 5). И хеширование, и шифрование реализованно в классах, для использования своих алгоритмов достаточно заменить прилагаемые классы своими.
Более подробно информация о подключении и о структуре пакетов данных будет представлена в следующей главе.
Общее описание
Прежде чем писать код, вначале более детально рассмотрим этапы подключения клиента к серверу и разработаем структуру пакетов данных. Для конкретных нужд имеет смысл разрабатывать структуру пакета индивидуально, чтобы оптимизировать дальнейшее программирование. В данном примере будем использовать структуру, которая подходит для большинства случаев.
В подавляющем большинстве случаев схема обмена данными между клиентом и сервером выглядит так: клиент подает запрос, сервер его обрабатывает и присылает ответ. В некоторых случаях сервер сам отправляет какие-либо данные клиенту (уведомления о различных событиях). И практически никогда не требуется обратный обмен данными, т.е. сервер самостоятельно делает запрос клиенту, а тот отправляет ему ответ. Исходя из этого будем учитывать что в ответе, отсылаемом сервером, обязательно должно быть поле, которое подтверждает успешное выполнение запроса или возвращает код ошибки запроса (например, ошибочный запрос, или на выполнение указанной операции недостаточно прав).
Кроме того, имеется еще один ньюанс. Дело в том, что клиент не знает, когда сервер подготовит ответ и даже придет ли ответ вообще (т.к. данные принимаются и отправляются асинхронно). Кроме того, пользователю будет удобнее, если в то время, пока будут подготавливаться данные для первого запроса, он (пользователь) сможет отправить второй запрос.
В свете указанного встает проблема, как определить, на какой запрос пришел ответ от сервера. Самый простой путь (и на мой взгляд, оптимальный) - каждый запрос будет предваряться неким идентификатором (например, счетчиком), и когда сервер отправляет ответ клиенту, он добавляет к этому ответу ижентификатор запроса. Разумеется, на клиенте необходимо реализовать способ, чтобы по идентификатору запроса можно было определить сам запрос, т.е. на клиенте будет некий буфер или очередь, в которую будут добавляться элементы-запросы (при отправке запроса), а при получении ответа на запрос элементы будут удаляться из очереди.
Кроме того, было бы неплохо предусмотреть механизм, который бы затруднял подделку пакетов данных. Способов для этого много, в данном примере выбран механизм цифровой подписи (к пакету данных добавляется хэш, потом полученный блок данных шифруется и снова хэшируется).
Есть и еще один ньюанс. Шифровать трафик следует после авторизации приложения (не пользователя). Причин для этого несколько, самая очевидная -- такой механизм позволяет использовать динамический пароль и алгоритм шифрования. Другая причина, менее очевидная -- если достоверность клиента не подтверждена, то не следует ему (клиенту) знать о способах шифрования данных (при наличии большого количества зашифрованной информации вероятность ее расшифровки повышается).
На основании указанного была разработана следующая схема обмена данными.
Блоки данных, передаваемые между сервером и клиентом, разделяются двойным "null" (ASCII 0x0000); это требуется из-за неизбежной фрагментации пакетов при передаче их через WinSock. Вследствии того, что символ "null" имеет особое значение (разделитель полей), следует исключить вероятность появления данного символа внутри блока данных. Один из способов -- преобразовывать двоичные данные в Hex-dump. Другой способ -- переделать программный код приема/отправки данных, например, добавляя перед каждым блоком данных его длину в байтах и загружая требуемое число байт.
Такая схема позволяет использовать одну и ту же подпрограмму для обработки пакета данных в любом режиме (прием/ответ, зашифрованный канал/открытый канал). В статье код этой подпрограммы не приводится, т.к. сам по себе он ничего не дает (помимо этой подпрограммы используется еще несколько подпрограмм), их лучше смотреть непосредственно в примере (ссылка 1, ссылка 4).
Теперь о том, как реализован обмен данными. Клиент формирует запрос (предварительно назначив ему идентификатор), помещает его в очередь (обработка очереди должна быть реализована в клиенте). К этому запросу при необходимости добавляются параметры. Полученный результат упаковывается в строку, к этой строке добавляется хэш. Полученный блок данных шифруется и к зашифрованному блоку также добавляется хэш (подписывающий уже зашифрованный пакет данных). Этот хэш и сами зашифрованные данные разделяются символом "null", к концу блока добавляется двойной "null".
Сервер принимает пакеты данных и помещает их в буфер. Как только в буфере находится последовательность <NULL><NULL>, сервер извлекает пакет данных и удаляет его из буфера. Полученный пакет данных проверяется на валидность и расшифровывается. Затем сервер выполняет запрос, полученный от клиента, и передает клиенту идентификатор сообщения (чтобы клиент мог определить, на какой запрос пришел ответ), код возврата, уведомляющий о успешном или неуспешном выполнении запроса, и данные, полученные в случае успешного выполнения запроса (при неуспешном выполнении запроса вместо данных приходит текст ошибки). Эти данные также упаковываются в строку, шифруются и подписываются и отсылаются клиенту.
Если клиент зарегистрирован, как получатель уведомлений, то сервер будет посылать клиенту уведомления о различных событиях, в этом случае код возврата является кодом события. Поскольку уведомление не является ответом на какой-либо запрос, то в качестве идентификатора используется последовательность "********". В текущей реализации клиент должен зарегистрироваться, как получатель уведомлений, при необходимости можно регистрировать в качестве получателей всех клиентов, которые подключаются к серверу.
Реализация сервера
Ссылка 2. Для сервера используется два сокета, wsServer (который при запуске сервера переводится в режим LISTEN) и индексированный wsClients(0), к копиям которого будут подключаться клиенты. К клиентским сокетам дополнительно создается массив пользовательского типа, в котором будет фиксироваться дополнительная информация.
Private Enum ClientTypeEnum ctGeneral = 1 ctOther2 = 2 ... End Enum Private Enum ClientErrorCodes cerrSuccess = &H0& cerrServerBusy = &HFF& cerrAuth_WrongAppID = &H100& cerrAuth_WrongAppPassword = &H101& cerrAuth_WrongUserData = &H110& cerrAuth_WrongUserLocked = &H111& cerrAuth_WrongUserAccess = &H112& cerrGeneralError = &HFFFF& End Enum Private Const EventMsgID As String = "********" Private Enum ClientConnectStates ccsNotConnect = 0 ccsConnecting = 10 ccsConnecting_WaitAppID = 11 ccsConnecting_WrongAppID = 12 ccsConnecting_SendPassword = 13 ccsConnecting_ReceivePassword = 14 ccsConnecting_WrongPassword = 15 ccsConnecting_Complete = 19 ccsAuthorizing = 20 ccsAuthorizing_WaitUser = 21 ccsAuthorizing_WrongUser = 22 ccsAuthorizing_WrongAccess = 23 ccsAuthorizing_Complete = 29 ccsConnect = 30 ccsDisconnecting = 90 End Enum Private Type WinSockInfo ConnectClock As Date ConnectState As ClientConnectStates Crypto As Crypt Client As ClientTypeEnum User As String Data As String Buffer As String End Type
Для сокета wsServer требуется такой код:
Private Sub wsServer_ConnectionRequest(ByVal requestID As Long) MakeNewConnection requestID, wsServer.RemoteHostIP, wsServer.RemotePort End Sub
Процедура MakeNewConnection должна делать следующее:
Private Sub MakeNewConnection(ByVal requestID As Long, Optional ByVal RemoteHostIP As String, Optional ByVal RemotePort As Long) Dim I As Long I = GetFreeSocket() If I = 0 Then wsClients(0).LocalPort = 0 wsClients(0).Accept requestID ClientSend 0, EventMsgID, cerrServerBusy, "Server busy." Exit Sub End If ws(I).ConnectClock = Now() ws(I).ConnectState = ccsConnecting Set ws(I).Crypto = New Crypt Load wsClients(I) wsClients(I).Accept requestID ws(I).ConnectState = ccsConnecting_WaitAppID End Sub
Фактически, клиент уже подключен, но он не будет работать с сервером, пока не пройдет авторизацию. Для этого на клиентском сокете имеется такой код:
Private Sub wsClients_DataArrival(Index As Integer, ByVal bytesTotal As Long) Dim I As Long, C As DataCheckResult, Data As String, MsgID As String, MsgBody As String, MsgParam As String If Index = 0 Then Exit Sub Data = Space$(bytesTotal) wsClients(Index).GetData Data, vbString, bytesTotal ws(Index).Buffer = ws(Index).Buffer & Data Do I = InStr(ws(Index).Buffer, vbNullCharDbl) If I = 0 Then Exit Do Data = Left$(ws(Index).Buffer, I - 1) ws(Index).Buffer = Mid$(ws(Index).Buffer, I + Len(vbNullCharDbl)) Select Case ws(Index).ConnectState Case ccsConnecting_WaitAppID C = ExtractDataString(Data, MsgID, MsgBody, MsgParam) Case ccsConnecting_ReceivePassword C = ExtractDataString(Data, MsgID, MsgBody, MsgParam) Case ccsAuthorizing_WaitUser C = ExtractDataString(Data, MsgID, MsgBody, MsgParam, ws(Index).Crypto) Case ccsConnect C = ExtractDataString(Data, MsgID, MsgBody, MsgParam, ws(Index).Crypto) Case Else C = -1 MsgID = vbNullString MsgBody = vbNullString MsgParam = vbNullString End Select If C = dcrSuccess Then If ws(Index).ConnectState = ccsConnect Then ClientRequest Index, MsgID, MsgBody, MsgParam Else ClientAuth Index, MsgBody End If End If Loop End Sub
Авторизацией пользователя занимается процедура ClientAuth. Функция ExtractDataString принимает зашифрованные данные и расшифровывает их, заодно проверяя валидность. Код процедуры ClientAuth схематично показан ниже:
Private Sub ClientAuth(ByVal ClientIndex As Long, Message As String) Dim S As String Select Case ws(ClientIndex).ConnectState Case ccsNotConnect Case ccsConnecting_WaitAppID 'В Message будет находится идентификатор типа клиента 'В данном случае используется Select Case, но лучше использовать базу данных Select Case Message Case "WSCLIENT" If vServerOptions.IPFilterClient Then If Not CheckIPRange(wsClients(ClientIndex).RemoteHostIP, IPFilterClientList()) Then ws(ClientIndex).ConnectState = ccsConnecting_WrongAppID ws(ClientIndex).ConnectState = ccsDisconnecting ClientSend ClientIndex, EventMsgID, cerrAuth_WrongAppID, "IP-address in not valid range for this application." Exit Sub End If End If ws(ClientIndex).Client = ctGeneral ws(ClientIndex).ConnectState = ccsConnecting_SendPassword ws(ClientIndex).Data = GenerateKeyPhrase() ClientSend ClientIndex, EventMsgID, cerrSuccess, ws(ClientIndex).Data Set ws(ClientIndex).Crypto = New Crypt ws(ClientIndex).Crypto.KeyString = "wscs demo" ws(ClientIndex).ConnectState = ccsConnecting_ReceivePassword Case Else ws(ClientIndex).ConnectState = ccsConnecting_WrongAppID ws(ClientIndex).ConnectState = ccsDisconnecting ClientSend ClientIndex, EventMsgID, cerrAuth_WrongAppID, "Application not registered in the database." End Select Case ccsConnecting_ReceivePassword 'Клиент должен преобразовать (зашифровать) полученную строку. 'В Message находится хэш на преобразованную строку. S = ws(ClientIndex).Crypto.Encrypt(ws(ClientIndex).Data) If Message = md5.DigestStrToHexStr(S) Then ws(ClientIndex).ConnectState = ccsConnecting_Complete ws(ClientIndex).Crypto.KeyString = ws(ClientIndex).Data ws(ClientIndex).Data = vbNullString ClientSend ClientIndex, EventMsgID, cerrSuccess, ws(ClientIndex).Data Select Case ws(ClientIndex).Client Case ctGeneral ws(ClientIndex).ConnectState = ccsAuthorizing ws(ClientIndex).ConnectState = ccsAuthorizing_WaitUser End Select Else ws(ClientIndex).ConnectState = ccsConnecting_WrongPassword ws(ClientIndex).ConnectState = ccsDisconnecting ClientSend ClientIndex, EventMsgID, cerrAuth_WrongAppPassword, "Wrong control phrase." End If Case ccsAuthorizing_WaitUser 'Авторизация приложения завершена, проводится авторизация пользователя 'В Message находится аутентификационная информация вида AUTH:@ ws(ClientIndex).User = vbNullString If Left$(Message, 6) = "AUTH: " Then S = Mid$(Message, 7) If InStrRev(S, "@") > 0 Then ws(ClientIndex).User = Left$(S, InStrRev(S, "@") - 1) ws(ClientIndex).Data = UCase$(Mid$(S, InStrRev(S, "@") + 1)) End If End If If Len(ws(ClientIndex).User) = 0 Then ClientSend ClientIndex, EventMsgID, cerrAuth_WrongUserData, "Invalid user login/password.", ws(ClientIndex).Crypto Else 'В данном случае используется Select Case, но, конечно, следует работать с БД Select Case ws(ClientIndex).User Case "wscs" S = md5.DigestStrToHexStr("admin") If S = ws(ClientIndex).Data Then ClientSend ClientIndex, EventMsgID, cerrAuth_WrongUserLocked, "User is locked.", ws(ClientIndex).Crypto Else ClientSend ClientIndex, EventMsgID, cerrAuth_WrongUserData, "Invalid user login/password.", ws(ClientIndex).Crypto End If Case "demo" S = md5.DigestStrToHexStr("demo") If S = ws(ClientIndex).Data Then If ws(ClientIndex).Client <> ctGeneral Then ws(ClientIndex).ConnectState = ccsAuthorizing_WrongAccess ws(ClientIndex).ConnectState = ccsDisconnecting ClientSend ClientIndex, EventMsgID, cerrAuth_WrongUserAccess, "Wrong user access.", ws(ClientIndex).Crypto Else ws(ClientIndex).ConnectState = ccsAuthorizing_Complete ClientSend ClientIndex, EventMsgID, cerrSuccess, "Access granted.", ws(ClientIndex).Crypto ws(ClientIndex).ConnectState = ccsConnect End If Else ClientSend ClientIndex, EventMsgID, cerrAuth_WrongUserData, "Invalid user login/password.", ws(ClientIndex).Crypto End If Case Else ClientSend ClientIndex, EventMsgID, cerrAuth_WrongUserData, "Invalid user login/password.", ws(ClientIndex).Crypto End Select End If End Select End Sub
Процедура ClientRequest получает все запросы всех клиентов.
Private Sub ClientRequest(ByVal ClientIndex As Long, MsgID As String, Data As String, Param As String) Dim msg As String, N() As String, V() As String, I As Long If ClientIndex = 0 Then Exit Sub If ws(ClientIndex).ConnectState <> ccsConnect Then Exit Sub 'Check request syntax 'Check user access 'Check client access 'If Check = False Then ' ClientSend ClientIndex, MsgID, cerrGeneralError, "General error", ws(ClientIndex).Crypto ' Exit Sub 'End If Call GetParams(Param, N(), V()) Select Case Data Case wsmcCommon_GetServerTime ClientSend ClientIndex, MsgID, cerrSuccess, ServerClock(), ws(ClientIndex).Crypto ... Case wsmcCommon_RegisterNotice msg = NoticeClientRegister(ClientIndex) If Len(msg) = 0 Then ClientSend ClientIndex, MsgID, cerrSuccess, , ws(ClientIndex).Crypto Else ClientSend ClientIndex, MsgID, cerrGeneralError, msg, ws(ClientIndex).Crypto End If Case wsmcCommon_UnRegisterNotice msg = NoticeClientUnregister(ClientIndex) If Len(msg) = 0 Then ClientSend ClientIndex, MsgID, cerrSuccess, , ws(ClientIndex).Crypto Else ClientSend ClientIndex, MsgID, cerrGeneralError, msg, ws(ClientIndex).Crypto End If End Select End Sub
Функция ClientSend используется для передачи данных клиенту. В ней реализуется шифрование и подписывание данных, после чего они отправляются на сокет.
Private Sub ClientSend(ByVal ClientIndex As Long, ByVal MsgID As String, ByVal Code As ClientErrorCodes, Optional ByVal Data As String, Optional Crypto As Crypt) Dim msg As String If ClientIndex > 0 Then If ws(ClientIndex).ConnectState = ccsNotConnect Then Exit Sub End If msg = Hex$(Code) If Len(msg) < 4 Then msg = String$(4 - Len(msg), "0") & msg If Len(Data) = 0 Then msg = MsgID & "@" & msg Else msg = MsgID & "@" & msg & Data End If If Not (Crypto Is Nothing) Then msg = Crypto.Encrypt(md5.DigestStrToHexStr(msg) & msg) msg = md5.DigestStrToHexStr(msg) & vbNullChar & msg End If wsClients(ClientIndex).SendData msg & vbNullCharDbl End Sub
Разумеется, это не весь код серверной подсистемы. Но его вполне достаточно, чтобы представить, как протекают рабочие процессы.
Реализация клиента
Ссылка 3. Для клиента код выглядит проще. Кроме того, поскольку клиентской подсистеме требуется авторизоваться только однажды, то имеет смысл разделить авторизацию и передачу данных и реализовать авторизацию в одном модуле. Это упростит обновление авторизации в случае нескольких типов клиентов (они будут работать с одним и тем же модулем и достаточно будет просто перекомпилировать проект).
Будем исходить из того, что на всех клиентских подсистемах есть форма frmMAIN, на которой имеется сокет wsClient, через который и будет происходит прием и передача данных.
Ниже приводится упрощенный модуль Authorize.
Option Explicit Public Enum ClientConnectStates ccsNotConnect = 0 ccsConnecting = 10 ccsConnecting_WaitAppID = 11 ccsConnecting_WrongAppID = 12 ccsConnecting_SendPassword = 13 ccsConnecting_ReceivePassword = 14 ccsConnecting_WrongPassword = 15 ccsConnecting_Complete = 19 ccsAuthorizing = 20 ccsAuthorizing_WaitUser = 21 ccsAuthorizing_WrongUser = 22 ccsAuthorizing_Complete = 29 ccsConnect = 30 ccsDisconnecting = 90 End Enum Public CurrentConnectState As ClientConnectStates Private AuthBuffer As String Public Const EventMsgID As String = "********" Public Enum ClientErrorCodes cerrSuccess = &H0& cerrServerBusy = &HFF& cerrAuth_WrongAppID = &H100& cerrAuth_WrongAppPassword = &H101& cerrAuth_WrongUserData = &H110& cerrAuth_WrongUserLocked = &H111& cerrAuth_WrongUserAccess = &H112& cerrGeneralError = &HFFFF& End Enum Sub ClientAuth_Recv(ByVal Code As ClientErrorCodes, ByVal Message As String) Dim C As String C = Hex$(Code): If Len(C) < 4 Then C = String$(4 - Len(C), "0") & C Select Case CurrentConnectState Case ccsConnecting Select Case Code Case cerrSuccess CurrentConnectState = ccsConnecting_WaitAppID ClientAuth_Send prj_ApplCode Case cerrServerBusy CurrentConnectState = ccsNotConnect MsgBox "Сервер перегружен" Case Else CurrentConnectState = ccsNotConnect MsgBox "При подключении произошла ошибка!" End Select Case ccsConnecting_WaitAppID Select Case Code Case cerrSuccess Crypt.KeyString = prj_ApplPassword AuthBuffer = Message CurrentConnectState = ccsConnecting_ReceivePassword ClientAuth_Send md5.DigestStrToHexStr(Crypt.Encrypt(Message)) Case cerrAuth_WrongAppID CurrentConnectState = ccsNotConnect MsgBox "Приложение '" & prj_ProductNameEng & "' не зарегистрировано на сервере." Case Else CurrentConnectState = ccsNotConnect MsgBox "При подключении произошла ошибка!" End Select Case ccsConnecting_ReceivePassword Select Case Code Case cerrSuccess Crypt.KeyString = AuthBuffer CurrentConnectState = ccsAuthorizing Call ClientAuth_Logon Case cerrAuth_WrongAppPassword CurrentConnectState = ccsNotConnect MsgBox "Невозможно зарегистрировать приложение" Case Else CurrentConnectState = ccsNotConnect MsgBox "При подключении произошла ошибка!" End Select Case ccsAuthorizing Case ccsAuthorizing_WaitUser Select Case Code Case cerrSuccess CurrentConnectState = ccsConnect Case cerrAuth_WrongUserData MsgBox "Невозможно войти в систему, неверные учетные данные." Call ClientAuth_Logon Case cerrAuth_WrongUserLocked CurrentConnectState = ccsNotConnect MsgBox "Невозможно войти в систему, учетная запись заблокированна." Case cerrAuth_WrongUserAccess CurrentConnectState = ccsNotConnect MsgBox "Невозможно войти в систему, доступ к подсистеме не разрешен." Case Else CurrentConnectState = ccsNotConnect MsgBox "При подключении произошла ошибка!" End Select End Select End Sub Sub ClientAuth_Send(ByVal Message As String) If CurrentConnectState = ccsNotConnect Then Exit Sub Message = EventMsgID & Message If CurrentConnectState > ccsConnecting_Complete Then Message = Crypt.Encrypt(md5.DigestStrToHexStr(Message) & Message) Message = md5.DigestStrToHexStr(Message) & vbNullChar & Message End If frmMAIN.wsClient.SendData Message & vbNullCharDbl End Sub Sub ClientAuth_Logon() 'Здесь отображается диалоговое окно, в котором пользователь вводит логин и пароль. 'После ввода данных на сервер отсылается строка вида: AUTH: <LOGIN>@<PWDHASH> 'где <LOGIN> - логин, а <PWDHASH> - хэш на пароль. CurrentConnectState = ccsAuthorizing_WaitUser ClientAuth_Send "AUTH: " & LOGIN & "@" & PWDHASH End Sub
Для сокета имеется такой код:
Private Sub wsClient_DataArrival(ByVal bytesTotal As Long) Dim I As Long, msg As String, MsgID As String, MsgCode As ClientErrorCodes, MsgBody As String msg = Space$(bytesTotal) wsClient.GetData msg, vbString, bytesTotal MsgBuff = MsgBuff & msg Do I = InStr(MsgBuff, vbNullCharDbl) If I = 0 Then Exit Do msg = Left$(MsgBuff, I - 1) MsgBuff = Mid$(MsgBuff, I + Len(vbNullCharDbl)) If CurrentConnectState = ccsConnect Then If ExtractDataString(msg, MsgID, MsgCode, MsgBody, Crypt) = dcrSuccess Then If MsgID = EventMsgID Then WinSock_Event (MsgCode), MsgBody Else WinSock_Processing MsgID, MsgCode, MsgBody End If End If Else If ExtractDataString(msg, MsgID, MsgCode, MsgBody) = dcrSuccess Then ClientAuth_Recv MsgCode, MsgBody End If End If Loop End Sub
Функция ClientAuth_Recv вызывается в процессе авторизации, функции WinSock_Event и WinSock_Processing вызываются при получении уведомлений и ответов соответственно. Сами эти функции не приводятся, т.к. их содержимое будет зависеть от требований к подсистеме. Процедура WinSock_Event получает код уведомления и данные. Процедура WinSock_Processing получает идентификатор сообщений (по которому можно будет определить, каков был запрос), код возврата и содержимое сообщения.
Сложности и проблемы
Большинство проблем связаны с клиентской подсистемой. На серверной части сложность только в одном -- обеспечить асинхронность обработки запросов от разных клиентов. Желательно было бы реализовать асинхронность даже на уровне запросов для одного клиента, т.е. чтобы два разных запроса от одного и того же клиента обрабатывались независимо друг от друга. Одно из решений в подобных случаях -- создавать потоки для обработки каждого запроса.
Но обычно достаточно реализовать асинхронность для каждого подключения, а обработку внутри подключений сделать синхронной. Для этих целей проект переделывать не потребуется, т.к. прием/передача данных по WinSock происходит асинхронно, а регистрация событий уже реализована.
С клиентом же дело обстоит сложнее, приходится реализовывать (в самом клиенте) очередь сообщений и усложнять код. В прилагаемом примере реализован один из вариантов организации очереди; на современных машинах он работает достаточно быстро.
Еще один момент, который следовало бы отметить -- передача больших объемов данных. Данная реализация клиент-серверной платформы мало подходит для постоянной передачи данных объемом свыше 10-15 Кб. Тем не менее, нет никаких ограничений на передачу данных, которые могут уместиться в тип данных VB String (около двух гигобайт). Для передачи данных, объем которых превышает 32 Кб нужно будет переделать класс MD5Hash; в данном классе длина текста запоминается в Integer, которое не может принимать значения, выходящие за пределы -32767...+32767. По всем вопросам, связанным с исправлением класса MD5Hash следует обращаться к его автору, Robert M. Hubley (e-mail).
Заключение
Данная статья была подготовлена для журнала VBStreets.
Использованный пример был вырезан из проекта, который еще не завершен, поэтому возможны некоторые ошибки и недоработки. Указания на эти ошибки, а также пожелания и рекомендации приветствуются, пишите о них сюда.
Использованный пример не имеет никакого практического значения, он предназначен только для демонстрации и пояснения. Вы можете использовать его в качестве основы для своих клиент-серверных систем. Кроме того, в примере использована библиотека функций (модули modCommon.bas и modWinAPI.bas), которые могут пригодится в разработке своих программ.
Как запустить проект. Запустить сервер (wscs_s.exe), стартовать сервер. Запустить клиент (wscs_c.exe). На сервере созданы два пользователя, demo и wscs (пароли совпадают с логином), второй пользователь заблокирован (т.е. для входа использовать demo/demo). На сервере можно вызвать окно журнала, чтобы видеть протокол авторизации.
Все ссылки представляют собой ZIP-архив, на архив установлен пароль "alibek09.narod.ru".
1. Готовый пример (исходник, zip).
2. Серверная часть (исходник, zip).
3. Клиентская часть (исходник, zip).
4. Демонстрация (exe, zip).
5. Класс Crypt (исходник, zip).
6. Класс MD5Hash (исходник, zip).
Материалы данной статьи не допускается размещать без указания на данную страницу.
Дата: 8 июня 2004 г.
e-mail
Эту статью читали раз(а)