Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Delphi: Сети > TSocketServer\TSocketClient и интернет


Автор: OXOTHUK 7.6.2007, 20:29
Как подсоединить сервера и клиента через интернет(У сервера фиксированный IP). У мня чтобы я ни писал в поле Adress, какой бы IP не вводил, всё равно не хочет коннектится.

ЗЫ если есть более удобные/легко осваиваемые способы прередачи record'а через интернет, то скажите, желательно с примером.

Автор: Snowy 7.6.2007, 20:36
Отладь сначала на одной машине.
Если там будет работать, значит проблемы на другом уровне.

Автор: OXOTHUK 7.6.2007, 20:58
на одной машине работает, даже если вводить бурду в адрес.

А какие могут быть проблемы?

Автор: Snowy 7.6.2007, 21:20
Цитата(OXOTHUK @  7.6.2007,  20:58 Найти цитируемый пост)
на одной машине работает, даже если вводить бурду в адрес.
Значит ты что-то напутал с адресом.
Не может запрос пойти на "бурду".

Добавлено через 24 секунды
Цитата(OXOTHUK @  7.6.2007,  20:58 Найти цитируемый пост)
А какие могут быть проблемы?
Обычные. Файрволы, закрытые порты...

Автор: OXOTHUK 7.6.2007, 21:28
Цитата(Snowy @  7.6.2007,  21:20 Найти цитируемый пост)
Значит ты что-то напутал с адресом.Не может запрос пойти на "бурду".

Но работает. Запускаю сервер, клиенту пишу адрес "asdfga", нажимаю актив и сенд, и у сервера появляется. (на одной машине, подозреваю, что если он не может найти адрес, то ищет на 127.0.0.1).
А вот через интернет не работает, файрволы пускают прогу. А может это быть если порт занят другой прогой?

ЗЫ а как надо называть Host-свойство у клиента? а то у мня не всегда подключается, иногда говорит, что еррор сокет 10060.(хотя я не этим сокетом пользуюсь) Но если изменить имя хоста, то всё нормально.

Автор: Snowy 7.6.2007, 22:58
Цитата(OXOTHUK @  7.6.2007,  21:28 Найти цитируемый пост)
подозреваю, что если он не может найти адрес, то ищет на 127.0.0.1
Неправильно подозреваешь.
Если ты прописал asdfga, а законнетилось на локальный, значит нифига ты не прописал.
Значит где-то ошибка, и твоё asdfga не играет никакой роли.

Цитата(OXOTHUK @  7.6.2007,  21:28 Найти цитируемый пост)
хотя я не этим сокетом пользуюсь
Это не сокет, а код ошибки.
Вбей его в гугль - сразу найдёшь, что она значит.

Автор: OXOTHUK 7.6.2007, 23:30
Цитата(Snowy @  7.6.2007,  22:58 Найти цитируемый пост)
Если ты прописал asdfga, а законнетилось на локальный, значит нифига ты не прописал.Значит где-то ошибка, и твоё asdfga не играет никакой роли.

Код

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Buttons, ScktComp, ExtCtrls;

type
  ITCMessage=record
    str: string[50];
    t: integer;
  end;

  TForm1 = class(TForm)
    cs1: TClientSocket;
    BitBtn1: TBitBtn;
    Button1: TButton;
    le1: TLabeledEdit;
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure FormDestroy(Sender: TObject);
    procedure BitBtn1Click(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
cs1.Active:=false;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
cs1.Active:=false;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
cs1.Address:=le1.Text;
cs1.Active:=true;
end;

procedure TForm1.BitBtn1Click(Sender: TObject);
var
  msg: ITCMessage;
  ms: TMemoryStream;
begin
  msg.str:='string';
  msg.t:=0;
  cs1.Socket.SendBuf(msg, sizeof(msg));
end;

end.

Ну что тут может быть не так?

Автор: misha_mike 8.6.2007, 16:06
Во-первых я что-то не вижу чтобы ты порт указал. А во-вторых используй свойство Host.

А вот это излишество:
Код

procedure TForm1.FormClose(Sender: TObject);
begin
cs1.Active:=false;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
cs1.Active:=false;
end;

Достаточно только FormDestroy.

И тут поосторожней:
Код

var
  msg: ITCMessage;
  ms: TMemoryStream;
begin
  msg.str:='string';
  msg.t:=0;
  cs1.Socket.SendBuf(msg, sizeof(msg));
end

Никто не гарантирует что на приемной стороне твоя структура прийдет целиком а не парой-тройкой кусочков последовательно (и чем больше твоя структура и чем медленнее канал тем больше вероятность ее разрыва, а начиная с нескольких килобайт размера разрываться она будет гарантированно). Также никто не гарантирует что два (или больше) последовательных SendBuf(...) небольшого размера приведут к генерации соответствующего количества событий OnRead на приемной стороне, а не слипнуться в один кусок.

Автор: OXOTHUK 8.6.2007, 16:07
misha_mike, 
порт указан в изначальных настройках, которые на обжект инспекторе.

АПД:
Сам допёр, спосебо всем.

Автор: OXOTHUK 10.6.2007, 09:53
Нозникла новая проблема: как передать такую струкруру:
Код

TEMessage = record
  names: array [1..30] of string[15];
  health: array [1..30] of integer;
  stats: array [1..30] of string[20];
  atack: array [1..30] of array [1..10] of string [15];
  namer: array [1..30] of integer;
  count: integer;
end;

Через SendBuf естесна не передаётся, но я попробывал сохранить в файл, а потом SendStream. Но получать приходится через RecieveBuf. И опять не доходит или что ещё. Есть какие-нибудь способы?

ЗЫ на одной машине всё нормально передаётся

Автор: misha_mike 10.6.2007, 16:21
Как не передается? Какую ошибку возвращает? Вообще для передачи больших блоков (более 2 килобайт) надо их нарезать на кусочки по одному-два килобайта, и передвать по кусочку пока не возникнет ошибка WSAEWOULDBLOCK. Потом ждать события OnWrite и повторять с места остановки до следующей ошибки. И так далее до конца буфера. Приходить она тоже будет кусками, причем совсем не обязательно такими, которыми ты ее нарезал при передаче, поэтому о сборе в единое целое тоже нужно позаботиться на приемной стороне.

P.S. А связь с localhost всегда более дубовая чем с удаленной машиной, много подобных ньюансов не вылазят потому что при связи с самим собой стек TCP/IP может работать далеко не на всю свою глубину....

P.P.S. А вообще такие вещи по сети лучше не гонять, и вместо крайне избыточной структуры с COUNT-ом перегони сначала COUNT, а потом соотвествующее количество неизбыточных структур такого вида (и обрати внимание на слово packed):
Код

TSMessage = packed record
  names: string[15];
  health: integer;
  stats: string[20];
  atack: array [1..10] of string [15];
  namer: integer;
end;

А не то тебя юзера проклянут.

Автор: OXOTHUK 10.6.2007, 18:44
Цитата(misha_mike @  10.6.2007,  16:21 Найти цитируемый пост)
Как не передается? Какую ошибку возвращает?

никакую, там просто не возникает события OnRead.
Цитата(misha_mike @  10.6.2007,  16:21 Найти цитируемый пост)
и обрати внимание на слово packed

а чем отличается packed record от record?  :lamo:

Но вроде работает.

АПД только не всегда доходит.
вот серверная:
Код

...
Socket.SendText(inttostr(count_msg));
for i:=1 to count_msg do
  Socket.SendBuf(msg[i], sizeof(TSMessage));
...

вот клиентская:
Код

procedure TForm1.ClientRead(Sender: TObject; Socket: TCustomWinSocket);
var
  i, count: integer;
  msg: array [1..30] of TSMessage;
begin
  count:=strtoint(Socket.ReceiveText);
  for i:=1 to count do
    begin
      Socket.ReceiveBuf(msg[i], sizeof(TSMessage));
    end;
...
end;

или можно как-то лучше реализовать? :lamo: я с сетями никогда не работал

Автор: misha_mike 10.6.2007, 20:14
Цитата
Цитата(misha_mike @  10.6.2007,  16:21 Найти цитируемый пост)
Как не передается? Какую ошибку возвращает?

никакую, там просто не возникает события OnRead.

И OnError не возникает? Не может этого быть...

Цитата
Цитата(misha_mike @  10.6.2007,  16:21 Найти цитируемый пост)
и обрати внимание на слово packed

а чем отличается packed record от record?  :lamo:

Тем что выравнивания нет и структура имеет именно такой размер, который имеют в сумме ее члены. В прочем об этом написано в любом учебнике и к сетям отношения не имеет.

Цитата
АПД только не всегда доходит.

Что такое АПД?

Цитата
вот серверная:
Код

...
Socket.SendText(inttostr(count_msg));
for i:=1 to count_msg do
  Socket.SendBuf(msg[i], sizeof(TSMessage));
...

Не передавай без крайней необходимости числа в виде текста, это кроме того что избыточно, в случае вещественных чисел обязательно приведет к проблемам. Пиши Socket.Send(count_msg, SizeOf(count_msg)), а на приемной стороне соответственно Socket.ReceiveBuf(count_msg, SizeOf(count_msg)), но ты про это пока забудь и читай дальше.

Цитата
вот клиентская:
Код

procedure TForm1.ClientRead(Sender: TObject; Socket: TCustomWinSocket);
var
  i, count: integer;
  msg: array [1..30] of TSMessage;
begin
  count:=strtoint(Socket.ReceiveText);
  for i:=1 to count do
    begin
      Socket.ReceiveBuf(msg[i], sizeof(TSMessage));
    end;
...
end;


Я же предупреждал, что так делать нельзя никогда. Никто не гарантирует что все что ты передал с одной стороны прийдет на другую именно так как ты ожидаешь. Во-первых рано или поздно случится так что в твой ClientRead прийдет count и три с половиной msg, а оставшиеся пять с половиной -- в следующем ClientRead, который совершенно не готов к такому повороту (не разобравшись в том что пришло, сразу читает Count и что-то там принимает). Во вторых очень бысто случится так, что в одном ClientRead прийдет то что ты передал в два или три захода! В первом случае ты получишь ошибку, а во втором просто проворонишь один или несколько пакетов.

Еще раз поясняю, прочитай очень внимательно. Со стопроцентной вероятностью ты столкнешся с такими ситуациями:

1. Разрезание.
ПЕРЕДАТЧИК: 
Вызываешь Socket.SendBuf(Data, 6000);
ПРИЕМНИК: 
Возникает событие ClientRead, в котором Socket.ReceiveLength = 4096, и через некоторое время опять возникает событие ClientRead, в котором Socket.ReceiveLength = 1904.
РЕЗЮМЕ:
То что ты передал одним куском, пришло несколькими.

2. Склеивание.
ПЕРЕДАТЧИК:
Вызываешь Socket.SendText('текст'), и через некоторое время Socket.SendBuf(Data, 8).
ПРИЕМНИК:
Возникает одно единственное событие ClientRead, в котором Socket.ReceiveLength = 13 (сумма длин текста и данных).
РЕЗЮМЕ:
То что ты передал несколькими Send-ами пришло одним куском.

3. Смешивание.
ПЕРЕДАТЧИК:
Вызываешь Socket.SendText('текст'), и через некоторое время Socket.SendBuf(Data, 6000).
ПРИЕМНИК:
Возникает событие ClientRead, в котором Socket.ReceiveLength = 4096 и в этот размер входит переданный первым Send-ом 'текст' и первые 4091 байт из буфера Data, переданного вторым Send-ом, и через некоторое время опять возникает событие ClientRead с оставшимися 1909 байтами из буфера Data, переданного вторым Send-ом.
РЕЗЮМЕ:
То, как ты передаешь данные никак не связанно с тем, как они прийдут. Сохраняется только последовательность, но никак не блочность.

4. Переполнение буфера передачи WinSock.
ПЕРЕДАТЧИК:
Вызываешь Socket.SendBuf(Data, 100000), тут же возникает OnError, в котором ErrorEvent = eeSend.
ПРИЕМНИК:
Ничего не приходит.
РЕЗЮМЕ:
Передавать в один присет более нескольких килобайт нельзя, нужно разрезать большой пакет на тесколько маленьких (не более пары килобайт) и передвать их до тех пор пока не возникнет OnError (при этом ErrorEvent должен быть равен eeSend, иначе прерываем передачу и генерируем ошибку разрыва канала связи). После этого нужно остановиться, дождаться события OnWrite и только тогда продолжить до следующего OnError. И так пока не закончатся данные для передачи.

Цитата
или можно как-то лучше реализовать? :lamo: я с сетями никогда не работал

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

Второй вариант -- воспользоваться оним из существующих протоколов верхнего уровня, например IRC. Благо соответствующих компонент достаточно.

Автор: OXOTHUK 11.6.2007, 12:41
Цитата(misha_mike @  10.6.2007,  20:14 Найти цитируемый пост)
И OnError не возникает? Не может этого быть...

сам вызов функции не проверял, но видимых ошибок нет никаких.

Цитата(misha_mike @  10.6.2007,  20:14 Найти цитируемый пост)
Что такое АПД?

UPD - updated, т.е. исправил пост и дописал.

Цитата(misha_mike @  10.6.2007,  20:14 Найти цитируемый пост)
Первый вариант -- реализовываешь свой протокол верхнего уровня...

тут мня возникает несколько вопросов: 1) чем принимать, т.е. каким типом? array of byte? 2) как склеивать\разрезать получившиеся части?

Цитата(misha_mike @  10.6.2007,  20:14 Найти цитируемый пост)
Второй вариант -- воспользоваться оним из существующих протоколов верхнего уровня, например IRC. Благо соответствующих компонент достаточно.

TInIRCServer/TInIRC подойдёт с вкладки Indy? Или есть что-то лучше? А есть какой-нибудь мануал как или пользоваться, а то они не похожи на сокеты. Желательно с примерами.
ЗЫ. Дельфийская справка у меня не работает.

Автор: misha_mike 11.6.2007, 13:12
Цитата
Цитата(misha_mike @  10.6.2007,  20:14 Найти цитируемый пост)
Первый вариант -- реализовываешь свой протокол верхнего уровня...

тут мня возникает несколько вопросов: 1) чем принимать, т.е. каким типом? array of byte? 2) как склеивать\разрезать получившиеся части?

Блин, это же основы работы с "сырыми" данными. Без этого ничего сложнее калькулятора не написать, а ты сразу игрушки с мультиплеером ваять собрался... Принимать надо указателем на динамический блок, передавать им же. Динамические массивы конечно тоже сгодятся, но ты не на джаве и не в дотнете пишешь чтобы на них выезжать в любой ситуации. Как производится работа с динамической памятью написано в ЛЮБОМ учебнике по Паскалю. Указательную арифметику для нарезки/склейки пакетов имеет смысл показывать только поле того как подобные отквоченному вопросы возникать перестанут в принципе. А спрашивать об тривиальных операциях по извлечению подмассива и склеиванию нескольких динамических массивов вовсе должно быть стыдно!

Цитата
Цитата(misha_mike @  10.6.2007,  20:14 Найти цитируемый пост)
Второй вариант -- воспользоваться оним из существующих протоколов верхнего уровня, например IRC. Благо соответствующих компонент достаточно.

TInIRCServer/TInIRC подойдёт с вкладки Indy? Или есть что-то лучше? А есть какой-нибудь мануал как или пользоваться, а то они не похожи на сокеты. Желательно с примерами.

Там примитив полнейший, да и не единственные это компоненты (и не единственный подходящий протокол).

Цитата
ЗЫ. Дельфийская справка у меня не работает.

Это не оправдание, либо чини либо вызывай ее руками. А основы языка (изложенные в любой книжке) рассказывать думаю тут желающих не много наберется.

Автор: OXOTHUK 11.6.2007, 14:03
Цитата(misha_mike @  11.6.2007,  13:12 Найти цитируемый пост)
Блин, это же основы работы с "сырыми" данными. Без этого ничего сложнее калькулятора не написать, а ты сразу игрушки с мультиплеером ваять собрался... Принимать надо указателем на динамический блок, передавать им же. Динамические массивы конечно тоже сгодятся, но ты не на джаве и не в дотнете пишешь чтобы на них выезжать в любой ситуации. Как производится работа с динамической памятью написано в ЛЮБОМ учебнике по Паскалю. Указательную арифметику для нарезки/склейки пакетов имеет смысл показывать только поле того как подобные отквоченному вопросы возникать перестанут в принципе. А спрашивать об тривиальных операциях по извлечению подмассива и склеиванию нескольких динамических массивов вовсе должно быть стыдно!

На с++ я себе это более менее представляю, но в дельфи у меня с этим труднее. Да и не игрушку я пишу, это для информационной панели, просто нужно чтобы данные отображались у всех.

Цитата(misha_mike @  11.6.2007,  13:12 Найти цитируемый пост)
Это не оправдание, либо чини либо вызывай ее руками. А основы языка (изложенные в любой книжке) рассказывать думаю тут желающих не много наберется.

Это невозможно, у мня Виста, а она категорически не поддерживает старые справки.

Цитата(misha_mike @  10.6.2007,  20:14 Найти цитируемый пост)
 помещается некоторая структура-заголовок, включающая размер передаваемого блока

Какую лучше выбрать структуру? если она будет большая, как я понял, не факт что она вся придёт, а если 1-2 символа\числа, то может встретится и не структура.

Цитата(misha_mike @  11.6.2007,  13:12 Найти цитируемый пост)
Там примитив полнейший, да и не единственные это компоненты (и не единственный подходящий протокол).

Какие ещё, к примеру, есть протоколы высокого уровня? чтобы был выбор.

Автор: misha_mike 11.6.2007, 14:25
Цитата
На с++ я себе это более менее представляю, но в дельфи у меня с этим труднее.

Тут точно так же, только кастовать указатель надо так:
Код

b:= PByte(Integer(Data)+i)^;

Где 
Код
b: Byte; Data: Pointer; i: Integer; PByte = ^Byte;
 (указатели на все основные типы уже описаны в стандартных библиотеках и начинаются они с буквы P).

Цитата
Это невозможно, у мня Виста, а она категорически не поддерживает старые справки.

Кто ж заставлял? Да и в новых версиях Delphi идет новая справка, попробуй извлечь от-туда.

Цитата
Цитата(misha_mike @  10.6.2007,  20:14 Найти цитируемый пост)
 помещается некоторая структура-заголовок, включающая размер передаваемого блока

Какую лучше выбрать структуру? если она будет большая, как я понял, не факт что она вся придёт, а если 1-2 символа\числа, то может встретится и не структура.

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

Цитата
Какие ещё, к примеру, есть протоколы высокого уровня? чтобы был выбор.

Ну хоть HTTP, через прокси можно гонять smile Хотя IRC практически идеален для этих целей, и очень многие производители игр с мультиплеером используют именно его.

Автор: OXOTHUK 12.6.2007, 15:25
я вроде почти сделал, всё работало, но когда я добавил поддержку обратной связи через структуру TCMessage, но почему-то перестала работать, передаются данные только при первом OnКоннект. Потом ни байта, проверял через файрвол. Ни ошибки, ничего. Просто не возникает OnRead ни где. А вот если взять более старую версию, где обраная связь по средствам передачи одной сторки маленькой, то там всё ок.
Вот прога:
Серверная:
Код

unit Main;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ScktComp, StdCtrls, XPMan, ExtCtrls;

type
  TSMessage = packed record
    names: string [15];
    health: integer;
    stats: string [20];
    atack: array [1..10] of string [15];
    namer: integer;
  end;

  TCMessage = packed record
    login, psw: string [15];
    Atack: integer;
  end;

  TForm1 = class(TForm)
    Server: TServerSocket;
    XPManifest1: TXPManifest;
    lbEne: TListBox;
    Button1: TButton;
    lePort: TLabeledEdit;
    Button2: TButton;
    procedure ServerClientConnect(Sender: TObject;
      Socket: TCustomWinSocket);
    procedure ServerClientDisconnect(Sender: TObject;
      Socket: TCustomWinSocket);
    procedure FormDestroy(Sender: TObject);
    procedure FormActivate(Sender: TObject);
    procedure ServerClientRead(Sender: TObject; Socket: TCustomWinSocket);
    procedure FormResize(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure ServerClientError(Sender: TObject; Socket: TCustomWinSocket;
      ErrorEvent: TErrorEvent; var ErrorCode: Integer);
  private
    { Private declarations }
  public
    count, count_msg, count_psw: integer;
    msg: array [1..30] of TSMessage;
    pasw: array [0..99] of string[15];
    logins: array [0..99] of string[15];
    users: array [0..99] of TCMessage;
    data, p: array [0..99] of pointer;
    data_len, len: array [0..99] of integer;
    procedure UpdateList;
    procedure Analyse(const conn: integer);
    procedure AddToData(var Dest, Src: pointer; const count, conn: integer);
    procedure DeleteBytes(var P: pointer; var size: integer; const count: integer);
    procedure FinallyseData(const conn: integer);
  end;

const
  CNames = 1;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.ServerClientConnect(Sender: TObject;
  Socket: TCustomWinSocket);
var
  i, com: integer;
begin
  inc(count);
  com:=CNames;
  Socket.SendBuf(com, sizeof(com));
  Socket.SendBuf(count_msg, sizeof(count_msg));
  for i:=1 to count_msg do
    begin
      Socket.SendText('HEADER');
      Socket.SendBuf(msg[i], sizeof(TSMessage));
    end;
end;

procedure TForm1.ServerClientDisconnect(Sender: TObject;
  Socket: TCustomWinSocket);
begin
  inc(count, -1);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  Server.Active:=false;
end;

procedure TForm1.FormActivate(Sender: TObject);
begin
  UpdateList;
end;

procedure TForm1.UpdateList;
var
  i, j, com: integer;
  f: textfile;
  s: string;
begin
  lbEne.Items.Clear;
  assignfile(f, 'DATA.txt');
  reset(f);
  readln(f, count_msg);
  for i:=1 to count_msg do
    begin
      readln(f, msg[i].names);
      readln(f, msg[i].health);
      readln(f, msg[i].stats);
      lbEne.Items.Add(msg[i].names+'   '+inttostr(msg[i].health)+' ÕÏ   '+msg[i].stats);
      for j:=1 to 10 do msg[i].atack[j]:='';
      msg[i].namer:=0;
    end;
  closefile(f);
  assignfile(f, 'passwords.dat');
  reset(f);
  readln(f, count_psw);
  for i:=0 to count_psw-1 do
    begin
      readln(f, s);
      logins[i]:=Copy(s, 1, Pos(#9, s));
      pasw[i]:=Copy(s, Pos(#9, s)+1, Length(s)-Pos(#9, s));
    end;
  closefile(f);
  for i:=0 to count-1 do
    begin
      if (users[i].login=logins[i]) and (users[i].psw=pasw[i]) then
        begin
          com:=CNames;
          Server.Socket.Connections[i].SendBuf(com, sizeof(com));
          Server.Socket.Connections[i].SendBuf(count_msg, sizeof(count_msg));
          for j:=1 to count_msg do
            begin
              Server.Socket.Connections[i].SendText('HEADER');
              Server.Socket.Connections[i].SendBuf(msg[j], sizeof(TSMessage));
            end;
        end;
    end;
end;

procedure TForm1.ServerClientRead(Sender: TObject;
  Socket: TCustomWinSocket);
var
  j, i: integer;
begin
  i:=-1;
  for j:=0 to count-1 do
    if (socket.RemoteHost=server.Socket.Connections[j].RemoteHost) and
      (socket.RemoteAddress=server.Socket.Connections[j].RemoteAddress) then i:=j;
  if (i=-1) then exit;
  len[i]:=socket.ReceiveLength;
  GetMem(p[i], len[i]);
  Socket.ReceiveBuf(p[i]^, len[i]);
  Analyse(i);
end;

procedure TForm1.Analyse(const conn: integer);
begin
  AddToData(data[conn], p[conn], len[conn], conn);
  FreeMem(p[conn]);
  len[conn]:=0;
  if (data_len[conn]>=sizeof(TCMessage)+6) then
    begin
      if (Char((PChar(data[conn]))^)='H') and (Char((PChar(data[conn])+1)^)='E') and (Char((PChar(data[conn])+2)^)='A') and
                (Char((PChar(data[conn])+3)^)='D') and (Char((PChar(data[conn])+4)^)='E') and (Char((PChar(data[conn])+5)^)='R') then
        begin
          DeleteBytes(data[conn], data_len[conn], 6);
          Move(data[conn]^, users[conn], sizeof(TCMessage));
          FreeMem(data[conn]);
          data[conn]:=nil;
          data_len[conn]:=0;
          if (users[conn].Atack<>-1) then FinallyseData(conn);
        end;
    end;
end;

procedure TForm1.FinallyseData(const conn: integer);
var
  idx, com, j: integer;
  s: string;
begin
  if (users[conn].login=logins[conn]) and (users[conn].psw=pasw[conn]) then
    begin
      idx:=users[conn].Atack+1;
      inc(msg[idx].namer);
      msg[idx].atack[msg[idx].namer]:=users[conn].login;
      lbEne.Items.Clear;
      for idx:=1 to count_msg do
        begin
          s:=msg[idx].names+'   '+inttostr(msg[idx].health)+' ÕÏ   '+msg[idx].stats+'  ';
          for j:=1 to msg[idx].namer do
            s:=s+' '+msg[idx].atack[j];
          lbEne.Items.Add(s);
        end;
    end;
  for idx:=0 to count-1 do
    begin
      if (users[idx].login=logins[idx]) and (users[idx].psw=pasw[idx]) then
        begin
          com:=CNames;
          Server.Socket.Connections[idx].SendBuf(com, sizeof(com));
          Server.Socket.Connections[idx].SendBuf(count_msg, sizeof(count_msg));
          for j:=1 to count_msg do
            begin
              Server.Socket.Connections[idx].SendText('HEADER');
              Server.Socket.Connections[idx].SendBuf(msg[j], sizeof(TSMessage));
            end;
        end;
    end;
end;

procedure TForm1.AddToData(var Dest, Src: pointer; const count, conn: integer);
var
  p2, ptemp: pointer;
begin
  if (count>0) then
    begin
      GetMem(ptemp, data_len[conn]+count);
      if (Dest<>nil) then
        begin
          Move(Dest^, ptemp^, data_len[conn]);
          p2:=PInteger(Integer(ptemp)+data_len[conn]);
        end
        else p2:=ptemp;
      Move(Src^, p2^, count);
      if (Dest<>nil) then FreeMem(Dest);
      data_len[conn]:=data_len[conn]+count;
      GetMem(Dest, data_len[conn]);
      Move(ptemp^, Dest^, data_len[conn]);
      FreeMem(ptemp);
    end;
end;

procedure TForm1.DeleteBytes(var P: pointer; var size: integer; const count: integer);
var
  p2, ptemp: pointer;
begin
  size:=size-count;
  if (size>0) then
    begin
      GetMem(ptemp, size);
      p2:=PInteger(Integer(P)+count);
      Move(p2^, ptemp^, size);
      FreeMem(P);
      GetMem(P, size);
      Move(ptemp^, P^, size);
      FreeMem(ptemp);
    end;
end;

procedure TForm1.FormResize(Sender: TObject);
begin
  if (Form1.ClientHeight<>256) then Form1.ClientHeight:=256;
  if (Form1.ClientWidth<304) then Form1.ClientWidth:=304;
  lbEne.Width:=Form1.ClientWidth-14;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  UpdateList;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  Server.Active:=false;
  Server.Port:=StrToInt(lePort.Text);
  Server.Active:=true;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
count:=0;
end;

procedure TForm1.ServerClientError(Sender: TObject;
  Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
  var ErrorCode: Integer);
begin
showmessage(inttostr(errorcode));
end;

end.

Клиентская:
Код

unit Main;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ScktComp, XPMan, StdCtrls, ExtCtrls;

type
  TSMessage = packed record
    names: string [15];
    health: integer;
    stats: string [20];
    atack: array [1..10] of string [15];
    namer: integer;
  end;

  TCMessage = packed record
    login, psw: string [15];
    Atack: integer;
  end;

  TForm1 = class(TForm)
    lbEne: TListBox;
    XPManifest1: TXPManifest;
    Client: TClientSocket;
    leIP: TLabeledEdit;
    leName: TLabeledEdit;
    bCon: TButton;
    bAtack: TButton;
    lePort: TLabeledEdit;
    lePasw: TLabeledEdit;
    procedure FormDestroy(Sender: TObject);
    procedure bConClick(Sender: TObject);
    procedure ClientRead(Sender: TObject; Socket: TCustomWinSocket);
    procedure bAtackClick(Sender: TObject);
    procedure FormResize(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure ClientConnect(Sender: TObject; Socket: TCustomWinSocket);
    procedure ClientDisconnect(Sender: TObject; Socket: TCustomWinSocket);
    procedure ClientError(Sender: TObject; Socket: TCustomWinSocket;
      ErrorEvent: TErrorEvent; var ErrorCode: Integer);
  private
    { Private declarations }
  public
    procedure AnalyseData;
    procedure FinallyseData;
    procedure DeleteBytes(var P: pointer; var size: integer; const count: integer);
    procedure AddToData(var Dest, Src: pointer; const count: integer);
  end;

const
  CNames = 1;

var
  Form1: TForm1;
  p, data, ptemp, p2: pointer;
  msg: array [1..30] of TSMessage;
  user: TCMessage;
  count_msg, real_count, real_len, len, data_len, com: integer;
  ended: boolean;

implementation

{$R *.dfm}

procedure TForm1.FormDestroy(Sender: TObject);
begin
  Client.Active:=false;
end;

procedure TForm1.bConClick(Sender: TObject);
begin
  if leIP.Text='' then
    begin
      ShowMessage('Please, enter Server IP!');
      exit;
    end;
  if leName.Text='' then
    begin
      ShowMessage('Plesase, enter your name!');
      exit;
    end;
  if (lePort.Text='') then
    begin
      ShowMessage('Please, enter port!');
      exit;
    end;
  if (lePasw.Text='') then
    begin
      ShowMessage('You don''t entered password!');
      exit;
    end;
  Client.Active:=false;
  try
    Client.Port:=StrToInt(lePort.Text);
  except
    ShowMessage('Please, enter valid port!');
    exit;
  end;
  Client.Host:=leIP.Text;
  Client.Active:=true;
  user.login:=leName.Text;
  user.psw:=lePasw.Text;
  ended:=true;
  bCon.Enabled:=false;
end;

procedure TForm1.ClientRead(Sender: TObject; Socket: TCustomWinSocket);
var
  j: integer;
  s: string;
begin
  len:=Socket.ReceiveLength;
  GetMem(p, len);
  Socket.ReceiveBuf(p^, len);
  AnalyseData;
end;

procedure TForm1.AnalyseData;
var
  i, j: integer;
  s: string;
begin
  if (ended) then
    begin
      ended:=false;
      com:=pinteger(integer(p))^;
      DeleteBytes(p, len, sizeof(integer));
    end;
  if (com=CNames) then
    begin
      AddToData(data, p, len);
      FreeMem(p);
      len:=0;
      if (real_count=-1)and(data_len>=4) then
        begin
          real_count:=pinteger(integer(data))^;
          count_msg:=0;
          DeleteBytes(data, data_len, sizeof(integer));
        end;
      if (data_len<>0)and(data_len>=(real_count*6+real_count*sizeof(TSMessage))) then
        begin
          for i:=count_msg+1 to real_count do
            begin
              if (Char((PChar(data))^)='H') and (Char((PChar(data)+1)^)='E') and (Char((PChar(data)+2)^)='A') and
                (Char((PChar(data)+3)^)='D') and (Char((PChar(data)+4)^)='E') and (Char((PChar(data)+5)^)='R') then
                  begin
                    DeleteBytes(data, data_len, 6);
                    inc(count_msg);
                    Move(data^, msg[count_msg], sizeof(TSMessage));
                    DeleteBytes(data, data_len, sizeof(TSMessage));
                  end;
            end;
          FinallyseData;
          real_count:=-1;
          FreeMem(data);
          data:=nil;
          data_len:=0;
        end;
    end;
end;

procedure TForm1.FinallyseData;
var
  i, j: integer;
  s: string;
begin
  lbEne.Items.Clear;
  for i:=1 to count_msg do
    begin
      s:=msg[i].names+'   '+inttostr(msg[i].health)+' ÕÏ   '+msg[i].stats+'  ';
      for j:=1 to msg[i].namer do
        s:=s+' '+msg[i].atack[j];
      lbEne.Items.Add(s);
    end;
  ended:=true;
end;

procedure TForm1.AddToData(var Dest, Src: pointer; const count: integer);
var
  p2, ptemp: pointer;
begin
  if (count>0) then
    begin
      GetMem(ptemp, data_len+count);
      if (Dest<>nil) then
        begin
          Move(Dest^, ptemp^, data_len);
          p2:=PInteger(Integer(ptemp)+data_len);
        end
        else p2:=ptemp;
      Move(Src^, p2^, count);
      if (Dest<>nil) then FreeMem(Dest);
      data_len:=data_len+count;
      GetMem(Dest, data_len);
      Move(ptemp^, Dest^, data_len);
      FreeMem(ptemp);
    end;
end;

procedure TForm1.DeleteBytes(var P: pointer; var size: integer; const count: integer);
var
  p2, ptemp: pointer;
begin
  size:=size-count;
  if (size>0) then
    begin
      GetMem(ptemp, size);
      p2:=PInteger(Integer(P)+count);
      Move(p2^, ptemp^, size);
      FreeMem(P);
      GetMem(P, size);
      Move(ptemp^, P^, size);
      FreeMem(ptemp);
    end;
end;

procedure TForm1.bAtackClick(Sender: TObject);
begin
  user.Atack:=lbEne.ItemIndex;
  if not(lbEne.ItemIndex<>-1) then
    begin
      ShowMessage('Select which enemy you will be atack.');
      exit;
    end;
  if (Client.Socket.Connected) then
    begin
      Client.Socket.SendText('HEADER');
      Client.Socket.SendBuf(user, sizeof(user));
    end;
end;

procedure TForm1.FormResize(Sender: TObject);
begin
  if (Form1.ClientHeight<>278) then Form1.ClientHeight:=278;
  if (Form1.ClientWidth<535) then Form1.ClientWidth:=535;
  lbEne.Width:=Form1.ClientWidth-14;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  data_len:=0;
  real_count:=-1;
end;

procedure TForm1.ClientConnect(Sender: TObject; Socket: TCustomWinSocket);
begin
  Form1.Caption:=Form1.Caption+' Connected';
  Socket.SendText('HEADER');
  user.Atack:=-1;
  Socket.SendBuf(user, sizeof(TCMessage));
end;

procedure TForm1.ClientDisconnect(Sender: TObject;
  Socket: TCustomWinSocket);
begin
  Form1.Caption:='DarkClient';
end;

procedure TForm1.ClientError(Sender: TObject; Socket: TCustomWinSocket;
  ErrorEvent: TErrorEvent; var ErrorCode: Integer);
begin
  showmessage(inttostr(errorcode));
end;

end.


ЗЫ не ругайте сильное, если написано абы как, я не гнался за производительностью и экономией памяти.

Автор: misha_mike 12.6.2007, 16:38
Значит так, я уже дважды писал про передачу данных, но ты не внял ;) В таком случае приведу цитату из хелпа, может тогда пронкнешся:

Цитата
Use SendBuf to write to the socket connection. Call this method from the OnSocketEvent event handler of a Windows socket object or in the OnWrite or OnClientWrite event handler of a socket component. Alternately, Use SendBuf to write when a connection is first formed when the socket does not expect notification that the socket on the other end of the connection is expecting to read.

For non-blocking sockets, the data is sent to the WinSock DLL which has it's own internal buffers. If the WinSock can accept additional data, SendBuf returns immediately with the number of bytes queued. If the WinSock internal buffer space is not able to accept the buffer being sent, SendBuf returns -1 and no data is queued at all. In this case, wait a bit for the WinSock to have a chance to send out already-queued data; then try again.

For blocking sockets, SendBuf returns the number of bytes actually written. 


Внимательно причитай выделенное, а особенно -- подчеркнутое (OnError действительно не возникает, возвращается ошибка). Что касается последнего выделенного фрагмента, то я бы не советовал так делать (хотя уверен что и так проканает), и ждать не "a bit", а вполне конкретного события -- OnWrite/OnClientWrite и только тогда продолжать. Я бы делал так:

Код

// наш собственный буфер передачи
var
  SendBuf: record
    Data: Pointer;
    Size: Integer;
  end;

......
// инициализируем перед открытием сокета!
  SendBuf.Data:= nil; 
  SendBuf.Size:= 0;
......

// no comment
function Min(const Arg1, Arg2: Integer): Integer;
begin
  if Arg1 < Arg2 then
    Result:= Arg1
  else
    Result:= Arg2;
end;

// передает сколько сможет по кускам и возвращает количество переданного
function TrySend(const Data; const Size: Integer): Integer; 
const
  MaxPacketSize = 1024;
begin
  Result:= 0;
  repeat
    Res:= Socket.SendBuf(Pointer(Integer(@Data)+Result)^, Min(MaxPacketSize, Size-Result));
    if Res <> -1 then Inc(Result, Res);
  until (Res = -1) or (Result = Size);
end;

// передает или сохраняет "на потом", можно совать какие угодно куски данных
procedure SafeSend(const Data; const Size: Integer);
var
  Sent: Integer;
begin
  if SendBuf.Size > 0 then
    begin
      ReallocMem(SendBuf.Data, SendBuf.Size+Size);
      Move(Data, Pointer(Integer(SendBuf.Data)+SendBuf.Size)^, Size);
      Inc(SendBuf.Size, Size);
    end
  else
    begin
      Sent:= TrySend(Data, Size);
      if Sent < Size then
        begin
          SendBuf.Size:= Size-Sent;
          GetMem(SendBuf.Data, SendBuf.Size);
          Move(Pointer(Integer(@Data)+Sent)^, SendBuf.Data^, SendBuf.Size);
        end;
    end;
end;

// обработчик сокетного события!
procedure OnWrite(Sender: TObject);
var
  Sent: Integer;
begin
  if SendBuf.Size > 0 then
    begin
      Sent:= TrySend(SendBuffer.Data, SendBuffer.Size);
      if (Sent > 0) and (Sent < SendBuffer.Size) then
        MoveMemory(SendBuffer.Data, Pointer(Integer(SendBuffer.Data)+Sent), SendBuffer.Size-Sent);
      Dec(SendBuffer.Size, Sent);
      ReallocMem(SendBuffer.Data, SendBuffer.Size);
    end;
end;

.......
// после закрытия сокета подчищаем за собой
FreeMem(SendBuffer.Data, SendBuffer.Size);
.......



Не ручаюсь за полную синтаксическую правильность, но идея именно такая. Теперь вызывая SafeSend можно не опасаться что что-то не уйдет (но сильно все равно не налягать потому как память не резиновая и буфер передачи ее сожрет очень быстро).

Теперь про твои художества:

Цитата
Код
inc(count, -1);

Код

Dec(count);


Цитата
Код

  if (Char((PChar(data[conn]))^)='H') and (Char((PChar(data[conn])+1)^)='E') and (Char((PChar(data[conn])+2)^)='A') and
                (Char((PChar(data[conn])+3)^)='D') and (Char((PChar(data[conn])+4)^)='E') and (Char((PChar(data[conn])+5)^)='R') then


А не проще ли вместо SendText('HEADER') делать SendText('HEADER'#0) и всю эту байду заменить на:
Код

if PChar(data[conn]) = 'HEADER' then ...

Да и вообще с такими сигнатурами надо поосторожнее, а ну как встретится это слово среди данных? Хотя дело хозяйское.

Цитата
Код
if users[conn].login=logins[conn] ...

Логин должен быть нечувствителен к регистру, заменяем на:
Код

if SameText(users[conn].login, logins[conn]) ...


Цитата
Код

      GetMem(ptemp, size);
      p2:=PInteger(Integer(P)+count);
      Move(p2^, ptemp^, size);
      FreeMem(P);
      GetMem(P, size);
      Move(ptemp^, P^, size);
      FreeMem(ptemp);

Есть две замечательные функции: ReallocMem (изменяет размер выделенного блока с сохранением данных и по возможности без новых гетмемов) и MoveMemory (тоже что и Move, но можно не беспокоиться что буферы источника и приемника пересекаются в памяти).

Цитата
Код

  if (Form1.ClientHeight<>256) then Form1.ClientHeight:=256;
  if (Form1.ClientWidth<304) then Form1.ClientWidth:=304;

Достаточно один раз (и из инспектора объектов, а не в коде):
Код

Form1.BorderStyle:= bsSingle;
Form1.BorderIcons:= Form1.BorderIcons-[biMaximize];

или установи Form1.Constraints

Цитата
Код

 for j:=0 to count-1 do
    if (socket.RemoteHost=server.Socket.Connections[j].RemoteHost) and
      (socket.RemoteAddress=server.Socket.Connections[j].RemoteAddress) then i:=j;

Гораздо лучше так:
Код

j:= Count-1;
while j >= 0 do
  if (Sender as TServerSocket).Socket.Connections[j] = Socket then
    begin
       i:= j;
       j:= -1;
    end
  else
    Dec(j);

Но не забывай что если клиент отвалится, то соответствующий ему элемент твоего массива станет неактуальным и лишним. Поэтому обрабатывай OnClientDisconnect.

P.S. Чего маешся вместо того чтобы IRC взять?

Автор: OXOTHUK 12.6.2007, 18:03
Цитата(misha_mike @  12.6.2007,  16:38 Найти цитируемый пост)
Значит так, я уже дважды писал про передачу данных, но ты не внял ;) В таком случае приведу цитату из хелпа, может тогда пронкнешся:

Понял, но не думал что при предаче 1 кб могут быть такие проблемы.

Цитата(misha_mike @  12.6.2007,  16:38 Найти цитируемый пост)
Цитата(OXOTHUK @  12.6.2007,  15:25 Найти цитируемый пост)
if (Form1.ClientHeight<>256) then Form1.ClientHeight:=256;  if (Form1.ClientWidth<304) then Form1.ClientWidth:=304;

Достаточно один раз (и из инспектора объектов, а не в коде):

У меня левую рамку можно двигать.

Цитата(misha_mike @  12.6.2007,  16:38 Найти цитируемый пост)
Но не забывай что если клиент отвалится, то соответствующий ему элемент твоего массива станет неактуальным и лишним. Поэтому обрабатывай OnClientDisconnect.

я их кол-во там уменьшаю. Он же сами сдвигаются?

В общем, я что-то переделал. Сервер стал посылать, но клиент всё равно не хочет, он посылает, но у сервера не возникает OnRead.
То, что получилось:
Клиент
Код

unit Main;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ScktComp, XPMan, StdCtrls, ExtCtrls;

type
  TSMessage = packed record
    names: string [15];
    health: integer;
    stats: string [20];
    atack: array [1..10] of string [15];
    namer: integer;
  end;

  TCMessage = packed record
    login, psw: string [15];
    Atack: integer;
  end;

  TForm1 = class(TForm)
    lbEne: TListBox;
    XPManifest1: TXPManifest;
    Client: TClientSocket;
    leIP: TLabeledEdit;
    leName: TLabeledEdit;
    bCon: TButton;
    bAtack: TButton;
    lePort: TLabeledEdit;
    lePasw: TLabeledEdit;
    procedure FormDestroy(Sender: TObject);
    procedure bConClick(Sender: TObject);
    procedure ClientRead(Sender: TObject; Socket: TCustomWinSocket);
    procedure bAtackClick(Sender: TObject);
    procedure FormResize(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure ClientConnect(Sender: TObject; Socket: TCustomWinSocket);
    procedure ClientDisconnect(Sender: TObject; Socket: TCustomWinSocket);
    procedure ClientError(Sender: TObject; Socket: TCustomWinSocket;
      ErrorEvent: TErrorEvent; var ErrorCode: Integer);
    procedure ClientWrite(Sender: TObject; Socket: TCustomWinSocket);
  private
    { Private declarations }
  public
    procedure AnalyseData;
    procedure FinallyseData;
    procedure DeleteBytes(var P: pointer; var size: integer; const count: integer);
    procedure AddToData(var Dest, Src: pointer; const count: integer);
    procedure SafeSend(const Data; const Size: Integer);
    function TrySend(const Data; const Size: Integer): Integer;
    function Min(const Arg1, Arg2: Integer): Integer;
  end;

const
  CNames = 1;

var
  Form1: TForm1;
  p, data, ptemp, p2: pointer;
  msg: array [1..30] of TSMessage;
  user: TCMessage;
  count_msg, real_count, real_len, len, data_len, com: integer;
  ended: boolean;
  SendBuf: record
    Data: Pointer;
    Size: Integer;
  end;

implementation

{$R *.dfm}

function TForm1.Min(const Arg1, Arg2: Integer): Integer;
begin
  if Arg1 < Arg2 then
    Result:= Arg1
  else
    Result:= Arg2;
end;

function TForm1.TrySend(const Data; const Size: Integer): Integer;
const
  MaxPacketSize = 1024;
var
  Res: integer;
begin
  Result:= 0;
  repeat
    Res:=Client.Socket.SendBuf(Pointer(Integer(@Data)+Result)^, Min(MaxPacketSize, Size-Result));
    if Res <> -1 then Inc(Result, Res);
  until (Res = -1) or (Result = Size);
end;

procedure TForm1.SafeSend(const Data; const Size: Integer);
var
  Sent: Integer;
begin
  if SendBuf.Size > 0 then
    begin
      ReallocMem(SendBuf.Data, SendBuf.Size+Size);
      Move(Data, Pointer(Integer(SendBuf.Data)+SendBuf.Size)^, Size);
    end
  else
    begin
      Sent:= TrySend(Data, Size);
      if Sent < Size then
        begin
          SendBuf.Size:= Size-Sent;
          GetMem(SendBuf.Data, SendBuf.Size);
          Move(Pointer(Integer(@Data)+Sent)^, SendBuf.Data^, SendBuf.Size);
        end;
    end;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  Client.Active:=false;
end;

procedure TForm1.bConClick(Sender: TObject);
begin
  if leIP.Text='' then
    begin
      ShowMessage('Please, enter Server IP!');
      exit;
    end;
  if leName.Text='' then
    begin
      ShowMessage('Plesase, enter your name!');
      exit;
    end;
  if (lePort.Text='') then
    begin
      ShowMessage('Please, enter port!');
      exit;
    end;
  if (lePasw.Text='') then
    begin
      ShowMessage('You don''t entered password!');
      exit;
    end;
  Client.Active:=false;
  try
    Client.Port:=StrToInt(lePort.Text);
  except
    ShowMessage('Please, enter valid port!');
    exit;
  end;
  Client.Host:=leIP.Text;
  Client.Active:=true;
  user.login:=leName.Text;
  user.psw:=lePasw.Text;
  ended:=true;
  bCon.Enabled:=false;
end;

procedure TForm1.ClientRead(Sender: TObject; Socket: TCustomWinSocket);
var
  j: integer;
  s: string;
begin
  len:=Socket.ReceiveLength;
  GetMem(p, len);
  Socket.ReceiveBuf(p^, len);
  AnalyseData;
end;

procedure TForm1.AnalyseData;
var
  i, j: integer;
  s: string;
begin
  if (ended) then
    begin
      ended:=false;
      com:=pinteger(integer(p))^;
      DeleteBytes(p, len, sizeof(integer));
    end;
  if (com=CNames) then
    begin
      AddToData(data, p, len);
      FreeMem(p);
      len:=0;
      if (real_count=-1)and(data_len>=4) then
        begin
          real_count:=pinteger(integer(data))^;
          count_msg:=0;
          DeleteBytes(data, data_len, sizeof(integer));
        end;
      if (data_len<>0)and(data_len>=(real_count*6+real_count*sizeof(TSMessage))) then
        begin
          for i:=count_msg+1 to real_count do
            begin
              if (Char((PChar(data))^)='H') and (Char((PChar(data)+1)^)='E') and (Char((PChar(data)+2)^)='A') and
                (Char((PChar(data)+3)^)='D') and (Char((PChar(data)+4)^)='E') and (Char((PChar(data)+5)^)='R') then
                  begin
                    DeleteBytes(data, data_len, 6);
                    inc(count_msg);
                    Move(data^, msg[count_msg], sizeof(TSMessage));
                    DeleteBytes(data, data_len, sizeof(TSMessage));
                  end;
            end;
          FinallyseData;
          real_count:=-1;
          FreeMem(data);
          data:=nil;
          data_len:=0;
        end;
    end;
end;

procedure TForm1.FinallyseData;
var
  i, j: integer;
  s: string;
begin
  lbEne.Items.Clear;
  for i:=1 to count_msg do
    begin
      s:=msg[i].names+'   '+inttostr(msg[i].health)+' ÕÏ   '+msg[i].stats+'  ';
      for j:=1 to msg[i].namer do
        s:=s+' '+msg[i].atack[j];
      lbEne.Items.Add(s);
    end;
  ended:=true;
end;

procedure TForm1.AddToData(var Dest, Src: pointer; const count: integer);
var
  p2, ptemp: pointer;
begin
  if (count>0) then
    begin
      GetMem(ptemp, data_len+count);
      if (Dest<>nil) then
        begin
          Move(Dest^, ptemp^, data_len);
          p2:=PInteger(Integer(ptemp)+data_len);
        end
        else p2:=ptemp;
      Move(Src^, p2^, count);
      if (Dest<>nil) then FreeMem(Dest);
      data_len:=data_len+count;
      GetMem(Dest, data_len);
      Move(ptemp^, Dest^, data_len);
      FreeMem(ptemp);
    end;
end;

procedure TForm1.DeleteBytes(var P: pointer; var size: integer; const count: integer);
var
  p2, ptemp: pointer;
begin
  size:=size-count;
  if (size>0) then
    begin
      GetMem(ptemp, size);
      p2:=PInteger(Integer(P)+count);
      Move(p2^, ptemp^, size);
      FreeMem(P);
      GetMem(P, size);
      Move(ptemp^, P^, size);
      FreeMem(ptemp);
    end;
end;

procedure TForm1.bAtackClick(Sender: TObject);
begin
  user.Atack:=lbEne.ItemIndex;
  if not(lbEne.ItemIndex<>-1) then
    begin
      ShowMessage('Select which enemy you will be atack.');
      exit;
    end;
  if (Client.Socket.Connected) then
    begin
      SafeSend('HEADER', 6);
      SafeSend(user, sizeof(TCMessage));
    end;
end;

procedure TForm1.FormResize(Sender: TObject);
begin
  if (Form1.ClientHeight<>278) then Form1.ClientHeight:=278;
  if (Form1.ClientWidth<535) then Form1.ClientWidth:=535;
  lbEne.Width:=Form1.ClientWidth-14;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  data_len:=0;
  real_count:=-1;
end;

procedure TForm1.ClientConnect(Sender: TObject; Socket: TCustomWinSocket);
begin
  Form1.Caption:=Form1.Caption+' Connected';
  SafeSend('HEADER', 6);
  user.Atack:=-1;
  SafeSend(user, sizeof(TCMessage));
end;

procedure TForm1.ClientDisconnect(Sender: TObject;
  Socket: TCustomWinSocket);
begin
  Form1.Caption:='DarkClient';
end;

procedure TForm1.ClientError(Sender: TObject; Socket: TCustomWinSocket;
  ErrorEvent: TErrorEvent; var ErrorCode: Integer);
begin
  showmessage(inttostr(errorcode));
end;

procedure TForm1.ClientWrite(Sender: TObject; Socket: TCustomWinSocket);
var
  Sent: Integer;
begin
  if SendBuf.Size > 0 then
    begin
      Sent:= TrySend(SendBuf.Data, SendBuf.Size);
      if (Sent > 0) and (Sent < SendBuf.Size) then
        MoveMemory(SendBuf.Data, Pointer(Integer(SendBuf.Data)+Sent), SendBuf.Size-Sent);
      Dec(SendBuf.Size, Sent);
      ReallocMem(SendBuf.Data, SendBuf.Size);
    end;
end;

end.


Сервер:
Код

unit Main;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ScktComp, StdCtrls, XPMan, ExtCtrls;

type
  TSMessage = packed record
    names: string [15];
    health: integer;
    stats: string [20];
    atack: array [1..10] of string [15];
    namer: integer;
  end;

  TCMessage = packed record
    login, psw: string [15];
    Atack: integer;
  end;

  TForm1 = class(TForm)
    Server: TServerSocket;
    XPManifest1: TXPManifest;
    lbEne: TListBox;
    Button1: TButton;
    lePort: TLabeledEdit;
    Button2: TButton;
    procedure ServerClientConnect(Sender: TObject;
      Socket: TCustomWinSocket);
    procedure ServerClientDisconnect(Sender: TObject;
      Socket: TCustomWinSocket);
    procedure FormDestroy(Sender: TObject);
    procedure FormActivate(Sender: TObject);
    procedure ServerClientRead(Sender: TObject; Socket: TCustomWinSocket);
    procedure FormResize(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure ServerClientError(Sender: TObject; Socket: TCustomWinSocket;
      ErrorEvent: TErrorEvent; var ErrorCode: Integer);
    procedure ServerClientWrite(Sender: TObject; Socket: TCustomWinSocket);
  private
    { Private declarations }
  public
    count, count_msg, count_psw: integer;
    msg: array [1..30] of TSMessage;
    pasw: array [0..99] of string[15];
    logins: array [0..99] of string[15];
    users: array [0..99] of TCMessage;
    data, p: array [0..99] of pointer;
    data_len, len: array [0..99] of integer;
    procedure UpdateList;
    procedure Analyse(const conn: integer);
    procedure AddToData(var Dest, Src: pointer; const count, conn: integer);
    procedure DeleteBytes(var P: pointer; var size: integer; const count: integer);
    procedure FinallyseData(const conn: integer);
    procedure SafeSend(const Data; const Size, conn: Integer);
    function TrySend(const Data; const Size, conn: Integer): Integer;
    function Min(const Arg1, Arg2: Integer): Integer;
  end;

const
  CNames = 1;

var
  Form1: TForm1;
  SendBuf: array [0..99] of record
    Data: Pointer;
    Size: Integer;
  end;

implementation

{$R *.dfm}

function TForm1.Min(const Arg1, Arg2: Integer): Integer;
begin
  if Arg1 < Arg2 then
    Result:= Arg1
  else
    Result:= Arg2;
end;

function TForm1.TrySend(const Data; const Size, conn: Integer): Integer;
const
  MaxPacketSize = 1024;
var
  Res: integer;
begin
  Result:= 0;
  repeat
    Res:=Server.Socket.Connections[conn].SendBuf(Pointer(Integer(@Data)+Result)^, Min(MaxPacketSize, Size-Result));
    if Res <> -1 then Inc(Result, Res);
  until (Res = -1) or (Result = Size);
end;

procedure TForm1.SafeSend(const Data; const Size, conn: Integer);
var
  Sent: Integer;
begin
  if SendBuf[conn].Size > 0 then
    begin
      ReallocMem(SendBuf[conn].Data, SendBuf[conn].Size+Size);
      Move(Data, Pointer(Integer(SendBuf[conn].Data)+SendBuf[conn].Size)^, Size);
    end
  else
    begin
      Sent:= TrySend(Data, Size, conn);
      if Sent < Size then
        begin
          SendBuf[conn].Size:= Size-Sent;
          GetMem(SendBuf[conn].Data, SendBuf[conn].Size);
          Move(Pointer(Integer(@Data)+Sent)^, SendBuf[conn].Data^, SendBuf[conn].Size);
        end;
    end;
end;

procedure TForm1.ServerClientConnect(Sender: TObject;
  Socket: TCustomWinSocket);
var
  i, com, j: integer;
begin
  inc(count);
  j:=Count-1;
  while j>=0 do
    if Server.Socket.Connections[j] = Socket then
      begin
        i:=j;
        j:=-1;
      end
    else
      Dec(j);
  j:=i;
  com:=CNames;
  SafeSend(com, sizeof(com), j);
  SafeSend(count_msg, sizeof(count_msg), j);
  for i:=1 to count_msg do
    begin
      SafeSend('HEADER', 6, j);
      SafeSend(msg[i], sizeof(TSMessage), j);
    end;
end;

procedure TForm1.ServerClientDisconnect(Sender: TObject;
  Socket: TCustomWinSocket);
begin
  inc(count, -1);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  Server.Active:=false;
end;

procedure TForm1.FormActivate(Sender: TObject);
begin
  UpdateList;
end;

procedure TForm1.UpdateList;
var
  i, j, com: integer;
  f: textfile;
  s: string;
begin
  lbEne.Items.Clear;
  assignfile(f, 'DATA.txt');
  reset(f);
  readln(f, count_msg);
  for i:=1 to count_msg do
    begin
      readln(f, msg[i].names);
      readln(f, msg[i].health);
      readln(f, msg[i].stats);
      lbEne.Items.Add(msg[i].names+'   '+inttostr(msg[i].health)+' ÕÏ   '+msg[i].stats);
      for j:=1 to 10 do msg[i].atack[j]:='';
      msg[i].namer:=0;
    end;
  closefile(f);
  assignfile(f, 'passwords.dat');
  reset(f);
  readln(f, count_psw);
  for i:=0 to count_psw-1 do
    begin
      readln(f, s);
      logins[i]:=Copy(s, 1, Pos(#9, s));
      pasw[i]:=Copy(s, Pos(#9, s)+1, Length(s)-Pos(#9, s));
    end;
  closefile(f);
  for i:=0 to count-1 do
    begin
      //if (SameText(users[i].login, logins[i])) and (users[i].psw=pasw[i]) then
        begin
          com:=CNames;
          SafeSend(com, sizeof(com), i);
          SafeSend(count_msg, sizeof(count_msg), i);
          for j:=1 to count_msg do
            begin
              SafeSend('HEADER', 6, i);
              SafeSend(msg[j], sizeof(TSMessage), i);
            end;
        end;
    end;
end;

procedure TForm1.ServerClientRead(Sender: TObject;
  Socket: TCustomWinSocket);
var
  j, i: integer;
begin
  i:=-1;
  j:=Count-1;
  while j>=0 do
    if Server.Socket.Connections[j] = Socket then
      begin
        i:=j;
        j:=-1;
      end
    else
      Dec(j);
  if (i=-1) then exit;
  len[i]:=socket.ReceiveLength;
  GetMem(p[i], len[i]);
  Socket.ReceiveBuf(p[i]^, len[i]);
  Analyse(i);
end;

procedure TForm1.Analyse(const conn: integer);
begin
  AddToData(data[conn], p[conn], len[conn], conn);
  FreeMem(p[conn]);
  len[conn]:=0;
  if (data_len[conn]>=sizeof(TCMessage)+6) then
    begin
      if (Char((PChar(data[conn]))^)='H') and (Char((PChar(data[conn])+1)^)='E') and (Char((PChar(data[conn])+2)^)='A') and
                (Char((PChar(data[conn])+3)^)='D') and (Char((PChar(data[conn])+4)^)='E') and (Char((PChar(data[conn])+5)^)='R') then
        begin
          DeleteBytes(data[conn], data_len[conn], 6);
          Move(data[conn]^, users[conn], sizeof(TCMessage));
          FreeMem(data[conn]);
          data[conn]:=nil;
          data_len[conn]:=0;
          if (users[conn].Atack<>-1) then FinallyseData(conn);
        end;
    end;
end;

procedure TForm1.FinallyseData(const conn: integer);
var
  idx, com, j: integer;
  s: string;
begin
  if (users[conn].login=logins[conn]) and (users[conn].psw=pasw[conn]) then
    begin
      idx:=users[conn].Atack+1;
      inc(msg[idx].namer);
      msg[idx].atack[msg[idx].namer]:=users[conn].login;
      lbEne.Items.Clear;
      for idx:=1 to count_msg do
        begin
          s:=msg[idx].names+'   '+inttostr(msg[idx].health)+' ÕÏ   '+msg[idx].stats+'  ';
          for j:=1 to msg[idx].namer do
            s:=s+' '+msg[idx].atack[j];
          lbEne.Items.Add(s);
        end;
    end;
  for idx:=0 to count-1 do
    begin
      if (users[idx].login=logins[idx]) and (users[idx].psw=pasw[idx]) then
        begin
          com:=CNames;
          SafeSend(com, sizeof(com), conn);
          SafeSend(count_msg, sizeof(count_msg), conn);
          for j:=1 to count_msg do
            begin
              SafeSend('HEADER', 6, conn);
              SafeSend(msg[j], sizeof(TSMessage), conn);
            end;
        end;
    end;
end;

procedure TForm1.AddToData(var Dest, Src: pointer; const count, conn: integer);
var
  p2, ptemp: pointer;
begin
  if (count>0) then
    begin
      GetMem(ptemp, data_len[conn]+count);
      if (Dest<>nil) then
        begin
          Move(Dest^, ptemp^, data_len[conn]);
          p2:=PInteger(Integer(ptemp)+data_len[conn]);
        end
        else p2:=ptemp;
      Move(Src^, p2^, count);
      if (Dest<>nil) then FreeMem(Dest);
      data_len[conn]:=data_len[conn]+count;
      GetMem(Dest, data_len[conn]);
      Move(ptemp^, Dest^, data_len[conn]);
      FreeMem(ptemp);
    end;
end;

procedure TForm1.DeleteBytes(var P: pointer; var size: integer; const count: integer);
var
  p2, ptemp: pointer;
begin
  size:=size-count;
  if (size>0) then
    begin
      GetMem(ptemp, size);
      p2:=PInteger(Integer(P)+count);
      Move(p2^, ptemp^, size);
      FreeMem(P);
      GetMem(P, size);
      Move(ptemp^, P^, size);
      FreeMem(ptemp);
    end;
end;

procedure TForm1.FormResize(Sender: TObject);
begin
  if (Form1.ClientHeight<>256) then Form1.ClientHeight:=256;
  if (Form1.ClientWidth<304) then Form1.ClientWidth:=304;
  lbEne.Width:=Form1.ClientWidth-14;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  UpdateList;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  Server.Active:=false;
  Server.Port:=StrToInt(lePort.Text);
  Server.Active:=true;
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  i: integer;
begin
count:=0;
for i:=0 to 99 do
  begin
    SendBuf[i].Data:=nil;
    SendBuf[i].Data:=0;
  end;
end;

procedure TForm1.ServerClientError(Sender: TObject;
  Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
  var ErrorCode: Integer);
begin
showmessage(inttostr(errorcode));
end;

procedure TForm1.ServerClientWrite(Sender: TObject;
  Socket: TCustomWinSocket);
var
  Sent, i, j: Integer;
begin
  j:=Count-1;
  while j>=0 do
    if Server.Socket.Connections[j] = Socket then
      begin
        i:=j;
        j:=-1;
      end
    else
      Dec(j);
  if SendBuf[i].Size > 0 then
    begin
      Sent:= TrySend(SendBuf[i].Data, SendBuf[i].Size, i);
      if (Sent > 0) and (Sent < SendBuf[i].Size) then
        MoveMemory(SendBuf[i].Data, Pointer(Integer(SendBuf[i].Data)+Sent), SendBuf[i].Size-Sent);
      Dec(SendBuf[i].Size, Sent);
      ReallocMem(SendBuf[i].Data, SendBuf[i].Size);
    end;
end;

end.


Цитата(misha_mike @  12.6.2007,  16:38 Найти цитируемый пост)
P.S. Чего маешся вместо того чтобы IRC взять?

нормальный так и не нашёл

ЗЫ на всякий случай http://ifolder.ru/2324208 http://ifolder.ru/2324222

Автор: misha_mike 13.6.2007, 00:49
Высылаю полностью рабочий семпл, написанный в согласии с тем что я тут говорил про передачу. Разбирайся и ищи свои ошибки сам, пригодится smile

Автор: OXOTHUK 13.6.2007, 12:31
Огромный спасиб!

Ошибка была в этом:
Код

if (Client.Socket.Connected) then
    begin
      //SafeSend('HEADER', 6);
      SafeSend(user, sizeof(TCMessage));
    end;

В закоментеном тексте, почему-то если его закоментить, на сервере перстаёт возникать онрид, хотя на сервере выполняются аналогичные действия и всё работает.

Автор: OXOTHUK 15.6.2007, 12:08
Я отправляю информацию, но как только буфер winsock'а переполняется, т.к. я отправлю свои 8кб, то вся моя информация складируется в буфер и отправляется потом через OnWrite. Но на приёмной стороне, пока он не переполнился - всё хорошо, после 8кб, начинает приходить бурда. Посмотрите, пожалуйста, код сервера, я никак не могу найти ошибку. 
ЗЫ точно ошибка в сервере.
Код

var
Form1: TForm1;
Server: TServerSocket;//понимаю что так нельзя, но штоб было понятно, что есть что.
SendBuf: array [0..99] of record
    Data: Pointer;
    Size: Integer;
  end;

...

function TForm1.Min(const Arg1, Arg2: Integer): Integer;
begin
  if Arg1 < Arg2 then
    Result:= Arg1
  else
    Result:= Arg2;
end;

function TForm1.TrySend(const Data; const Size, conn: Integer): Integer;
const
  MaxPacketSize = 1024;
var
  Res: integer;
begin
  Result:= 0;
  repeat
    Res:=Server.Socket.Connections[conn].SendBuf(Pointer(Integer(@Data)+Result)^, Min(MaxPacketSize, Size-Result));
    if Res <> -1 then Inc(Result, Res);
  until (Res = -1) or (Result = Size);
end;

procedure TForm1.SafeSend(const Data; const Size, conn: Integer);
var
  Sent: Integer;
begin
  if SendBuf[conn].Size > 0 then
    begin
      ReallocMem(SendBuf[conn].Data, SendBuf[conn].Size+Size);
      Move(Data, Pointer(Integer(SendBuf[conn].Data)+SendBuf[conn].Size)^, Size);
      Inc(SendBuf[conn].Size, Size);
    end
  else
    begin
      Sent:= TrySend(Data, Size, conn);
      if Sent < Size then
        begin
          SendBuf[conn].Size:= Size-Sent;
          GetMem(SendBuf[conn].Data, SendBuf[conn].Size);
          Move(Pointer(Integer(@Data)+Sent)^, SendBuf[conn].Data^, SendBuf[conn].Size);
        end;
    end;
end;

...
многа-многа раз SafeSend(чего-нибудь);
...

procedure TForm1.ServerClientWrite(Sender: TObject;
  Socket: TCustomWinSocket);
var
  Sent, i, j: Integer;
begin
  j:=Count-1;
  while j>=0 do
    if Server.Socket.Connections[j] = Socket then
      begin
        i:=j;
        j:=-1;
      end
    else
      Dec(j);
  if SendBuf[i].Size > 0 then
    begin
      Sent:= TrySend(SendBuf[i].Data, SendBuf[i].Size, i);
      if (Sent > 0) and (Sent < SendBuf[i].Size) then
        MoveMemory(SendBuf[i].Data, Pointer(Integer(SendBuf[i].Data)+Sent), SendBuf[i].Size-Sent);
      Dec(SendBuf[i].Size, Sent);
      ReallocMem(SendBuf[i].Data, SendBuf[i].Size);
    end;
end;

Автор: misha_mike 15.6.2007, 13:53
Вот что бывает когда бездумно копируешь чужой код. Я допустил помарку, а кто-то ее слепо скопировал.

Вместо:
Код

Sent:= TrySend(SendBuf[i].Data, SendBuf[i].Size, i);


Нужно:
Код

Sent:= TrySend(SendBuf[i].Data^, SendBuf[i].Size, i);


И на обеих сторонах!

P.S. В написанных мной тестовых приложениях этой ошибки, кстати, нет, так что внимательнее.

Автор: OXOTHUK 15.6.2007, 14:33
Цитата(misha_mike @  15.6.2007,  13:53 Найти цитируемый пост)
В написанных мной тестовых приложениях этой ошибки, кстати, нет, так что внимательнее.

я сверялся, но такой маленький символ трудно заметить.

А можно ещё вопрос? Что произойдёт если связь с клиентов оборвётся, т.е. не будет OnDisconnect. Вылезет ошибка, а что будет с массивом Server.Socket.Connections?

Автор: misha_mike 15.6.2007, 15:40
Цитата
Цитата(misha_mike @  15.6.2007,  13:53 Найти цитируемый пост)
В написанных мной тестовых приложениях этой ошибки, кстати, нет, так что внимательнее.

я сверялся, но такой маленький символ трудно заметить.

Дело не в значительности/заметности символа, а в том что он делает. Даже при беглом изучании этой строчки становится ясно что что-то не так. Без этого символа на передачу уходило не содержимое буфера, а значение указателя и следом за ним еще (Size-4) байт из сегмента данных (другие переменные или просто мусор). В одном случае это вызывает просто появление непонятных данных, в ряде других ведет к Access Violation.

Цитата
А можно ещё вопрос? Что произойдёт если связь с клиентов оборвётся, т.е. не будет OnDisconnect.

Это смотря как оборвется. Если упадет приложение, то OnDosconnect на другой стороне возникнет. Если отвалится сетевой интерфейс -- аналогично (но только на том хосте, где эта неприятность и случилась).

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

Цитата
Вылезет ошибка, а что будет с массивом Server.Socket.Connections?

Я когда столкнулся с проблемой "тихих" разрывов, просто реализовал на каждой стороне передачу раз в минуту специального короткого пакета, который на принимающей стороне просто игнорируется. Во-певых это не дает всяким шлюзам убить линк по тайм-ауту. Во-вторых, если "тихий" разрыв все-таки произошел -- при передаче такого idle-пакета немедленно возникает ошибка и сокет отключается. В случае серверного сокета это обозначает и удаление ячейки из Socket.Connections. Вот только возникает ли OnDisconnect, я не помню, надо пробовать.


Автор: OXOTHUK 15.6.2007, 15:48
Цитата(misha_mike @  15.6.2007,  15:40 Найти цитируемый пост)
Это смотря как оборвется

просто у клиента отключится интернет, или если например убить клиент в процессах. Тогда приложение закрывается, а OnDestroy формы не вызывается, т.е. Client.Active:=false не происходит.

Автор: misha_mike 15.6.2007, 16:04
Цитата(OXOTHUK @ 15.6.2007,  15:48)
Цитата(misha_mike @  15.6.2007,  15:40 Найти цитируемый пост)
Это смотря как оборвется

просто у клиента отключится интернет, или если например убить клиент в процессах. Тогда приложение закрывается, а OnDestroy формы не вызывается, т.е. Client.Active:=false не происходит.

Перечитай мой последний пост еще раз. Не важно произойдет ли дестрой формы, при завершении приложения (в т.ч. и аварийном) система сама подчищает все занятые ресурсы и посылает уведомление другой стороне что канал закрыт. Если у клиета спонтанно отвалится интернет, то спасет только периодическое тормошение канала, как я и сказал.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)