Пример сокетных соединений (пересылка буфера).
На этот раз я решил рассмотреть пересылку не только текстовой информации. Нам приходило несколько писем с просьбами продолжить серию статей о сокетных соединениях. Спасибо что оценили, это стало мне стимулом при написании этой статьи.
Приступим. Для начала рекомендую скачать исходники (6 КБ) всего здесь далее обговоренного. Писал на Delphi 7 (напоминаю, что сокетные компоненты просто так не лежат в палитре. Меню Component->Install Packages...->Add. Указываем файл $(DELPHI)\bin\dclsockets70.bpl). В конце концов просто pas-файл покорчуете.
Сама статья будет небольшим описанием всего что Вы там сможете найти. Конечно подсказки есть, но это так, для радования глаза (моего, не Вашего).
Начнем с начала. Создаем форму. Кидаем все необходимые компоненты. Перечень: bClient: TClientSocket; bServer: TServerSocket; RichEdit: TRichEdit; RadioGroup: TRadioGroup; edAddress: TEdit; btnGO: TButton; edNick: TEdit; btnSend: TButton; edTxt: TEdit; btnSys: TButton; Указываем в сокетах (сокетные компоненты bClient и bServer, где сервер а где клиент думаю понятно) ОДИНАКОВЫЕ порты, плиз будьте внимательны. У меня порт 812, как название одной известной российской панк-группы! Обязательно зацените, если не слышали, отличный отечественный продукт (мне заплатили за рекламу :)). edAddress нам нужен для указания адреса сервера, если это будет клиент. RichEdit - собственно поле для вывода всего: сообщения, etc. RadioGroup - позволит нам выбрать тип приложения: сервер или клиент. btgGo - кнопуля, с помощью которой активируем или уничтожаем сокет. btnSend - посылает текстовое сообщение. btnSys - посылает системное сообщение (так, для примера). edNick и edTxt - Ваш ник и текст сообщения соответственно. Я создал для примера два типа: TMes = packed record ID: Word; {ID = 0} Nick: String[20]; Txt: String[200]; end; Первый просто сообщения с ником. Второй - системный. ID у TMes должно быть равно 0, а у TSys - 1.
Какие при создании типов нужно соблюдать правила?
Во-первых, если пишите там строки, то ограничивайте их, иначе баги обеспечены. Хотя знакомый предложил динамическую компоновку буфера, вместо рассмотренной здесь статической. Интересное дело, нам удалось в среднем сократить размер каждого пересылаемого пакета на 40%. Да и размер используемой программой памяти тоже. Если интересует, пишите на мыло, отвечу!
Во-вторых, если разные сообщения, то для их разделения нужны идентификаторы. Правильно? Обычно одного байта хватает (у Вас больше 256 типов?!), но в примере я использовал двухбайтовую переменную ID типа Word. Учтите, что идентификаторы должны быть первыми в описании записи. Не забудьте, что TCP-пакеты разбиваются, поэтому всегда следует хорошо продумывать их структуру.
Также у меня есть глобальная константа MaxSize. В ней хранится максимальный размер пакетов. Посидите, посчитайте на калькуляторе или создайте глобальную переменную, а при создании формы SizeOf'ом вычислите максимальный. Как хотите, но это значение потом понадобится.
Я предлагаю две процедуры, преобразующие пакет в буфер и наоборот: procedure PctToBuf(var Packet; var buf; Size: Word); var arPacket: array[1..MaxSize] of byte absolute Packet; arBuf: array[1..MaxSize] of byte absolute buf; i: Word; begin for i:=1 to Size do arBuf[i]:=arPacket[i]; end; В принципе, в примере можно обойтись и без них, но при написании сложных программ они ой как нужны.
Далее обрабатываем клик по кнопке btnGo, т. е. мы либо запускаем, либо закрываем. А что, нам поможет выбрать RadioGroup, если ItemIndex равен 0, то клиент, иначе сервер. procedure TfmMain.btnGOClick(Sender: TObject); begin if RadioGroup.ItemIndex=0 then if bClient.Active then bClient.Active:=False {Останавливаем клиента} else begin bClient.Address:=edAddress.Text; bClient.Active:=True; {Запускаем клиента} Application.ProcessMessages; end else if bServer.Active then begin bServer.Active:=False; {Останавливаем сервер} RichEdit.SelAttributes.Color:=clBlack; RichEdit.Lines.Add('>> Server closed'); end else begin bServer.Active:=True; {Запускаем сервер} RichEdit.SelAttributes.Color:=clBlack; RichEdit.Lines.Add('>> Server started'); end; Далее описываем все события клиента, ну почти все: onError, onConnecting, onConnect, onDisconnect. Всегда их обрабатывайте, в целях избежания недоразумений, коих в нашей жизни так много (Таня, привет :))…
Приступаем к описанию событий сервера. Все разжевывать не буду, и так все понятно из названий.
При нажатии btnSend посылаем буфер TMes. TMes заполняем значениями edNick.Text и edTxt.Text. Осталось только решить, кто его посылает, сервер или клиент. Вот решение: procedure TfmMain.btnSendClick(Sender: TObject); var Mes: TMes; buf: array[1..SizeOf(TMes)] of byte; i: Word; begin Mes.ID:=0; if bClient.Active then begin Mes.Nick:=edNick.Text; Mes.Txt:=edTxt.Text; PctToBuf(Mes,buf,SizeOf(TMes)); {В принципе, это совсем не нужно! Это я так, для наглядности} bClient.Socket.SendBuf(buf,SizeOf(TMes)); {bClient.Socket.SendBuf(mes,SizeOf(TMes)); Вот так сразу тоже можно!} end else if bServer.Socket.ActiveConnections>0 then begin Mes.Nick:=edNick.Text; Mes.Txt:=edTxt.Text; PctToBuf(Mes,buf,SizeOf(TMes)); for i:=0 to bServer.Socket.ActiveConnections-1 do bServer.Socket.Connections[i].SendBuf(buf,SizeOf(TMes)); RichEdit.SelAttributes.Color:=clBlue; RichEdit.Lines.Add(Mes.Nick+': '+Mes.Txt); end; end; Пошлем теперь и буфер TSys при нажатии btnSys: procedure TfmMain.btnSysClick(Sender: TObject); var Sys: TSys; str: String; i: Word; begin Sys.ID:=1; Str:='Fuck admin!'; {Тривиально, извините} InputQuery('Сообщение','Текст',Str); Sys.Body:=Str; if bClient.Active then bClient.Socket.SendBuf(sys,SizeOf(TSys)) else if bServer.Socket.ActiveConnections>0 then begin for i:=0 to bServer.Socket.ActiveConnections-1 do bServer.Socket.Connections[i].SendBuf(sys,SizeOf(TSys)); RichEdit.SelAttributes.Color:=clRed; RichEdit.Lines.Add('>> '+Sys.Body); end; end; А как же принимать информацию? Вот как это выглядит у сервера: procedure TfmMain.bServerClientRead(Sender: TObject; Socket: TCustomWinSocket); var buf: array[1..MaxSize] of byte; Size: Word; ID: Word; i: Word; Sys: ^TSys; Mes: ^TMes; begin Size:=Socket.ReceiveLength; {Принимаемое кол-во байт} Socket.ReceiveBuf(buf,Size); {Эта функция возвращает кол-во принятых байт. Можете сравнить с Size для корректности} BufToPct(ID,buf,2); case ID of 0: begin New(Mes); BufToPct(Mes^,buf,Size); RichEdit.SelAttributes.Color:=clBlue; RichEdit.Lines.Add(Mes^.Nick+': '+Mes^.Txt); {Теперь отправим его всем} for i:=0 to bServer.Socket.ActiveConnections-1 do bServer.Socket.Connections[i].SendBuf(Mes^,Size); Dispose(Mes); end; 1: begin New(Sys); BufToPct(Sys^,buf,Size); RichEdit.SelAttributes.Color:=clRed; RichEdit.Lines.Add('>> '+Sys^.Body); {Теперь отправим его всем} for i:=0 to bServer.Socket.ActiveConnections-1 do bServer.Socket.Connections[i].SendBuf(Sys^,Size); Dispose(Sys); end; end; end; Думаю понятно, что у клиента то же самое, только без отправки данных остальным.
Если есть вопросы, или Вы заметили у меня ошибки, пожалуйста пишите мне на мыло. Красивые девушки могут и просто так писать.
Что еще осталось сказать? Если Вы заметили, то при отправке сообщений клиентом они автоматически в RichEdit не добавляются. Сервер получает их и рассылает всем, поэтому этот же клиент и получит свое сообщение, после чего его добавит. Это позволяет серверу творить различные безумия с сообщениями (цензура, например, если Вы пишите чат).
Страница сайта http://silicontaiga.ru
Оригинал находится по адресу http://silicontaiga.ru/home.asp?artId=5682 |