Взаимодействие клиента с сервером

Тип работы:
Курсовая
Предмет:
Программирование


Узнать стоимость

Детальная информация о работе

Выдержка из работы

[Type text][Type text][Type text]

СОДЕРЖАНИЕ

1. Исследование предметной области

1.1 Постановка задачи

1.3 Описание SQLite

1.2.1 Устройство и характеристики СУБД SQLite

1.2.2 Методы работы с базой данных

1.3 Описание PIPE под Windows

1.3.1 Общие понятия

1.3.2 Именованные каналы

1.3.3 Методы WinAPI для передачи данных

2. Замысел технического решения

2.1 Реализация взаимодействия через PIPE

2.2 Исполнение запросов к SQLite

3. Описание программы

3.1 Сервер

3.2 Клиент

3.3 API

Заключение

Список используемой литературы

ПриложениЯ

1. ИССЛЕДОВАНИЕ ПРЕДМЕТНОЙ ОБЛАСТИ

1.1 Постановка задачи

СУБД SQLite является легковесной встраиваемой, это значит, что все операции с данными выполняются внутри приложения без использования сервера базы данных. Исходный код данной СУБД находится в свободном доступе. Доступ к данным в базе происходит через подключения к базе данных, которые мы открываем через вызов соответственной функции DLL.

Для взаимодействия приложения с СУБД SQLite в настоящий момент используются различные интерфейсы, такие как встроенный интерфейс SQLite, JDBC, ODBC и т. п. Однако, нет реализации интерфейса, поддерживающего взаимодействие клиента с сервером СУБД при помощи Pipe под Windows.

Таким образом, целью курсового проекта является разработка API взаимодействия клиентских приложении? с сервером СУБД через Pipe под Windows.

Учитывая специфику выбранной СУБД и требование к реализации поддержки Pipe, приходится использовать трехзвенную модель взаимодействия клиентского приложения с СУБД. В выбранной модели запрос от клиентского приложения поступает в сервис среднего звена, который, обработав запрос, передает его на выполнение в СУБД SQLite. Результат также обрабатывается сервисом среднего звена, после чего передается клиенту.

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

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

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

1.2 Описание SQLite

сервер клиентский информация шифрование

1.2.1 Устройство и характеристики СУБД SQLite

СУБД SQLite является встраиваемой реляционной базой данных. SQLite не использует парадигму клиент-сервер, то есть SQLite не является отдельно работающим процессом, с которым взаимодействует программа, а предоставляет библиотеку, с которой программа компонуется и движок становится составной частью программы. Таким образом, в качестве протокола обмена используются вызовы функций (API) библиотеки SQLite. Такой подход уменьшает накладные расходы, время отклика и упрощает программу.

SQlite очень тщательно тестируется перед каждым выпуском новой версии. Данная СУБД устойчива к «утечкам памяти» и ошибкам дискового ввода/вывода.

SQLite доступна в исходных кодах (на языке C), использовать готовую DLL или в виде утилиты для командной строки.

Доступ к БД происходит через «подключения» к БД, который открывается через вызов соответствующей функции DLL. При открытии указывается имя файла БД. Если такого нету -- он автоматически создается.

Допустимо открывать множество подключений к одной и тоже БД (через имя файла) в одном или разных приложениях. Система использует механизмы блокировки доступа к файлу на уровне ОС.

SQLite может быть собрана в однопоточном варианте (параметр компиляции SQLITE_THREADSAFE = 0). В этом случае нельзя одновременно использовать несколько потоков, поскольку полностью отсутствует код синхронизации. Однако, в таком случае повышается производительность за счет экономии на алгоритмах синхронизации.

Проверить, есть ли многопоточность можно через вызов sqlite3_threadsafe (): если вернула 0, то это однопоточный SQLite.

По умолчанию, SQLite собран с поддержкой потоков (sqlite3. dll).

Есть два способа использования многопоточного SQLite: serialized и multi-thread.

Serialized (надо указать флаг SQLITE_OPEN_FULLMUTEX при открытии соединения). В этом режиме потоки могут как угодно дергать вызовы SQLite, никаких ограничений. Но все вызовы блокируют друг друга и обрабатываются строго последовательно.

Multi-thread (SQLITE_OPEN_NOMUTEX). В этом режиме нельзя использовать одно и то же соединение одновременно из нескольких потоков (но допускается одновременное использование разных соединений разными потоками). Обычно используется именно этот режим.

Важной особенностью транзакционных баз данных, таких как SQLite является «атомарная транзакция». Это означает, что-либо все изменения в базе данных в рамках одной транзакции вступают в силу, либо ни один из них не происходит. Так как, из-за физических ограничений, нельзя одновременно проводить запись в разные участки файла, механизм транзакций создает иллюзияю, что все операции, относящиеся в разные участки файла базы данных происходят мгновенно и одновременно.

В случае возникновения сбоя, вызванного отключением питания или сбоем системы, СУБД SQLite способна вернуть прежнее состояние базы данных до начала совершения транзакции.

1.2.2 Методы работы с базой данных

Для взаимодействия с базой данных SQLite используются стандартный набор функций, описанных в Windows DLL файле, поставляемом с БД. Также можно использовать отдельные модули, в которых реализованы функции под конкретный framework и язык программирования.

Основной задачей СУБД SQLite является оценка и выполнение полученного SQL запроса. Для подключения к СУБД в программе требуется иметь 2 объекта:

— Объект соединения (database connection object);

— Подготовленный объект оператора (prepared statement object).

Объект подключения и оператора обрабатываются небольшим набором функций из C/C++ интерфейса:

— sqlite3_open ();

— sqlite3_prepare ();

— sqlite3_step ();

— sqlite3_column ();

— sqlite3_finalize ();

— sqlite3_close ().

Для исполнения SQL выражения приложение должно выполнить следующие шаги:

1) Создание подготовленного оператора, используя sqlite3_prepare ().

2) Выполнение подготовленного оператора, используя sqlite3_step (), один или несколько раз.

3) Для запросов извлечение результатов, используя sqlite3_column () между двумя вызовами sqlite3_step ().

4) Уничтожение подготовленного оператора путем вызова sqlite3_finalize ().

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

Также в библиотеку SQLite входит утилита командной строки, которая позволяет пользователю вручную вводить и выполнять SQL запросы.

1.3 Описание PIPE под Windows

1.3.1 Общие понятия

В операционных системах Microsoft Windows для передачи данных между процессами используются каналы PIPE. Это средство позволяет организовать передачу данных между локальными процессами, а также между процессами, запущенными на различных рабочих станциях в сети. Существуют две разновидности каналов Pipe — именованные (Named Pipes) и анонимные (Anonymous Pipes). Именованным каналам при создании присваивается имя, которое доступно для других процессов. Зная имя какой-либо рабочей станции в сети, процесс может получить доступ к каналу, созданному на этой рабочей станции. Анонимные каналы обычно используются для организации передачи данных между родительскими и дочерними процессами, запущенными на одной рабочей станции или на «отдельно стоящем» компьютере.

Через канал можно передавать данные только между двумя процессами. Один из процессов создает канал, другой открывает его. После этого оба процесса могут передавать данные через канал в одну или обе стороны. Приложения могут выполнять над каналами Pipe синхронные или асинхронные операции, аналогично тому, как это можно делать с файлами. В случае использования асинхронных операций необходимо отдельно организовать синхронизацию доступа.

1.3.2 Именованные каналы

Named Pipe представляет собой канал для взаимодействия одного сервера и нескольких клиентов, подключаемых к нему по имени канала. Все клиенты подключаются по одному и тому же имени, на для каждого клиента создаются свои буферы и маркеры, и предоставляет отдельный канал для связи клиент — сервер. Такой подход позволяет использовать одновременно нескольким клиентам один и тот же канал. Если один серверный процесс использует несколько реализаций канала для связи с несколькими клиенсткими, то общее количество реализаций может быть меньше, чем потенциальное максимальное количество клиентов. Это связано с тем, что клиенты могут использовать реализации по очереди, если только они не пожелают связаться с серверным процессом все одновременно.

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

Именованные каналы не существуют постоянно, то есть не могут, в отличии от реализации pipe в Unix, быть созданы как специальные файлы. В ОС Windows имеют временные имена, которые автоматически могут быть уничтожены самой операционной системой после закрытия последней ссылки на канал.

Имена каналов в общем случае имеют следующий вид:

\ИмяСервераpipeИмяКанала

Именованные каналы могут быть использованы для организации взаимодействия приложений, работающих на одном устройстве, или между приложениями на разных компьютерах одной сети.

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

1.3.3 Методы WinAPI для передачи данных

Передача данных в именованный канал идет как при работе с файлом.

Канал Pipe может быть ориентирован либо на передачу потока байт, либо на передачу сообщений. В первом случае данные через канал передаются по байтам, во втором — отдельными блоками заданной длины. При создании канала можно определить, будет канал использоваться для чтения, записи или для чтения и записи.

Сервер не должен вызывать блокировку канала, пока клиент не закончит чтение / запись. В противном случае может возникнуть состояние гонки, что приведет к непредсказуемым результатам.

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

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

Для осуществления межпроцессного взаимодействия используются такие функции WinAPI как:

— Для создания именованного канал вызывается функция CreateNamedPipe:

HANDLE WINAPI CreateNamedPipe (

_In_ LPCTSTR lpName,

_In_ DWORD dwOpenMode,

_In_ DWORD dwPipeMode,

_In_ DWORD nMaxInstances,

_In_ DWORD nOutBufferSize,

_In_ DWORD nInBufferSize,

_In_ DWORD nDefaultTimeOut,

_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes

);

— Для ожидания подключения клиента на сервере вызывается функция ConnectNamedPipe:

BOOL WINAPI ConnectNamedPipe (

_In_ HANDLE hNamedPipe,

_Inout_opt_ LPOVERLAPPED lpOverlapped

);

— Для записи канал инициализируется подобно фалу, то есть используется функция CreateFile:

HANDLE WINAPI CreateFile (

LPCTSTR lpFileName,

_In_ DWORD dwDesiredAccess,

_In_ DWORD dwShareMode,

_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,

_In_ DWORD dwCreationDisposition,

_In_ DWORD dwFlagsAndAttributes,

_In_opt_ HANDLE hTemplateFile

);

— Для передачи данных в канал используется функция WriteFile:

BOOL WINAPI WriteFile (

_In_ HANDLE hFile,

_In_ LPCVOID lpBuffer,

_In_ DWORD nNumberOfBytesToWrite,

_Out_opt_ LPDWORD lpNumberOfBytesWritten,

_Inout_opt_ LPOVERLAPPED lpOverlapped

);

— Чтение из канала производится функцией ReadFile:

BOOL WINAPI ReadFile (

_In_ HANDLE hFile,

_Out_ LPVOID lpBuffer,

_In_ DWORD nNumberOfBytesToRead,

_Out_opt_ LPDWORD lpNumberOfBytesRead,

_Inout_opt_ LPOVERLAPPED lpOverlapped

);

— Закрытие канала производится функцией CloseHandle:

BOOL WINAPI CloseHandle (

_In_ HANDLE hObject

);

2. ЗАМЫСЕЛ ТЕХНИЧЕСКОГО РЕШЕНИЯ

Задача по реализации клиентского API к СУБД с помощью PIPE под Windows сводится к 2 основным этапам:

1) Реализация клиент-серверного API для взаимодействия при помощи PIPE;

2) Исполнение запроса от клиента на сервере СУБД.

В проекте реализованы SQL команды, не возвращающие ответ пользователю. То есть такие как Insert, Update, Delete и т. п. После исполнения клиент только получает либо ответ об успешном выполнении, либо информацию об ошибке.

2.1 Реализация взаимодействия через PIPE

Существует два вида каналов: именованные и анонимные. Анонимные используются для передачи между родительским и дочерним приложением. Так как в данном проекте используются независимые сервер и клиент, анонимные каналы использовать не представляется возможным.

Поэтому для передачи SQL запроса с клиентского приложения на сервер, где находится СУБД SQLite, используется метод взаимодействия при помощи именованных каналов. При запуске сервера создается именованные канал. После по названию канала клиентское приложение связывается с сервером

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

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

Клиент представляет собой консольное приложение. В нем происходит ввод SQL запроса к серверу, его передача, прием и отображение ответа.

Для взаимодействия клиентского и серверного приложения требуются следующие функции:

— Подключение клиента к серверу;

— Передача строки запроса, введенной в клиентском приложении пользователем;

— Прием ответа от сервера о результате исполнения или о возникших ошибках;

— Отключение клиента от сервера.

2.2 Исполнение запросов к SQLite

Во время запуска сервера происходит подключение к СУБД. При подключении создается дескриптор подключения, за счет которого выполнятся все запросы к базе данных.

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

После исполнения запроса проводится проверка на возникшие ошибки. Если произошла ошибка, то информация о ней передается на клиент, а СУБД никаких изменений в базу данных не вносит.

В случае успешного выполнения запроса клиенту передается ответ об успешном выполнении, а на сервере управление переходит к вызвавшему потоку.

3. ОПИСАНИЕ ПРОГРАММЫ

3.1 Сервер

Структурная схема сервера представлена на «рис. 1». Интерфейс сервера представлен на «рис. 2».

При запуске сервера создается именованный канал с именем \. \pipe\SamplePipe. Канал открывается для чтения и записи. Режим приема сообщений. Буферы размером в 512 байт.

Рис. 1. Структурная схема сервера

После успешного создания канала происходит подключение к базе данных при помощи функции sqlite3_open (). Работа ведется с базой данных my1_database. dblite. Если ее существует файла, то создается новая пустая база данных. В противном случае открывается уже существующая.

Так как сервер многопоточный, дескриптор подключения для всех потоков один. Таким образом, для всех клиентов существует одно подключение к базе данных.

Если все предыдущие шаги выполнены успешно, сервер переходит в ожидание подключения клиента. После подключения создается поток, в котором происходит обработка подключения. А основной поток переходит в ожидание нового подключения.

Блок-схема работы потока представлена на «рис. 3». В созданном потоке происходит чтение входящей строки. Для этого вызывается функция ReadFile (). Если во время чтения не произошло ошибок, то вызывается функция void GetAnswerToRequest (wchar_t* pchRequest, LPTSTR pchReply, LPDWORD pchBytes). Блок-схема работы функции GetAnswerToRequest представлена на рис. 4.

Так как API SQLite работает со строками типа char, а при работе с PIPE строки типа wchar_t. Поэтому перед передачей SQL запроса происходит приведение его типа к char. После передачи полученного запроса и его исполнения клиенту передается либо строка с ошибкой, либо ответ, что запрос исполнен успешно.

Рис. 2. Результат работы серверного приложения после запуска

Рис. 3. Структурная схема одного потока на сервере

Рис. 4. Структурная схема функции GetAnswerToRequest

3.2 Клиент

Результат запуска клиента представлен на «рис. 5». Его структурная схема клиента представлена на «рис. 6».

Основной задачей клиентского приложения является демонстрация возможностей разработанного API. Для этого создается объект server, конструкотору которого передается имя канала, к которому подключиться.

Рис. 5. Результат работы клиентского приложения после запуска

Рис. 6. Структурная схема клиентского приложения

Исполняемый запрос пользователь вводит с клавиатуры. После нажатия клавиши Enter Происходит передача запроса на сервер, его исполнение и возврат результата.

После исполнения запроса клиент прекращает свою работу.

3.3 API

Для взаимодействия с сервером реализован класс Pipe_SERVER, исходный код которого находится в файле «SQLite_API. h» и представлен в Приложении. Функции в API:

— int connect () — функция для подключения к серверу. Возвращает 0, если подключение прошло успешно. В случае ошибки происходит вывод текста ошибки и функция возвращает 1.

— int send_data (wchar_t *chRequest, int length) — функция для передачи строки запроса chRequest длиной length. В случае успешной передачи возвращает 0. Иначе возвращает 1 и отключает от сервера.

— int recieve_data () — функция, предназначенная для получения ответа от сервера. Возвращает 0 в случае успешного исполнения. В случае ошибки возвращает 0 и отключает от сервера.

— void cleanup () — функция для отключения от сервера.

Для использования функция API создается объект класса Pipe_SERVER, с помощью которого и происходит все взаимодействие.

В конструкторе класса происходит предварительная настройка подключения, то есть инициализация имени канала.

Для подключения к серверу требуется вызвать метод connect (). В этом методе с помощью функции WinAPI CreateFile () происходит инициализация канала на стороне клиента. То есть сохраняется HANDLE, через который происходит запись и прием сообщений.

С помощью метода send_data (wchar_t *chRequest, int length) производится передача строки chRequest длиной length байт. Передача запроса выполняется при помощи вызова функции WriteFile ().

Для получения ответа от сервера вызывается метод recieve_data (). С помощью функции ReadFile () происходит чтение из канала в переменную chResponse, являющуюся глобальной в классе. После успешного чтения прочитанное сообщение можно получить, обратившись к chResponse из кода клиента.

С помощью метода cleanup () производится отключение от сервера, вызывая функцию CloseHandle (). После закрытия соединения вся работа с объектом завершается.

Заново можно использовать существующий объект соединения, вызвав функцию connect (). Объект нельзя связать с другим сервером.

ЗАКЛЮЧЕНИЕ

В заключение следует сделать вывод, что использование Pipe на ОС Windows упрощает разработку различных клиент-серверных приложений. Засчет того, что в Pipe используются такие же методы WinAPI, как и для работы с файлами, время разработки существенно сокращается.

В ходе выполнения курсового проекта был разработан интерфейс, позволяющий с помощью Pipe в ОС Windows реализовать взаимосвязь клиентского приложения с сервером СУБД.

Данный интерфейс не требует привязки к определенной СУБД так как передается только SQL запрос. А сами алгоритмы работы с СУБД реализованы на стороне сервера. Есть возможность использовать на сервере любой интерфейс для работы с СУБД.

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

Так как разработанный API является прослойкой между клиентом и сервером и выполняет транспортные функции, то имеется возможность увеличения функциональности сервера и клиента без внесения изменений в API.

СПИСОК ИСПОЛЬЗУЕМОЙ ЛИТЕРАТУРЫ

1) http: //msdn. microsoft. com

2) http: //www. sqlite. org

3) Программирование для Windows NT. — Александр Фролов, Григорий фролов. Том 27, часть 2. М.: Диалог-МИФИ, 1996

4) Системы баз данных. Полныи? курс. — Гарсиа-Молина Г., Ульман Дж., Уидом Дж. Вильямс, 2003. -1088с.

5) C++ для профессионалов. — Николас А. Солтер, Майкл Л. Клепер. Диалектика, Вильямс, 2006

6) Using SQLite. — Jay A. Kreibich — O’Reilly Media 2010

ПРИЛОЖЕНИЕ 1

CppNamedPipeServer. cpp

#include < windows. h>

#include < stdio. h>

#include < tchar. h>

#include < strsafe. h>

#include «iostream»

#include < string>

#include «atlstr. h»

#include «sqlite3. h»

#define BUFSIZE 512

using namespace std;

DWORD WINAPI InstanceThread (LPVOID);

VOID GetAnswerToRequest (LPTSTR, LPTSTR, LPDWORD);

sqlite3 *db = 0; // хэндл объекта соединение к БД

char *err = 0;

int _tmain (VOID)

{

BOOL fConnected = FALSE;

DWORD dwThreadId = 0;

HANDLE hPipe = INVALID_HANDLE_VALUE, hThread = NULL;

LPTSTR lpszPipename = TEXT («\\. \pipe\SamplePipe»);

if (sqlite3_open («db1. dblite», & db))

printf («Error in open/create DB: %sn», sqlite3_errmsg (db));

for (; ;)

{

_tprintf (TEXT («nPipe Server: Main thread awaiting client connection on %sn»), lpszPipename);

hPipe = CreateNamedPipe (

lpszPipename, // pipe name

PIPE_ACCESS_DUPLEX, // read/write access

PIPE_TYPE_MESSAGE | // message type pipe

PIPE_READMODE_MESSAGE | // message-read mode

PIPE_WAIT, // blocking mode

PIPE_UNLIMITED_INSTANCES, // max. instances

BUFSIZE, // output buffer size

BUFSIZE, // input buffer size

0, // client time-out

NULL); // default security attribute

if (hPipe == INVALID_HANDLE_VALUE)

{

_tprintf (TEXT («CreateNamedPipe failed, GLE=%d. n»), GetLastError ());

return -1;

}

fConnected = ConnectNamedPipe (hPipe, NULL) ?

TRUE: (GetLastError () == ERROR_PIPE_CONNECTED);

if (fConnected)

{

printf («Client connected, creating a processing thread. n»);

// Create a thread for this client.

hThread = CreateThread (

NULL, // no security attribute

0, // default stack size

InstanceThread, // thread proc

(LPVOID) hPipe, // thread parameter

0, // not suspended

& dwThreadId); // returns thread ID

if (hThread == NULL)

{

_tprintf (TEXT («CreateThread failed, GLE=%d. n»), GetLastError ());

return -1;

}

else CloseHandle (hThread);

}

else

// The client could not connect, so close the pipe.

CloseHandle (hPipe);

}

return 0;

}

DWORD WINAPI InstanceThread (LPVOID lpvParam)

{

HANDLE hHeap = GetProcessHeap ();

TCHAR* pchRequest = (TCHAR*)HeapAlloc (hHeap, 0, BUFSIZE*sizeof (TCHAR));

TCHAR* pchReply = (TCHAR*)HeapAlloc (hHeap, 0, BUFSIZE*sizeof (TCHAR));

DWORD cbBytesRead = 0, cbReplyBytes = 0, cbWritten = 0;

BOOL fSuccess = FALSE;

HANDLE hPipe = NULL;

if (lpvParam == NULL)

{

printf («nERROR — Pipe Server Failure: n»);

printf («InstanceThread got an unexpected NULL value in lpvParam. n»);

printf («InstanceThread exitting. n»);

if (pchReply ≠ NULL) HeapFree (hHeap, 0, pchReply);

if (pchRequest ≠ NULL) HeapFree (hHeap, 0, pchRequest);

return (DWORD)-1;

}

if (pchRequest == NULL)

{

printf («nERROR — Pipe Server Failure: n»);

printf («InstanceThread got an unexpected NULL heap allocation. n»);

printf («InstanceThread exitting. n»);

if (pchReply ≠ NULL) HeapFree (hHeap, 0, pchReply);

return (DWORD)-1;

}

if (pchReply == NULL)

{

printf («nERROR — Pipe Server Failure: n»);

printf («InstanceThread got an unexpected NULL heap allocation. n»);

printf («InstanceThread exitting. n»);

if (pchRequest ≠ NULL) HeapFree (hHeap, 0, pchRequest);

return (DWORD)-1;

}

// Print verbose messages. In production code, this should be for debugging only.

printf («InstanceThread created, receiving and processing messages. n»);

hPipe = (HANDLE) lpvParam;

// Loop until done reading

while (1)

{

fSuccess = ReadFile (

hPipe, // handle to pipe

pchRequest, // buffer to receive data

BUFSIZE*sizeof (TCHAR), // size of buffer

& cbBytesRead, // number of bytes read

NULL); // not overlapped I/O

if (!fSuccess || cbBytesRead == 0)

{

if (GetLastError () == ERROR_BROKEN_PIPE)

{

_tprintf (TEXT («InstanceThread: client disconnected. n»), GetLastError ());

}

else

{

_tprintf (TEXT («InstanceThread ReadFile failed, GLE=%d. n»), GetLastError ());

}

break;

}

// Process the incoming message.

GetAnswerToRequest (pchRequest, pchReply, & cbReplyBytes);

// Write the reply to the pipe.

fSuccess = WriteFile (

hPipe, // handle to pipe

pchReply, // buffer to write from

cbReplyBytes, // number of bytes to write

& cbWritten, // number of bytes written

NULL); // not overlapped I/O

if (!fSuccess || cbReplyBytes ≠ cbWritten)

{

_tprintf (TEXT («InstanceThread WriteFile failed, GLE=%d. n»), GetLastError ());

break;

}

}

FlushFileBuffers (hPipe);

DisconnectNamedPipe (hPipe);

CloseHandle (hPipe);

HeapFree (hHeap, 0, pchRequest);

HeapFree (hHeap, 0, pchReply);

printf («InstanceThread exitting. n»);

return 1;

}

VOID GetAnswerToRequest (wchar_t* pchRequest,

LPTSTR pchReply,

LPDWORD pchBytes)

{

_tprintf (TEXT («Client Request String: «%s"n»), pchRequest);

wcout < < pchRequest < < _T («(wchar_t *)») < < endl;

size_t origsize = wcslen (pchRequest) + 1;

size_t convertedChars = 0;

const size_t newsize = origsize*2;

char *sqlreq = new char[newsize];

wcstombs_s (& convertedChars, sqlreq, newsize, pchRequest, _TRUNCATE);

cout < <"SQL request: «< < sqlreq < < endl;

wchar_t *response;

response = L" «;

if (sqlite3_exec (db, sqlreq, 0, 0, & err))

{

printf («SQL Error: %sn», err);

sqlite3_free (err);

response = L"SQL Error";

} else response = L"Success";

// Check the outgoing message to make sure it’s not too long for the buffer.

if (FAILED (StringCchCopy (pchReply, BUFSIZE, response)))

{

*pchBytes = 0;

pchReply[0] = 0;

printf («StringCchCopy failed, no outgoing message. n»);

return;

}

*pchBytes = (lstrlen (pchReply)+1)*sizeof (TCHAR);

}

ПРИЛОЖЕНИЕ 2

CppNamedPipeClient. cpp

#pragma region Includes

#include < stdio. h>

#include < windows. h>

#include «iostream»

#pragma endregion

#include «SQLite_API. h»

// The full name of the pipe in the format of \servernamepipepipename.

#define SERVER_NAME L". «

#define PIPE_NAME L"SamplePipe"

#define FULL_PIPE_NAME L"\" SERVER_NAME L"\pipe" PIPE_NAME

#define BUFFER_SIZE 1024

// Request message from client to server.

#define REQUEST_MESSAGE L"CREATE TABLE IF NOT EXISTS foo (a, b, c); INSERT INTO FOO VALUES (1,2,3); INSERT INTO FOO SELECT * FROM FOO; «

using namespace std;

int wmain (int argc, wchar_t* argv[])

{

wchar_t req_str[200];

Pipe_SERVER server (FULL_PIPE_NAME);

// Try to open the named pipe identified by the pipe name.

int err = server. connect ();

if (err == 1) {

cout < < «Error while connection»;

server. cleanup ();

system («pause»);

return 0;

}

wprintf (L"Enter Request: «);

wscanf (L"%[^rn]", req_str);

//

// Send a request from client to server

//

if (server. send_data (req_str, sizeof (req_str)) ≠0) {

cout < < «error while sending data»;

server. cleanup ();

system («pause»);

return 0;

} else {

cout < < «Success in sending"< <endl;

}

//

// Receive a response from server.

//

//

//Code from exhample

if (server. recieve_data () == 0)

wprintf (L"Recieved successfull. Data: «%s"n», server. chResponse);

//

server. cleanup ();

system («pause»);

return server. dwError;

}

ПРИЛОЖЕНИЕ 3

SQLite_API. h

#include < stdio. h>

#include < windows. h>

#include «iostream»

#pragma endregion

class Pipe_SERVER

{

private:

HANDLE hPipe;

LPCWSTR FULL_PIPE_NAME;

static const int BUFFER_SIZE = 1024;

public:

DWORD dwError;

TCHAR chResponse[BUFFER_SIZE]; //Answers from server

Pipe_SERVER (LPCWSTR PIPE_NAME){

hPipe = INVALID_HANDLE_VALUE;

dwError = ERROR_SUCCESS;

FULL_PIPE_NAME = PIPE_NAME;

}

int connect (){

while (TRUE){

hPipe = CreateFile (

FULL_PIPE_NAME, // Pipe name

GENERIC_READ | GENERIC_WRITE, // Read and write access

0, // No sharing

NULL, // Default security attributes

OPEN_EXISTING, // Opens existing pipe

0, // Default attributes

NULL // No template file

);

if (hPipe ≠ INVALID_HANDLE_VALUE)

{

wprintf (L"The named pipe (%s) is connected. n", FULL_PIPE_NAME);

break;

}

dwError = GetLastError ();

if (ERROR_PIPE_BUSY ≠ dwError)

{

wprintf (L"Unable to open named pipe w/err 0x%08lxn", dwError);

return 1;

}

// All pipe instances are busy, so wait for 5 seconds.

if (!WaitNamedPipe (FULL_PIPE_NAME, 5000))

{

dwError = GetLastError ();

wprintf (L"Could not open pipe: 5 second wait timed out. «);

return 1;

}

}

// Set the read mode and the blocking mode of the named pipe. In this

// sample, we set data to be read from the pipe as a stream of messages.

DWORD dwMode = PIPE_READMODE_MESSAGE;

if (!SetNamedPipeHandleState (hPipe, & dwMode, NULL, NULL))

{

dwError = GetLastError ();

wprintf (L"SetNamedPipeHandleState failed w/err 0x%08lxn", dwError);

return 0;

}

return 0;

}

//

int send_data (wchar_t *chRequest, int length){

DWORD cbWritten;

wprintf (L"Sizeof %d of message: %sn", length, chRequest);

;

if (!WriteFile (

hPipe, // Handle of the pipe

chRequest, // Message to be written

length, // Number of bytes to write

& cbWritten, // Number of bytes written

NULL // Not overlapped

))

{

dwError = GetLastError ();

wprintf (L"WriteFile to pipe failed w/err 0x%08lxn", dwError);

return 1;

cleanup ();

}

wprintf (L"Send %ld bytes to server: «%s"n», cbWritten, chRequest);

return 0;

}

int recieve_data (){

BOOL fFinishRead = FALSE;

do

{

DWORD cbResponse, cbRead;

cbResponse = sizeof (chResponse);

fFinishRead = ReadFile (

hPipe, // Handle of the pipe

chResponse, // Buffer to receive the reply

cbResponse, // Size of buffer in bytes

& cbRead, // Number of bytes read

NULL // Not overlapped

);

if (!fFinishRead & & ERROR_MORE_DATA ≠ GetLastError ())

{

dwError = GetLastError ();

wprintf (L"ReadFile from pipe failed w/err 0x%08lxn", dwError);

cleanup ();

return 1;

}

} while (!fFinishRead); // Repeat loop if ERROR_MORE_DATA

return 0;

}

void cleanup (){

// Centralized cleanup for all allocated resources.

if (hPipe ≠ INVALID_HANDLE_VALUE)

{

CloseHandle (hPipe);

hPipe = INVALID_HANDLE_VALUE;

}

std: :cout < < «Cleanup»;

}

};

ПоказатьСвернуть
Заполнить форму текущей работой