Главная страница
Top.Mail.Ru    Яндекс.Метрика
Текущий архив: 2006.12.03;
Скачать: CL | DM;

Вниз

Как лучше реализовывать архитектуру клиент-сервер   Найти похожие ветки 

 
bobah ©   (2006-07-12 16:43) [0]

Здравствуйте.

Начну с описания. Существует у меня три вида программ. 1 - БД , 2 - сервер опроса приборов (клиент БД), 3 - клиентское ПО (ПО с которым взаимодействует пользователь). Это не трехзвенная модель в полном понимании.  Программа 3 допустим вставляет запись в БД, при этом обращается непосредственно в БД. В программе 3 есть журнал событий, котором отображаются изменения БД. Для того чтобы сообщить всем клиентам об изменениях прога 3 взаимодействует с 2 с помощью сокетов. То есть по сокетам передаются сообщения типа refresh. Клиент посылает серверу опроса сообщение, а сервер опроса рассылает это же сообщение всем остальным клиентам.
Теперь конкретнее. Я использую WinSock 2.2 асинхронную модель на событиях. С клиентом всё ясно, а вот с сервером не всё. Важно чтобы сообщения, приходящие на сервер, уходили на другие клиенты в том порядке, в котором они приходят на сервер. Как это обеспечить?
На данный момент серверная часть работает в одном отдельном потоке. Причем события read от всех клиентов обрабатываются в одной функции WSAWaitForMultipleEvents.


 
Сергей М. ©   (2006-07-12 17:00) [1]


> Как это обеспечить?


А что этому мешает ?


> три вида программ. 1 - БД


БД не является программой.


 
bobah ©   (2006-07-12 17:12) [2]

В частности волнует вопрос, события обрабатывютя после WSAWaitForMultipleEvents в том порядке в котором они наступили. Пример - сервер обрабатывает read от одного клиента, в это время наступило скажем три события, дале когда дело доходит до функции WSAWaitForMultipleEvents, события будут обрабатываться в том же порядке как они наступили или по порядку согласно массиву Events?
И ещё, если от сообщения одного клиента придет только часть, потом придет сообщение друго клиента, а далее оставшаяся часть сообщения первого клиента - такое может быть?

Хорошо БД - это служба, сервис какая разница, это не принципиально.


 
Сергей М. ©   (2006-07-12 17:20) [3]


> события будут обрабатываться в том же порядке как они наступили
> или по порядку согласно массиву Events?


Да.


> далее оставшаяся часть сообщения первого клиента - такое
> может быть?


Нет, не может, если конкретный ивент ассоциирован с конкретным клиентом.


 
bobah ©   (2006-07-12 17:35) [4]

1)Всё же не догнал, если есть несколько активных ивэнтов, то они будут обработаны в WSA_WAIT_EVENT_0+x в порядке согласно массиву Events? Или всё таки в порядке их наступления?

2)То есть ивэнт на рид сработает только тогда, когда всё сообщение от клиента будет в буфере? А как же разделения сообщений на пакеты?

Кстате используется протокол TCP.


 
Сергей М. ©   (2006-07-12 17:46) [5]


> 1)


Не обязательно.


> 2)


Нет.
Ивент сигнализирует лишь о непустом буфере.


> Кстати


 
bobah ©   (2006-07-12 17:57) [6]


  Events[0]:=Self.CloseEvent;
   Events[1]:=ServSockEvent;
   CountEvents := 2;
   // Асициируем событие Accept, переводимся в неблокирующий режим
   WSAEventSelect(ServSocket,Events[1],FD_ACCEPT);

   while not Terminated do
   begin
     sync := WSAWaitForMultipleEvents(CountEvents, @Events, false, INFINITE, false);
     case sync of
       WSA_WAIT_EVENT_0  : break;   // close event
       WSA_WAIT_EVENT_0+1:          // Accept
       begin
         WSAEnumNetworkEvents(ServSocket,Events[1],@NetworkEvents);
         if NetworkEvents.lNetworkEvents and FD_ACCEPT>0 then
         if NetworkEvents.iErrorCode[FD_ACCEPT_BIT]=0 then
         begin
           NewClientSock;
         end else
         begin
           // Error := "Ошибка подключения клиента";
         end;
       end;
       WSA_WAIT_EVENT_0+2..WSA_WAIT_EVENT_0+WSA_MAXIMUM_WAIT_EVENTS-1: // Клиентские сокеты
       begin
         ClientIndex := Sync - WSA_WAIT_EVENT_0;
         ClientSock := PClientSock(ClientConnects[ClientIndex-2]);
         WSAEnumNetworkEvents(ClientSock^.Socket,Events[ClientIndex],
           @NetworkEvents);

         if NetworkEvents.lNetworkEvents and FD_READ>0 then
         if NetworkEvents.iErrorCode[FD_READ_BIT]=0 then
         begin
           // Пришли данные, которые надо прочитать
           RecievePacket(ClientSock,ClientIndex-2);
         end else
         begin
           // Произошла ошибка. Надо сообщить о ней и завершить нить
           //CloseSocket(S);
         end;
         if NetworkEvents.lNetworkEvents and FD_CLOSE>0 then
         begin
           // Связь разорвана
           if NetworkEvents.iErrorCode[FD_CLOSE_BIT]=0 then
           begin
             // Свзяь закрыта корректно
           end else
           begin
             // Связь разорвана в результате сбоя сети
           end;
           // В любом случае надо закрыть сокет и завершить нить
           CloseClientSock(ClientIndex);
         end;
       end;
     end; // case
   end;


Для наглядности кусок кода.
Я чего-то весь запутался. Так как же обеспечить чтобы сообщения, приходящие на сервер, уходили на другие клиенты в том порядке, в котором они приходят на сервер?


 
Сергей М. ©   (2006-07-13 09:05) [7]


> уходили на другие клиенты


А где у тебя в коде это происходит ? Я не вижу ..
Чтение данных из некоего гнезда в коде фигурирует , а записи данных в некие гнезда нигде нет ..

Я вообще не вижу причин ставить под сомнение тот самый "порядок". TCP - поточно-ориентированный протокол, прием-передача данных в нем организована по принципу, сходному с FIFO (очередь). Если передатчик отправил данные в порядке A -> B -> C, то приемник их получит точно в таком же порядке временного следования.

Кстати, твой алгоритм терпит крах при кол-ве ивентов, большем или равном 64 - это ограничение для Wait-функций такого рода.


 
Сергей М. ©   (2006-07-13 09:10) [8]


> Как лучше реализовывать архитектуру клиент-сервер


Посмотри как реализован сервер в компоненте TServerSocket (scktcomp.pas), в режиме stThreadBlocking это весьма простая и не самая худшая реализация серверной транспортной логики.


 
bobah ©   (2006-07-13 09:41) [9]

Ну вобщем этот для начала код привел. Чтоб пальцем в облака не тыкать...

Вот оставшиеся функции... (возможно они еще сыроваты)

procedure TSocketServThread.RecievePacket(ClientSock: PClientSock; ClientIndex: Integer);
var RecvBytes: Integer;
   TimeBuff: Array[0..SOCK_RBUFF_LEN] of Byte;
begin
 with ClientSock^ do
 begin
   RecvBytes := Recv(Socket,RBuff,SOCK_RBUFF_LEN,0);
   if RecvBytes <> SOCKET_ERROR then
   begin
     Move(RBuff,TimeBuff[RIndex],RecvBytes);
     if TimeBuff[0] = RecvBytes + RIndex then
     begin
       // Пакет полнстью собран
       ProccessRecvPacket(ClientSock,TimeBuff,ClientIndex);
       RIndex := 0;
     end else
     begin
       // Пакет не полный
       RIndex := RecvBytes;
     end;
   end;
 end;  
end;

------------------------------------

procedure TSocketServThread.ProccessRecvPacket(ClientSock: PClientSock; Buff: Array of Byte; ClientIndex: Integer);
var SBuff: Array[0..SOCK_RBUFF_LEN] of Byte;
       i: Integer;
begin
 // Здесь по идее надо разбирать пакет
 // но в случае сервера просто смотрим, если код 01 (байт 2)
 // то это запрос на USER ID и над вернуть ответ с код OK - 1
 // c кодом ERRROR - 0;
 // байт 1 - 3 - длинна пакета
 // байт 2 - 1 - код пакета
 // байт 3 - STATE - байт состояния
 if Buff[1] = $01 then // Запрос User ID
 begin
   SBuff[0] := $03;
   SBuff[1] := $01;
   SBuff[2] := Byte(SetUserID(ClientSock,Buff[2])); // 1 - OK
   Send(ClientSock^.Socket,SBuff,3,0);
 end else
 begin
   // Оставшиеся сообщения раздаем всем остальным клиентам
   for i:=0 to ClientConnects.Count-1 do
   begin
     if (i = ClientIndex) or (PClientSock(ClientConnects[i])^.UserID = 0) then Continue;
     Send(PClientSock(ClientConnects[i])^.Socket,Buff,Buff[0],0);
   end;
 end;
end;

-------------------------------

procedure TSocketServThread.NewClientSock;
var NewClient: PClientSock;
   SizeSockAddr: Integer;
begin

 // Принимаем подключение
 New(NewClient);
 with NewClient^ do
 begin
   SizeSockAddr := SizeOf(SockAddr);
   Socket := Accept(ServSocket,@SockAddr,@SizeSockAddr);

   // Проверяем
   if Socket = INVALID_SOCKET then
   begin
     Shutdown(Socket,SD_SEND);
     KillClientSock(Socket);
     Dispose(NewClient);
     exit;
   end;

   // Создаем событие для данного клиентского сокета
   SockEvent := WSACreateEvent;
   if SockEvent = 0 then
   begin
     Shutdown(Socket,SD_SEND);
     KillClientSock(Socket);
     Dispose(NewClient);
     exit;
   end;

   // Инициализируем параметры
   RIndex := 0;
   UserID := 0;

   if CountEvents >= WSA_MAXIMUM_WAIT_EVENTS then
   begin
     // Превышен лимит подключений
     Shutdown(Socket,SD_SEND);
     KillClientSock(Socket);
     Dispose(NewClient);
     exit;
   end else
   begin
     // Если подключений мало
     Events[CountEvents] := SockEvent;
     WSAEventSelect(Socket,Events[CountEvents],FD_READ or FD_CLOSE);
     inc(CountEvents);
   end;

   ClientConnects.Add(NewClient);
 end;
end;


Как можно видеть из последней функции крах на 64 ивэнта не происходит.
Логику TCP я под сомнение не ставлю.

> Если передатчик отправил данные в порядке A -> B -> C, то приемник их получит точно в таком же порядке временного следования.

С этим я согласен. Не сомневаюсь что данные придут на сервер в том же порядке. Я сомневаюсь при моем коде данные уйдут на клиенты в том же порядке в коком пришли.

> Посмотри как реализован сервер в компоненте TServerSocket (scktcomp.pas)

Посмотрю


 
Сергей М. ©   (2006-07-13 10:02) [10]


> bobah ©   (13.07.06 09:41) [9]


Отложи на время свои сомнения по поводу порядка.
В коде как минимум 2 серьезных ошибки.

1. Аккумулирующий буфер приема TimeBuff не должен быть локальным - время жизни лок.переменной равно времени жизни процедуры.

2. Отсутствует анализ результата вызова ф-ции Send(), в то время как он  обязателен !


 
bobah ©   (2006-07-13 10:16) [11]

> 1. Аккумулирующий буфер приема TimeBuff не должен быть локальным - время жизни лок.переменной равно времени жизни процедуры.

А нам больше и не нужно. TimeBuff наполняется в RecievePacket испоьлзуется  ProccessRecvPacket... А-А-А или вы хотите сказать, что при вызове Send в неблокирующем режиме TimeBuff уже может не существовать? Если так то не хорошо. А как можно бороться?

>  2. Отсутствует анализ результата вызова ф-ции Send(), в то время как он  обязателен !

В смысле на ошибки. Можно подробнее?

P.S. Мой код имеет ли право на жизнь для моих целей?


 
Сергей М. ©   (2006-07-13 10:51) [12]


> bobah ©   (13.07.06 10:16) [11]


А каков тогда смысл статической переменно RIndex, если она после возврата из RecievePacket() содержит индекс элемента уже несуществующего массива ?


> Можно подробнее?


Справку по Send() ты уже проштудировал ?


> Мой код имеет ли право на жизнь для моих целей?


Конечно имеет, но его еще доводить и доводить до ума.


 
bobah ©   (2006-07-13 13:43) [13]

Я переписал код.


type
  PClientSock = ^TClientSock;
  TClientSock = record
   Socket: TSocket;
   SockAddr: TSockAddrIn;
   RBuff: Array[0..255] of Byte;
   SBuff: Array[0..255] of Byte;
   SendReady: Boolean;
   RIndex: Integer;
   UserID: Byte;
   SockEvent: TWSAEvent;
 end;

---------------------------------

procedure TSocketServThread.RecievePacket(ClientSock: PClientSock; ClientIndex: Integer);
var RecvBytes,SendBytes: Integer;
begin
 with ClientSock^ do
 begin
   if UserID = 0 then  // Если этот клиент не авторизован
   begin
     // Если клиент не подключен может придти только запрос на авторизацию
     // поэтому читаем только три байта
     RecvBytes := Recv(Socket,RBuff,3,0);
     if RecvBytes <> SOCKET_ERROR then
     begin
       Move(RBuff,SBuff[RIndex],RecvBytes);
       if SBuff[0] = RecvBytes + RIndex then
       begin
         // Пакет полностью получен
         RIndex := 0;
         SBuff[2] := Byte(SetUserID(ClientSock,SBuff[2])); // 1 - OK
         SendBytes := Send(Socket,SBuff,3,0);
         if SendBytes <> SOCKET_ERROR then
         begin
           //if SendBytes < 3 then
         end else
         begin
           // Будем обробатывать ошибки потом
           // CloseSocket...
         end;
       end else
       begin
         // Пакет получен не полностью
         RIndex := RecvBytes + Rindex;
       end;
     end;
   end else // Если это авторизованный пользователь
   begin
     RecvBytes := Recv(Socket,RBuff,SOCK_RBUFF_LEN,0);
     Move(RBuff,SBuff,RecvBytes);
     // Сколько сервер данных прочитал столько и отправил другим
     // клиентам. Сам клиент приведет в порядок пакеты.
     for i:=0 to ClientConnects.Count-1 do
     begin
       if (i = ClientIndex) or (PClientSock(ClientConnects[i])^.UserID = 0) then Continue;
       SendBytes := Send(PClientSock(ClientConnects[i])^.Socket,SBuff,RecvBytes,0);
       if SendBytes <> SOCKET_ERROR then
       begin
         //if SendBytes < RecvBytes then;
       end else
       begin
         // Будем обробатывать ошибки потом
         // CloseSocket...
       end;
     end;
   end;
 end;

-----------------------------------

procedure TSocketServThread.Execute;
var Sync: Cardinal;
   NetworkEvents: TWSANetworkEvents;
   ClientIndex: Integer;
   ClientSock: PClientSock;
begin
 if FOpened then
 begin
   Events[0]:=Self.CloseEvent;
   Events[1]:=ServSockEvent;
   CountEvents := 2;
   // Асициируем событие Accept, переводимся в неблокирующий режим
   WSAEventSelect(ServSocket,Events[1],FD_ACCEPT);

   while not Terminated do
   begin
     sync := WSAWaitForMultipleEvents(CountEvents, @Events, false, INFINITE, false);
     case sync of
       WSA_WAIT_EVENT_0  : break;   // close event
       WSA_WAIT_EVENT_0+1:          // Accept
       begin
         WSAEnumNetworkEvents(ServSocket,Events[1],@NetworkEvents);
         if NetworkEvents.lNetworkEvents and FD_ACCEPT>0 then
         if NetworkEvents.iErrorCode[FD_ACCEPT_BIT]=0 then
         begin
           NewClientSock;
         end else
         begin
           // Error := "Ошибка подключения клиента";
         end;
       end;
       WSA_WAIT_EVENT_0+2..WSA_WAIT_EVENT_0+WSA_MAXIMUM_WAIT_EVENTS-1: // Клиентские сокеты
       begin
         ClientIndex := Sync - WSA_WAIT_EVENT_0;
         ClientSock := PClientSock(ClientConnects[ClientIndex-2]);
         WSAEnumNetworkEvents(ClientSock^.Socket,Events[ClientIndex],
           @NetworkEvents);

         if NetworkEvents.lNetworkEvents and FD_READ>0 then
         if NetworkEvents.iErrorCode[FD_READ_BIT]=0 then
         begin
           // Пришли данные, которые надо прочитать
           RecievePacket(ClientSock,ClientIndex-2);
         end else
         begin
           // Произошла ошибка. Надо сообщить о ней и завершить нить
           //CloseSocket(S);
         end;

         if NetworkEvents.lNetworkEvents and FD_WRITE>0 then
         if NetworkEvents.iErrorCode[FD_WRITE_BIT]=0 then
         begin
             // Данные успешно записались
         end else
         begin
             // Ошибки при записи произошли
             // CloseSocket...
         end;

         if NetworkEvents.lNetworkEvents and FD_CLOSE>0 then
         begin
           // Связь разорвана
           if NetworkEvents.iErrorCode[FD_CLOSE_BIT]=0 then
           begin
             // Свзяь закрыта корректно
           end else
           begin
             // Связь разорвана в результате сбоя сети
           end;
           // В любом случае надо закрыть сокет и завершить нить
           CloseClientSock(ClientIndex);
         end
       end;
     end; // case
   end;
 end; // FOpened
end;



Функцию Send() проштудировал.
TimeBuff = SBuff вынес из процедуры, теперь существует она пока существует сокет.
В случае если произошла ошибка Send закрываем соответсвующий
сокет. Пока конкретные ошибки не рассматриваю, рассмотрю их поже.
Что я ещё упустил? Нужно ли ещё как-то следить за Send()?


 
Сергей М. ©   (2006-07-14 08:22) [14]


> TimeBuff = SBuff вынес из процедуры


Уже лучше.


> В случае если произошла ошибка Send закрываем соответсвующий
> сокет


> Что я ещё упустил?


Упустил самое главное - не проанализировал причину ошибки и тут же ничтоже сумняшеся бросился закрывать соединение.
А ведь ошибка эта вполне могла быть связана с достижением лимита размера буфера передачи !

Цитата из справки:

Return Values

If no error occurs, send returns the total number of bytes sent. (Note that this can be less than the number indicated by len.) Otherwise, a value of SOCKET_ERROR is returned, and a specific error code can be retrieved by calling WSAGetLastError.

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


 
bobah ©   (2006-07-14 09:47) [15]

Так всё становится яснее.

> следует повторить неудачную передачу позднее

Для этого необхадимо создать дополнительный буффер для отправки запоздавших сообщений? Или смещать буффер SBuff на Sindex какой-нибудь?
Да, кстате, а как такая ситуация может возникнуть?

И вот теперь насчет синхронизации можно нормально сформулировать вопрос.

1)Допустим наступил FD_READ от ClientSock1, началась обработка пакета функция RecievePacket(). Во время выполнения RecievePacket() от ClientSock1, взевелись события FD_READ от ClientSock2 и ClientSock3. После выполнения RecievePacket() от ClientSock1. Мы имеем в WSAWaitForMultipleEvent два FD_READ. И вот собственно вопрос, можно ли при такой ситуации гарантировать что данные прочитаются в RecievePacket()
в том порядке, в коком они пришли, т.е. сначала пакет от ClientSock2 и только потом от ClientSock3?

2)Допустим ситуация следующая от ClientSock1 пришел ну очень большой пакет за один присест данные в RecievePacket() прочитаться не смогли. Во время обработки данных от ClientSock1 в RecievePacket(), взевлось событие FD_READ от ClientSock2 с ну очень маленьким пакетом. В функции WSAWaitForMultipleEvents наступает событие от ClientSock2, в результате пришедшие данные от ClientSock2 обрабатываются в RecievePacket() за один присест. За тем приходит оставшаяся часть данных от ClientSock1.
Таким образом на все клиенты приходят данные следующего вида:
 1/2*ClientSock1 - > 1*ClientSock2 -> 1/2*ClientSock1
Вопрос: Такое может случиться?


 
Сергей М. ©   (2006-07-14 10:46) [16]


> как такая ситуация может возникнуть?


Очень просто.
Ф-ция Send() в действительности не занимается фактической передачей данных, ее задача - поместить указанные данных в некую внутреннюю очередь передачи (буфер передачи), которую неявно создает ядро winsock. Очередь индивидуальна для каждого сокета, размер ее фиксирован в каждый момент времени и по умолчанию равен 8К (зависит от ряда иных важных общесистемных параметров).  Очередь освобождается с головы по мере фактической передачи имеющихся в ней данных. Попытка поместить в хвост очереди данные общего размера (т.е. при однократным или многократном последовательном вызове ф-ции send), превышающего свободное место в очереди, приводит к отказу самого последнего по времени вызова ф-ции send, при котором был достигнут лимит.
Если возникает такая ситуация, winsock сообщает об этом кодом отказа WSAGetLastError = WSAEWOULDBLOCK и впоследствии обязательно известит о фактическом освобождении очереди событием FD_WRITE. Кр.того, событие FD_WRITE возникает однократно всякий раз при успешном установлении соединения, сигнализируя о готовности сокета к передаче.


> Для этого необхадимо создать дополнительный буффер для отправки
> запоздавших сообщений? Или смещать буффер SBuff на Sindex
> какой-нибудь?


Самым простым и очевидным решением будет использование не буфера (тем более фиксированного размера), а стрима (например, TMemoryStream), фактически представляющего "резиновую" (варьируемого размера) локальную очередь.
Данные, которые требуется передать, записываются в хвост стрима в любом количестве в порядке поступления (очередь растет). Данные, которые передаются на вход ф-ции Send(), извлекаются из головы стрима порциями размером не более размера внутреннего буфера передачи winsock (см. GetSockOpt(SO_SNDBUF)) (очередь сокращается при успешном выполнении Send()


> Такое может случиться?


Да, может.
Но я не вижу в этом никакой проблемы.


 
atruhin ©   (2006-07-14 14:03) [17]

А с какой БД работаешь? Вроде все многопользовательские БД поддерживают отправку event. И все уже реализованно до тебя.


 
bobah ©   (2006-07-14 14:08) [18]

Хорошо с Send() разобрались. Код в процессе написания... Завтра полужу его сюда. Спасибо за помощь.

Цель задачи: сокеты в моей системе нужны чтобы своевременно обновлять данные в клиентах. Т.е. один клиент скажем добавил запись в БД и передал сообщение серверу, чтобы последний разослал это сообщение это остальным клиентам. Мне бы хотелось чтоб в этих сообщениях передовалась точная информация об изменениях в БД, чтоб клиент менял у себя в контролах только нужные записи.

> Но я не вижу в этом никакой проблемы.

Есть решение?

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

Однако, вариант со строгой последовательностью стал мне ПРИНЦИПИАЛЬНО интересен.

Что думаем об этом?


 
bobah ©   (2006-07-14 14:10) [19]

> atruhin

MS SQL Server 2000.


 
Сергей М. ©   (2006-07-14 14:49) [20]


> сокеты в моей системе нужны чтобы своевременно обновлять
> данные в клиентах


Понятно.
Выбрось затею из головы - дурная она.


> Есть решение?
>


Проблему и решать не надо, ибо она надумана по причине плохой твоей ориентации в механизме транзакций.

Вопрос по сути д.б. задан не в Сетях, а в Базах.


 
Сергей М. ©   (2006-07-14 14:52) [21]

Зачем вообще тебе те самые сокеты ?

Ты сочиняешь механизм для ЛВС или для глоб.сети ?


 
bobah ©   (2006-07-14 14:55) [22]

> Проблему и решать не надо, ибо она надумана по причине плохой твоей ориентации в механизме транзакций.

Что-то не пойму причем здесь транзакции.

>  Вопрос по сути д.б. задан не в Сетях, а в Базах.

Я вообще-то его в WinApi задал:)


 
bobah ©   (2006-07-14 14:55) [23]

> Ты сочиняешь механизм для ЛВС или для глоб.сети ?

Для ЛВС


 
Сергей М. ©   (2006-07-14 14:57) [24]

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


 
Сергей М. ©   (2006-07-14 14:58) [25]


> Что-то не пойму причем здесь транзакции.


Все с тобой понятно.
Дальнейший разговор бесплоден.
До встречи в Базах.


 
Сергей М. ©   (2006-07-14 15:01) [26]


> Я вообще-то его в WinApi задал


тем болле не понятно, что тебя заставило задать вопрос в WinApi.

Суть вопроса, как выясняется, не имеет к WinApi никакого отношения - ты концептуально "плаваешь" в вопросах работы в многопользовательской СУБД.


 
bobah ©   (2006-07-14 15:26) [27]

Таа-к начались разногласия. Ну ничего я всё поясню.

1) Сервер, кроме обновлений, уведомляет юзеров о ходе процесса опроса приборов.

2) В клиентском присутствует журнал событий, который сообщает об события проихсодящих в системе, в том числе и об изменениях в БД. Обновлять БД кажду секунду или каждые 5 сек (неважно), мне показалось не лучшей идеей.

3) > тем болле не понятно, что тебя заставило задать вопрос в WinApi.

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

4) >  ... ты концептуально "плаваешь" в вопросах работы в многопользовательской СУБД...
Ошибка подтверждения транзакции известит 1-го юзера в т.ч. и о том, что другой юзер успел изменить ту самую запись, пока 1-й юзер телился с подтверждением. Первая же мысль нормального юзера - выполнить рифреш НД или конкретной записи в нем.
Пнервая же мысль ненормального программера - заср.ть 1-гоь юзера нотификациями о том, что кто-то его опередил.


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

> заср.ть 1-гоь юзера нотификациями о том, что кто-то его опередил.

Нет у меня нотификаций.

Прежде чем начинать наезжать можно и спросить.

P.S. Обновление в сокетах, пажалуй, только как дополнение к основной работе сокетов - управление приборам через серевер и GSM модем, который связан с приборами.


 
Сергей М. ©   (2006-07-14 15:33) [28]

Если у тебя ЛВС под управлением MSWindows, сокеты тебе нафих не нужны - для этой сети есь родные транспортныет технологии/механизмы.
Засим вопрос отпадает сам по себе.

А даже если те самые сокеты и нужны, то для этого существуют готовые компоненты, реализующие TCP/IP-транспорт. Все твои вопросы по этому транспорту моментально отпадают при использовании, например, Indy. Источники же твоей склонности именно к WSAPI тлобой не конкретизированы, поэтому дальнейший разговор лишен смысла.


 
bobah ©   (2006-07-14 15:43) [29]

> Источники же твоей склонности именно к WSAPI тлобой не конкретизированы

Это позволит мне разобраться в самой сути работы это технологии на практике. Кроме того, мне так больше нравиться. И, вероятно, плохого не случиться если я буду использовать именно их. Так хоть сразу видно что происходит. Ну ладно, это не важно.

А за помощь спасибо.


 
Сергей М. ©   (2006-07-14 16:04) [30]


> Это позволит мне разобраться в самой сути работы это технологии
> на практике


Для сей цели тебе был рекомендован scktcomp.pas.
Судя по вопросам, ты даже не пошевелился в этом направлении.
А зря.


 
Медвед   (2006-07-14 16:50) [31]

на скл.ру была статья на тему сабжа
суть ее в том, что клиент открывает дополнительное соединение с сервером бд в отдельном потоке и вызвает в нем некую процедуру
эта процедура ждет пока засигналит определенный именованый объект синхронизации - например Event
когда необходимо послать уведомления, в триггере или SP переводим объект в сигнальное состояние и все клиенты вызвавшие процедуру получают ответ
процедуры ожидания ивента и установки его в сигнальное состояние оформляются в виде ext stored procedures


 
bobah ©   (2006-07-17 13:31) [32]

> Для сей цели тебе был рекомендован scktcomp.pas.

За выходные просмотрел.

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

Возник вопрос, как справоцировать программу к ошибке WSAEWOULDBLOCK при вызове SEND()? Пока реализовано так:


procedure TSocketClientThread.Execute;
var ClientEvents: Array[0..2] of TWSAEvent;
   Sync,SendBytes: Cardinal;
   NetworkEvents: TWSANetworkEvents;
begin
 if FOpened then
 begin
   ClientEvents[0]:=Self.CloseEvent;
   ClientEvents[1]:=ClientSockEvent;
   ClientEvents[2]:=SuspendEvent;
   
   while not Terminated do
   begin
     sync := WSAWaitForMultipleEvents(3, @ClientEvents, false, INFINITE, false);
     case sync of
       WSA_WAIT_EVENT_0  : break;   // close event
       WSA_WAIT_EVENT_0+1:          // ClientSockEvent
       begin
         WSAEnumNetworkEvents(ClientSocket,ClientEvents[1],@NetworkEvents);

         if NetworkEvents.lNetworkEvents and FD_WRITE>0 then
         if NetworkEvents.iErrorCode[FD_WRITE_BIT]=0 then
         begin
           // Обработка пакета
           // TODO: Проверить на правильность отправки данных
           if TxData.Len <> 0 then
           begin
             SendBytes := Send(ClientSocket,TxData.Buff[0],TxData.Len,0);
             if SendBytes <> SOCKET_ERROR then
             begin
               TxData.Len := TxData.Len - SendBytes;
               SetLength(TxData.Buff,TxData.Len);
             end else
             begin
               if WSAGetLastError <> WSAEWOULDBLOCK then
               begin
                 CloseClientSocket;
               end;
             end;
           end;
         end else
         begin
           // Error := "Ошибка отправки данных";
           CloseClientSocket;
         end;
<---- Обрыв ---->

function TSocketClientThread.ProcRecvMsg: Boolean;
var SendBytes: Cardinal;
begin
 Result := True;
 if RxData.Buff[1] = $01 then
 begin
   SetLength(TxData.Buff,TxData.Len+3);
   TxData.Buff[TxData.Len] := $03;
   TxData.Buff[TxData.Len+1] := $01;
   TxData.Buff[TxData.Len+2] := Byte(SetUserID(RxData.Buff[2])); // 1-ok
   TxData.Len := TxData.Len+3;
 end;

 // Запись пакета
 SendBytes := Send(ClientSocket,TxData.Buff[0],TxData.Len,0);
 if SendBytes <> SOCKET_ERROR then
 begin
   TxData.Len := TxData.Len - SendBytes;
   SetLength(TxData.Buff,TxData.Len);
 end else
 begin
   if WSAGetLastError <> WSAEWOULDBLOCK then
   begin
     CloseClientSocket;
     Result := False;
   end;
 end;
end;


RxData.Buff,TxDataBuff: Array of Byte;

Для решения проблемы с своевременным обновлением данных решил копать в направлении:

> Медвед

Только, хочу сделать второе соединение на стороне сервера, т.к. использую MSDE 2000.


 
Медвед   (2006-07-17 14:56) [33]

>Возник вопрос, как справоцировать программу к ошибке WSAEWOULDBLOCK при вызове SEND()?
этого делать не нужно
при необходимости отправки данных нужно их добавить в очередь и после этого вызывать процедуру Ч которая будет пытаться отправлять данные из очереди
при событии ФД_ВРАЙТ просто вызываем Ч

>Только, хочу сделать второе соединение на стороне сервера, т.к. использую MSDE 2000.
а что у мсде2000 нет ограничений на локальные подключения а на удаленные есть ?


 
bobah ©   (2006-07-17 17:10) [34]

> а что у мсде2000 нет ограничений на локальные подключения а на удаленные есть ?

Нет, просто думал сделать одно дополнительное соединение на сервере, а на клиентах не делать, просто по сетке сообщать им о том, что пора обновляться. тогда у меня будет три клиента.

> этого делать не нужно

Просто, хотел эту ситуацию воспроизвести и посмотреть как ведет себя программа, делал на localhost, какой бы буффер не делал ( 200000 байт) все равно всё отправлялось.

>  при необходимости отправки данных нужно их добавить в очередь и после этого вызывать процедуру Ч которая будет пытаться отправлять данные из очереди
при событии ФД_ВРАЙТ просто вызываем Ч


Я привел в последнем сообщении код отправки SEND(), похоже?


 
Slym ©   (2006-07-18 06:08) [35]

WSAEWOULDBLOCK не будет... сокет у тебя и так блокирующий...


 
Slym ©   (2006-07-18 06:11) [36]

UDP бродкаст не пробовал?


 
Медведъ   (2006-07-18 08:51) [37]

>делал на localhost, какой бы буффер не делал ( 200000 байт) все равно всё отправлялось.
в реальной сети все будет сложнее

>Я привел в последнем сообщении код отправки SEND(), похоже?
нет

if SendBytes=SOCKET_ERROR then
begin
 if WSAGetLastError<><>WSAEWOULDBLOCK then
 begin
    CloseClientSocket;
    Result := False;
 end
else
begin
  TxData.Len := TxData.Len - SendBytes;
  SetLength(TxData.Buff,TxData.Len);
end


 
bobah ©   (2006-07-18 10:35) [38]

> WSAEWOULDBLOCK не будет... сокет у тебя и так блокирующий...

Сокет у меня не блокирующий, функция WSAEventSelect, вызывается раньше чем в Excute;

> UDP бродкаст не пробовал?

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

> в реальной сети все будет сложнее

Попробуем в реальной сетке.

Насчет кода:

То есть должно быть так:


// Запись пакета
SendBytes := Send(ClientSocket,TxData.Buff[0],TxData.Len,0);
if SendBytes <> SOCKET_ERROR then
begin
  TxData.Len := TxData.Len - SendBytes;
  SetLength(TxData.Buff,TxData.Len);
end else
begin
  if WSAGetLastError <> WSAEWOULDBLOCK then
  begin
    CloseClientSocket;
    Result := False;
  end else
  begin
    TxData.Len := TxData.Len - SendBytes;
    SetLength(TxData.Buff,TxData.Len);
  end;
end;
end;


В этом случае, если пытаться сократить буффер при ошибке WSAEWOULDBLOCK


TxData.Len := TxData.Len - SendBytes;
SetLength(TxData.Buff,TxData.Len);


То буффер, наборот, станет больше, т.к. SendBytes = SOCKET_ERROR = -1
Судя по справке на Send() в случа WSAEWOULDBLOCK надо пробовать отправлять данные позже


 
Медведъ   (2006-07-18 10:42) [39]

>bobah ©   (18.07.06 10:35) [38]
[37] посмотри еще раз, и сравни с [38]
в моем случае если WOULDBLOCK ничего не происходит, ф-ия просто возвращает управление, когда придет ФД_ВРАЙТ она будет вызвана вновь и попытается отправить данные из очереди


 
bobah ©   (2006-07-18 10:55) [40]

Тогда там должно быть наверное ; пропущена и end [37].

И чем тогда [37] отличается от моего кода приведенного в [32]?

Извиняюсь, если чего-то не вижу, но я не вижу.



Страницы: 1 2 вся ветка

Текущий архив: 2006.12.03;
Скачать: CL | DM;

Наверх




Память: 0.65 MB
Время: 0.053 c
15-1163498467
DelphiN!
2006-11-14 13:01
2006.12.03
Перенос Delphi6 на другой компьютер


15-1163494338
Calibr
2006-11-14 11:52
2006.12.03
Порт сети?


2-1163193479
Riply
2006-11-11 00:17
2006.12.03
Перечисление глобальных переменных проекта.


15-1163143464
Vlad Oshin
2006-11-10 10:24
2006.12.03
18 тыс руб в Москве, на 2 месяца исп. срока, кв 8тр на 2х


2-1163484998
nayer
2006-11-14 09:16
2006.12.03
текущая дата в Paradox