Форум программистов
 

Восстановите пароль или Зарегистрируйтесь на форуме, о проблемах и с заказом рекламы пишите сюда - alarforum@yandex.ru, проверяйте папку спам!

Вернуться   Форум программистов > Delphi программирование > Работа с сетью в Delphi
Регистрация

Восстановить пароль
Повторная активизация e-mail

Купить рекламу на форуме - 42 тыс руб за месяц

Ответ
 
Опции темы Поиск в этой теме
Старый 20.12.2012, 16:03   #1
ispovedn1k
 
Регистрация: 20.12.2012
Сообщений: 7
По умолчанию Серверное приложение с TCPServer. Правильно закрыть соединение.

Здравствуйте, товарищи!

Пишу серверное многопользовательское приложение, Delphi 7.
За основу взял пример отсюда http://softengines.ru/showthread.php?t=6863
На всякий случай продублирую код
Код:
…
type 
  TMyClientThread = class(TClientSocketThread)
    private
       FCmd: String;
       FData: String;
protected
    procedure SyncProc; override;
end; 
…
procedure  TMyClientThread. SyncProc;
begin
   if FCmd = ‘CLR’ then
     Form1.ListBox2.Clear
else
     if FCmd =’TXT’ then
       Form1.ListBox2.Items.Add(FData)
else 
    if FCmd = ‘BYE’ then begin
      Terminate;
    Form1.ListBox1.Items.Add(‘Отключился:’+ ClientSocket.RemoteHost);
  end;
  else
    if FCmd = ‘’ then
    Form1.ListBox1.Items.Add(‘Подключился:’+ ClientSocket.RemoteHost);
end;

procedure TForm1.RormCreate(Sender: TObject);
begin
      Caption:=’Сервер’;
      ListBox1.Clear;
      ListBox2.Clear;
      TcpServer1.LocalPort :=’5050’;
      TcpServer1.Active := ‘True’;
end;
procedure TForm1. TcpServer1GetThread(Sender: TObject;
       var  ClientSocketThread:  TClientSocketThread);
begin
   ClientSocketThread:= 
      TMyClientThread.Create(TcpServer1.ServerSocketThread);
end;
procedure TForm1. TcpServer1Accept(Sender: TObject;
      ClientSocket:TCustomIpClient);
begin
      with (ClientSocket.GetThreadObject as TMyClientThread) do begin
          FCmd:=’’;
          ExecuteSyncProc;
             while not Terminated do begin
                      FCmd:= ClientSocket.Receiveln;
                      if FCmd = ‘TXT’ then
                                  FData:= ClientSocket.Receiveln;
                     if FCmd <> ‘’ then
                                  ExecuteSyncProc;
      end;
   end;
end;
Итак, сам компонент TCPSocket ожидает подключения в отдельном потоке. При подключении клиента создается клиентский поток, в котором уже выполняется общение с клиентом.

Суть-вопросы.

1. Для закрытия соединения при получении команды 'BYE' используется вызов Terminate, который (как я понимаю) убивает клиентский поток. Правильно ли это? Не надо ли вызывать методы Disconnect, Close класса ClientSocket ? Проясните этот момент, кто знает.

2. Для меня сейчас более актуально разобраться в следующем:
если клиент не отправляет команду 'BYE', а просто вырубается, рвется соединение и т.п., то цикл while в TcpServer1Accept начинает бесконечно гонять по кругу. Каждый раз ClientSocket.Receiveln возвращает пустую строку. Т.е. получается, что сокет висит в каком-то неопределенном состоянии (пробовал определять эту ситуацию проверкой ClientSocket.Connected, но ничего не вышло, остается TRUE), пользовательский поток гоняет по кругу цикл, который уже ничего не сделает, хотя должен умереть.
Как правильно отследить и обработать эту ситуацию?

В общем, научите правильно закрывать соединения со стороны сервера во всех случаях. Заранее спасибо.

Последний раз редактировалось ispovedn1k; 20.12.2012 в 16:07.
ispovedn1k вне форума Ответить с цитированием
Старый 20.12.2012, 16:54   #2
xoodoo
Форумчанин
 
Регистрация: 11.04.2012
Сообщений: 212
По умолчанию

Цитата:
вызов Terminate, который (как я понимаю) убивает клиентский поток. Правильно ли это?
Неправильно.
Вызов Terminate всего лишь взводит флаг Terminated.


И цикл
Код:
while not Terminated and ClientSocket.Connected do
следует организовывать в перекрытом TMyClientThread.Execute

Разрыв соединения о инициативе удаленного партнера, уходящего по-английски не попрощавшись, детектируется в обработчиках OnDisconnect и OnError.
Вызов ClientSocket.Disconnect при обнаружении таких событий обязателен, иначе ClientSocket останется в статусе Connected.
xoodoo вне форума Ответить с цитированием
Старый 21.12.2012, 19:03   #3
ispovedn1k
 
Регистрация: 20.12.2012
Сообщений: 7
По умолчанию

Пока не смог разобраться, как правильно писать код перекрытой процедуры Execute.
Мое вмешательство в нее приводит к тому, что потоки начинают плодиться как кролики. А если внутри
Execute я еще и цикл объявлю, то они становятся бессмертными кроликами!

Однако, читая документацию, я нашел в описании метода SyncProc класса TClientSocketThread пример.
Приведу его тут для наглядности

Код:
TMyThread = Class(TClientSocketThread)
protected
  procedure SyncProc; override;
public
  Str: TStrings;
  constructor Create(Sst: TServerSocketThread);
  destructor Destroy; override;
end;

{The override of SyncProc is declared as}

procedure TMyThread.SyncProc;
begin
  Form1.Memo1.Lines.AddStrings(Str);
end;

{Now, the form (which we'll call Form1) has a TMemo object called Memo1, and let's say we want to add
lines of text to Memo1 that are to be received from a connection handled by a specified TCustomIpClient
object. A method for performing that task is shown below.}

procedure TForm1.TcpServer1Accept(Sender: TObject;
  ClientSocket: TCustomIpClient);
var
  s: string;
  MyThread: TMyThread;
begin
  MyThread := ClientSocket.GetThreadObject as TMyThread;
  with MyThread do
  begin
    Str.Clear;
    Str.Add('*** Connection Accepted ***');
    Str.Add('Remote Host: ' + ClientSocket.LookupHostName(ClientSocket.RemoteHost) +
      ' (' + ClientSocket.RemoteHost + ')');

    Str.Add('Begin message ===================');
    s := ClientSocket.Receiveln;
    while s <> '' do
    begin
      Str.Add(s);
      s := ClientSocket.Receiveln;
    end;
    Str.Add('End of message ==================');
    ExecuteSyncProc;
  end;
end;
Из чего я делаю вывод, что устраивать цикл, внутри TcpServer1Accept - обычное дело.
Однако, если кто-либо покажет, как правильно перегружать Execute, буду признателен.

Пойду разбираться с событиями onDisconnect и onError.

З.Ы. пишу тут это все для того, чтобы потом кто-то смог быстро найти ответ и не шагал по моим граблям.
Поэтому за помощь и правильные советы буду очень признателен я и не только...
ispovedn1k вне форума Ответить с цитированием
Старый 21.12.2012, 19:48   #4
xoodoo
Форумчанин
 
Регистрация: 11.04.2012
Сообщений: 212
По умолчанию

Если ты не планируешь выходить за рамки D7 и/или не боишься трудностей с адаптацией D7-кода к последующим версиям среды, то откажись пока не поздно от TTCPServer в пользу TServerSocket
xoodoo вне форума Ответить с цитированием
Старый 21.12.2012, 20:20   #5
ispovedn1k
 
Регистрация: 20.12.2012
Сообщений: 7
По умолчанию

Наверное стоящая мысль. Я только разбираюсь с инструментами. С Делфи последний раз имел дело лет 10 назад еще в школе. Сейчас надо написать серверное приложение, способное обрабатывать много клиентских подключений, выполняющую при этом роль шлюза к БД.
С элементами такого проекта я раньше баловался на языке С. Обдумываю вариант, чтобы сделать просто консольное приложение на чистом Api без лишних библиотек и сторонних компонентов. Кажется мне, что этим можно (при грамотном подходе) повысить производительность. Можно даже сделать, при должном старании, версии и под Linux и под Windows.
С другой стороны, есть готовые компоненты, с реализованной многопоточностью. Клиентское приложение, которое мне еще предстоит дорабатывать, тоже написано на D7. Но вот беда, по сути среду приходится изучать заново.

Какой подход посоветуете?

З.Ы. сейчас будет пост про OnErrpr, OnDisconnect, OnDestroyHandle
ispovedn1k вне форума Ответить с цитированием
Старый 21.12.2012, 20:33   #6
ispovedn1k
 
Регистрация: 20.12.2012
Сообщений: 7
По умолчанию

Итак, я поиграл немного с событиями и обработчиками. Как это было:
Код:
type
    TMyClientThread = class(TClientSocketThread)
    private
        FCmd: string;
        FData: string;
    protected
        procedure SyncProc; override;
    public
{Тут я добавил обработчики для наиболее интересных для себя событий}

{Для обработки события OnError. Возникает при фейле создания, исползования или закрытия сокета (так написано в документации, ну так я понял то, что там написано)}
        procedure ClientSocketError(Sender: TObject;
			ErrorCode: Integer);
{Для обработки OnDisconnect. Возникает сразу после закрытия подключения к серверу. Источник сведений тот же.}
        procedure ClientSocketDisconnect(Sender: TObject);
{Для обработки OnDestroyHandle. Возникает, когда сокет деактивируется. Что бы это значило?}
        procedure ClientSocketDestroyHandle(Sender: TObject);
    end;
Далее собственно обработчики:
Код:
procedure TMyClientThread.ClientSocketDisconnect(Sender: TObject);
begin
	MessageBox(0, 'ClientSocketDisconnect', 'Message', MB_OK);
end;


procedure TMyClientThread.ClientSocketDestroyHandle(Sender: TObject);
begin
	MessageBox(0, 'ClientSocketDestroyHandle', 'Message', MB_OK);
end;


procedure TMyClientThread.ClientSocketError(Sender: TObject;
    		ErrorCode: Integer);
begin
    MessageBox(0, PChar('Error: '+IntToStr(ErrorCode)), 'Error', MB_OK);
end;
Код для синхронизации не изменился. Лишь чуть изменился обработчик принятия соединения.
Код:
procedure TMyClientThread.SyncProc;
begin
	if FCmd = 'CLR' then
		Form1.Memo1.Lines.Clear
	else if FCmd = 'TXT' then
		Form1.Memo1.Lines.Add(FData)
	else if FCmd = 'BYE' then begin
		Terminate;
        Form1.Memo1.Lines.Add('Disconnected: '+ClientSocket.RemoteHost);
	end
	else if FCmd = '' then
    	Form1.Memo1.Lines.Add('Connected: '+ClientSocket.RemoteHost)
    else
		Form1.Memo1.Lines.Add('Unknown command: ' + FCmd);
end;


procedure TForm1.TcpServer1Accept(Sender: TObject;
  ClientSocket: TCustomIpClient);

begin
     with (ClientSocket.GetThreadObject as TMyClientThread) do begin
{Вот они все изменения. Указываем, чем обрабатывать события.}
	ClientSocket.OnError := ClientSocketError;
        ClientSocket.OnDisconnect := ClientSocketDisconnect;
        ClientSocket.OnDestroyHandle := ClientSocketDestroyHandle;
          FCmd := '';
          ExecuteSyncProc;

          while not Terminated do
          begin
              FCmd := ClientSocket.Receiveln;
              if FCmd = 'TXT' then
                 FData := ClientSocket.Receiveln;
              if FCmd <> '' then
                 ExecuteSyncProc;
          end;
     end;
end;
А теперь результаты.

При нормальном отключении (т.е. клиет посылает строчку BYE) происходит OnDisconnect, OnDestroyHandle).
Если клиент уходит "по-английски", то не происходит ничего! Клиентский поток зависает в бесконечном цикле, подминает под себя ресурсы процессора и... все!
Единственно что, OnError срабатывает при закрытии серверного приложения, если было некорректное отключение. на долю секунды можно увидеть мелькнувшее
сообщение об ошибке.

Последний раз редактировалось ispovedn1k; 21.12.2012 в 20:38.
ispovedn1k вне форума Ответить с цитированием
Старый 21.12.2012, 20:38   #7
xoodoo
Форумчанин
 
Регистрация: 11.04.2012
Сообщений: 212
По умолчанию

Цитата:
сейчас будет пост про OnErrpr, OnDisconnect, OnDestroyHandle
Не будет.
В случае с TServerSocket в режиме stThreadBlocking о событийном механизме обработки клиентского соединения можно вообще забыть.
xoodoo вне форума Ответить с цитированием
Старый 21.12.2012, 20:42   #8
ispovedn1k
 
Регистрация: 20.12.2012
Сообщений: 7
По умолчанию

Ну вот, а я так старался. Ладно. Пойду смотреть, что предлагает TServerSocket
ispovedn1k вне форума Ответить с цитированием
Старый 21.12.2012, 21:02   #9
Slym
Участник клуба
 
Регистрация: 07.12.2011
Сообщений: 1,025
По умолчанию

TServerSocket не предлагает Receiveln - это небольшой недостаток
Не стесняемся, плюсуем!
Slym вне форума Ответить с цитированием
Старый 21.12.2012, 21:04   #10
ispovedn1k
 
Регистрация: 20.12.2012
Сообщений: 7
По умолчанию

Хочу спросить, пока еще можно.

После обрыва соединения в потоке бесконечно гоняется цикл. Receiveln всегда возвращает пустую строку. А что если, скажем, после 5 подряд пустых строк вызывать Terminate?

Попробовал, в этом случае закрывается соединение на сервере. Вроде бы разумно.
ispovedn1k вне форума Ответить с цитированием
Ответ


Купить рекламу на форуме - 42 тыс руб за месяц



Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
Клиент-Серверное приложение...? spamer Общие вопросы по программированию, компьютерный форум 2 15.03.2012 12:42
Клиент Серверное приложение BARNEY Общие вопросы Delphi 2 28.10.2010 09:30
Серверное приложение Pliks Общие вопросы по Java, Java SE, Kotlin 2 20.11.2009 18:52
серверное приложение blackbanny Фриланс 5 13.10.2009 02:11
Клиет - серверное приложение Mitron Работа с сетью в Delphi 6 20.08.2007 19:57