Файл: «Вариант 21. Модель клиент-сервер».pdf

ВУЗ: Не указан

Категория: Курсовая работа

Дисциплина: Не указана

Добавлен: 18.06.2023

Просмотров: 99

Скачиваний: 3

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

Для перевода данных из узлового порядка в сетевой и обратно используют функцию htons или её аналоги. [7] Прототип этой функции: uint16_t htons(uint16_t hostshort);

Для присвоения сокету какого-то имени в зависимости от адресного домена используется функция bind, имеющая следующий прототип: int bind(int s, char * name, int namelen); В качестве параметра s указывают дескриптор сокета, которому дают имя. Name –имя сокета, namelen –длина имени. [8]

Чтобы информировать клиента об ожидании запросов связи используют функцию listen, которая имеет следующий прототип: int listen(int s, int backlog); В качестве первого параметра указывают сокет от которого ожидают передачу, а в качестве второго – количество одновременных запросов. Для проведения операций над наборами сигналов используется функции sigsetops, которые в зависимости от вызова могут инициализировать набор сигналов, добавить сигнал или удалить сигнал и т.д. [8]

Для принятия сервером связи на сокет используется функция accept. Вызов этой функции является блокирующим. Прототип функции: int accept(int s, char * name, int* anamelen); аргумент s –дескриптор сокета для принятия связей от клиента, 2 аргумент –имя клиента, 3 аргумент –длина структуры адреса.

Для работы с адресами используют функции inet_..., которые выполняют различные функции, как например: преобразование IP-адреса в двоичный код (в заданном порядке), извлечь сетевой номер, преобразование в строковый вид, создать сетевой адрес и т.д. [8] Для закрытия установленных соединений используют функцию close.

Тогда, серверная часть может быть написана в таком виде: #include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <errno.h>#include <string.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h> #include <time.h> #include <sys/wait.h>#include <signal.h>#define MYPORT 1025#define BACKLOG 10

void sigchld_handler(int s){while(wait(NULL) > 0);}int main(void){time_t timer;int sockfd, new_fd;struct sockaddr_in my_addr;struct sockaddr_in their_addr;sin_size;struct sigaction sa;int yes=1;if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){perror("socket");exit(1);}if(setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int)) == -1){perror("setsockopt");exit(1);}my_addr.sin_family = AF_INET;my_addr.sin_port = htons(MYPORT);my_addr.sin_addr.s_addr = INADDR_ANY;memset(&(my_addr.sin_zero), 0, 8);if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1){perror("bind");exit(1);}if (listen(sockfd, BACKLOG) == -1){perror("listen");exit(1);}sa.sa_handler = sigchld_handler;sigemptyset(&sa.sa_mask);sa.sa_flags = SA_RESTART;if (sigaction(SIGCHLD, &sa, NULL) == -1) {perror("sigaction");exit(1);}while(1){sin_size = sizeof(struct sockaddr_in);if ((new_fd=accept(sockfd, (struct sockaddr *)&their_addr,&sin_size)) == -1){continue;}printf("Received request from Client: %s:%d\n",inet_ntoa(their_addr.sin_addr),MYPORT);if (!fork()){close(sockfd);timer = time(NULL);if (send(new_fd, ctime(&timer), 30, 0) == -1)perror("send");close(new_fd);exit(0);}close(new_fd); }return 0;}

Мы получили серверную часть нашей модели –теперь необходимо поговорить о клиентской части.[9] Клиент должен получить информацию о машине в сети. Для этих целей служит структура hostent. struct hostent {char *h_name;char **h_aliases;int h_addrtype;int h_length; char **h_addr_list; }#define h_addr h_addr_list[0]


Полями структуры hostent являются:h_name-(официальное имя машины); h_aliases-(оканчивающийся нулем массив альтернативных имен машины); h_addrtype-(тип адреса; в настоящее время всегда AF_INET); h_length-(длина адреса в байтах); h_addr_list-(оканчивающийся нулем массив сетевых адресов машины в сетевом порядке байтов); h_addr-(первый адрес в h_addr_list определен для совместимости с более ранними версиями).

Для доступа к машине и для изменения имени машины используются функции gethostname и sethostname со следующими прототипами: int gethostname(char *name, size_t len);int sethostname(const char *name, size_t len);  

Для установки соединения с сервером используют функцию connect со следующим прототипом: int connect(int s, char * name, int namelen); где аргумент s- это дескриптор клиента, name –имя сервера и namelen –длина name. Тогда клиентскую часть можно записать следующим образом: #include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <errno.h>#include <string.h>#include <netdb.h>#include <sys/types.h>#include <netinet/in.h>#include <sys/socket.h> #define PORT 1025 #define MAXDATASIZE 500

int main(int argc, char *argv[]){int sockfd, numbytes;char buf[MAXDATASIZE];struct hostent *he;struct sockaddr_in their_addr;char hostn[400];char ipadd[400];struct hostent *hostIP;if ((gethostname(hostn, sizeof(hostn))) == 0){hostIP = gethostbyname(hostn);}else{printf("ERROR:FC4539 - IP Address not found.");}if (argc != 2){fprintf(stderr,"usage: client hostname\n");exit(1);}if ((he=gethostbyname(argv[1])) == NULL){perror("gethostbyname");exit(1);}if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){perror("socket");exit(1);}their_addr.sin_family = AF_INET;their_addr.sin_port = htons(PORT);their_addr.sin_addr = *((struct in_addr *)he->h_addr);memset(&(their_addr.sin_zero), 0, 8);(connect(sockfd, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) == -1){perror("connect");exit(1);}if ((numbytes=recv(sockfd, buf, MAXDATASIZE-1, 0)) ==-1){perror("recv");exit(1);}buf[numbytes] = 0;printf("\n\nLocalhost: %s\n", inet_ntoa(*(struct in_addr *)hostIP->h_addr));printf("Local Port: %d\n", PORT);printf("Remote Host: %s\n",inet_ntoa(their_addr.sin_addr));printf("Received daytime data: %s\n",buf);close(sockfd);return 0;}

Мы получили базисный каркас клиент-серверного приложения, который абсолютно справляется со своей задачей, но что самое главное – рабочую модель, которая может быть разобрана с проектировочной стороны вопроса. [1] Данная модель может быть усложнена, но базовые принципы сохраняются. Программирование клиент-серверных приложений на C под Linux отличается своей простотой и элегантностью, все технологии или дополнительные задачи, которые нужно будет добавить могут быть добавлены уже в готовую модель без нужды переписывания кода заново. [2]

3.3 ОСОБЕННОСТИ РЕАЛИЗАЦИИ КЛИЕНТ-СЕРВЕРА ПОД WINDOWS

В 3.1 была довольно разгромная оценка выбора Windows в качестве операционной системы для серверной части (важно отметить, что серверная и клиентская части могут быть реализованы в разных операционных системах и даже используя разный стек технологий. Оценки даются с позиции содержания толстой серверной части «в отрыве» от клиентской), из-за чего могло сложиться неправильное представление о том, что Windows безнадёжна и ничего путного в плане клиент-серверной архитектуры на ней нельзя добиться. [3] Чтобы это опровергнуть (напоминаю, что все оценки в главе 3.1 давались лишь в сравнении, а однозначный вывод так и не был сформулирован) попробуем реализовать WEB-сокетный клиент-сервер, который был указан в главе 3.1 как тот, что более приспособлен для Linux (так это и есть, но это не мешает ему прекрасно работать под Windows). Итак, перед началом непосредственного разбора стоит упомянуть, что в отличие от более элегантного кода Linux, решительно невозможно добиться той же лаконичности в данном случае, несмотря даже на гораздо более высокий уровень абстракции (разумеется будет использоваться стек-технологий Microsoft, в частности .NET и C#) – поэтому, чтобы не растягивать работу искусственно будут приведены лишь выдержки из этой программы (выделение этого кода в приложение также считаю излишним, поскольку большая часть кода будут занимать служебные функции, которые не имеют непосредственного отношения к разбираемой теме). [5] Итак, основная работа по соединению происходит инкапсулировано в реализациях класса WebSocket или классов наследуемых от него. Работа с сокетами происходит в классе System.NET.Sockets.Socket. И вообще, лаконичность и возможность сильного контроля С, сменилось на большую семантическую связь и большой вариативности (что обеспечивается иногда даже низкоуровневой реализацией отдельных классов, что безусловно было сделано для более тесной интеграции с Winapi). [7] В целом, сами по себе эти классы не предоставляют какой-то уникальный набор инструментов по управлению соответствующими структурами и, по сути, внутри имеют очень много общего с уже рассмотренными структурами и функциями в СИ. Приведём пример реализации соединения: using System;using System.Collections.Generic;using System.Linq;using System.IO;using System.Threading.Tasks;namespace Fleck{public class WebSocketConnection : IWebSocketConnection{public WebSocketConnection(ISocket socket, Action<IWebSocketConnection> initialize, Func<byte[], WebSocketHttpRequest> parseRequest, Func<WebSocketHttpRequest, IHandler> handlerFactory, Func<IEnumerable<string>, string> negotiateSubProtocol){Socket = socket;OnOpen = () => { };OnClose = () => { };OnMessage = x => { };OnBinary = x => { };OnPing = x => SendPong(x);OnPong = x => { };OnError = x => { };_initialize = initialize;_handlerFactory = handlerFactory;_parseRequest = parseRequest;_negotiateSubProtocol = negotiateSubProtocol;}public ISocket Socket { get; set; }private readonly Action<IWebSocketConnection> _initialize;private readonly Func<WebSocketHttpRequest, IHandler> _handlerFactory;private readonly Func<IEnumerable<string>, string> _negotiateSubProtocol;readonly Func<byte[], WebSocketHttpRequest> _parseRequest;public IHandler Handler { get; set; }private bool _closing;private bool _closed;private const int ReadSize = 1024 * 4;public Action OnOpen { get; set; }public Action OnClose { get; set; }public Action<string> OnMessage { get; set; }public Action<byte[]> OnBinary { get; set; }public Action<byte[]> OnPing { get; set; }public Action<byte[]> OnPong { get; set; }public Action<Exception> OnError { get; set; }public IWebSocketConnectionInfo ConnectionInfo { get; private set; }public bool IsAvailable {get { return !_closing && !_closed && Socket.Connected; }}public Task Send(string message){return Send(message, Handler.FrameText);}public Task Send(byte[] message){return Send(message, Handler.FrameBinary);}public Task SendPing(byte[] message){return Send(message, Handler.FramePing);}public Task SendPong(byte[] message){return Send(message, Handler.FramePong);}private Task Send<T>(T message, Func<T, byte[]> createFrame){if (Handler == null)throw new InvalidOperationException("Cannot send before handshake");if (!IsAvailable){const string errorMessage = "Data sent while closing or after close. Ignoring.";FleckLog.Warn(errorMessage);var taskForException = new TaskCompletionSource<object>(); taskForException.SetException(new ConnectionNotAvailableException(errorMessage));return taskForException.Task;}var bytes = createFrame(message);return SendBytes(bytes);}public void StartReceiving(){var data = new List<byte>(ReadSize);var buffer = new byte[ReadSize]; Read(data,buffer);}public void Close(){Close(WebSocketStatusCodes.NormalClosure);}public void Close(int code){if (!IsAvailable)return;_closing = true;if (Handler == null) {CloseSocket();return;}var bytes = Handler.FrameClose(code);if (bytes.Length == 0)CloseSocket();else SendBytes(bytes, CloseSocket);}public void CreateHandler(IEnumerable<byte> data){var request = _parseRequest(data.ToArray()); if (request == null)return; Handler = _handlerFactory(request); if (Handler == null) return; var subProtocol = _negotiateSubProtocol(request.SubProtocols); ConnectionInfo = WebSocketConnectionInfo.Create(request, Socket.RemoteIpAddress, Socket.RemotePort, subProtocol); _initialize(this);var handshake Handler.CreateHandshake(subProtocol);SendBytes(handshake, OnOpen);}private void Read(List<byte> data, byte[] buffer) {if (!IsAvailable)return;Socket.Receive(buffer, r =>{if (r <= 0) {FleckLog.Debug("0 bytes read. Closing."); CloseSocket();return;}FleckLog.Debug(r + " bytes read");var readBytes = buffer.Take(r); if (Handler != null) {Handler.Receive(readBytes);} else {data.AddRange(readBytes);CreateHandler(data);}Read(data, buffer);},HandleReadError);}private void HandleReadError(Exception e){if (e is AggregateException) {var agg = e as AggregateException; HandleReadError(agg.InnerException);return;}if (e is ObjectDisposedException){FleckLog.Debug("Swallowing ObjectDisposedException", e); return;}OnError(e);if (e is WebSocketException) {FleckLog.Debug("Error while reading", e);Close(((WebSocketException)e).StatusCode);} else if (e is SubProtocolNegotiationFailureException) {FleckLog.Debug(e.Message); Close(WebSocketStatusCodes.ProtocolError);} else if (e is IOException) {FleckLog.Debug("Error while reading", e); Close(WebSocketStatusCodes.AbnormalClosure);}else{FleckLog.Error("Application Error", e);Close(WebSocketStatusCodes.InternalServerError);}}private Task SendBytes(byte[] bytes, Action callback = null) {return Socket.Send(bytes, () =>{FleckLog.Debug("Sent " + bytes.Length + " bytes"); if (callback != null) callback();},e =>{if (e is IOException) FleckLog.Debug("Failed to send. Disconnecting.", e); else FleckLog.Info("Failed to send. Disconnecting.", e); CloseSocket();});}private void CloseSocket() {_closing = true; OnClose();_closed = true;Socket.Close();Socket.Dispose();_closing = false;}}}


Для человека владеющим языком C# этот код не принесёт большого количества новой информации, но тем не менее, это является полностью рабочей моделью WEB соединения. [8] В ней происходит уже описанная выше логика работы, а сам код можно читать как семантически-понятный набор инструкций, что хоть и не делает сложность разработки ниже, но сокращает количество текста, необходимого для объяснений. [9] Для понимания контекста подключения приведём также код самого сервера на сокетах: public class WebSocketServer : IWebSocketServer{private readonly string _scheme;private readonly IPAddress _locationIP; private Action<IWebSocketConnection> _config;public WebSocketServer(string location){var uri = new Uri(location); Port = uri.Port; Location = location; _locationIP = ParseIPAddress(uri); _scheme = uri.Scheme; var socket = new Socket(_locationIP.AddressFamily, SocketType.Stream, ProtocolType.IP); if(!MonoHelper.IsRunningOnMono()){ #if __MonoCS__ #else #if !NET45 if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) #endif {socket.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, false);} #if !NET45 if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1); }#endif #endif}ListenerSocket = new SocketWrapper(socket); SupportedSubProtocols = new string[0];} public ISocket ListenerSocket { get; set; } public string Location { get; private set; } public int Port { get; private set; } public X509Certificate2 Certificate { get; set; } public SslProtocols EnabledSslProtocols { get; set; } public IEnumerable<string> SupportedSubProtocols { get; set; } public bool RestartAfterListenError {get; set; }public bool IsSecure;{get { return _scheme == "wss" && Certificate != null; }}public void Dispose();{ListenerSocket.Dispose();}private IPAddress ParseIPAddress(Uri uri){string ipStr = uri.Host; if (ipStr == "0.0.0.0" ){return IPAddress.Any;}else if(ipStr == "[0000:0000:0000:0000:0000:0000:0000:0000]") {return IPAddress.IPv6Any;} else {try {return IPAddress.Parse(ipStr);} catch (Exception ex){throw new FormatException("Failed to parse the IP address part of the location. Please make sure you specify a valid IP address. Use 0.0.0.0 or [::] to listen on all interfaces.", ex); }}}public void Start(Action<IWebSocketConnection> config){var ipLocal=new IPEndPoint(_locationIP, Port); ListenerSocket.Bind(ipLocal); ListenerSocket.Listen(100); Port=((IPEndPoint)ListenerSocket.LocalEndPoint).Port; FleckLog.Info(string.Format("Server started at {0} (actual port {1})", Location, Port)); if(_scheme=="wss"){if(Certificate==null){FleckLog.Error("Scheme cannot be 'wss' without a Certificate"); return; }if(EnabledSslProtocols==SslProtocols.None)EnabledSslProtocols=SslProtocols.Tls; FleckLog.Debug("Using default TLS 1.0 security protocol."); }}ListenForClients(); _config=config; }private void ListenForClients(){ListenerSocket.Accept(OnClientConnect, e =>{FleckLog.Error("Listener socket is closed", e); if(RestartAfterListenError){FleckLog.Info("Listener socket restarting"); try{ListenerSocket.Dispose(); var socket=new Socket(_locationIP.AddressFamily, SocketType.Stream, ProtocolType.IP); ListenerSocket=new SocketWrapper(socket); Start(_config); FleckLog.Info("Listener socket restarted"); }catch (Exception ex) {FleckLog.Error("Listener could not be restarted", ex); }}}); }private void OnClientConnect(ISocket clientSocket) {if (clientSocket==null) return; // socket closed FleckLog.Debug(String.Format("Client connected from {0}:{1}", clientSocket.RemoteIpAddress, clientSocket.RemotePort.ToString())); ListenForClients(); WebSocketConnection connection=null; connection=new WebSocketConnection(clientSocket, _config, bytes=>RequestParser.Parse(bytes, _scheme), r => HandlerFactory.BuildHandler(r, s=>connection.OnMessage(s), connection.Close, b=>connection.OnBinary(b), b=>connection.OnPing(b), b=>connection.OnPong(b)), s=>SubProtocolNegotiator.Negotiate(SupportedSubProtocols, s)); if(IsSecure) {FleckLog.Debug("Authenticating Secure Connection"); clientSocket, .Authenticate(Certificate, EnabledSslProtocols, connection.StartReceiving, e=>FleckLog.Warn("Failed to Authenticate", e));} else {connection.StartReceiving();}}}}


Как может быть видно из этих фрагментов кода – разработка сервера на Windows обладает рядом преимуществ, а именно: сочетание достоинств низкоуровнего подхода в сочетании с высоким уровнем абстракции, понятность кода, простого распределения нагрузки на процессы, удобную модульную структуру и в целом возможность воспроизведения всего стека современных технологий в заданных условиях. [9] Тем не менее, нельзя однозначно назвать готовое решение для какой-то задачи, любые предпочтения без производительных оценок, полученных в конкретном контексте обречены сводится к выбору предпочтений. Однако, благодаря разбору указанных моделей можно говорить об отличии одного подхода от другого, что несомненно будет играть при объективной оценке для реализации конкретной задачи. [8]

ЗАКЛЮЧЕНИЕ

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

  • Были Изучены основные понятия связанные с клиент-серверной моделью
  • Были Изучены протоколы взаимодействия между клиентской и серверной частью
  • Были Изучены основные технологии, использующие клиент-серверную модель
  • Составлены критерии выбора стека технологий для составления клиент-серверной модели под конкретные задачи
  • Были Разработаны клиент-серверные приложения и на их основе изучены прикладные приёмы создания клиент серверной модели

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