Интерфейс Mailslot на примере работы чата Intranet Chat
Довольно популярная в начале 2000-ых годов программа “Intranet Chat” или просто iChat предназначена для обмена текстовыми сообщениями в локальной сети. В работе она использует клиент-серверный интерфейс mailslot. Кроме того, передаваемые данные программа шифрует, используя криптографический алгоритм RC4. Поэтому сначала рассмотрим этот алгоритм шифрования.
1Алгоритм шифрования RC4и его реализация на VB.NET
Алгоритм RC4 или, точнее, его открытая предполагаемая реализация ARC4, довольно прост в своей идее и прост в реализации. Коротко, суть в том, что биты исходного сообщения через операцию «исключающего или» (XOR) объединяется с битами ключа, и таким образом получается зашифрованное сообщение. Если зашифрованное сообщение пропустить через тот же алгоритм с тем же ключом, то получается расшифрованное сообщение. Длина ключа равняется длине сообщения, поэтому теоретически взломать такой алгоритм практически невозможно. Однако на практике для генерации ключа используются механизмы, называемые генераторами псевдослучайных последовательностей (ПСП).
Псевдослучайная последовательность – это такая последовательность, которая содержит в себе периодически повторяющуюся последовательность битов. Преимущество ПСП в том, что они легко получаются аппаратным или программным способом.
Не будем изобретать велосипед, возьмём за основу вот эту реализацию кодера и декодера алгоритма RC4, перепишем её на язык VB.NET и введём несколько улучшений.
Кодер и декодер RC4 на VB.NET (разворачивается)
Option Strict On Option Explicit On Option Infer On Imports System.Runtime.CompilerServices ''' <summary> ''' Алгоритм шифрования и расшифрования RC4. ''' </summary> Public Class Rc4 ''' <summary> ''' Ключ шифрования (и расшифрования). ''' </summary> Public ReadOnly Property Key As Byte() #Region "CTOR" ''' <summary> ''' Инициализация алгоритма RC4 заданным ключом. ''' </summary> ''' <param name="key"></param> Public Sub New(key As Byte()) Me.Key = key End Sub Private S(255) As Integer 'Вектор перестановки. Private X As Integer 'Счётчик. Private Y As Integer 'Счётчик. ''' <summary> ''' Инициализирует вектор перестановки заданным ключом ''' (алгоритм ключевого расписания, key-scheduling algorithm). ''' </summary> ''' <param name="key">Байты ключа.</param> Private Sub Init(key() As Byte) X = 0 Y = 0 For i As Integer = 0 To 255 S(i) = i Next Dim j As Integer = 0 Dim keyLen As Integer = key.Length For i As Integer = 0 To 255 j = (j + S(i) + CInt(key(i Mod keyLen))) Mod 256 S.Swap(i, j) Next End Sub #End Region '/CTOR #Region "OPEN METHODS" ''' <summary> ''' Кодирует массив. ''' </summary> ''' <param name="openTextBytes">Массив байтов открытого текста.</param> Public Function Encode(openTextBytes As Byte()) As Byte() Init(Key) Dim ecnode As Byte() = New Byte(openTextBytes.Length - 1) {} For i As Integer = 0 To openTextBytes.Length - 1 ecnode(i) = (openTextBytes(i) Xor GetNextItem()) Next Return ecnode End Function ''' <summary> ''' Декодирует массив. ''' </summary> ''' <param name="encodedBytes">Зашифрованный массив.</param> Public Function Decode(encodedBytes As Byte()) As Byte() Return Encode(encodedBytes) End Function #End Region '/OPEN METHODS ''' <summary> ''' Возвращает следующий элемент ПСП. ''' </summary> Private Function GetNextItem() As Byte X = (X + 1) Mod 256 Y = (Y + S(X)) Mod 256 S.Swap(X, Y) Dim ind As Integer = (S(X) + S(Y)) Mod 256 Return CByte(S(ind)) End Function End Class ''' <summary> ''' Класс, расширяющий функциональность заданных типов. ''' </summary> <Extension()> Friend Module SwapExt ''' <summary> ''' Меняет местами заданные элементы массива. ''' </summary> ''' <typeparam name="T">Тип массива.</typeparam> ''' <param name="arr">Массив.</param> ''' <param name="index1">Индекс 1-го элемента.</param> ''' <param name="index2">Индекс 2-го элемента.</param> <Extension()> Public Sub Swap(Of T)(arr() As T, index1 As Integer, index2 As Integer) Dim temp As T = arr(index1) arr(index1) = arr(index2) arr(index2) = temp End Sub End Module
Пользоваться нашим кодером и декодером очень просто: объявляем объект типа RC4, передавая ему ключ шифрования. Далее вызываем один из двух открытых методов: Encode() или Decode(). Первый – для зашифрования, второй – для расшифрования сообщения. Например, так:
Dim key As Byte() = Text.Encoding.UTF8.GetBytes("Мой супер секретный ключ") Dim rc As New Rc4(key) Dim message as Byte() = Text.Encoding.UTF8.GetBytes("Моё сообщение, которое будет зашифровано алгоритмом RC4.") Dim encoded As Byte() = rc.Encode(message) 'байты зашифрованного сообщения Dim restored as Byte() = rc.Decode(encoded) Dim restoredText as String = Text.Encoding.UTF8.GetString(restored) 'восстановленный текст
Скомпилированная программа для расшифрования зашифрованных сообщений "Intranet Chat" – в приложении к статье внизу. Она написана на основе того кода, который здесь приведён.
2Реализация mailslot на языке VB.NET
Mailslot (мэйлслот) – это клиент-серверный интерфейс, с помощью которого можно передавать данные от одного процесса другому в операционной системе и по сети. Мэйлслот рассчитан на передачу небольших (до 424 байтов) сообщений, в том числе широковещательных (один отправитель, а получателей – много).
В системе должен быть один сервер мэйлслотов, который создаёт мэйлслот (локально). Клиентов может быть один или несколько. Данные передаются только в одну сторону: от клиента – к серверу. Для работы данного механизма сервером используются функции CreateMailslot() (создать мэйлслот) и ReadFile() (прочитать данные от клиентов), а клиентом – CreateFile() (создать файл) и WriteFile() (записать данные в файл), а также несколько других, менее интересных для нас. Обращение к серверу мэйлслотов осуществляется по имени. Зная имя мэйлслота клиенты могут записывать в него данные. Сервер мэйлслотов хранит эти данные, а при остановке сервера все данные теряются.
На самом деле, никакие файлы при использовании мэйлслотов не создаются: данные записываются в псевдофайл, расположенный в памяти компьютера.
Имена мэйлслотов образуются по такому принципу. Если мэйлслот расположен на локальном компьютере (регистр не учитывается):
\\.\mailslot\[путь\]имяМэйлслота
Если мэйлслот расположен на удалённом компьютере:
\\имяКомпьютера\mailslot\[путь\]имяМэйлслота
Для записи во все мэйлслоты с данным именем:
\\*\mailslot\[путь\]имяМэйлслота
В ОС Windows работа с мэйлслотами реализуется в системной библиотеке kernel32.dll. А описание данной технологии можно найти на сайте Microsoft.
Для реализации своего сервера и клиента мэйлслотов нам прежде всего понадобится вспомогательный класс, который содержит функциональность для работы с нативными (неуправляемыми) указателями, которые и являются однозначными определителями мэйлслота.
Вспомогательный класс для Mailslot на VB.NET (разворачивается)
Imports Microsoft.Win32.SafeHandles Imports System.Runtime.ConstrainedExecution Imports System.Runtime.InteropServices Imports System.Security Imports System.Security.Permissions Namespace Mailslots <SuppressUnmanagedCodeSecurity()> <SecurityCritical(SecurityCriticalScope.Everything)> <HostProtection(SecurityAction.LinkDemand, MayLeakOnAbort:=True), SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode:=True)> Public Class SafeMailslotHandle Inherits SafeHandleZeroOrMinusOneIsInvalid #Region "NATIVE" <ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)> <DllImport("kernel32.dll")> Private Shared Function CloseHandle(handle As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean End Function #End Region '/NATIVE #Region "CTOR" Private Sub New() MyBase.New(True) End Sub Public Sub New(preexistingHandle As IntPtr, ownsHandle As Boolean) MyBase.New(ownsHandle) MyBase.SetHandle(preexistingHandle) End Sub #End Region '/CTOR Protected Overrides Function ReleaseHandle() As Boolean Return SafeMailslotHandle.CloseHandle(Me.handle) End Function End Class End Namespace
Сервер Mailslot непосредственно в данной работе нам не потребуется, но для полноты изложения прикладываю и его код. Если вы захотите написать полноценую замену для чата Intranet Chat, этот код вам понадобится.
Сервер Mailslot на VB.NET (разворачивается)
Imports System.ComponentModel Imports System.Runtime.InteropServices Imports System.Security Imports System.Text Namespace Mailslots ''' <summary> ''' Сервер Mailslot. ''' </summary> <SuppressUnmanagedCodeSecurity()> Public Class MailslotServer Implements IDisposable #Region "NATIVE" <DllImport("kernel32.dll")> Private Shared Function CreateMailslot(mailslotName As String, nMaxMessageSize As UInteger, lReadTimeout As Integer, securityAttributes As IntPtr) As SafeMailslotHandle End Function <DllImport("kernel32.dll")> Private Shared Function GetMailslotInfo(hMailslot As SafeMailslotHandle, lpMaxMessageSize As IntPtr, <Out()> ByRef lpNextSize As Integer, <Out()> ByRef lpMessageCount As Integer, lpReadTimeout As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean End Function <DllImport("kernel32.dll")> Private Shared Function ReadFile(handle As SafeMailslotHandle, bytes As Byte(), numBytesToRead As Integer, <Out()> ByRef numBytesRead As Integer, overlapped As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean End Function #End Region '/NATIVE ''' <summary> ''' Число ожидающих сообщений. Обновляется только после вызова <see cref="GetNextMessage()"/>. ''' </summary> Public ReadOnly Property NumberMessages As Integer Get Return _NumberMessages End Get End Property Private _NumberMessages As Integer = 0 #Region "CTOR" Private Handle As SafeMailslotHandle Public Sub New(name As String) Handle = CreateMailslot($"\\.\mailslot\{name}", 0, 0, IntPtr.Zero) 'обратите внимание: данный синтаксис поддерживается только начиная с VS2019, в более ранних версиях используйте метод String.Format() If Handle.IsInvalid Then Throw New Win32Exception("Ошибка создания Mailslot.CreateMailslot()") End If End Sub Private Sub Dispose() Implements IDisposable.Dispose If (Handle IsNot Nothing) Then Handle.Close() Handle = Nothing End If End Sub #End Region '/CTOR #Region "METHODS" Private Const NO_MESSAGE As Integer = -1 ''' <summary> ''' Получает следующее сообщение. ''' </summary> Public Function GetNextMessage() As String Dim messageBytes As Integer If (Not GetMailslotInfo(Handle, IntPtr.Zero, messageBytes, _NumberMessages, IntPtr.Zero)) Then Throw New Win32Exception("Ошибка в Mailslot.GetMailslotInfo()") End If If (messageBytes = NO_MESSAGE) Then Return String.Empty End If Dim buffer As Byte() = New Byte(messageBytes - 1) {} Dim bytesRead As Integer Dim succeeded As Boolean = ReadFile(Handle, buffer, messageBytes, bytesRead, IntPtr.Zero) If (Not succeeded) OrElse (bytesRead = 0) Then Throw New Win32Exception("Ошибка в Mailslot.ReadFile()") End If Dim result As String = Encoding.Unicode.GetString(buffer) Return result End Function #End Region '/METHODS End Class '/MailslotServer End Namespace
Теперь напишем клиент протокола Mailsot:
Клиент Mailslot на VB.NET (разворачивается)
Imports System.ComponentModel Imports System.Runtime.InteropServices Imports System.Security Imports System.Text Namespace Mailslots ''' <summary> ''' Клиент Mailslot. ''' </summary> <SuppressUnmanagedCodeSecurity()> Public Class MailslotClient Implements IDisposable Private Handle As SafeMailslotHandle Private ReadOnly Name As String Private ReadOnly Machine As String #Region "ENUMS" <Flags()> Public Enum FileDesiredAccess As UInteger GenericRead = &H80000000UI GenericWrite = &H40000000UI GenericExecute = &H20000000UI GenericAll = &H10000000UI End Enum <Flags()> Public Enum FileShareMode As UInteger Zero = 0UI FileShareRead = 1UI FileShareWrite = 2UI FileShareDelete = 4UI End Enum Public Enum FileCreationDisposition As UInteger CreateNew = 1UI CreateAlways OpenExisting OpenAlways TruncateExisting End Enum #End Region '/ENUMS #Region "NATIVE" <DllImport("kernel32.dll")> Private Shared Function CreateFile(fileName As String, desiredAccess As FileDesiredAccess, shareMode As FileShareMode, securityAttributes As IntPtr, creationDisposition As FileCreationDisposition, flagsAndAttributes As Integer, hTemplateFile As IntPtr) As SafeMailslotHandle End Function <DllImport("kernel32.dll")> Private Shared Function WriteFile(handle As SafeMailslotHandle, bytes As Byte(), numBytesToWrite As Integer, <Out()> ByRef numBytesWritten As Integer, overlapped As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean End Function #End Region '/NATIVE #Region "CTOR" Public Sub New(name As String) Me.New(name, ".") End Sub Public Sub New(name As String, machine As String) Me.Name = name Me.Machine = machine End Sub Private Sub CreateHandle() Dim fileName As String = $"\\{Machine}\MAILSLOT\{Name}" Handle = CreateFile(fileName, FileDesiredAccess.GenericWrite, FileShareMode.FileShareRead, IntPtr.Zero, FileCreationDisposition.OpenExisting, 0, IntPtr.Zero) If Handle.IsInvalid Then Throw New Win32Exception("Ошибка создания указателя CreateHandle()") End If End Sub Private Sub Dispose() Implements IDisposable.Dispose If (Handle IsNot Nothing) Then Handle.Close() Handle = Nothing End If End Sub #End Region '/CTOR #Region "METHODS" ''' <summary> ''' Отправляет сообщение. ''' </summary> ''' <param name="msg">Байты сообщения.</param> Public Overridable Sub SendMessage(msg As String) Dim message As Byte() = Encoding.UTF8.GetBytes(msg) SendMessage(message) End Sub ''' <summary> ''' Отправляет сообщение. ''' </summary> ''' <param name="msg">Текст сообщения.</param> Public Overridable Sub SendMessage(msg As Byte()) If (Handle Is Nothing) Then CreateHandle() End If Dim bytesWritten As Integer Dim succeeded As Boolean = WriteFile(Handle, msg, msg.Length, bytesWritten, IntPtr.Zero) If (Not succeeded) OrElse (msg.Length <> bytesWritten) Then Me.Dispose() Throw New Win32Exception("Ошибка отправки сообщения SendMessage()") End If End Sub #End Region '/METHODS End Class '/MailslotClient End Namespace
Использовать напрямую данный клиент с программой IChat мы пока ещё не можем, т.к. прежде нужно изучить протокол, по которому разные экземпляры программы «общаются» друг с другом в сети.
3Изучение работы программы "Intranet Chat" с помощью Wireshark
Теперь, когда мы умеем расшифровывать зашифрованные алгоритмом RC4 сообщения и имеем представление о протоколе Mailslot, можем приступить к более пристальному изучению программы "Intranet Chat". Для изучения протокола обмена клиентов программы iChat между собой воспользуется знаменитой программой Wireshark – сниффером сетевого трафика.
Сниффер – это программа захвата сетевого трафика. Зачастую снифферы обладают функциональностью анализа принимемых сетевых пакетов, а также их удобного представления, сохранения и другими возможностями.
Запустим Wireshark, выберем сетевой интерфейс, по которому будут идти данные и запустим мониторинг сетевых пакетов. Чтобы он показывал нам только интересующие данные протокола Mailslot, применим следующий фильтр (поле "1" на рисунке):
mailslot && !browser
Здесь мы включаем все сообщения протокола Mailslot, за исключением сообщений браузера, которые используются операционнной системой. Теперь запустим программу "Intranet Chat" и сразу же увидим в сниффере «улов». В анализаторе заголовков сетевых пакетов, в разделе SMB Mailslot Protocol можно сразу же увидеть, какое имя у мэйлслота программы Intranet Chat: ichat047.
Теперь поставим курсор мыши на поле данных Data (2 на рисунке), откроем правой кнопкой мыши контекстное меню и скопируем (3) в буфер обмена данные (4) пакета. Вставим скопированное в наш декодер и получим команду, которую отправляет программа "Intranet Chat".
Чтобы вставить текст из буфера обмена в консоль, нужно кликнуть мышью в заголовке окна консоли на иконку в левом верхнем углу, а затем выбрать Правка - Вставить.
Кстати, в массиве байтов в 16-ном представлении, который мы вставляем в декодер, байты могут отделяться друг от друга пробелами, двоеточиями, тире или вообще идти слитно – это не важно, программа всё равно распознает их и расшифрует сообщение.
Изучив собранные сниффером пакеты, мы увидим, что при запуске программа iChat отправляет два сообщения:
‼iChat‼‼1‼‼AAVE‼‼DISCONNECT‼‼iTCniaM‼ ‼iChat‼‼2‼‼AAVE‼‼CONNECT‼‼iTCniaM‼‼1‼‼AAVE‼‼‼‼Hello all!‼‼*‼‼1.21b4‼‼0‼
Здесь на себя обращают внимание слова DISCONNECT и CONNECT; вполне очевидно, что это команды отключения клиента (видимо, для предотвращения коллизий) и затем подключения к чату. "1.21b4" – это конечно же версия IChat клиента.
Теперь отправим сообщение из программы iChat в общий чат и посмотрим, какие пакеты нам покажет Wireshark. Вот пример стандартного сообщения всем участникам чата:
‼iChat‼‼3‼‼AAVE‼‼TEXT‼‼iTCniaM‼‼Hello, Soltau.ru‼‼‼
Здесь можно отметить следующие группы:
Группа | Назначение |
---|---|
‼ | Разделитель между группами; это байт 0x13 |
iChat | Признак сообщения программы "Intranet Chat" |
3 | Порядковый номер сообщения от данного клиента; начинается с 1 и увеличивается на 1 после каждого сообщения от данного отправителя |
AAVE | Имя компьютера, с которого отправлено сообщение |
TEXT | Признак текстового сообщения |
iTCniaM | Имя общего чата в программе iChat |
Hello, Soltau.ru | Текст сообщения |
Отправим приватное сообщение кому-нибудь в чате. При этом программа Intranet Chat передаст следующее:
‼iChat‼‼46‼‼AAVE‼‼TEXT‼‼gsMTCI‼‼ Hello, Soltau.ru‼‼VIRPC‼
Здесь всё аналогично, кроме того, что признак приватного сообщения – строка "gsMTCI" и адрес получателя в конце. В данном случае это VIRPC.
Если мы немного понаблюдаем за сетевым трафиком, то отметим ещё одну интересную особенность. С определённым периодом (обычно от 1-2 до нескольких минут) программа Intranet Chat каждого из пользователей в сети «отмечается» в общем чате, посылая сообщение «я тут!», которые выглядят так:
‼iChat‼‼12‼‼AAVE‼‼REFRESH‼‼iTCniaM‼‼1‼‼AAVE‼‼‼‼Hello All!‼‼*‼‼1.21b4‼‼0‼
Думаю, тут комментарии излишни. Если какой-то из участников чата слишком долго не посылает поднобное сообщение, он будет отмечен как выбывший.
Продолжая подобным образом, отправляя программой iChat публичные сообщения, приватные сообщения, обновляя список участников чата, перезапуская программу и проводя с ней другие действия, перехватывая и анализируя сетевой трафик, можно собрать достаточно сведений о том, как именно она работает.
4Создание клиента чата IChat на языке VB.NET
Теперь мы знаем достаточно чтобы написать минимально действующий клиент для программы Intranet Chat, который будет отправлять сообщения в общий чат и в приватный чат заданному адресату. Напишем класс, реализующий клиент мэйлслотов для IChat, который будет производным от нашего ранее написанного класса MailslotClient:
Клиент для программы "Intranet Chat" на VB.NET (разворачивается)
Namespace Mailslots ''' <summary> ''' Клиент для передачи сообщений в общий чат программы "Intranet Chat". ''' </summary> Public Class IChatClient Inherits MailslotClient Implements IDisposable #Region "ПОЛЯ" ''' <summary> ''' Кодер/декодер алгоритма RC4. ''' </summary> Private ReadOnly Rc As New Rc4(GetBytes("tahci")) ''' <summary> ''' Счётчик сообщений, отправленных клиентом. Начинается с 1 и увеличивается на 1 с каждым сообщением. ''' </summary> Private MessageCounter As Integer = 0 #End Region '/ПОЛЯ #Region "КОНСТРУКТОРЫ" ''' <summary> ''' Создаёт клиента для обмена сообщениями в общем чате. ''' </summary> Public Sub New() MyBase.New("ichat047", "*") Disconnect() Connect() End Sub Protected Sub New(machine As String) MyBase.New("ichat047", machine) Disconnect() Connect() End Sub #End Region '/КОНСТРУКТОРЫ #Region "OPEN METHODS" ''' <summary> ''' Отправляет сообщение в общий чат. ''' </summary> ''' <param name="msg">Текст сообщения.</param> Public Overrides Sub SendMessage(msg As String) MessageCounter += 1 Dim wrapped As Byte() = GetWrappedMessage(msg) Dim encoded As Byte() = Rc.Encode(wrapped) MyBase.SendMessage(encoded) End Sub #End Region '/OPEN METHODS #Region "ЗАЩИЩЁННЫЕ МЕТОДЫ" ''' <summary> ''' Возвращает сообщение с нужным для публичного сообщения обрамлением. ''' </summary> ''' <param name="msg">Текст сообщения.</param> ''' <remarks>Вот как выглядит сообщение чата: ''' ‼iChat‼‼48‼‼AAVE‼‼TEXT‼‼iTCniaM‼‼Message text‼‼‼ ''' </remarks> Protected Overridable Function GetWrappedMessage(msg As String) As Byte() Dim wrapped As New List(Of Byte) wrapped.AddRange(GetCommon()) wrapped.AddRange(GetBytes("TEXT")) wrapped.AddRange(GetDelim(2)) wrapped.AddRange(GetBytes(GetLineName())) '"iTCniaM")) 'имя линии для общего чата. wrapped.AddRange(GetDelim(2)) wrapped.AddRange(GetBytes(msg)) wrapped.AddRange(GetDelim(3)) Debug.WriteLine("Message=" & Text.Encoding.GetEncoding(1251).GetString(wrapped.ToArray())) Return wrapped.ToArray() End Function ''' <summary> ''' Возвращает часть сообщения, общую для всех: счётчик сообщений и имя отправителя. ''' </summary> Protected Function GetCommon() As List(Of Byte) Dim common As New List(Of Byte) common.AddRange(GetDelim(1)) common.AddRange(GetBytes("iChat")) common.AddRange(GetDelim(2)) common.AddRange(GetBytes(MessageCounter.ToString())) common.AddRange(GetDelim(2)) Dim sender As String = GetComputerName() common.AddRange(GetBytes(sender)) common.AddRange(GetDelim(2)) Return common End Function ''' <summary> ''' Преобразует строку в массив байтов в кодировке Windows-1251. ''' </summary> Protected Function GetBytes(s As String) As Byte() Return Text.Encoding.GetEncoding(1251).GetBytes(s) End Function ''' <summary> ''' Возвращает заданное количество разделителей. ''' </summary> ''' <param name="num">Необходимое количество разделителей.</param> ''' <remarks>В программе iChat разделителем является байт 0x13.</remarks> Protected Function GetDelim(num As Integer) As List(Of Byte) Dim d As New List(Of Byte) For i As Integer = 0 To num - 1 d.Add(&H13) Next Return d End Function #End Region '/ЗАЩИЩЁННЫЕ МЕТОДЫ #Region "ЗАКРЫТЫЕ МЕТОДЫ" ''' <summary> ''' Отключается от общего чата. ''' </summary> Private Sub Disconnect() MessageCounter += 1 Dim wrapped As New List(Of Byte) wrapped.AddRange(GetCommon()) wrapped.AddRange(GetBytes("DISCONNECT")) wrapped.AddRange(GetDelim(2)) wrapped.AddRange(GetBytes(GetLineName())) 'имя линии чата wrapped.AddRange(GetDelim(1)) Debug.WriteLine("Disconnect=" & Text.Encoding.GetEncoding(1251).GetString(wrapped.ToArray())) Dim encoded As Byte() = Rc.Encode(wrapped.ToArray()) MyBase.SendMessage(encoded) End Sub ''' <summary> ''' Подключиться к общему чату. ''' </summary> Private Sub Connect() MessageCounter += 1 Dim wrapped As New List(Of Byte) wrapped.AddRange(GetCommon()) wrapped.AddRange(GetBytes("CONNECT")) wrapped.AddRange(GetDelim(2)) wrapped.AddRange(GetBytes(GetLineName())) 'имя линии чата wrapped.AddRange(GetDelim(2)) wrapped.AddRange(GetBytes(GetUserName())) wrapped.AddRange(GetDelim(2)) wrapped.AddRange(GetBytes(GetComputerName())) wrapped.AddRange(GetDelim(4)) wrapped.AddRange(GetBytes("Hello all!")) wrapped.AddRange(GetDelim(2)) wrapped.AddRange(GetBytes(GetDestination())) 'получатель или "*" wrapped.AddRange(GetDelim(2)) wrapped.AddRange(GetBytes(GetVersion())) 'версия программы wrapped.AddRange(GetDelim(2)) wrapped.AddRange(GetBytes(GetStatus())) wrapped.AddRange(GetDelim(1)) Debug.WriteLine("Connect=" & Text.Encoding.GetEncoding(1251).GetString(wrapped.ToArray())) Dim encoded As Byte() = Rc.Encode(wrapped.ToArray()) MyBase.SendMessage(encoded) End Sub ''' <summary> ''' Возвращает имя линии общего чата. ''' </summary> Private Function GetLineName() As String Return "iTCniaM" End Function ''' <summary> ''' Возвращает статус. ''' </summary> Private Function GetStatus() As String Return "0" End Function ''' <summary> ''' Возвращает версию программы iChat. ''' </summary> Private Function GetVersion() As String Return "1.21b4" End Function ''' <summary> ''' Получатель. Для публичного чата это "*". ''' </summary> Private Function GetDestination() As String Return "*" End Function ''' <summary> ''' Возвращает DNS имя компьютера, на котором запущен клиент iChat. ''' </summary> Private Function GetComputerName() As String Return System.Net.Dns.GetHostName() End Function ''' <summary> ''' Возвращает имя текущего пользователя Windows. ''' </summary> Private Function GetUserName() As String Return Environment.UserName End Function #End Region '/ЗАКРЫТЫЕ МЕТОДЫ Protected Overloads Sub Dispose() Implements IDisposable.Dispose Disconnect() 'не забудем отключиться от чата при завершении работы MyBase.Dispose() End Sub End Class End Namespace
Также напишем клиент для отправки приватных сообщений в чат iChat. Он будет наследоваться от класса публичного клиента, но будет лишь переопределять метод GetWrappedMessage(), который генерирует «обвязку» сообщения, а остальное практически целиком берётся из базового класса.
Приватный клиент для программы "Intranet Chat" на VB.NET (разворачивается)
Namespace Mailslots ''' <summary> ''' Клиент для передачи приватных сообщений программы "Intranet Chat". ''' </summary> Public Class IChatPrivateClient Inherits IChatClient #Region "КОНСТРУКТОР" ''' <summary> ''' Имя компьютера адресата. ''' </summary> Private ReadOnly Destination As String ''' <summary> ''' Создаёт клиента для обмена приватными сообщениями с заданным компьютером. ''' </summary> ''' <param name="dest">Имя адресата.</param> Public Sub New(dest As String) MyBase.New(dest) Destination = dest End Sub #End Region '/КОНСТРУКТОР #Region "ЗАЩИЩЁННЫЕ МЕТОДЫ" ''' <summary> ''' Возвращает сообщение с нужным для приватного сообщения обрамлением. ''' </summary> ''' <param name="msg">Текст сообщения.</param> ''' <remarks>Вот как выглядит приватное сообщение чата: ''' ‼iChat‼‼49‼‼AAVE‼‼TEXT‼‼gsMTCI‼‼ Hello‼‼VIRPC‼ ''' </remarks> Protected Overrides Function GetWrappedMessage(msg As String) As Byte() Dim wrapped As New List(Of Byte) wrapped.AddRange(GetCommon()) wrapped.AddRange(GetBytes("TEXT")) wrapped.AddRange(GetDelim(2)) wrapped.AddRange(GetBytes(GetLineName())) 'имя линии приватного чата. wrapped.AddRange(GetDelim(2)) wrapped.AddRange(GetBytes(msg)) wrapped.AddRange(GetDelim(2)) wrapped.AddRange(GetBytes(Destination)) wrapped.AddRange(GetDelim(1)) Return wrapped.ToArray() End Function ''' <summary> ''' Возвращает имя линии приватного чата. ''' </summary> Private Function GetLineName() As String Return "gsMTCI" End Function #End Region '/ЗАЩИЩЁННЫЕ МЕТОДЫ End Class End Namespace
Использовать написанный клиент будем, например, так:
Using iChat As New IChatClient() While True Console.Write("Введите сообщение: ") Dim msg As String = Console.ReadLine() iChat.SendMessage(msg) End While End Using
Конечно, данный клиент не обладает полнотой функциональности программы IChat. Например, мы ещё не научили его обновлять список компьютеров в сети, докладывать о своём статусе и т.д. Но мы взяли нужное направление, и если интересно – теперь вы знаете, что делать дальше.
Давайте проверим, как всё это работает. Запустим в локальной сети несколько экземпляров программы IChat.
Для имитации локальной сети я буду использовать виртуальную машину с установленной ОС Windows 7, которая находится в той же рабочей группе, что и основной компьютер. Таким образом у меня будет сеть, состоящая из двух машин.
На отдельном компьютере запустим нашу тестовую программу с клиентом iChat.
Обратите внимание, что при запущенной программе "Intranet Chat" написанный нами клиент работать не будет. Это связано с тем, что возникает коллизия в нумерации сообщений в чате. Как мы помним, при запуске программа iChat отправляет несколько сообщений на отключение и подключение к чату. Наш клиент при запуске также отправляет сообщения, и они имеют те же порядковые номера. Сервер программы iChat отвергнет пакеты и не отобразит их в чате.
Если при запуске нашего клиента указать имя адресата, то мы получим возможность отправлять ему приватные сообщения.
Читать сообщения чата мы, к сожалению, не можем, потому в нашей тестовой программе отсутствует сервер мэйлслотов. Но если мы добавим его и реализуем логику обработки сообщений, то получим собственную полноценную реализацию чата, совместимого с программой "Intranet Chat".
Download attachments:
- Установщик программы iChat (758 Downloads)
- Декодер алгоритма RC4 (730 Downloads)
- Тестовый клиент для программы IChat (756 Downloads)