Войти
ПрограммированиеФорумСеть

Отправить пакет с указанного порта

Страницы: 1 2 Следующая »
#0
19:32, 9 янв. 2012

Здравствуйте!
Стоит задача отправлять UDP пакеты с жестко заданного порта, ессно при условии что он свободен. Используется WinSock + Delphi.

Открываю порт так:

type
  TNetIP = array [0..3] of Byte;
  TBuffer = array [0..BUF_SIZE - 1] of Byte;

procedure TLiteUDP.Open;
begin
    if WSAStartup($0101, wData) = 0 then
    begin
        log('Initialized');
        log('Description : ' + WData.szDescription);
        log('MaxSockets  : ' + IntToStr(WData.iMaxSockets));
        log('MaxSize UDP : ' + IntToStr(Wdata.iMaxUdpDg));
    end
    else
    begin
        log('not initialized');
        exit;
    end;

    Sock := socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if Sock = INVALID_SOCKET then
    begin
        Log('error: can''t create socket object');
        Exit;
    end;

    ServerAddr.sin_family := AF_INET;
    ServerAddr.sin_port   := htons(DEFAULT_PORT_IN);

    if bind(Sock, ServerAddr, SizeOf(ServerAddr)) <> 0 then
    begin
        Log('error: can''t bind socket on port');
        Exit;
    end;
    Log('Socket: ' + IntToStr(Sock));

    ThreadID := CreateThread(nil, 0, @RecvProc, self, 0, TID);
    Stopped  := false;
end;

Шлю данные так:

function TLiteUDP.Send(const Source; Size: integer; IP: TNetIP; Port: Integer): Boolean;
var
    DstAddr : sockaddr_in;
    Buffer  : TBuffer;
    Count   : integer;
    p       : pointer;
begin
    FillChar(DstAddr.sin_zero, SizeOf(DstAddr.sin_zero), 0);
    DstAddr.sin_addr.S_addr := INADDR_ANY;
    DstAddr.sin_port        := htons(Port);
    DstAddr.sin_family      := AF_INET;
    p := @IP;
    DstAddr.sin_addr.S_addr := Cardinal(p^);

    move(Source, Buffer[0], Size);
    Count := sendto(Sock, Buffer[0], Size, 0, DstAddr, SizeOf(DstAddr));
    inc(TraficOut, Count);
    inc(TraficOutBPS, Count);

    if ( Count <> Size ) then
    begin
        Log('error: sendto');
        result := false;
    end
    else
        result := true;
end;

В результате пакеты приходят с абсолютно случайного порта.
Что нужно исправить, чтобы пакеты всегда высылались с указанного порта?
Правильно ли я понимаю, что через один и тот же порт можно как слать, так и принимать пакеты?
Или порты обязательно должны быть разными?
Пакеты шлются внутри локалки, NAT в пересылке не участвует, его не затрагиваем.


#1
19:37, 9 янв. 2012

Barbanel
> В результате пакеты приходят с абсолютно случайного порта.

Так и должно быть.  Есть сокет с прибитым к нему портом, который ты слушаешь, на этот порт приходят соединения, для каждого входящего соединения система создает еще по одному сокету со свободным номером порта.

Таким образом, номер порта входящего соединения нельзя использовать как идентификатор чего либо.

#2
20:21, 9 янв. 2012

0iStalker
Вы угадали цель. В будущем пакеты будут приходить от клиентов сидящих за NAT, вследствии чего ответные пакеты обратно нужно отправлять именно на этот порт
Насколько я разобрался в ситуации с портами и натом, схема должна быть такой:

Есть сервер с белым IP.
Клиент открывает порт (к примеру 2000) и шлет серверу сообщение-запрос.
После прохождения NAT сообщение меняет свой "исходящий" порт (скажем на 45111).
Сервер, получая сообщение смотрит с какого порта подключился клиент и шлет на этот порт ответ.
NAT передает этот пакет обратно на 2000 порт клиента.
тут тонкий (для меня) момент: всегда ли NAT будет соотносить "входящий" порт 45111 с портом клиента 2000?

Может есть литература именно на эту тему? то что я сейчас изучаю, лишь общие вопросы, которые позволили сделать то что уже сделано.
Подскажите плиз, в какую сторону мне нужно копать, чтобы реализовать отправку данных по такому принципу?

#3
16:59, 12 янв. 2012

Народ, хелп!
Просмотрел приличное количество примеров, всюду сделано так же.
делаю бинд сокета к порту 2000, с этого сокета отправляю пакет на адрес 127.0.0.1 порт 2001.
В другом приложении слушаю порт 2001, принимаю свой пакет функцией recvfrom, но в структуре from номер порта не исходный 2000, а произвольный другой.
В чем тут ошибка? Что и где нужно прописать чтобы получить в структуре from порт 2000 ?

#4
18:54, 12 янв. 2012

Barbanel
> делаю бинд сокета к порту 2000, с этого сокета отправляю пакет на адрес
> 127.0.0.1 порт 2001.
бинд заставляет сокет слушать порт 2000, а не отправлять с него. Как отправлять с порта не знаю, да и вообще имхо схема какая-то левая. Пусть просто клиенты открывают 2000 порт и слушают, сервер будет отправлять сообщения на clienthost:2000, должно приходить.

#5
19:06, 12 янв. 2012

kipar
> бинд заставляет сокет слушать порт 2000, а не отправлять с него.
Это немного прояснило ситуацию.

> Как отправлять с порта не знаю, да и вообще имхо схема какая-то левая.
Нормальная схема, особенно при работе через NAT.

> сервер будет отправлять сообщения на clienthost:2000, должно приходить.
Да, в этом случае приходит. Но проблема в том что после прохождения NAT пакет меняет исходящий порт, и чтобы сервер мог отослать сообщение обратно (а клиент его соответственно принять), нужно слать на тот порт, с которого сообщение пришло (в нашем случае - пришло от NAT).

Главная закавыка именно отправить с указанного порта.
Как сделать это в коде - я не могу понять.
Хелп ми плиз!
  >_<

#6
21:26, 12 янв. 2012

Barbanel
> тут тонкий (для меня) момент: всегда ли NAT будет соотносить "входящий" порт
> 45111 с портом клиента 2000?
Всегда. Иначе какой смысл в NATе? Не заморачивайтесь на на НАТе. Считайте, что никакого НАТа нет. Общайтесь с клиентом, на том порту с которого прилетел поток данных.

> Может есть литература именно на эту тему? то что я сейчас изучаю, лишь общие
> вопросы, которые позволили сделать то что уже сделано.
> Подскажите плиз, в какую сторону мне нужно копать, чтобы реализовать отправку
> данных по такому принципу?

1. Не используйте UDP. Потому что в противном случае вы рано или поздно напишете свою реализацию TCP. Используйте tcp сразу.
2. Наберите в гугле man 2 socket, man 2 bind, man 2 listen, man 2 connect, man 8 ipfw, man 8 iptables. Возможно найдете информацию на русском. Даже скорее всего найдете на русском.

> Главная закавыка именно отправить с указанного порта.
man 2 listen.

Всё вышеперечисленное не совсем Дельфы и винсок, а даже наоборот - это UNIX и Си. Но если вы это прочтёте и поймете, а там не очень сложно, то потом будете писать клиент-серверные приложения на любом языке, для любой ОС.

#7
14:54, 13 янв. 2012

Barbanel
Я бы ещё попробовал :
optval = 1;
optlen = sizeof( optval );
setsockopt( Sock, SOL_SOCKET, SO_BROADCAST, &optval, optlen )
И ещё - после bind (...) выполнить в консоли netstat -aln (  или как эта команда в винде ? я не помню уже :-) ) на предмет DEFAULT_PORT_IN
По идее - уже должен быть сокет на указанном порту.
Кстати, а константа DEFAULT_PORT_IN - случайно не 0 ? ;-)

#8
15:17, 13 янв. 2012

SergK
> setsockopt( Sock, SOL_SOCKET, SO_BROADCAST, &optval, optlen )
Сделал пару дней назад, уже после этого поста. Но всеже, это не совсем то что нужно.

> И ещё - после bind (...) выполнить в консоли netstat -aln ( или как эта команда в винде ? я не помню уже :-) )
netstat -a -p UDP -b
выдает список процесов с открытыми UPD портами
да, уже висит на указанном (2000) порту, сразу после bind

> Кстати, а константа DEFAULT_PORT_IN - случайно не 0 ? ;-)
нет :-) DEFAULT_PORT_IN=2000 и в списке netstat тоже 2000

#9
15:22, 13 янв. 2012

kipar
Перепроверь свой код ещё раз, что везде всё без ошибок отрабатывает (bind и проче).
Всегда так делал, как ты описываешь, но ещё раз проверил сниффером работу именно этого (см. ниже) сампла udp_send | Отправить пакет с указанного порта

int main(int argc,char** argv)
{
  WORD wVersionRequested;
  WSADATA wsaData;
  int err;

  wVersionRequested = MAKEWORD( 2, 2 );

  err = WSAStartup( wVersionRequested, &wsaData );
  if ( err != 0 )
  {
    return 1;
  }

  SOCKET sock=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
  if (sock==INVALID_SOCKET)
  {
    printf("sock error");
    return 1;
  }
  sockaddr_in service;
  service.sin_family = AF_INET;
  service.sin_addr.s_addr = inet_addr("172.20.106.114");
  service.sin_port = htons(2000);

  if (bind(sock,(SOCKADDR*)&service,sizeof(service)) == SOCKET_ERROR)
  {
    printf("bind error");
    return 1;
  }

  sockaddr_in service1;
  service1.sin_family = AF_INET;
  service1.sin_addr.s_addr = inet_addr("172.20.106.115");
  service1.sin_port = htons(2001);

  if (sendto(sock,"test",4,0,(SOCKADDR*)&service1,sizeof(service1))!=4)
  {
    printf("sendto error");
    return 1;
  }
  return 0;
}
#10
15:39, 13 янв. 2012

pentagra
Про сниффер не подумал, спасибо, попробую, отпишусь.

UPD отрабатывает без ошибок. лог чист, всюду "OK"

#11
16:36, 13 янв. 2012

Barbanel
Можно код приемной функции ?

#12
17:02, 13 янв. 2012

SergK
> Можно код приемной функции ?
Показал прием только одноги типа пакета, остальные принимаются почти так же (только ответ не шлется сразу же).

const
  BUF_SIZE  = 1024 * 64;
  MAX_PACKET_SIZE = 1024 * 2;

type
  TBuffer = array [0..BUF_SIZE - 1] of Byte;

  TGeneralPacket = packed record
    PacketType : TPacketType;
    PacketSize : Word;
    Data : array[0..MAX_PACKET_SIZE-1] of byte;
  end;

procedure RecvProc(LiteUDP: TLiteUDP); stdcall;
var
  Count     : LongInt;
  From      : TSockAddrIn;
  FromLen   : LongInt;
  Packet    : TGeneralPacket;
  Buffer    : TBuffer;
  Error     : Integer;
begin
    while True do
    begin
        FromLen := SizeOf(From);
        ZeroMemory(@From, SizeOf(From));
        Count := recvfrom(LiteUDP.Sock, Buffer, SizeOf(Buffer), 0, From, FromLen);
        if Count > 0 then
        begin
            inc(LiteUDP.TraficIn, Count);
            inc(LiteUDP.TraficInBPS, Count);
            inc(LiteUDP.PackCntIn);

            move(Buffer, Packet, Count);
            LiteUDP.GetDataProc(Packet, From);
        end
        else
        begin
            Error := WSAGetLastError;
            LiteUDP.Log('error: socket receive error, listen thread stoped. Error ' + IntToStr(Error));
            LiteUDP.Stopped := true;
            break;
        end;
    end;
end;

procedure TNetworkGame.GetData(Packet: TGeneralPacket; From: TSockAddrIn);
var
    Player_ID: integer;
begin
    Player_ID := 0;
    self.ReadPacket(Packet, Player_ID, From);
end;

procedure TNetworkGame.ReadPacket(Packet : TGeneralPacket; var Player_ID: integer; From : TSockAddrIn);
var
    Player  : TBasicPlayer;
    offset  : integer;

    GeneralPacket  : TGeneralPacket;
    Request_Packet : PPacket_REQUEST_CONNECT;
    Accept_Packet  : TPacket_REQUEST_CONNECT_ACCEPT;
begin
    case Packet.PacketType of
    PT_REQUEST_CONNECT : // запрос на подключение
        begin
            Request_Packet := @Packet.Data[0];

            // создаем нового игрока
            Player := self.CreatePlayer();
            Player.IP   := inet_ntoa(from.sin_addr);
            Player.Port := from.sin_port;
            Player.Name := Request_Packet.PlayerName;
            Player.LastPacket := GetTickCount();

            // возвращаем игроку его UID
            Accept_Packet.UID         := Player.UID;

            // сразу ответим игроку
            // подготавливаем пакет для игрока
            ClearPacket(@GeneralPacket);
            SetPacket(@GeneralPacket, PT_REQUEST_CONNECT_ACCEPT, Accept_Packet);

            // шлем игроку извещение о принятии
            Net.Send(GeneralPacket, SIZE_GENERAL_HEAD + GeneralPacket.PacketSize, Player.IP, Player.Port);

            Log('Connected ' + Player.Name + ' ( UID:' + IntToStr(Player.UID) + ' IP:' + Player.IP + ':' + IntToStr(Player.Port) + ')');
        end;

    ...тут вырезана обработка других пакетов...

    end;
end;
#13
17:19, 13 янв. 2012

Я олень, я понял свою ошибку.

Поставил сниффер, увидел что пакет приходит с правильного, 2000 порта.
SergK попросил код приема, я выложил и тольно потом понял что неправильно определяю порт.

Вместо

Player.Port := from.sin_port;
нужно
Player.Port := htons(from.sin_port);

Огромное спасибо всем за участие, особенно pentagra и SergK!

#14
17:44, 13 янв. 2012

Barbanel
Я собственно и просил код, так как подозревал отсутствие htons.  :-)
Успехов !

Страницы: 1 2 Следующая »
ПрограммированиеФорумСеть

Тема в архиве.