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

В этом примере мы разработаем простой сервер и простую клиентскую программу, ведущих между собой "неспешный" диалог. Клиента построим по технике Windows Forms , а сервер - Windows Service . Сервер будет иметь набор готовых маркированных ответов, ждать маркированных запросов клиентов и отвечать им соответствующими сообщениями. Это настроит нас на создание еще более сложной системы - просмотру дистанционных рисунков из БД, которой мы займемся позже.

Создание клиента

Начнем с клиентской программы, которая может запускаться во многих экземплярах ("http://msdn.microsoft.com/ru-ru/library/system.net.sockets.tcplistener.accepttcpclient.aspx ")


Таблица 19.7.
Элемент Свойство Значение
Form Text Client
Size 300; 300
ListBox (Name) listBox
Dock Top
Font Arial; 12pt
Items
  1. Привет!
  2. Лелик
  3. Как жизнь
  4. Оттопыремся сегодня?
  5. Ну тогда пока!
SelectionMode One
Size 292; 119
Button (Name) btnSubmit
AutoSize True
Font Arial; 10pt
Location 96; 127
Size 101; 29
Text Отправить
TextBox (Name) textBox
Dock Bottom
Location 0; 162
Multiline True
ScrollBars Vertical
Size 292; 105

Остальные нужные настройки элементов формы добавим программно.

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; // Дополнительные пространства имен using System.IO; using System.Net; using System.Net.Sockets; using System.Threading; namespace SimpleClient { public partial class Form1: Form { int port = 12000; String hostName = "127.0.0.1";// local TcpClient client = null;// Ссылка на клиента public Form1() { InitializeComponent(); // Выделить первый элемент списка listBox.SelectedIndex = 0; listBox.Focus(); // Контекстное меню для очистки TextBox ContextMenuStrip contextMenu = new ContextMenuStrip(); textBox.ContextMenuStrip = contextMenu; ToolStripMenuItem item = new ToolStripMenuItem("Очистить"); contextMenu.Items.Add(item); item.MouseDown += new MouseEventHandler(item_MouseDown); } // Отослать запрос и получить ответ private void btnSubmit_Click(object sender, EventArgs e) { if (listBox.SelectedIndices.Count == 0) { MessageBox.Show("Выделите сообщение"); return; } try { // Создаем клиента, соединенного с сервером client = new TcpClient(hostName, port); // Сами задаем размеры буферов обмена (Необязательно!) client.SendBufferSize = client.ReceiveBufferSize = 1024; } catch { MessageBox.Show("Сервер не готов!"); return; } // Записываем запрос в протокол AddString("Клиент: " + listBox.SelectedItem.ToString()); // Создаем потоки NetworkStream, соединенные с сервером NetworkStream streamIn = client.GetStream(); NetworkStream streamOut = client.GetStream(); StreamReader readerStream = new StreamReader(streamIn); StreamWriter writerStream = new StreamWriter(streamOut); // Отсылаем запрос серверу writerStream.WriteLine(listBox.SelectedItem.ToString()); writerStream.Flush(); // Читаем ответ String receiverData = readerStream.ReadLine(); // Записываем ответ в протокол AddString("Сервер: " + receiverData); // Закрываем соединение и потоки, порядок неважен client.Close(); writerStream.Close(); readerStream.Close(); } // Добавление строки, когда TextBox включен в режиме Multiline private void AddString(String line) { StringBuilder sb = new StringBuilder(textBox.Text); StringWriter sw = new StringWriter(sb); sw.WriteLine(line); textBox.Text = sw.ToString(); } // Очистка списка через контекстное меню void item_MouseDown(object sender, MouseEventArgs e) { textBox.Clear(); listBox.Focus(); } } }

Получив соединение с сервером, мы создаем два потока NetworkStream и упаковываем их в оболочки, удобные для управления чтением/записью. Обмен с сервером отображаем в протоколе TextBox . Для очистки протокола динамически создали контекстное меню.

Класс TcpClient , который мы использовали в коде, является высокоуровневой (и упрощенной) оболочкой сокета (класса Socket ). Если потребуется более низкоуровневое управление сокетом (более детальное), то ссылка на него хранится в свойстве TcpClient.Client . Но поскольку это свойство защищенное (protected ), то доступ к нему возможен только из производного от TcpClient класса.

Если сейчас запустить приложение SimpleClient , то оно будет работать, но при попытке что-то отослать на сервер, будет выдаваться сообщение, что сервер не готов. Сервера-то еще пока вообще нет, создадим его.

Создание сервера

Серверную программу сделаем резидентной по шаблону Windows Service , как мы это делали в предыдущем примере, хотя можно сделать и с интерфейсом, главное, чтобы он был запущен в единственном экземпляре на локальном компьютере. Если программа-сервер включена в глобальную сеть, то с используемым IP и портом она должна быть единственной в этой сети. Поэтому на использование сетевого IP для глобальной сети нужно получать разрешение.



using System; using System.ComponentModel; using System.Configuration.Install; using System.ServiceProcess; namespace SimpleServer { // Во время установки сборки следует вызвать установщик public partial class Installer1: Installer { private ServiceInstaller serviceInstaller; private ServiceProcessInstaller serviceProcessInstaller; public Installer1() { // Создаем настройки для Службы serviceInstaller = new ServiceInstaller(); serviceProcessInstaller = new ServiceProcessInstaller(); // Имя Службы для машины и пользователя serviceInstaller.ServiceName = "SimpleServerServiceName"; serviceInstaller.DisplayName = "SimpleServer"; serviceInstaller.StartType = ServiceStartMode.Manual;// Запуск вручную // Как будет запускаться Служба this.serviceProcessInstaller.Account = ServiceAccount.LocalService; this.serviceProcessInstaller.Password = null; this.serviceProcessInstaller.Username = null; // Добавляем настройки в коллекцию текущего объекта this.Installers.AddRange(new Installer { serviceInstaller, serviceProcessInstaller }); } } }

using System; using System.Collections.Generic; using System.Text; // Дополнительные пространства имен using System.IO; using System.Net; using System.Net.Sockets; using System.Threading; using System.ServiceProcess; using System.Collections; namespace SimpleServer { class Service1: ServiceBase { TcpListener server = null;// Ссылка на сервер int port = 12000; String hostName = "127.0.0.1";// local IPAddress localAddr; String answers = { "1. Ты кто?", "2. Привет, Лелик!", "3. Лучше всех!", "4. Конечно, на полную катушку", "5. До вечера!" }; // Конструктор public Service1() { localAddr = IPAddress.Parse(hostName);// Конвертируем в другой формат Thread thread = new Thread(ExecuteLoop); thread.IsBackground = true; thread.Start(); } private void ExecuteLoop() { try { server = new TcpListener(localAddr, port);// Создаем сервер-слушатель server.Start();// Запускаем сервер String data; // Бесконечный цикл прослушивания клиентов while (true) { if (!server.Pending())// Очередь запросов пуста continue; TcpClient client = server.AcceptTcpClient();// Текущий клиент // Сами задаем размеры буферов обмена (Необязательно!) // По умолчанию оба буфера установлены размером по 8192 байта client.SendBufferSize = client.ReceiveBufferSize = 1024; // Подключаем NetworkStream и погружаем для удобства в оболочки NetworkStream streamIn = client.GetStream(); NetworkStream streamOut = client.GetStream(); StreamReader readerStream = new StreamReader(streamIn); StreamWriter writerStream = new StreamWriter(streamOut); // Читаем запрос data = readerStream.ReadLine(); // Отправляем ответ int index; if (int.TryParse(data.Substring(0, data.IndexOf(".")), out index)) data = answers; else data = data.ToUpper(); writerStream.WriteLine(data); writerStream.Flush(); // Закрываем соединение и потоки, порядок неважен client.Close(); readerStream.Close(); writerStream.Close(); } } catch (SocketException) { } finally { // Останавливаем сервер server.Stop(); } } } }

Чтобы передать данные и получить ответ, клиентское приложение создает двунаправленный сокет (или два однонаправленных), в котором указывает адрес соединения с сокетом другого, серверного, приложения. Если соединение установлено (сервер работает), то клиентское приложение подключает к сокету сетевой поток NetworkStream и через него выполняет передачу и прием данных.

На другой стороне соединения сервер TcpListener в бесконечном цикле прослушивает очередь соединений с клиентами. Если какой-то клиент с ним соединился (server.Pending()!=false ), то сервер извлекает этого клиента методом AcceptTcpClient() - создает сокет для приема/передачи с готовым обратным адресом, создает двунаправленный поток (или два однонаправленных), затем читает запрос и передает ответ.



Обратите внимание, что если код работы нашей серверной программы не упаковать в отдельную нить Thread (поток выполнения), то в окне служб эта программа операционной системой запускаться не будет (попробуйте!). Причина в том, что в коде метода ExecuteLoop() в сервере используется бесконечный цикл прослушивания очереди запросов клиентов. Если этот цикл оставить в основном потоке выполнения (Thread ) приложения, то оно просто зациклится и не сможет само нормально завершиться. Поэтому код с циклом мы помещаем в отдельный поток (трэд) и делаем его фоновым, чтобы он закрывался вместе с основным потоком приложения (трэдом сервера).

Важное замечание

Поток NetworkStream является двухсторонним фиксированной длины. Методом GetStream() он только устанавливает адресное соединение между сокетами клиента и сервера. Но реальная его длина определяется сообщением отправляющей стороны. Можно для приема/передачи использовать один поток, но тогда длина сообщения, отправляемого сервером, не должна превышать длину сообщения, принятого им от клиента (чуть глаза не отсидел!). Поэтому мы и используем на каждой стороне два потока для раздельной однонаправленной передачи между двумя узлами сетевого соединения.

Пример 3. Клиент-серверное приложение просмотра рисунков из БД

На предыдущем простом примере мы познакомились (чуть-чуть) с пронципами создания сетевых приложений. А теперь построим более сложный пример, когда клиент запрашивает рисунки, а сервер извлекает их из хранилища и посылает клиенту. В Упражнении 7 нами было разработано три разных хранилища рисунков и три программы просмотра. В данном примере воспользуемся БД Pictures.my2.mdb с готовыми рисунками и на ее основе создадим сетевое приложение (для тех, кто не делал Упражнение 7 , БД прилагается в каталоге Source/Data ).

Построение клиента

Для клиента построим оконное приложение типа WPF с пользовательским интерфейсом, частично заимствованным из Примера 6 Упражнения 7 .


Сервер не готов, ждите! Мы пытаемся связаться, извините за неудобства...

Для вывода заставки с текстом о неготовности сервера мы применили элемент Viewbox , в который поместили еще один элемент Border с текстовым содержимым. Такой "огород" позволит увеличивать заставку пропорционально размеру окна. Однако введение элемента Viewbox начинает заметно притормаживать перерисовку интерфейса при перемещениях окна, потому что он пытается постоянно пересчитывать масштабы своих дочерних элементов. Имена мы присвоили только тем интерфейсным элементам, которыми собираемся управлять в процедурном коде.

using System; using System.Collections.Generic; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Media.Imaging; using System.Windows.Shapes; // Дополнительные пространства имен для Stream using System.IO; using IO = System.IO; // Псевдоним для адресации Path using System.Windows.Threading; // Для DispatcherTimer // Дополнительные пространства имен для Socket //using System.Net; using System.Net.Sockets; using System.Collections; // List namespace PicturesClientDB { public partial class Window1: Window { int port = 12000; String hostName = "127.0.0.1"; // local TcpClient client = null; // Ссылка на клиента String sendMessage = "!!!GetNames!!!"; // Запрос на список (позаковыристей) Char separator = { "#" }; // Для преобразования ответа в массив имен DispatcherTimer timer; // Таймер // Конструктор public Window1() { InitializeComponent(); // Создаем и запускаем таймер timer = new DispatcherTimer(); timer.Tick += new EventHandler(timer_Tick); timer.Interval = TimeSpan.FromSeconds(1); timer.Start(); } // Инициирует обращение к серверу void timer_Tick(object sender, EventArgs e) { Execute(listBox); } private void listBox_SelectionChanged(object sender, SelectionChangedEventArgs e) { Execute((ListBox)sender); } void Execute(ListBox lst) { // Заполняем список именами рисунков try { // Если сервер доступен, создаем клиента client = new TcpClient(hostName, port); } catch { // Сервер не готов, запускаем таймер и выходим if (Prompt.Visibility != Visibility.Visible) { Prompt.Visibility = Visibility.Visible; timer.Start(); } return; } switch (sendMessage) { case "!!!GetNames!!!": // Получаем и привязываем имена рисунков к списку lst.ItemsSource = GetNames(); // Выделяем первый элемент списка, чтобы вызвать SelectionChanged lst.SelectedIndex = 0; lst.Focus(); sendMessage = ""; break; default: // Скрываем сообщение и останавливаем таймер if (Prompt.Visibility == Visibility.Visible) { Prompt.Visibility = Visibility.Hidden; timer.Stop(); } // Получаем рисунок и отображаем пользователю кистью String name = lst.SelectedValue.ToString(); BitmapImage bi = new BitmapImage(); bi.BeginInit(); // Получаем от сервера рисунок и обертываем его в поток памяти bi.StreamSource = new MemoryStream(GetPicture(name)); bi.EndInit(); Pictures.picture.ImageSource = bi;// Передаем рисунок фону Border break; } } private String GetNames() { String names; // Создаем потоки сетевых соединений StreamReader readerStream = new StreamReader(client.GetStream()); StreamWriter writerStream = new StreamWriter(client.GetStream()); // Отсылаем запрос серверу writerStream.WriteLine(sendMessage); writerStream.Flush(); // Читаем ответ String receiverData = readerStream.ReadLine(); names = receiverData.Split(separator);// Преобразуем в строковый массив // Закрываем соединение и потоки, порядок неважен client.Close(); writerStream.Close(); readerStream.Close(); return names; } Byte GetPicture(String name) { // Создаем потоки сетевых соединений NetworkStream readerStream = client.GetStream(); StreamWriter writerStream = new StreamWriter(client.GetStream()); // Отсылаем запрос серверу writerStream.WriteLine(name); writerStream.Flush(); // Читаем ответ // ReceiveBufferSize - размер буфера для входящих данных // SendBufferSize - размер буфера для исходящих данных List list = new List(client.ReceiveBufferSize);// С приращением capacity Byte bytes = new Byte; // Размер буфера сокета int count = 0; // Порции входящих данных while ((count = readerStream.Read(bytes, 0, bytes.Length)) != 0) for (int i = 0; i < count; i++) list.Add(bytes[i]); // Преобразуем в массив результата bytes = new Byte; list.CopyTo(bytes); // Закрываем соединение и потоки, порядок неважен client.Close(); writerStream.Close(); readerStream.Close(); return bytes; } } // Для привязки к ресурсу class Pictures { // Поле public static ImageBrush picture = new ImageBrush(); static Pictures() { // Дежурный рисунок заставки picture.ImageSource = new BitmapImage(new Uri(@"flower2.jpg", UriKind.Relative)); picture.Stretch = Stretch.Fill; picture.Opacity = 1.0D; } // Привязываемое в интерфейсе свойство public static ImageBrush Picture { get { return picture; } } } }

Обратите внимание, что при отображении рисунков мы отказались от традиционного элемента Image , как это делали в предыдущем упражнении. А для разнообразия поступили совершенно нетрадиционно (по турецки). Теперь мы рисунки будем отображать кистью ImageBrush в фоне прямоугольника Border через привязанный к нему объект Pictures . Конечно, в жизни так извращаться вряд ли придется, но и такой вариант где-нибудь может пригодиться.


Заставка появится сразу же, как будет обнаружен факт отсутствия или остановки сервера. А после обнаружения сервера заставка исчезнет. Этот механизм немедленно сработает благодаря используемому нами системному таймеру . Однако, сервера пока еще совсем нет и следует его изготовить.

Построение сервера БД как службу
  • Командой File/Add/New Project добавьте к решению NetworkStream новый проект с именем PicturesServerDB типа Windows Service


using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.ServiceProcess; using System.Text; // Дополнительные пространства имен для ADO.NET using System.Data.OleDb; using System.Data.Common; // Дополнительные пространства имен using System.IO; using System.Net; using System.Net.Sockets; using System.Threading; using System.Collections; namespace PicturesServerDB { public partial class Service1: ServiceBase { int port = 12000; String hostName = "127.0.0.1"; // local IPAddress localAddr; TcpListener server = null; // Ссылка на сервер String separator = "#"; // Разделитель имен в строке ответа String connectionString; // Строка соединения с БД public Service1() { // Извлекаем в поле строку соединения с БД из файла App.config connectionString = System.Configuration.ConfigurationManager. ConnectionStrings["PicturesDB"].ConnectionString; // Конвертируем IP в другой формат localAddr = IPAddress.Parse(hostName); // Запускаем в новом потоке (ните) Thread thread = new Thread(ExecuteLoop); thread.IsBackground = true; thread.Start(); } private void ExecuteLoop() { try { server = new TcpListener(localAddr, port);// Создаем сервер-слушатель server.Start();// Запускаем сервер // Бесконечный цикл прослушивания клиентов while (true) { // Проверяем очередь соединений if (!server.Pending())// Очередь запросов пуста continue; TcpClient client = server.AcceptTcpClient();// Текущий клиент // Создаем потоки сетевых соединений StreamReader readerStream = new StreamReader(client.GetStream()); NetworkStream streamOut = client.GetStream(); StreamWriter writerStream = new StreamWriter(streamOut); // Читаем команду клиента String receiverData = readerStream.ReadLine(); // Распознаем и исполняем switch (receiverData) { case "!!!GetNames!!!":// Посылаем имена, разделенные сепаратором String names = GetNames(); writerStream.WriteLine(names); // Используем через оболочку writerStream.Flush(); break; default:// Посылаем рисунок Byte bytes = GetPicture(receiverData); streamOut.Write(bytes, 0, bytes.Length);// Используем напрямую streamOut.Flush(); break; } // Закрываем соединение и потоки, порядок неважен client.Close(); readerStream.Close(); writerStream.Close(); } } finally { // Останавливаем сервер server.Stop(); } } // Извлечение из БД имен рисунков и упаковка их в одну строку для пересылки клиенту string GetNames() { // Создаем и настраиваем инфраструктуру ADO.NET OleDbConnection conn = new OleDbConnection(connectionString); OleDbCommand cmd = new OleDbCommand("SELECT FileName FROM MyTable"); cmd.Connection = conn; conn.Open(); // Извлекаем имена рисунков OleDbDataReader reader = cmd.ExecuteReader(CommandBehavior.CloseConnection); // Формируем строку исходящих данных StringBuilder sb = new StringBuilder(); foreach (DbDataRecord record in reader)// Равносильно чтению reader.Read() sb.Append(((string)record["FileName"]).Trim() + separator); // Соединение здесь закроет сам объект DataReader после прочтения всех данных // в соответствии с соглашением при его создании CommandBehavior.CloseConnection // Удаляем лишний последний символ сепаратора sb.Replace(separator, String.Empty, sb.ToString(). LastIndexOf(separator), separator.Length); return sb.ToString(); } // Извлечение из БД самого рисунка для отправки клиенту byte GetPicture(String name) { // Создаем и настраиваем инфраструктуру ADO.NET OleDbConnection conn = new OleDbConnection(); conn.ConnectionString = connectionString; // Создаем и настраиваем объект команды, параметризованной по имени рисунка OleDbCommand cmd = new OleDbCommand(); cmd.Connection = conn; cmd.CommandType = CommandType.Text; // Необязательно! Установлено по умолчанию cmd.CommandText = "SELECT Picture FROM MyTable WHERE FileName=?"; cmd.Parameters.Add(new OleDbParameter()); cmd.Parameters.Value = name;// Имя рисунка OleDbDataAdapter adapter = new OleDbDataAdapter(cmd); // Извлекаем рисунок из БД DataTable table = new DataTable(); adapter.Fill(table); byte bytes = (byte)table.Rows["Picture"]; // Подключаемся к рисунку return bytes; } } }

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

Клиент-серверная архитектура

Архитектура «Клиент-Сервер» представляет собой взаимодействие структурных компонентов в сети на основе определенных данной сети, где структурными компонентами являются сервер и узлы-поставщики определенных специализированных функций (сервисов), а также клиенты, которые пользуются данным сервисом. Специфические функции принято делить на три группы на основе решения определенных задач:

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

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

Принцип работы клиент-серверной архитектуры

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

Клиент-серверная архитектура: применение технологии

Данная архитектура используется для доступа к различным ресурсам с использованием сетевых технологий: баз данных, почтовые серверы, файрволы, прокси-серверы. Разработка клиент-серверных приложений позволяет повысить безопасность, надежность и производительность используемых приложений и сети в целом. Наиболее часто клиент-серверные приложения используются для автоматизации бизнеса.

Департамент общего и профессионального образования Брянской области

Государственное образовательное учреждение

Клинцовский текстильный техникум

ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ АВТОМАТИЗИРОВАННЫХ ИНФОРМАЦИОННЫХ СИСТЕМ

Технология «Клиент – сервер»

Студент гр. А-90______________________(Петроченко А.О.)

Преподаватель _______________________ (Широкова А.Л.)

Клинцы – 2011

1. Серверы. Основные понятия серверов

2. Модель клиент-сервер

3. Классификация стандартных серверов

4. Вывод

1. Серверы. Основные понятия серверов

Сервер (от англ. server, обслуживающий). В зависимости от предназначения существует несколько определений понятия сервер.

1. Сервер (сеть) - логический или физический узел сети, обслуживающий запросы к одному адресу и/или доменному имени (смежным доменным именам), состоящий из одного или системы аппаратных серверов, на котором выполняются один или система серверных программ

2. Сервер (программное обеспечение) - программное обеспечение, принимающее запросы от клиентов (в архитектуре клиент-сервер).

3. Сервер (аппаратное обеспечение) - компьютер (или специальное компьютерное оборудование) выделенный и/или специализированный для выполнения определенных сервисных функций.

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

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

2. Модель клиент-сервер

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

Процессы, реализующие некоторую службу, например службу файловой системы или базы данных, называются серверами (servers). Процессы, запрашивающие службы у серверов путем посылки запроса и последующего ожидания ответа от сервера, называются клиентами (clients) .

По такой схеме могут быть построены системы обработки данных на основе СУБД, почтовые и другие системы. Мы будем говорить о базах данных и системах на их основе. И здесь удобнее будет не просто рассматривать клиент-серверную архитектуру, а сравнить ее с другой - файл-серверной.

В файл-серверной системе данные хранятся на файловом сервере (например, Novell NetWare или Windows NT Server), а их обработка осуществляется на рабочих станциях, на которых, как правило, функционирует одна из, так называемых, "настольных СУБД" - Access, FoxPro, Paradox и т.п..

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

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


Рис. Сравнение файл-серверной и клиент-серверной моделей

В клиент-серверной системе функционируют (как минимум) два приложения - клиент и сервер, делящие между собой те функции, которые в файл-серверной архитектуре целиком выполняет приложение на рабочей станции. Хранением и непосредственным манипулированием данными занимается сервер баз данных, в качестве которого может выступать Microsoft SQL Server, Oracle, Sybase и т.п..

Формированием пользовательского интерфейса занимается клиент, для построения которого можно использовать целый ряд специальных инструментов, а также большинство настольных СУБД. Логика обработки данных может выполняться как на клиенте, так и на сервере. Клиент посылает на сервер запросы, сформулированные, как правило, на языке SQL. Сервер обрабатывает эти запросы и передает клиенту результат (разумеется, клиентов может быть много).

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

Что дает архитектура клиент-сервер?

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

Надежность

Сервер баз данных осуществляет модификацию данных на основе механизма транзакций, который придает любой совокупности операций, объявленных как транзакция, следующие свойства:

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

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

Масштабируемость

Масштабируемость - способность системы адаптироваться к росту количества пользователей и объема базы данных при адекватном повышении производительности аппаратной платформы, без замены программного обеспечения.

Общеизвестно, что возможности настольных СУБД серьезно ограничены - это пять-семь пользователей и 30-50 Мб, соответственно. Цифры, разумеется, представляют собой некие средние значения, в конкретных случаях они могут отклоняться как в ту, так и в другую сторону. Что наиболее существенно, эти барьеры нельзя преодолеть за счет наращивания возможностей аппаратуры.

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

Безопасность

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

Гибкость

В приложении, работающем с данными, можно выделить три логических слоя:

  • пользовательского интерфейса ;
  • правил логической обработки (бизнес-правил);
  • управления данными (не следует только путать логические слои с физическими уровнями, о которых речь пойдет ниже).

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

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

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

Рис. Трехуровневая модель клиент-серверного приложения

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

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

1) В файл-серверной системе мы "просто" вносим изменения в приложение и обновляем его версии на рабочих станциях. Но это "просто" влечет за собой максимальные трудозатраты.

2) В двухуровневой клиент-серверной системе, если алгоритм расчета зарплаты реализован на сервере в виде правила расчета зарплаты, его выполняет сервер бизнес-правил, выполненный, например, в виде OLE-сервера, и мы обновим один из его объектов, ничего не меняя ни в клиентском приложении, ни на сервере баз данных.

Client-server ) - сетевая архитектура, в которой устройства являются либо клиентами , либо серверами . Клиентом (front end) является запрашивающая машина (обычно ПК), сервером (back end) - машина, которая отвечает на запрос. Оба термина (клиент и сервер) могут применяться как к физическим устройствам, так и к программному обеспечению.

Сеть с выделенным сервером (англ. Сlient/Server network ) - это локальная вычислительная сеть (LAN) , в которой сетевые устройства централизованы и управляются одним или несколькими серверами. Индивидуальные рабочие станции или клиенты (такие, как ПК) должны обращаться к ресурсам сети через сервер(ы).


Wikimedia Foundation . 2010 .

Смотреть что такое "Клиент-серверное приложение" в других словарях:

    Может значить: Прикладная компьютерная программа см. Прикладное программное обеспечение. Веб приложение клиент серверное приложение, в котором клиентом выступает браузер, а сервером веб сервер. Приложение (лингвистика) … … Википедия

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

    Тип Общество с ограниченной ответственностью Год основания … Википедия

    Веб приложение клиент серверное приложение, в котором клиентом выступает браузер, а сервером веб сервер. Браузер может являться реализацией так называемых тонких клиентов. Браузер способен отображать веб страницы и, как правило, входит в состав… … Википедия

    Веб приложение клиент серверное приложение, в котором клиентом выступает браузер, а сервером веб сервер. Браузер может являться реализацией так называемых тонких клиентов. Браузер способен отображать веб страницы и, как правило, входит в состав… … Википедия

Под клиент-серверным приложением мы будем понимать информационную систему, основанную на использовании серверов баз данных (см. длинное замечание в конце раздела 2.1). Общее представление информационной системы в архитектуре "клиент-сервер" показано на рисунке 2.3.

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

(Здесь опять проявляются недостатки в терминологии. Обычно, когда компания объявляет о выпуске очередного сервера баз данных, то неявно понимается, что имеется и клиентская составляющая этого продукта. Сочетание "клиентская часть сервера баз данных" кажется несколько странным, но нам придется пользоваться именно этим термином.)

Рис. 2.3. Общее представление информационной системы в архитектуре "клиент-сервер"

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

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

Здесь необходимо сделать еще два замечания.

  1. Обычно компании, производящие развитые серверы баз данных, стремятся к тому, чтобы обеспечить возможность использования своих продуктов не только в стандартных на сегодняшний день TCP/IP-ориентированных сетях, но в сетях, основанных на других протоколах (например, SNA или IPX/SPX). Поэтому при организации сетевых взаимодействий между клиентской и серверной частями СУБД часто используются не стандартные средства высокого уровня (например, механизмы программных гнезд или вызовов удаленных процедур), а собственные функционально подобные средства, менее зависящие от особенностей сетевых транспортных протоколов.
  2. Когда мы говорим об интерфейсе на основе языка SQL, нужно отдавать себе отчет в том, что несмотря на титанические усилия по стандартизации этого языка, нет такой реализации, в которой стандартные средства языка не были бы расширены. Необдуманное использование расширений языка приводит к полной зависимости приложения от конкретного производителя сервера баз данных.

Мы остановимся на этих вопросах более подробно в четвертой части курса.

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

  • Сервер производит компиляцию полученного оператора. Не будем здесь останавливаться на том, какой целевой язык используется конкретным компилятором; в разных реализациях применяются различные подходы (примеры см. в части 4). Главное, что в любом случае на основе информации, содержащейся в таблицах-каталогах базы данных производится преобразование непроцедурного представления оператора в некоторую процедуру его выполнения.
  • Далее (если компиляция завершилась успешно) происходит выполнение оператора. Мы снова не будем обсуждать технические детали, поскольку они различаются в реализациях. Рассмотрим возможные действия операторов SQL.
    • Оператор может относиться к классу операторов определения (или создания) объектов базы данных (точнее и правильнее было бы говорить про элементы схемы базы данных или про объекты метабазы данных). В частности, могут определяться домены, таблицы, ограничения целостности, триггеры, привилегии пользователей, хранимые процедуры. В любом случае, при выполнении оператора создания элемента схемы базы данных соответствующая информация помещается в таблицы-каталоги базы данных (в таблицы метабазы данных). Ограничения целостности обычно сохраняются в метабазе данных прямо в текстовом представлении. Для действий, определенных в триггерах, и хранимых процедур вырабатывается и сохраняется в таблицах-каталогах процедурный выполняемый код. Заметим, что ограничения целостности, триггеры и хранимые процедуры являются, в некотором смысле, представителями приложения в поддерживаемой сервером базе данных; они составляют основу серверной части приложения (см. ниже).
    • При выполнении операторов выборки данных на основе содержимого затрагиваемых запросом таблиц и, возможно, с использованием поддерживаемых в базе данных индексов формируется результирующий набор данных (мы намеренно не используем здесь термин "результирующая таблица", поскольку в зависимости от конкретного вида оператора результат может быть упорядоченным, а таблицы, т.е. отношения неупорядочены по определению). Серверная часть СУБД пересылает результат клиентской части, и окончательная обработка производится уже в клиентской части приложения.
    • При выполнении операторов модификации содержимого базы данных (INSERT, UPDATE, DELETE) проверяется, что не будут нарушены определенные к этому моменту ограничения целостности (те, которые относятся к классу немедленно проверяемых), после чего выполняется соответствующее действие (сопровождаемое модификацией всех соответствующих индексов и журнализацией изменений). Далее сервер проверяет, не затрагивает ли данное изменение условие срабатывания какого-либо триггера, и если такой триггер обнаруживается, выполняет процедуру его действия. Эта процедура может включать дополнительные операторы модификации базы данных, которые могут вызвать срабатывание других триггеров и т.д. Можно считать, что те действия, которые выполняются на сервере баз данных при проверке удовлетворенности ограничений целостности и при срабатывании триггеров, представляют собой действия серверной части приложения.

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

В целом, в файл-серверной архитектуре мы имеем "толстого" клиента и очень "тонкий" сервер в том смысле, что почти вся работа выполняется на стороне клиента, а от сервера требуется только достаточная емкость дисковой памяти (рисунок 2.2).

Рис. 2.2. "Толстый" клиент и "тонкий" сервер в файл-серверной архитектуре

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

Статьи по теме: