Synapse. UDP client and server sample

Модератор: Модераторы

Аватара пользователя
B4rr4cuda
энтузиаст
Сообщения: 693
Зарегистрирован: 28.12.2007 06:48:35

Synapse. UDP client and server sample

Сообщение B4rr4cuda »

Сам долго мучился, пока нашел работоспособный пример, поэтому выкладываю для страждущих.

Код: Выделить всё

program synudp;

{$APPTYPE CONSOLE}

uses
  SysUtils, Classes,
  blcksock;

const
  CONST_PORT = '32000';

type
  TUDPThread = class( TThread )
  private
    function  GetLog: String;
    procedure OnStatus(Sender: TObject; Reason: THookSocketReason; const Value: string);
  protected
    m_Log : TStringList;
    m_pSocket : TUDPBlockSocket;
  public
    constructor Create; virtual;
    destructor  Destroy; override;
    property Log : String read GetLog;
  end;

  TUDPServer = class( TUDPThread )
  protected
    procedure Execute; override;
  end;

  TUDPClient = class( TUDPThread )
  protected
    procedure Execute; override;
  end;

{ TUDPThread }

constructor TUDPThread.Create;
begin
  inherited Create( false );
  FreeOnTerminate := false;
  m_Log := TStringList.Create;
  m_pSocket := TUDPBlockSocket.Create;
  m_pSocket.OnStatus := @OnStatus;
end;

destructor TUDPThread.Destroy;
begin
  FreeAndNil( m_pSocket );
  FreeAndNil( m_Log );
  inherited;
end;

function TUDPThread.GetLog: String;
begin
  Result := m_Log.Text;
end;

procedure TUDPThread.OnStatus(Sender: TObject; Reason: THookSocketReason;
  const Value: string);
var
  sReason : String;
begin
  case Reason of
    HR_ResolvingBegin : sReason := 'HR_ResolvingBegin';
    HR_ResolvingEnd : sReason := 'HR_ResolvingEnd';
    HR_SocketCreate : sReason := 'HR_SocketCreate';
    HR_SocketClose : sReason := 'HR_SocketClose';
    HR_Bind : sReason := 'HR_Bind';
    HR_Connect : sReason := 'HR_Connect';
    HR_CanRead : sReason := 'HR_CanRead';
    HR_CanWrite : sReason := 'HR_CanWrite';
    HR_Listen : sReason := 'HR_Listen';
    HR_Accept : sReason := 'HR_Accept';
    HR_ReadCount : sReason := 'HR_ReadCount';
    HR_WriteCount : sReason := 'HR_WriteCount';
    HR_Wait : sReason := 'HR_Wait';
    HR_Error : sReason := 'HR_Error';
  end;
  m_Log.Add( sReason + ': ' + Value );
end;

{ TUDPServer }

procedure TUDPServer.Execute;
var
  sResult : String;
begin
  m_pSocket.Bind( cAnyHost, CONST_PORT );
  if ( m_pSocket.LastError = 0 ) then
  repeat
    sResult := m_pSocket.RecvPacket( -1 );
    m_Log.Add( sResult );
  until terminated or ( sResult = 'exit' );
end;

{ TUDPClient }

procedure TUDPClient.Execute;
var
  ii : Integer;
begin
  m_pSocket.Connect( cLocalhost, CONST_PORT );
  for ii := 0 to 3 do
  begin
    m_pSocket.SendString( IntToStr( ii ) );
    Sleep( 100 );
  end;
  m_pSocket.SendString( 'exit' );
end;

var
  pClient : TUDPClient;
  pServer : TUDPServer;

begin
  pServer := TUDPServer.Create;
  try
    Sleep( 100 );
    pClient := TUDPClient.Create;
    try
      pServer.WaitFor;
      WriteLn( '*************** Client ***************' );
      WriteLn( pClient.Log );
      WriteLn( '*************** Server ***************' );
      WriteLn( pServer.Log );
      ReadLn;
    finally
      FreeAndNil( pClient );
    end;
  finally
    FreeAndNil( pServer );
  end;
end.
Polinom2686
незнакомец
Сообщения: 8
Зарегистрирован: 02.09.2009 02:01:29

Сообщение Polinom2686 »

Спасибо тебе, хороший человек. :D
Аватара пользователя
VirtUX
энтузиаст
Сообщения: 880
Зарегистрирован: 05.02.2008 09:52:19
Откуда: Крым, Алушта

Сообщение VirtUX »

Спасибо :)
Аватара пользователя
VirtUX
энтузиаст
Сообщения: 880
Зарегистрирован: 05.02.2008 09:52:19
Откуда: Крым, Алушта

Сообщение VirtUX »

Что-то не могу найти в модуле blcksock как узнать адрес отправителя пакета?
Т.е.: я ожидаю пакет:

Код: Выделить всё

...
FSocket.Bind( cAnyHost, FPort );
...
sResult := FSocket.RecvTerminated( -1, #254#255 );
...

Теперь мне нужно ответить отправителю, что я все получил. Но как узнать адрес приславшего дейтаграмму?
===========
и параллельный вопрос:
сколько раз, и как правильно я могу связывать UDP-сокет с различными адресатами?
Т.е.: создан сокет, связываем его:

Код: Выделить всё

FSocket.Connect( '10.254.7.12', '10201' );

отправляем дейтаграмму.
Теперь нужно отправить ДГ другому адресату. Могу-ли я использовать тот же сокет? Если да, то как правильно связать с другим адресатом? Нужны-ли еще какие действия между одним и следующим FSocket.Connect?
Аватара пользователя
B4rr4cuda
энтузиаст
Сообщения: 693
Зарегистрирован: 28.12.2007 06:48:35

Сообщение B4rr4cuda »

VirtUX писал(а):Но как узнать адрес приславшего дейтаграмму?

Если память не подводит, то так :

Код: Выделить всё

FSocket.RemoteSin.sin_addr


VirtUX писал(а):Теперь нужно отправить ДГ другому адресату. Могу-ли я использовать тот же сокет? Если да, то как правильно связать с другим адресатом? Нужны-ли еще какие действия между одним и следующим FSocket.Connect?

Отключиться разве что.. я бы, на всякий случай, все же освобождал бы и создавал заново. Но это так.. дую на воду.

Да, кстати.. а зачем адрес, чтоб отправить ответ? Что мешает отослать отправителю инфу тем же сокетом с того же соединения?
Что-то типа такого:

Код: Выделить всё

          vRecvSockStr:='';
          //если есть инфа в буфере, то читаем
          if FBlockSock.WaitingData>0 then begin //пришла инфа по сокету
            vRecvSockStr := FBlockSock.RecvPacket(1000);
            CatchDisconnect(vRecvSockStr);
            SendInfoToIPC(vRecvSockStr);
          end;
         //Обработка ошибок сокета
          if FBlockSock.lastError<>0 then begin
            AddLog(inttostr(FBlockSock.LastError)+':'+FBlockSock.GetErrorDescEx,LogError);
            break;
            //если не connection timeout
            //вообще, вопрос, надо ли отваливаться?
            //по идее сервер должен пахать как проклятый.
            //if LastError<>10060 then break;
          end
          else begin
             //Обработка пришедшей команды сервером IPC
             vRecvIPCStr:=GetIPCCommand;
             if vRecvIPCStr<>'' then
               FBlockSock.SendString(vRecvIPCStr);
          end;
          sleep(200);

          end;
Аватара пользователя
VirtUX
энтузиаст
Сообщения: 880
Зарегистрирован: 05.02.2008 09:52:19
Откуда: Крым, Алушта

Сообщение VirtUX »

А перед FSocket.RemoteSin.sin_addr вызвать FSocket.GetSinRemote; нужно, или это не обязательно?
Аватара пользователя
B4rr4cuda
энтузиаст
Сообщения: 693
Зарегистрирован: 28.12.2007 06:48:35

Сообщение B4rr4cuda »

Не в курсе, надо проверять..
Аватара пользователя
VirtUX
энтузиаст
Сообщения: 880
Зарегистрирован: 05.02.2008 09:52:19
Откуда: Крым, Алушта

Сообщение VirtUX »

B4rr4cuda писал(а):Что мешает отослать отправителю инфу тем же сокетом с того же соединения?

Сообщить об успешном получении дейтаграммы таким образом можно. Но тут ситуация в следующем:
- пришел запрос на сервер от клиента;
- сервер ставит его в очередь инструкций на обработку, и продолжает ожидать запросы от различных клиентов;
- поток обработки запросов выполняет инструкцию из очереди, и, возможно, отправляет результат выполнения клиенту;
- в момент отправки результата, принимающий запросы сокет может уже иметь в поле RemoteSin адрес и порт другого клиента.
Т.е. сервер не прекращает принимать запросы от одних клиентов в тот момент, пока формируются ответы для других клиентов. Поэтому я и спрашиваю про возможность использования одного сокета, для отправки дейтаграмм различным клиентам.
Аватара пользователя
Ichthyander
энтузиаст
Сообщения: 701
Зарегистрирован: 04.04.2007 08:32:43
Откуда: Астрахань
Контактная информация:

Сообщение Ichthyander »

VirtUX А если передать этому же "потоку обработки запросов" объект-сокет? То есть сервер получил от клиента пакет, создает новый поток куда помещает данные сокета и "забывает" о нем..? Этот поток и отвечает клиенту. Я так делал, правда с TCP-сервером.
На UDP сервере я все делал в одном потоке, то есть поток обрабатывал запрос клиента, отвечал ему и после этого сервер готов принимать новый пакет.
Аватара пользователя
VirtUX
энтузиаст
Сообщения: 880
Зарегистрирован: 05.02.2008 09:52:19
Откуда: Крым, Алушта

Сообщение VirtUX »

Ichthyander писал(а):А если передать этому же "потоку обработки запросов" объект-сокет?

Дело в том, что в поле удаленного адреса этого объекта значение будет меняться при поступлении новых пакетов от других клиентов. И функция Send может отправить пакет клиенту, который на данный момент прислал запрос последним.
В TCP другая история в отличии от UDP. Там для каждого клиента плодятся отдельные сокеты.
Аватара пользователя
VirtUX
энтузиаст
Сообщения: 880
Зарегистрирован: 05.02.2008 09:52:19
Откуда: Крым, Алушта

Сообщение VirtUX »

странное дело. В локалке пакеты путешествуют от клиента к серверу и обратно отлично. Но через интернет не работает.
Сервер находится за роутером. Порт по которому клиент шлет запросы на роутере проброшен.
Соединяюсь с сервером так:

Код: Выделить всё

FSocket.Connect( FIPServer, FPortServer );
; где FIPServer - WAN-адрес роутера смотрящий в интернет (с рабочего места клиента пингуется), FPortServer - порт сервера (порт проброшен на локальный адрес сервера). IP и порт для клиентского сокета не указываю (оставлено на милость synapse). Хотя пробовал жесткую привязку к локальному IP и такому же порту как на сервере - результат тот же - негативный.
Сервер привязан так:

Код: Выделить всё

FSocket.Bind( FIP, FPort ); 
; где FIP - локальный адрес сервера, FPort - порт сервера.
Пакеты не приходят на сервер. Что не так?
Mr.Smart
долгожитель
Сообщения: 1796
Зарегистрирован: 29.03.2008 00:01:11
Откуда: из леса!

Сообщение Mr.Smart »

Смотрите сетевые настройки. Проброску портов, FireWall и т.д.
Аватара пользователя
VirtUX
энтузиаст
Сообщения: 880
Зарегистрирован: 05.02.2008 09:52:19
Откуда: Крым, Алушта

Сообщение VirtUX »

Mr.Smart писал(а):FireWall

Забыл открыть порт в брандмауре Windows Server :oops: (До этого тестил на рабочих станциях)
Спасибо за намек :)
Аватара пользователя
VirtUX
энтузиаст
Сообщения: 880
Зарегистрирован: 05.02.2008 09:52:19
Откуда: Крым, Алушта

Сообщение VirtUX »

B4rr4cuda писал(а):Не в курсе, надо проверять..

После считывания данных из сокета FSocket.Recv.... становятся доступны IP и Port:

Код: Выделить всё

nowIP:= FSocket.GetRemoteSinIP;
nowPort:= IntToStr( FSocket.GetRemoteSinPort );

P.S.
Я столкнулся с проблемой, что разные ОС по умолчанию выделяют под сокет буфер разного размера. Поэтому не забываем про:

Код: Выделить всё

FSocket.SizeSendBuffer := 65000;
FSocket.SizeRecvBuffer := 65000;
Аватара пользователя
VirtUX
энтузиаст
Сообщения: 880
Зарегистрирован: 05.02.2008 09:52:19
Откуда: Крым, Алушта

Сообщение VirtUX »

Странности продолжаются. Мало того, что размер пакета зависит от ОС, так еще и от типа соединения. В локалке и внутренней сети провайдера пакеты гуляют нормально. Но через интернет не более ~1400 байт. Поэтому при попытке отправить пакет 1500 байт - он не доходит :(
Интересует вопрос: это не лечится? пакеты по 1400 байт оооочень маленькие. И если я разделяю сообщение на части по 1000 байт, то по очень долгу загружается ответ от сервера, что мне никак нет приемлемо! Есть-ли возможность отправлять пакеты по 65000 байт?
Использую blcksock из Synapse.

Добавлено спустя 8 минут 3 секунды:
Разница теста: 1 минута 21 секунда (если делить по 1000 байт), против 7 секунд (если делить по 65000).
Не полный код теста:

Код: Выделить всё

{ Приращиваемый блок }
sAppend:='7777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777';
{ Размер блока }
i := 100;
  while i < 65001 do begin

      sSend += sAppend;
      fTimeStartBlock:= GetTickCount64;
      sAns := Cln.Send(sSend);       // Отправка сообщения на сервер. Возвращает то же самое сообщение (если все ОК :))
      lenAns:= Length( sAns );
      if lenAns <> i then sAns:= CodeChars(sAns)    // Если пришло не то, что нужно, то показываю коды символов пришедшего ответа
      else sAns:= IntToStr(lenAns);                        // иначе длину ответа (остатки от прошлого кода)
      fTimeStopBlock:= GetTickCount64;
      { Показываем время ожидания ответа }
      ListBox1.Items.Append(IntToStr(i) + ':' + sAns + ' >>> ' + IntToStr(fTimeStopBlock - fTimeStartBlock));
      ListBox1.Repaint;
      i += 100;

  end;
Ответить