В продолжении темы про сервера и клиенты...
Автор изначального примера реализовал как для клиента на сервере, так и для клиента у пользователя вот такой код. Программа может через три процедуры SendMessage, SendStream, SendFile обмениваться с сервером сообщениями. Процедуры помещают задачу в список, и ниже приведенный код выполняет отправку. Как писал выше, один из багов реализации чтение списка задом наперед. Но это еще не все.
- Код: Выделить всё
- procedure TClientThread.Execute;
 var
 Msg: String;
 MsgTyp: Integer;
 Params: TStringArray;
 Size: Int64;
 MS: TMemoryStream;
 I: Integer;
 FileName: String;
 Path: String;
 List: TList;
 Task: TTask;
 FCurPing: QWord;
 begin
 FLastPing := GetTickCount64;
 if FTCPBase.FTCPBaseType = tcpServer then
 BroadcastConnection;
 while not Terminated do
 begin
 if (not FBusy) and (not FNeedToBreak) then
 begin
 FBusy := True;
 try
 List := FTaskList.TaskList.LockList;
 try
 for I := List.Count - 1 downto 0 do
 begin
 if FNeedToBreak then
 Break;
 if ProcessTask(TTask(List[I])) then
 begin
 FLastPing := GetTickCount64;
 Task := TTask(List.Items[I]);
 if Task <> nil then
 FTaskList.DeleteTask(Task);
 List.Delete(I);
 end;
 end;
 finally
 FTaskList.TaskList.UnlockList;
 end;
 finally
 FBusy := False;
 end;
 end;
 
 if not RecvMessage(500, MsgTyp, Msg, Params) then
 begin
 DoDisconnect;
 Break;
 end;
 case MsgTyp of
 0: begin
 FLastPing := GetTickCount64;
 if not FTCPBase.FIgnoreMessage then
 DoRecv(0, Msg, Params, nil);
 end;
 1: begin
 FLastPing := GetTickCount64;
 Size := StrToInt64Def(Params[Length(Params) - 1], -1);
 if Size > 0 then
 begin
 SetLength(Params, Length(Params) - 1);
 MS := TMemoryStream.Create;
 try
 MS.SetSize(Size);
 MS.Position := 0;
 if RecvStream(MS) then
 DoRecv(1, Msg, Params, MS);
 finally
 MS.Free;
 end;
 end
 end;
 2: begin
 FLastPing := GetTickCount64;
 Size := StrToInt64Def(Params[Length(Params) - 2], -1);
 FileName := Params[Length(Params) - 1];
 SetLength(Params, Length(Params) - 2);
 if Size > 0 then
 begin
 Path := AddDirSeparator(GetDownloadDir(FTCPBase.FDownloadDirectory));
 if DirectoryExists(Path) then
 begin
 Path := Path + FConnection.FUser  + '_'  + FileName;
 if RecvFile(Path, Size) then
 DoRecv(2, Msg, Params, nil, Path)
 end
 else
 DoError(rsInvalidDirectory, 0);
 end
 else
 FileCreate(Params[0] + Params[1]);
 end;
 end;
 if (FTCPBase.FTCPBaseType = tcpServer) then
 begin
 FCurPing := GetTickCount64;
 if (FCurPing - FLastPing > PingTimeoutServer) then
 begin
 DoDisconnect;
 Break;
 end;
 end
 else if (FTCPBase.FTCPBaseType = tcpClient) then
 begin
 FCurPing := GetTickCount64;
 if (FCurPing - FLastPing > PingTimeoutClient) then
 FTaskList.AddTask('MESSAGE', 'PING', [], nil, '');
 end;
 end;
 FDisconnected := True;
 while not Terminated do
 Sleep(100);
 end;
 
Программа в принципе работала, и общалась. Но как то медленно. Я понимал что это странно, но не придавал значения. Для мое задачи скорость не главное.. пока не уперся в одну проблему. О ней чуть позже. А вот тормоза живут здесь
- Код: Выделить всё
- if not RecvMessage(500, MsgTyp, Msg, Params) then
Чтение из сокета происходит каждый цикл с ожиданием 500мс. В принципе логично, но медленно. Кто играл в CS поймет. А если пытаться менять параметры таймингов тут и в прочих частях реализации работоспобность падает в ноль.
Сейчас реализация бегает шустро вот с таким вариантом(код черновой).
1. За один цикл производится отправка только одного сообщения из списка
2. За один цикл производится прием только одного сообщения.
3. За счет ниже приведенных SocketActive, SocketCanRead, SocketCanWrite, ReadBeginDialog проверяется живой ли вообще сокет, и есть ли что то в буфере, в случае записи есть ли место куда писать. Если есть работаем по долгому пути и читаем, пишем с длиииинными таймингами. А если все пусто или занято, то идем мимо до следующего повтора.
- Код: Выделить всё
- procedure TClientOnServerThread.Execute;
 var
 Msg     : String;
 iDebugCicle,
 iBadResult,
 IdleCounter,
 MsgTyp  : Integer;
 Params  : TStringArray;
 Size    : Int64;
 MS      : TMemoryStream;
 iStart,
 I,iWhileCount : Integer;
 FileName      : String;
 Path          : String;
 List          : TList;
 Task          : TTask;
 FLastIdleTick,
 FCurIdleTick,
 FLastTick,
 FCurTick,
 FLastTick2,
 FCurTick2     : QWord;
 DoIdleControl,
 DoPing        : Boolean;
 begin
 FCurTick    := GetTickCount64;
 FLastTick   := FCurTick;
 FCurTick2   := 0;
 FLastTick2  := 0;
 FCurIdleTick   := FCurTick;
 FLastIdleTick  := FCurTick;
 DoPing         := False;
 DoIdleControl  :=False;
 iWhileCount    := 100;
 
 
 if (FTCPBase.TCPBaseType = tcpServer)and(FTCPBase.ActiveOrConnected) then
 BroadcastConnection(FConnection.FUser);
 
 while not Terminated do
 begin
 MsgTyp      :=-1;
 Msg         :='';
 Params      :=[];
 IdleCounter :=0;
 FCurTick    :=GetTickCount64;
 iStart      :=0;
 
 if not SocketActive then
 begin
 DoDisconnect;
 Break;
 end;
 
 //Write data
 if (not FBusy) and (not NeedToBreak) then
 begin
 FBusy := True;
 try
 List := FTaskList.TaskList.LockList;
 try
 iDebugCicle:=0;
 //Отправка
 if (List.Count>0)and(SocketCanWrite) then
 begin
 i:=0;
 iStart:=SocketActive;
 if iStart=1 then
 begin
 if ProcessTask(TTask(List[I])) then
 begin
 inc(iDebugCicle);
 inc(IdleCounter);
 Task := TTask(List.Items[I]);
 if Task <> nil then
 FTaskList.DeleteTask(Task);
 List.Delete(I);
 end;
 end;
 end;
 
 if iDebugCicle>1 then
 begin
 DoDebugMessage(format('Sended %d messages',[iDebugCicle]));
 end;
 finally
 FTaskList.TaskList.UnlockList;
 end;
 
 //Read data
 iDebugCicle:=0;
 i:=ReadBeginDialog;
 if i=1 then
 begin
 if not RecvMessage(TIMEOUT_LCICLE_RECVMESSAGE, MsgTyp, Msg, Params) then
 begin
 i:=-1;
 DoDisconnect;
 Break;
 end;
 end
 else if i=-1 then
 begin
 DoDisconnect;
 Break;
 end;
 
 if (i=1)and(not FNeedToBreak) then
 begin
 inc(iDebugCicle);
 case MsgTyp of
 0: begin
 if ShortCompareText(Msg,'PING')=0 then
 begin
 if Length(Params)=1 then
 begin
 if ShortCompareText(Params[0],'BEGIN')=0 then
 begin
 FTaskList.AddTask('PING', 'PING', ['END'], nil, '');
 end
 else if ShortCompareText(Params[0],'END')=0 then
 begin
 DoPing         := False;
 FCurTick2      := FCurTick;
 Ping           := FCurTick2 - FLastTick2;
 end;
 end;
 end
 else begin
 inc(IdleCounter);
 DoRecv(0, Msg, Params, nil);
 end;
 end;
 1: begin  //stream
 inc(IdleCounter);
 Size      := StrToInt64Def(Params[Length(Params) - 1], -1);
 if Size > 0 then
 begin
 SetLength(Params, Length(Params) - 1);
 MS := TMemoryStream.Create;
 try
 MS.SetSize(Size);
 MS.Position := 0;
 if RecvStream(MS) then
 DoRecv(1, Msg, Params, MS);
 finally
 MS.Free;
 end;
 end
 end;
 2: begin  //file
 inc(IdleCounter);
 Size      := StrToInt64Def(Params[Length(Params) - 2], -1);
 FileName  := Params[Length(Params) - 1];
 SetLength(Params, Length(Params) - 2);
 if Size > 0 then
 begin
 Path := AddDirSeparator(GetDownloadDir(FTCPBase.DownloadDirectory));
 if DirectoryExists(Path) then
 begin
 Path := ConcatPaths([Path,FileName]);
 if RecvFile(Path, Size) then
 DoRecv(2, Msg, Params, nil, Path)
 end
 else
 DoWriteErrorLog(rsInvalidDirectory, 0);
 end;
 end;
 end;
 end;
 
 if iDebugCicle>1 then
 begin
 DoDebugMessage(format('Readed %d messages',[iDebugCicle]));
 end;
 
 finally
 FBusy := False;
 end;
 
 if (FTCPBase.TCPBaseType = tcpServer)and (not NeedToBreak) then
 begin
 FCurIdleTick    := FCurTick;
 //Раз в KARINA_PING_PERIODONSERVER программа проверяет свой пинг
 if (Ping > KARINA_PING_DISCONNECT) then
 begin
 DoWriteLog(Format('Disconnect by high ping(Limit %d ms)',[KARINA_PING_DISCONNECT]));
 Sleep(10);
 DoDisconnect;
 Break;
 end
 else if (DoPing)and(FCurTick - FLastTick > KARINA_PING_PERIODONSERVER) then
 begin
 DoPing         := False;
 Ping           := 9999;
 DoWriteLog(Format('Disconnect by very high ping(Limit %d ms)',[KARINA_PING_PERIODONSERVER]));
 Sleep(10);
 DoDisconnect;
 Break;
 end
 else if (FCurTick - FLastTick > KARINA_PING_PERIODONSERVER)and (FTCPBase.ClientActionCount=0) then
 begin
 FLastTick  := FCurTick;
 FLastTick2 := FCurTick;
 DoPing     := True;
 FTaskList.AddTask('PING', 'PING', ['BEGIN'], nil, '');
 end;
 end;
 
 end;
 
 Sleep(10);
 
 dec(iWhileCount);
 if iWhileCount<=0 then
 begin
 iWhileCount:=100;
 Synchronize(@SynchronizeData);
 end;
 
 end; //цикл
 
 if (FTCPBase.TCPBaseType = tcpServer)and(FTCPBase.ActiveOrConnected) then
 BroadcastDisconnection(FConnection.FUser);
 
 DoWriteLog(Format(rsMessageClientEndLogStats,[FLogCounterMessage,FLogCounterError,FLogCounterInternal]));
 
 FDisconnected := True;
 while not Terminated do
 begin
 Sleep(100);
 end;
 end;
 
- Код: Выделить всё
- function TBaseThread.SocketActive: Boolean;
 begin
 Result := (FBlockSocket.Socket = INVALID_SOCKET) or (FBlockSocket.CanRead(0) and (FBlockSocket.WaitingData = 0));
 Result := not Result;
 end;
 
 function TBaseThread.SocketCanRead: Boolean;
 begin
 Result := ((FBlockSocket.Socket <> INVALID_SOCKET) And (FBlockSocket.CanRead(0)));
 end;
 
 function TBaseThread.SocketCanWrite: Boolean;
 begin
 Result := ((FBlockSocket.Socket <> INVALID_SOCKET) And (FBlockSocket.CanWrite(0)));
 end;
 
 function TBaseThread.ReadBeginDialog(ATimeOut: Integer): integer;
 var
 sRead: ShortString;
 begin
 Result   := 0;
 if not SocketActive then
 begin
 Result   := -1;
 end
 else if FBlockSocket.WaitingData>0 then
 begin
 Result   := 1;
 end;
 end;
 
Что пока не понимаю....Все работа по циклу с базовыми функциями отправки, но если я хочу сделать к примеру так
- Код: Выделить всё
- function TBaseThread.ReadBeginDialog(ATimeOut: Integer): integer;
 var
 sRead: ShortString;
 begin
 Result   := 0;
 sRead    := '';
 sRead    := FBlockSocket.RecvPacket(ATimeOut);
 if (FBlockSocket.LastError > 0) and (FBlockSocket.LastError <> WSAETIMEDOUT) then
 begin
 Result := -1;
 end
 else begin
 if ShortCompareText(sRead,'BEGIN')=0 then
 begin
 FBlockSocket.SendString('START');
 Result := 1;
 end;
 end;
 end;
 
т.е. клиент прислал короткое BEGIN, сервер ему отвечает START и тем самым переключается в режим "Я ТЕБЯ ВНИМАТЕЛЬНО СЛУШАЮ", то "BEGIN" о казывается и в последующих функциях чтения данных. Хотя по идее его уже извлекли из буфера и он не должен там сидеть. А функция чтения тупит.. и говорит что команду не знает такую. В исходниках работы есть Purge, но толку ноль от него...
вторая беда в частоте команд. Как я понимаю, мы пишем в сокет, если он пустой, если нет то ждем когда освободится. Но на деле кода который копаю получается что(образно) код
- Код: Выделить всё
- SendMessage('UPDATE',1,'Поле1','Значение 1');
 SendMessage('UPDATE',1,'Поле2','Значение 3');
 SendMessage('UPDATE',1,'Поле3','Значение 3');
не полностью доходит, а код
- Код: Выделить всё
- SendMessage('UPDATE',1,'Поле1','Значение 1');
 Sleep(100);
 SendMessage('UPDATE',1,'Поле2','Значение 3');
 Sleep(100);
 SendMessage('UPDATE',1,'Поле3','Значение 3');
Стабилен. (*Теперь по закону подлости я должен сам пойти и случайно увидеть причину этого всего)
Вот из-за непоняток работы буфера сокета и не полюбил Indy когда то.