Форум: "Сети";
Текущий архив: 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]?
Извиняюсь, если чего-то не вижу, но я не вижу.
← →
Медведъ (2006-07-18 11:17) [41]>bobah © (18.07.06 10:55) [40]
ничем не отличается, ступил, сорри
← →
Slym © (2006-07-18 12:08) [42]Зачем самоделку делаешь Чем TServerSocket не устроил?
← →
bobah © (2006-07-18 13:56) [43]> Зачем самоделку делаешь Чем TServerSocket не устроил?
Поздно, я уже сделал:). Плюс ко всему мне так понятнее.
← →
bobah © (2006-07-18 13:58) [44]Я ОЧЕНЬ люблю функции типа WaitForMultipleObject. В них можно event от других потоков получать, а для моих реализаций это очень удобно.
← →
Evgeny V © (2006-07-18 14:03) [45]Почитал - думается как быстро реагирует пользователь на изменения показаний приборов не критично, опаздание в плюс несколько секунд допустимо ? Если да, рекомендую модель предложенную Сергей М. А именно подумать о возможности использования клиент-серверной CУБД. Сокеты в явном виде в этом случае можно не использовать (не нужны просто).
В итоге имеем СУБД, сервис(ы) получающие данные от приборов и записывающие их в БД с указанием даты и времени измерения, клиентское приложение, которое по таймеру
(можно и по событиям-алертам субд, если субд поддерживает такой механизм, но лучше по таймеру на мой взгляд, почему -это отдельная тема)
получает обновления с БД, причем в том порядке сортировки, который вам наиболее удобен, т.е. проблема
"
> приходящие на сервер, уходили на другие клиенты в том порядке,
>
> в котором они приходят на сервер
" отпадает, клиент сам выбирает данные в нужном ему порядке.
← →
bobah © (2006-07-18 15:35) [46]> Evgeny V
При разработке данной системы, я так и предполагал, что все взаимодействия будут осуществлятся через СУБД, без всяких сокетов. При этом я очень много всего пересмотрел, передумал:
вплоть до того что сменил БД с MySQL4 -> MySQL5 -> MSDE2000. Да, у меня есть журнал событий с указанием времени всех изменений, по которму можно обновлять данные в клиентах, причем только необхадимую часть.
Потом я загорелся сокетами, даже уже не помню почему...
В данный момент все эти механизмы в средней части написания: сокеты, БД, опрос приборов через модем и т.д.
Поэтому сейчас возвращаться, так сказать, к началу не хочеться.
Поэтому окончательно я пришел к следующей схеме:
1) клиенты и сервер непосредственно взаимодействуют с базой данных, причем обе стороны могут её модифицировать.
2)Основное назначение сокетов это, так сказать, общение между клиентом и прибором в "реальном времени", причем эти данные не обязательно сохраняются в базе данных.Иниаторм общения может быть как сервер, так и клиент.
Побочное назначение сокетов (раз уж они есть) это передача ивэнтов с сервера на клиенты сигналящие, что пора обновить данные с БД. Причем ивэнты формируются НЕ от клиентов, а от БД.
3)сервер - это не сервер приложений, а сервер опроса приборов.
> опаздание в плюс несколько секунд допустимо?
Не кретично. Тепло, штука инерционная.
← →
Evgeny V © (2006-07-18 16:00) [47]Вот как раз и у нас есть датчик темпиратуры. Event - ну можно формировать аварийные например Event на сокетах или еще каким способом, хотя например в IB/FB или оракл есть соотвествующий механизм. Про MSDE2000 сказать не могу - не знаю. Но так как тепло штука инерционная и датчик возможно не пожарный, а просто мониторинг например состояния погоды или климатики в серверной например, то и без events можно обойтись на мой взгляд, а работать на обновление данных чисто по таймеру из БД.
Задача в этом случае (в случае без сокетов) значительно упрощается на мой взгляд. Хотя конечно условия и постановка вашей задачи вам видней
:-)
Cервер опроса приборов - и у нас он примерно так же зовется, программа опроса приборов выполнена в виде сервиса(ов).
Страницы: 1 2 вся ветка
Форум: "Сети";
Текущий архив: 2006.12.03;
Скачать: [xml.tar.bz2];
Память: 0.67 MB
Время: 0.044 c