На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
! Соблюдайте общие правила форума
Пожалуйста, выделяйте текст программы тегом [сode=pas] ... [/сode]. Для этого используйте кнопку [code=pas] в форме ответа или комбобокс, если нужно вставить код на языке, отличном от Дельфи/Паскаля.
Указывайте точные версии Delphi и используемых сетевых библиотек.

Не приветствуется поднятие старых тем. Если ваш вопрос перекликается со старой темой, то для вопроса лучше создать новую тему, а старую указать в первом сообщении с описанием взаимосвязи.

Внимание:
попытки открытия обсуждений реализации вредоносного ПО, включая различные интерпретации спам-ботов, наказывается предупреждением на 30 дней.
Повторная попытка - 60 дней. Последующие попытки бан.
Мат в разделе - бан на три месяца...

Полезные ссылки:
user posted image MSDN Library user posted image FAQ раздела user posted image Поиск по разделу user posted image Как правильно задавать вопросы


Выразить свое отношение к модераторам раздела можно здесь: user posted image Krid, user posted image Rouse_

Модераторы: Krid, Rouse_
  
> WinSock, функция select, зависание сервера
    Доброго времени суток.
    Можно ли как-нибудь сделать так, чтобы сервер не зависал, не выделяя accept в отдельную нить?

    ExpandedWrap disabled
      unit CServer;
      { Класс сервера
      }
       
      interface
       
      uses Classes,SysUtils,WinSock,Dialogs,StdCtrls,CPredokClientServer,FGetErrorString;
       
      // Информация о поключившемся клиенте
      type
        PConnection=^TConnection;     // Информация о поключившемся клиенте
        TConnection=record
          ClientAddr:string;     // Строковое представление адреса клиента
          ClientSocket:TSocket;  // Сокет, созданный accept для взаимодействия с клиентом
          Deleted:Boolean;       // Если FALSE, то соединение с клиентом утеряно
      end;
       
      type
        TServer=class(TPredokClientServer)
        private
          FClientSocket:TSocket;        // Сокет клиента
          FClientSocketAddr:TSockAddr;  // Адрес клиента
          FConnections:TList;           // Список подключившихся клиентов
          FMemoLog:TMemo;
          // Привязка сокета к адресу
          procedure PrivyazkaKAdresu(MemoLog:TMemo);
          // Создание соединения с клиентом
          procedure SozdatSoedineniyeSKlientom;
          // Принять сообщение
          procedure PrinyatSoobchsheniye(var Connection: TConnection);
        public
          // Запуск сервера
          procedure StartServer(IpAddres:string;Port:word;TypeProtokol,TypeSocket:char;MemoLog:TMemo);
      end;
       
       
      implementation
       
      // ---------- Запуск сервера ---------------------------------------------------
      // - IpAddres - ip-адрес
      // - Port - номер порта
      // - TypeProtokol - тип сокера (UDP,TCP)
      // - TypeSocket - тип сокета (С - сервер, К - клиент)
      // - MemoLog - Лог
      procedure TServer.StartServer(IpAddres:string;Port:word;TypeProtokol,TypeSocket:char;MemoLog:TMemo);
      begin
      FMemoLog:=MemoLog;
      // Создание сокета
      CreateSocket(IpAddres,Port,TypeProtokol,TypeSocket);
      // Привязка сокета к адресу
      PrivyazkaKAdresu(MemoLog);
      // Создание соединения с клиентом
      SozdatSoedineniyeSKlientom;
      end;
       
      // ---------- Привязка сокета к адресу -----------------------------------------
      procedure TServer.PrivyazkaKAdresu;
      begin
        // Сокет, созданный с помощью функции socket не привязан ни к какому адрему.
        // Привязка осуществляется функцией bind.
        // function bind(s:TSocket; var addr:TSockAddr; namelen:Integer): Integer;
        // - s - дескриптор сокета, который привязывается к адресу
        // - addr - адрес, к которому требуется привязать сокет
        // - namelen - длина структуры, содержащей адрес
        if bind(FSocket,FSocketAddr,SizeOf(TSockAddr)) = SOCKET_ERROR
          then
          begin
            MessageDlg('Ошибка при привязке сокета к адресу ' + GetErrorString, mtError, [mbOK], 0);
            Exit;
          end
          else FMemoLog.Lines.Append('Сервер успешно запущен');
      end;
       
      // ---------- Создание соединения с клиентом -----------------------------------
      procedure TServer.SozdatSoedineniyeSKlientom;
      var
        TimeOut:TimeVal;     // Таймаут для функции select
        SetSockets:TFDSet;   // Множество сокетов для функции select
        ClientAddrLen:Integer;
        NewConnection:PConnection; // Вспомогательная переменная, использующаяся при создании нового соединения
        x:word;
      begin
        FConnections:=TList.Create;
        // Перевод сокета в режим ожидания соединения
        // function listen(s:TSocket; backlog:Integer): Integer;
        // - s - дескриптор сокета, который переводится в режим ожидания
        // - backlog - размер очереди подключений (для сокета, находящегося в режиме
        //             ожидания, создаётся очередь подключений). Максимально возможный
        //             размер очереди
        // Функция вовращает 0 в случае успешного завершения или SOCKET_ERROR в случае
        // ошибки.
        // Когда клиент вызывает функцию connect, и по указанному в ней адресу имеется
        // сокет, находящийся в режиме ожидания подключения, то информация о клиенте
        // помещается в очередь подключений этого сокета.
        if listen(FSocket, SOMAXCONN)=SOCKET_ERROR
          then MessageDlg('Невозможно установить сокет в режим прослушивания. '+GetErrorString,mtError,[mbOK],0);
        // Устанавливаем таймаут, равный 0, чтобы select ничего не ждала, а возвращала готовность
        // сокетов на момент вызова
        Timeout.tv_sec:=0;    // число секунд
        Timeout.tv_usec:=0;   // число микросекунд
        // Начало цикла для подключения и общения с клиентами
        repeat
          // Сначала проверяем, готов ли слушающий сокет. Если он готов, это означает,
          // что есть подключившийся, но не обработанный функцией accept клиент
          // procedure FD_ZERO(var FDSet:TFDSet); - инициализирует множество FDSet
          FD_ZERO(SetSockets);
          // procedure FDSet(Socket:TSocket; var FDSet:TFDSet); - добавляет сокет Socket в множество FDSet
          FD_SET(FSocket,SetSockets);
          // Проверка готовности сокета
          // function select(nfds:Integer; readfds,writefds,exceptfds:PFDSet; timeout:PTimeVal): LongInt;
          // - nfds - оставлен только для совместимости со старыми версиями библиотеки сокетов, в новых игнорируется
          // - readfds, writefds, exceptfds - указатели на множество сокетов, которые нужно проверять
          //   (не будут блокировать нить readfds-recv,recvfrom; writefds-send,sento;
          //    exceptfds - неудача попытки соединения, получения высокоуровневых данных)
          // - timeout - время, которое функция будет ожидать, пока хотя бы один из сокетов не будет готов к требуемой операции
          if select(0,@SetSockets,nil,nil,@Timeout)=SOCKET_ERROR
            then MessageDlg('Ошибка при проверке готовности слушающего сокета. ' + GetErrorString, mtError, [mbOK], 0);
          // Если функция select оставила сокет в множестве, значит, зафиксировано подключение клиента, и функция accept
          // не приведёт к блокированию нити.
          // procedure FD_ISSET(Socket:TSocket; FDSet:TFDSet) - определяет, входит ли Socket в множество FDSet
          if FD_ISSET(FSocket,SetSockets)
            then
            begin
              ClientAddrLen:=SizeOf(FClientSocketAddr);
              // Ожидаем подключение клиента
              // Извлечение из очереди соединений информации соединении и создание сокета для
              // его обслуживания
              // function accept(s:TSocket; addr:PSockAddr; addrlen: PInteger): TSocket;
              // - s - сокет, который находится в режиме ожидания соединения
              // - addr - адрес клиента, установившего соединение
              // - addrlen - длина буфера, в который будет помещён адрес клиента
              // В случае ошибки возвращается INVALID_SOCKET, при успешном завершении - дескриптор
              // сокета, созданного библиотекой сокетов и предназначенного для обслуживания
              // данного соединения. Этот сокет уже привязан к адресу и соединён с сокетом клиента,
              // установившего соединение.
              // Если на момент вызова функции accept очередь соединений пуста, то нить,
              // вызвавшая её блокируется до тех пор, пока какой-нибудь клиент не полключиться
              // к серверу.
              FClientSocket:=accept(FSocket,@FClientSocketAddr,@ClientAddrLen);
              if FClientSocket=INVALID_SOCKET then
              begin
                MessageDlg('Ошибка при ожидании подключения клиента: ' + GetErrorString, mtError, [mbOK], 0);
                break;
              end
              else
              begin
                // Создаём в динамической памяти новый экземпляр TConnection и заполняем его данными, соответствующими
                // подключившемуся клиенту
                New(NewConnection);
                NewConnection.ClientSocket:=FClientSocket;
                NewConnection.ClientAddr:=Format('%u.%u.%u.%u:%u', [
                                                  Ord(FClientSocketAddr.sin_addr.S_un_b.s_b1),
                                                  Ord(FClientSocketAddr.sin_addr.S_un_b.s_b2),
                                                  Ord(FClientSocketAddr.sin_addr.S_un_b.s_b3),
                                                  Ord(FClientSocketAddr.sin_addr.S_un_b.s_b4),
                                                  ntohs(FClientSocketAddr.sin_port)]);
                NewConnection.Deleted:=False;
                // Добавляем соединение в список
                FConnections.Add(NewConnection);
                FMemoLog.Lines.Append('Зафиксировано подключение с адреса '+NewConnection.ClientAddr);
              end;
            end;
          if FConnections.Count>0
            then
            begin
              // Теперь проверяем готовность всех сокетов подключившихся клиентов
              FD_ZERO(SetSockets);
              for x:=0 to FConnections.Count-1
                do FD_SET(PConnection(FConnections[x])^.ClientSocket,SetSockets);
              if select(0, @SetSockets, nil, nil, @Timeout) = SOCKET_ERROR
                then MessageDlg('Ошибка при проверке готовности слушающего сокета. ' + GetErrorString, mtError, [mbOK], 0);
              // Проверяем, какие сокеты select оставила в множестве и вызываем ...
              for x:=0 to FConnections.Count-1
                do if FD_ISSET(PConnection(FConnections[x])^.ClientSocket,SetSockets)
                  then PrinyatSoobchsheniye(PConnection(FConnections[x])^);  
              // Проверяем отключвшихся клиентов
              for x:=FConnections.Count-1 downto 0
                do if PConnection(FConnections[x])^.Deleted
                  then
                  begin
                    closesocket(PConnection(FConnections[x])^.ClientSocket);
                    Dispose(PConnection(FConnections[x]));
                    FConnections.Delete(x);
                  end;
            end;
          Sleep(100);
        until False;
        for x:=FConnections.Count-1 downto 0 do
        begin
          closesocket(PConnection(FConnections[x])^.ClientSocket);
          Dispose(PConnection(FConnections[x]));
        end;
        FConnections.Free;
        FMemoLog.Lines.Append('Сервер завершил работу');
      end;
       
      // ---------- Принять сообщение ------------------------------------------------
      procedure TServer.PrinyatSoobchsheniye(var Connection: TConnection);
      var
        Mes:string[255];   // Сообщение клиента
      begin
        // Если выполнение функции не дойдёт до конца, это значит, что связь
        // с клиентом по тем или иным причинам потеряна. В этом случае
        // поле Deleted останется равным False, и ресурсы, выделенные для
        // данного клиента, будут освобождены. Если же функция выполнится
        // до конца, полю Deleted будет вновь присвоено значение False,
        // т.е. ресурсы не будут удалены.
        Connection.Deleted:=True;
        // Принять сообщение
        // function recv(s:TSocket; var buf,len,flags:Integer):Integer;
        // - s - сокет, который служит для передачи данных
        // - buf - буфер, в котором хранятся данные для оправки
        // - len - размер этих данных в байтах
        // - flags - указывает некоторые дополнительные опциии, которые в большинстве
        //           случаев не нужны
        case recv(Connection.ClientSocket,Mes,SizeOf(Mes),0) of
          -1: // Ошибка при принятии
          begin
            MessageDlg('Произошла ошибка при чтении '+GetErrorString,mtError,[mbOK],0);
            exit;
          end;
          0:  // Клиент закрыл соединение
          begin
            FMemoLog.Lines.Append('Клиент '+Connection.ClientAddr+' закрыл соединение ');
            exit;
          end
          else FMemoLog.Lines.Append(Mes);
        end;
        // Если выполнение дошло до этого места, значит, связь с клиентом
        // не потеряна, и ресурсы освобождать не надо.
        Connection.Deleted := False;
      end;
       
      end.


    Добавлено
    Предок сервера
    ExpandedWrap disabled
      unit CPredokClientServer;
      { Класс, который содержит функции, являющиеся общими для клиента и сервера,
        а также некоторые другие для WinSock}
       
      interface
       
      uses
        SysUtils,Dialogs,WinSock,Windows,FGetErrorString;
       
      type
        TPredokClientServer=class
        public
          FSocket:TSocket;       // Сокет
          FSocketAddr:TSockAddr; // Адрес, к которому привязывается сокет
          // Описание структуры TSockAddr
          // - sin_family - семейство протоколов (для TCP/IP - PF_INET)
          // - sin_port - номер порта, к которому привязывается сокет
          // - sin_addr.S_addr - адрес для привязки сокета.
          // - sin_zero - должна быть заполнена нулями (не несёт никакой смысловой
          //              нагрузки, нужно только для увеличения размера структуры
          //              до 16 байтов
          // Создание сокета
          procedure CreateSocket(IpAddres:string;Port:word;TypeProtokol,TypeSocket:char);
      end;
       
      implementation
       
      // ---------- Создание сокета --------------------------------------------------
      // - IpAddres - ip-адрес
      // - Port - номер порта
      // - TypeProtokol - тип сокера (UDP,TCP)
      // - TypeSocket - тип сокета (С - сервер, К - клиент)
      procedure TPredokClientServer.CreateSocket(IpAddres:string;Port:word;TypeProtokol,TypeSocket:char);
      begin
        // Формируем адрес сервера, к которому надо подключиться
        // FillChar - стандартная процедура Паскаля, некоторую область памяти заданными
        // значениями. В данном случае 0
        FillChar(FSocketAddr.sin_zero,SizeOf(FSocketAddr.sin_zero),0);
        // Семейство протоколов
        FSocketAddr.sin_family:=AF_INET;
        // Ip-адрес
        // inet_addr - для преобразования адреса из строки в 32-х битное число
        case TypeSocket of
          'К': FSocketAddr.sin_addr.S_addr := inet_addr(PChar(IpAddres));
          'С': FSocketAddr.sin_addr.S_addr := inet_addr(PChar(IpAddres));
          //'С': FSocketAddr.sin_addr.S_addr:=htonl(INADDR_ANY);    
        end;
        // Для совместимости со старыми версиями Delphi приводим константу INADDR_NONE
        // к типу u_long
        if FSocketAddr.sin_addr.S_addr=u_long(INADDR_NONE)
        then
        begin
          MessageDlg('Синтаксическая ошибка в IP-адресе',mtError,[mbOK],0);
          Exit;
        end;
        // Преобразование в сетевой формат
        // Функция htons служит для преобразования номера порта из привычного нам в
        // сетевой. Если номер порта оставить нулевым, то система сама выберет для
        // сокета свободный порт с номером от 1024 до 5000
        FSocketAddr.sin_port:=htons(Port);
        // Создание сокета
        // function socket (AF,SocketType,Protocol:Integer):TSocket;
        // - AF - какой стек протоколов используется (для TCP/IP - AF_INET)
        // - SocketType - тир сокета (SOCK_STREAM - потоковый, SOCK_DGRAM - дейтаграммный)
        // - Protocol - тип протокола TCP - потоковый, UDP - дейтаграммный
        case TypeProtokol of
          'T':FSocket:=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
          'U':FSocket:=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
        end;
        if FSocket = INVALID_SOCKET then
        begin
          MessageDlg('Ошибка при создании сокета: ' + GetErrorString, mtError, [mbOK], 0);
          Exit;
        end;
      end;
      У select есть таймаут
        Есть, пробовал менять значения, не помогло :(
          Правильно ли я понимаю, что хочется "неблокирующих сокетов"?
          http://www.rsdn.ru/forum/network/1262011.flat.aspx
          1 пользователей читают эту тему (1 гостей и 0 скрытых пользователей)
          0 пользователей:


          Рейтинг@Mail.ru
          [ Script execution time: 0.2366 ]   [ 15 queries used ]   [ Generated: 4.07.26, 21:00 GMT ]