Конференция "Сети" » Помогите с winsocket в WinAPI [D6, D7, WinXP]
 
  • chemelin (15.01.08 12:30) [0]
    Нужно написать на winsocket без VCL компонентов клиент-сервер которые будут обмениваться данными, помогите пожалуйста, если можно исходник или ссылки на статьи или готовые варианты реализации, буду очень благодарен, малое представление имею о winsocket но не могу реализовать передачу данных!
  • chemelin (15.01.08 12:38) [1]
    продолжение вопроса...
    Вот у меня есть акая реализация сервер-клмент, подправьте что не так.
    TCPClient
    procedure TestWinSockError(S:String);
    var
    iErr:Integer;
    sFullErr:String;
    begin
    sFullErr:='Неизвестная ошибка';
    iErr:=WSAGetLastError();

    case iErr of
     WSANOTINITIALISED: sFullErr:='Нужно сначала вызвать функцию WSASturtup, а потом создавать сокет';
    WSAENETDOWN: sFullErr:='Cвязь нарушена, возможные причины - отошёл кабель или отключились от Интернета';
    WSAEADDRINUSE: sFullErr:='Указанный адрес уже используется';
    WSAEFAULT: sFullErr:='Параметры name и namelen не соответствуют выбранной адресации. Параметр namelen может быть меньше необходимого значения, а name содержать некорректные данные';
    WSAEINPROGRESS: sFullErr:='Выполняется операция в блокирующем режиме. Вы уже запустили на выполнение какую-то функцию и нужно дождаться завершения её работы';
    WSAEINVAL: sFullErr:='Сокет уже связан с адресом';
    WSAENOBUFS: sFullErr:='Недостаточно буферов, слишком много соединений';
    WSAENOTSOCK: sFullErr:='Неверный дескриптор сокета';
     WSAEISCONN: sFullErr:='Сокет уже подключён';
     WSAEMFILE: sFullErr:='Нет больше доступных дескрипторов';
    end;

    MessageBox(0, PChar('Ошибка в функции '+S+' - '+sFullErr), 'Ошибка', 0);
    end;

    function LookupName(name:String): TInAddr;
    var
    HostEnt: PHostEnt;
    InAddr: TInAddr;
    begin
    if name[4]='.' then
     InAddr.s_addr := inet_addr(PChar(name))
    else
     begin
     HostEnt := gethostbyname(PChar(name));
     FillChar(InAddr, SizeOf(InAddr), 0);
     if HostEnt <> nil then
      begin
       with InAddr, HostEnt^ do
        begin
         S_un_b.s_b1 := h_addr^[0];
         S_un_b.s_b2 := h_addr^[1];
         S_un_b.s_b3 := h_addr^[2];
         S_un_b.s_b4 := h_addr^[3];
        end;
      end
     end;
     Result := InAddr;
    end;

    procedure TTCPClientForm.btSendClick(Sender: TObject);
    var
    wData : WSADATA;
    sServerListen: TSOCKET;
    server_addr : sockaddr_in;
    iRet : Integer;
    sRecvBuff : array [0..255] of char;
    begin
    // Загрузка WinSock
    if WSAStartup(MAKEWORD(1,1), wData) <> 0 then
    begin
      MessageBox(0, 'Не могу загрузить WinSock', 'Ошибка', 0);
      exit;
    end;

    // Создание сокета
    sServerListen := socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
    if sServerListen = INVALID_SOCKET then
     begin
      MessageBox(0, 'Ошибка создания сокета', 'Ошибка', 0);
      exit;
     end;

    // Запонение структуры адреса
    server_addr.sin_addr.s_addr := htonl(INADDR_ANY);
    server_addr.sin_family := AF_INET;
    server_addr.sin_port := htons(5050);
    server_addr.sin_addr := LookupName(edServer.Text);

    if (connect(sServerListen, server_addr, sizeof(server_addr)) = SOCKET_ERROR) then
     begin
      TestWinSockError('Send');
      exit;
     end;

    sRecvBuff:='get';
    iRet := send(sServerListen, sRecvBuff, 3, 0);
    if (iRet = SOCKET_ERROR) then
      begin
       MessageBox(0, 'Ошибка передачи данных', 'Внимание!!!', 0);
       exit;
      end;

    iRet := recv(sServerListen, sRecvBuff, 1024, 0);
    if (iRet = SOCKET_ERROR) then
     begin
      MessageBox(0, 'Ошибка получения данных', 'Внимание!!!', 0);
      exit;
     end;  

    edServer.Text:=sRecvBuff;
    CloseSocket(sServerListen);
    end;

    end.
    ---------------------------------
    TCPServer
    procedure TestWinSockError(S:String);
    var
    iErr:Integer;
    sFullErr:String;
    begin
    sFullErr:='Неизвестная ошибка';
    iErr:=WSAGetLastError();

    case iErr of
     WSANOTINITIALISED: sFullErr:='Нужно сначала вызвать функцию WSASturtup, а потом создавать сокет';
    WSAENETDOWN: sFullErr:='Cвязь нарушена, возможные причины - отошёл кабель или отключились от Интернета';
    WSAEADDRINUSE: sFullErr:='Указанный адрес уже используется';
    WSAEFAULT: sFullErr:='Параметры name и namelen не соответствуют выбранной адресации. Параметр namelen может быть меньше необходимого значения, а name содержать некорректные данные';
    WSAEINPROGRESS: sFullErr:='Выполняется операция в блокирующем режиме. Вы уже запустили на выполнение какую-то функцию и нужно дождаться завершения её работы';
    WSAEINVAL: sFullErr:='Сокет уже связан с адресом';
    WSAENOBUFS: sFullErr:='Недостаточно буферов, слишком много соединений';
    WSAENOTSOCK: sFullErr:='Неверный дескриптор сокета';
     WSAEISCONN: sFullErr:='Сокет уже подключён';
     WSAEMFILE: sFullErr:='Нет больше доступных дескрипторов';
    end;

    MessageBox(0, PChar('Ошибка в функции '+S+' - '+sFullErr), 'Ошибка', 0);
    end;

    function TestFuncError(iErr:Integer; FuncName:String):Boolean;
    begin
    Result:=false;
    if iErr = SOCKET_ERROR then
     begin
      TestWinSockError(FuncName);
      Result:=true;
     end;
    end;

    procedure TForm1.bStartServerClick(Sender: TObject);
    var
    wData : WSADATA;
    sServerListen, sClient : TSOCKET;
    localaddr, clientaddr : sockaddr_in;
    iSize : Integer;
    s1 : TCPClientThread;
    begin
    // Загрузка WinSock
    if WSAStartup(MAKEWORD(1,1), wData) <> 0 then
    begin
      MessageBox(0, 'Не могу загрузить WinSock', 'Ошибка', 0);
      exit;
    end;

    // Создание сокета
    sServerListen := socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
    if sServerListen = INVALID_SOCKET then
     begin
      MessageBox(0, 'Ошибка создания сокета', 'Ошибка', 0);
      exit;
     end;

    // Запонение структуры адреса
    localaddr.sin_addr.s_addr := htonl(INADDR_ANY);
    localaddr.sin_family := AF_INET;
    localaddr.sin_port := htons(5050);

    //Связывание сокета с локальным адресом
    if bind(sServerListen, localaddr, sizeof(localaddr)) = SOCKET_ERROR then
     begin
      TestWinSockError('Bind');
      exit;
     end;

    //Прослушивание
    if TestFuncError(listen(sServerListen, 4), 'Listen') then
     exit;

    MessageBox(0, 'Сервер запущен', 'Внимание!!!', 0);
    while (true) do
     begin
      iSize := sizeof(clientaddr);

      //Приём нового соединения
      sClient := accept(sServerListen, @clientaddr, @iSize);
      if sClient = INVALID_SOCKET then
       begin
        TestWinSockError('accept');
        break;
       end;

      // Соединение принято, создаём поток
      s1:=TCPClientThread.Create(true);
      s1.Sock:=sClient;
      s1.Resume;
     end;
    closesocket(sServerListen);
    end;

    end.

    Пробывал сам сделать не выходит!
  • Сергей М. © (15.01.08 12:44) [2]

    > не выходит


    Что конкретно "не выходит" ?
  • chemelin (15.01.08 12:51) [3]

    > Что конкретно "не выходит" ?

    Не выходит корректно передать драные с клиента на сервер и на оборот, допустим с клиента передаю строку "Hello"  и чтобы на сервера она отобразилась в Edit'e, ну и обратная связь! (образный пример)
  • chemelin (15.01.08 12:54) [4]
    Вот кусочек кода с UDP протокола, работает. Но не могу перенести этот код в TCP протокол (в тот код что писал выше)!

    //Прослушивание
    iSize:=sizeof(clientaddr);
    if recvfrom(sServerListen, sRecvStr, 255, 0,
        clientaddr, iSize)<>SOCKET_ERROR then
     Label1.Caption:=sRecvStr;

  • Григорьев Антон © (15.01.08 12:58) [5]
    Навскидку ошибок не видно, но код нити TCPClientThread не приведён, может, там что не так. Только просьба - пользуйтесь кнопкой "код" справа от поля ввода, а то неоформленный листинг читать тяжело.
  • Сергей М. © (15.01.08 13:05) [6]

    > chemelin   (15.01.08 12:51) [3]


    Что говорит отладчик ?
  • chemelin (15.01.08 13:09) [7]

    > пользуйтесь кнопкой "код" справа от поля ввода

    Давно был на этом форуме, забыл за кнопку "код", извиняюсь.

    Делал вот так:

    ...
    var
    sRecvStr : array [0..255] of char;
    ...

      //Приём нового соединения
     sClient :=accept(sServerListen, @sRecvStr, @iSize);
      if sClient = INVALID_SOCKET then
       begin
        TestWinSockError('accept');
       end;
        edit1.Text  := sRecvStr;
    ...


    После приёма данных, в edit1 выводит такой вот символ "|" заместь "get"
  • Сергей М. © (15.01.08 13:17) [8]
    Функция accept() не предназначена для приема данных, для этого существует ф-ция recv()
  • chemelin (15.01.08 13:28) [9]

    > Функция accept()

    а для чего она?
    Делаю фот так:

    ...
    recv(sServerListen, buf, 1024, 0);
    edit1.Text  := sRecvStr;
    ...


    в чем ошибка?
  • chemelin (15.01.08 13:47) [10]
    О, по идее разобрался. Когда сделаю что будет все работать как надо, выложу здесь кодинг, для примера другим!
  • Сергей М. © (15.01.08 13:47) [11]

    > для чего она?


    Для ожидания запросов на соединение и собственно установки соединения.


    > Делаю фот так


    Полную ерунду ты делаешь.

    Справку по Winsock API ты вообще читал перед тем как лепить код ?

    Там же черным по белому написано:

    The Windows Sockets recv function receives data from a socket.

    int recv (

       SOCKET s,
       char FAR* buf,
       int len,
       int flags
      );

    ..

    s

    [in] A descriptor identifying a connected socket.

    А ты что толкаешь первым параметром ? Ты туда толкаешь дескриптор слушающего сокета.
    Разницу между слушающим сокетом и сокетом, ассоциированным с успешно установленным соединением, осюсяешь ?
    Получается что не осюсяешь.
    Ну так вперед - читать документацию !
  • Сергей М. © (15.01.08 13:49) [12]

    > выложу здесь кодинг


    Не надо.

    Твоему дурному примеру могут последовать неокрепшие умы твентинпрограммеров)
  • chemelin (15.01.08 13:54) [13]

    > Не надо.

    Я видел что я неправильно был здесь написал

    recv(sServerListen, buf, 1024, 0);
    edit1.Text  := sRecvStr;


    не хотел снова лепить сообщение.
    Вот смотри:

    ...
    var
    sRecvBuff, sSendBuff : array [0..255] of char;
    ret:Integer;
    s:String;
    begin
    while(true) do
     begin
      ret := recv(sock, sRecvBuff, 1024, 0);
      if (ret = 0) then
       Continue
      else
       if (ret = SOCKET_ERROR) then
        begin
         MessageBox(0, 'Ошибка получения данных', 'Внимание!!!', 0);
         exit;
        end;

      s:=sRecvBuff;
      if s[Length(s)]=#10 then
       s:=Copy(s, 1, Length(s)-2);
      if s<>'get' then
       continue;
      sSendBuff:='Command get OK';

      ret := send(sock, sSendBuff, sizeof(sSendBuff), 0);
      if (ret = SOCKET_ERROR) then
        begin
         MessageBox(0, 'Ошибка передачи данных', 'Внимание!!!', 0);
         break;
        end;
     end;
    CloseSocket(sock);
    ...

  • Сергей М. © (15.01.08 14:05) [14]

    > Вот смотри


    Ну вижу.

    И что ?
  • chemelin (15.01.08 14:10) [15]
    Всем спасибо за внимание! Можете тему закрывать!
  • Сергей М. © (15.01.08 14:51) [16]
    Все же традиционный вопрос - готовые компоненты чем не угодили ?)

    Только не надо бухтеть про "размер")
  • chemelin (15.01.08 15:27) [17]

    > Только не надо бухтеть про "размер")

    ну в том то и дело что нужен маленький размер, с готовыми компонентами вопросов нет, с ними я уже работал воб-щем то сложности с ними нету. Ну и нужно затвердить знание WinAPI (хорошая вешь хоть и мороки хватает).
  • Сергей М. © (15.01.08 15:32) [18]

    > в том то и дело что нужен маленький размер


    Только не надо бухтеть про дискетку и про нищих)


    > нужно затвердить знание WinAPI


    Тут не затвердением пахнет - тут пахнет многократным штудированием от начала до конца)

    Ну и , к слову, WinAPI <> WinsockAPI
  • chemelin (15.01.08 15:38) [19]
    ещё один вопрос! Когда сервер стоит на прослушивании, ожидания приёма информации, он как будто бы повисает и никаких действий выполнить нельзя,
    все из за цикла
    while (true) do

    это как то исправить
  • chemelin (15.01.08 15:40) [20]

    > Тут не затвердением пахнет - тут пахнет многократным штудированием
    > от начала до конца)

    признаюсь, WinAPI учил изначально на C++ , но WinAPI  на Delphi очень пож на WinAPI С++.
    И хватит меня критиковать!
  • Сергей М. © (15.01.08 16:04) [21]

    > chemelin   (15.01.08 15:38) [19]


    > как то исправить


    Оч просто - либо задействовать неблок.режим слушающего гнезда либо вынести "стояние на прослушивании" в отдельный кодовый поток.


    > И хватит меня критиковать


    Изволишь чтобы тебя хвалили за принципиальное нежелание читать документацию ?
  • Григорьев Антон © (15.01.08 16:10) [22]

    > chemelin   (15.01.08 15:38) [19]
    > ещё один вопрос! Когда сервер стоит на прослушивании, ожидания
    > приёма информации, он как будто бы повисает и никаких действий
    > выполнить нельзя,
    > все из за цикла while (true) do это как то исправить

    Это не только из-за цикла while True do, это, в основном, из-за того, что функция accept по умолчанию работает в блокирующем режиме. Вариантов исправления много:

    1. Использовать select с таймаутом и вызывать accept только если select показал готовность сокета

    2. Перевести слушающий сокет в неблокирующий режим.

    3. Используя WSAAsyncSelect, связать сокет с оконным сообщением и вызывать accept только по приходу этого сообщения.

    4. Используя WSAEventSelect, связать сокет с событием и при необходимости проверять состояние этого события с помощью WSAWaitForMultipleEvents.

    5. Использовать AcceptEx в режиме перекрытого ввода-вывода.

    6. Делать accept в отдельной нити, которая больше ничем другим заниматься не будет - тогда её зависание всей остальной программе будет по барабану.

    Ну и от цикла тоже (за исключением варианта 6), конечно, надо избавиться. Например, если вы выберете вариант 1 или 2, можно проверять по таймеру.
  • chemelin (15.01.08 16:21) [23]

    > Изволишь чтобы тебя хвалили за принципиальное нежелание
    > читать документацию ?

    Учту!


    > Григорьев Антон

    Спасибо!
  • Григорьев Антон © (15.01.08 16:33) [24]

    > chemelin   (15.01.08 12:30)  
    > если можно исходник или ссылки на статьи

    Кстати, а вот это видели?
    http://www.delphikingdom.com/asp/viewitem.asp?catalogid=1021
    http://www.delphikingdom.com/asp/viewitem.asp?catalogid=1060
  • chemelin (15.01.08 17:19) [25]

    > 6. Делать accept в отдельной нити, которая больше ничем
    > другим заниматься не будет - тогда её зависание всей остальной
    > программе будет по барабану.

    Покажи пожалуйста на примере я что-то не очень понял как делать accept в отдельной нити, статью прочитал. Извиняюсь! Буду очень благодарен!
  • Григорьев Антон © (15.01.08 18:34) [26]

    > chemelin   (15.01.08 17:19) [25]

    И что же конкретно вам непонятно? С нитями работать вроде умеете...
  • chemelin (15.01.08 18:44) [27]
    Никак не могу убрать блокировку! ((((
    Пробую это, не помогает, точнее с значением "1", ошибка:

       Arg:=1;
     IOCtlSocket(sServerListen,FIONBIO,Arg);

  • Григорьев Антон © (15.01.08 19:22) [28]
    Так вы в отдельную нить выносите или неблокирующий сокет делаете?

    И какая же ошибка возникает?
  • chemelin (15.01.08 19:28) [29]
    Вот смотрите:

    //Создание Сервера
    procedure crestserver;
    var
    wData : WSADATA;
    sServerListen, sClient : TSOCKET;
    localaddr, clientaddr : sockaddr_in;
    iSize : Integer;
    s1 : TCPClientThread;
    begin
    // Загрузка WinSock
    if WSAStartup(MAKEWORD(1,1), wData) <> 0 then
    begin
      MessageBox(0, 'Не могу загрузить WinSock', 'Ошибка', 0);
      exit;
    end;

    // Создание сокета
    sServerListen := socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
    if sServerListen = INVALID_SOCKET then
     begin
      MessageBox(0, 'Ошибка создания сокета', 'Ошибка', 0);
      exit;
     end;

    // Запонение структуры адреса
    localaddr.sin_addr.s_addr := htonl(INADDR_ANY);
    localaddr.sin_family := AF_INET;
    localaddr.sin_port := htons(5050);

    //Связывание сокета с локальным адресом
    if bind(sServerListen, localaddr, sizeof(localaddr)) = SOCKET_ERROR then
     begin
      TestWinSockError('Bind');
      exit;
     end;

    //Прослушивание
    if TestFuncError(listen(sServerListen, 4), 'Listen') then
     exit;

    MessageBox(0, 'Сервер запущен', 'Внимание!!!', 0);

    while (true) do
     begin
      iSize := sizeof(clientaddr);

      //Приём нового соединения

      sClient := accept(sServerListen, @clientaddr, @iSize);

      if sClient = INVALID_SOCKET then
       begin
        TestWinSockError('accept');
        break;
       end;

      // Соединение принято, создаём поток
      s1:=TCPClientThread.Create(true);
      s1.Sock:=sClient;
      s1.Resume;
     end;
    closesocket(sServerListen);
    end;


    При таком создании сервера происходит повисание, я толком не понял что такое "нить". Куда и какой здесь нужно вставить строки кода что бы не происходило повисание?? Помогите, я просто с сокетами работал очень мало.
  • Григорьев Антон © (15.01.08 19:52) [30]
    Нить - это то, что вы называете потоком. Я предпочитаю слово "нить", потому что поток - это не только thread, но и stream, возникает путаница. А код надо вставлять в тело специально созданной для этого нити. Вы же TCPClientThread сделали, значит, и с этим разберётесь.
  • Сергей М. © (15.01.08 19:57) [31]

    > chemelin


    Ты действительно идиот или оным прикидываешься ?)
  • chemelin (15.01.08 20:36) [32]

    > Ты действительно идиот или оным прикидываешься ?)

    прикидываюсь
  • Сергей М. © (15.01.08 21:18) [33]
    А смысл ?
  • chemelin (15.01.08 21:26) [34]
    Спасибо Вам за поддержку, уже все сделал, все работает, мне было лень читать документацию.
 
Конференция "Сети" » Помогите с winsocket в WinAPI [D6, D7, WinXP]
Есть новые Нет новых   [134431   +15][b:0][p:0.003]