|
Пример сокетных соединений (пересылка буфера).
На этот раз я решил рассмотреть пересылку не только текстовой информации. Нам приходило несколько писем с просьбами продолжить серию статей о сокетных соединениях. Спасибо что оценили, это стало мне стимулом при написании этой статьи.
Приступим. Для начала рекомендую скачать исходники (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://www.silicontaiga.ru
Оригинал находится по адресу http://www.silicontaiga.ru/home.asp?artId=5682 |