Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "Сети";
Текущий архив: 2006.12.03;
Скачать: [xml.tar.bz2];

Вниз

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

 
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;
Скачать: [xml.tar.bz2];

Наверх





Память: 0.64 MB
Время: 0.039 c
2-1163846665
Youngkoder
2006-11-18 13:44
2006.12.03
TStrings


5-1144519995
Суслик
2006-04-08 22:13
2006.12.03
Регистрация своих расширений IDE


1-1161160285
Ale-xs
2006-10-18 12:31
2006.12.03
Как узнать путь запуска Win сервиса из самого сервиса


15-1163715524
Adder
2006-11-17 01:18
2006.12.03
А я вот хочу кого-нибудь обидеть!


15-1163421348
Двоишник
2006-11-13 15:35
2006.12.03
Prolog





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский