Пример сокетных соединений (пересылка буфера).
На этот раз я решил рассмотреть пересылку не только текстовой информации. Нам приходило несколько писем с просьбами продолжить серию статей о сокетных соединениях. Спасибо что оценили, это стало мне стимулом при написании этой статьи.
Приступим. Для начала рекомендую скачать исходники (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;
TSys = packed record
ID: Word; {ID = 1}
Body: String[100];
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;
procedure BufToPct(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
arPacket[i]:=arBuf[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;
{Далее меняем состояния компонент, интерфейс блин}
if bClient.Active or bServer.Active then begin
btnGo.Caption:='STOP';
btnSend.Enabled:=True;
btnSys.Enabled:=True;
edAddress.ReadOnly:=True;
RadioGroup.Enabled:=False;
end
else begin
btnGo.Caption:='GO!';
btnSend.Enabled:=False;
btnSys.Enabled:=False;
edAddress.ReadOnly:=False;
RadioGroup.Enabled:=True;
end;
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 не добавляются. Сервер получает их и рассылает всем, поэтому этот же клиент и получит свое сообщение, после чего его добавит. Это позволяет серверу творить различные безумия с сообщениями (цензура, например, если Вы пишите чат).
Файлы для загрузки:
|