Глава 4:Сетевое программирование (часть2)

Установка окружения Я могу дать вам одну рекомендацию, пригодную независимо от того, какую технологию вы выберете. В большинстве сетей, с которыми мне приходилось иметь дело, рабочие станции имеют собственный жесткий диск или несколько мегабайт дополнительной памяти. Вы повысите производительность, если направите все временные файлы FoxPro на локальный диск или на виртуальный диск вместо того, чтобы качать их по сети. Вставьте следующие команды в файл Config.Fp:


TMPFILES=C:\WORK
OVERLAY=C:\WORK
где C:\WORK - каталог на диске рабочей станции


Если у вас достаточно памяти для организации виртуального диска, способного вместить как рабочие файлы, так и FOXPROL.OVL, тем лучше. Именно так я поступаю при работе на своем компьютере. Прирост скорости весьма заметен. FOXPROL.OVL имеет размер примерно 1,5 Мб, и к нему обращаются каждый раз, когда необходимо выполнить одну из многочисленных команд, отсутствующих в корневом сегменте FOXPROL.EXE.

Чем больше у вас имеется памяти, тем лучше. Стандартная память исключительно важна для FoxPro, также как и большой объем расширенной (expanded) памяти - 4 МБ хорошо, но 6 или 8 МБ еще лучше. Похоже, что парни, работающие в Quarterdeck, получили знамение свыше о том, как нужно писать программы управления памятью. Вы немедленно ощутите разницу.

В многопользовательской среде вам, может быть, придется загружать команду DOS Share. Novell Netware 386, похоже, больше не требует этого при работе с FoxPro, но LANtastic и Invisible она нужна до сих пор, также как это может потребоваться для других сред. Если происходит блокировка сети, используйте Share, если нет, не нужно. Кстати, LANtastic задохнется, если вы не установите Share c указанием примерно 1000 файлов и 5000 блокировок. Не спрашивайте почему. Для четырех пользователей попробуйте F=5000 и L=12000.

И, наконец, добавьте EXCLUSIVE=OFF в файл CONFIG.FP. Вы можете получить аналогичный результат командой SET EXCLUSIVE OFF в вашей программе, но я обычно пишу свои многопользовательские приложения в однопользовательской среде, а затем переношу их на сеть. Приятно не думать о том, что каждый раз нужно устанавливать или снимать комментарий с первой строки программы.

Обработка ошибок блокировок файлов и записей

Если вы хотите обеспечить целостность каждой записи при редактировании, аксиомой является перехват ошибок исполнения с последующей выдачей сообщений, отражающих суть возникшего конфликта, при столкновении с ошибками под номерами 108, 109, 1503. Кроме того, вы можете отлавливать и другие ошибки с выводом меню для пользователя. В Листинге 4-1 приведена процедура обработки ошибок, которую использую я сам. Процедура была написана для моего журнала доктором Дэвидом Льюисом, разработчиком программ обработки мемо-полей для FoxBASE и FoxPro.

Приведенная процедура обрабатывает не только ошибки, специфические для многопользовательских приложений, но также и ряд других. Листинг 4-2 (Проверка работы обработчика ошибок для неудачной принудительной блокировки) содержит программу тестирования для этой процедуры.

Повторное использование записей

Во многих системах шансы на то, что два пользователя начнут одновременно редактировать одну и ту же запись либо весьма малы, либо просто равны нулю. В таком случае нет необходимости изобретать что-то особенное; REPLACE NEXT 1 обеспечит установку и снятие блокировки, а это все, что вам нужно. Однако, возникнут проблемы с удалением записей.

Если вы не можете использовать упаковку, что делать с удаленными записями? Наилучшим найденным мною решением является повторное их использование. Вот что происходит, когда пользователь удаляет запись:

  • Поля записи обнуляются.
  • В одно из полей заносится метка, позволяющая найти запись если пользователь захотел добавить новую.
  • Запись помечается как удаленная.
Я использую тильду "~", как символ на клавиатуре с наибольшим значением ASCII. После занесения этого символа в ключевое поле, запись оказывается в нижней части таблицы. Это позволяет вам обработать все предшествующие записи циклом вроде:

DO WHILE NOT EOF() AND KEY <> [~]

или

SCAN WHILE KEY <> [~],

где KEY - ключевое выражение. Всегда можно найти и задействовать ранее удаленную запись таким образом:

GO BOTTOM
IF KEY <> [~]
APPEND BLANK
ENDIF


Для обнуления полей в записи я использую следующие команды:

SCATTER MEMVAR BLANK
GATHER MEMVAR
REPLACE NEXT 1 ;
WITH [~],(KEY2 WITH [~],...,KEYn WITH [~])


Команда REPLACE NEXT 1 позволяет вам выполнить маленький трюк в многопользовательской среде: замена значений поля без принудительного блокирования записи.

В Листинге 4-3 (Пример повторного использования удаленных записей)приведен пример многопользовательской программы, которая прекрасно работает без каких-либо принудительных блокировок файлов или записей. Если вы, скажем, удалите запись 2, а после этого попытаетесь добавить новую командой ADD, вы увидите, что это снова запись номер 2. Она снова полна жизни и содержит новую информацию. Никаких упаковок, никаких удалений, все работает гладко.

Использовать ли прямое обращение к записи?

Мне иногда приходится иметь дело с программами, написанными так, что все поля GET обращаются непосредственно к полям записи файла. Это может быть причиной множества проблем при работе в сети. Я никогда, ни при каких обстоятельствах не обращаюсь к полям записи напрямую, если программа предназначена для работы в сети. Вам приходится ставить таймер для READ на тот случай, если кто-нибудь оставил программу в режиме ввода и ушел домой. Вы не можете заблокировать файл, если кто-нибудь обращается к полю через READ. В таком подходе просто нет необходимости. SCATTER MEMVAR/GATHER MEMVAR, разве это трудно?

Для облегчения использования такого подхода конструктор экранов имеет опцию Memory variables. Вам следует привыкнуть к такому подходу. Раньше мне приходилось открывать код и убирать псевдонимы перед именами переменных, они тут просто не нужны.

Обработка повреждений

При работе в сети есть еще одна проблема, которую я называю pilot error. Это событие происходит, когда пользователь слышит "...ое время семнадцать часов", выключает компьютер и уходит домой. Пользователи FoxPro не работают с терминалами как пользователи UNIX. Хоть они и туповатые, при работе с терминалами вы не испортите индексные файлы. FoxPro это может и почти наверняка сделает.

Постарайтесь научить пользователей правильному выходу из FoxPro. Пригрозите им физической расправой, если необходимо. Кроме того, есть кое-что, что можно сделать для снижения потенциального урона.

Снабдите ваши экраны ввода таймером (READ TIMEOUT). По истечении установленного периода времени READ автоматически завершается. Вы можете проверить как завершилась последняя команда READ вызовом функции READKEY(1), если функция возвращает 2, значит сработал таймер. То же самое можно сделать для BROWSE. Предлагаю вам стандартизированную функцию TIMEOUT(), приведенную в Листинге 4-4 (Пример обработки выхода из READ по таймеру)

Раньше я предпочитал сбрасывать содержимое буферов командой FLUSH после каждой группы команд REPLACE NEXT 1. Сотрудники службы технической поддержки Fox Software объяснили, что теперь в этом нет необходимости. Я не настолько хорошо разбираюсь в технической стороне дела, чтобы понять почему так. Хотя, если они так говорят - это хорошие новости. Так что пока можете не использовать FLUSH.

Заключение

Программирование для сети не является непреодолимой проблемой. Если вам нужно установить блокировку на файл или запись, то техника известна и проста. Для выполнения системных задач, таких как перестройка индексов или построение словаря данных системы, вам придется выключить всех остальных пользователей. Однако, для большинства приложений повторное использование ранее удаленных записей, команды REPLACE NEXT 1 и неприменение пяти страшных команд вполне достаточно.


 

Листинг 4-2

* Program-ID.....: MultiLoc.PRG
* Purpose........: Example of record and file locking in multiuser systems SET COLOR OF SCHEME 2 TO ;
W+/W ,N/W ,W+/W ,N/W ,N/W ,W+/R
* Disabled,Enabled,Borders,Titles,Message,Selected,Hotkeys

ON ERROR DO FIXERROR WITH ;
ERROR(),PROGRAM(),LINENO(),MESSAGE(),MESSAGE(1)

nchoice = 1

USE CUSTOMER && any file will do...

DO WHILE .T. && menu loop....

@ 23, 0 CLEAR
@ 23, 0 PROMPT [ Add ] MESSAGE [ Add a record] ;
COLOR ,GR+/B,,,GR+/B,W+/R
@ 23, $+1 PROMPT [ Edit ] MESSAGE [ Edit this record] ;
COLOR ,GR+/B,,,GR+/B,W+/R
@ 23, $+1 PROMPT [ Zap ] MESSAGE [ Erase all records in the file] ;
COLOR ,GR+/B,,,GR+/B,W+/R
@ 23, $+1 PROMPT [ Quit ] MESSAGE [ Leave this magnificent program];
COLOR ,GR+/B,,,GR+/B,W+/R

menu to nchoice

@ 23, 0 CLEAR

choice = IIF ( nchoice = 0 , [Q] , SUBSTR ( [AEZQ] , nchoice , 1 ) )

DO CASE

CASE choice = [E]

DO EditRec

CASE choice = [Z]

IF CONFIRM()
IF FLOCK()
ZAP
ENDIF
ENDIF

CASE choice = [Q]

EXIT

ENDCASE

ENDDO

RETURN



PROCEDURE EditRec

Cancelled = .F.
IF RLOCK()

SCATTER MEMVAR
DO EditVars && Force an error for demonstration

IF MOD(READKEY(),256) <> 12 .AND. .NOT. Cancelled
GATHER MEMVAR
DO DispRec
ENDIF

UNLOCK

ENDIF

RETURN


FUNCTION CONFIRM

PRIVATE What

DEFINE POPUP CONFIRM FROM 5, 33
DEFINE BAR 1 OF CONFIRM PROMPT [ Are you sure? ] SKIP
DEFINE BAR 2 OF CONFIRM PROMPT [\-] SKIP
DEFINE BAR 3 OF CONFIRM PROMPT [ Yes]
DEFINE BAR 4 OF CONFIRM PROMPT [\-] SKIP
DEFINE BAR 5 OF CONFIRM PROMPT [ No]

ON SELECTION POPUP CONFIRM DEACTIVATE POPUP CONFIRM

ACTIVATE POPUP CONFIRM

What = IIF ( [Yes] $ PROMPT() , .T. , .F. )
RELEASE POPUP CONFIRM

RETURN What