Пример сервера для программы обмена сообщениями между клиентами и сервером

Лабораторная работа №2

Каналы и сокеты

Требования и условия к выполнению лабораторной работы

1. Задания выполняются на С++. Что-то иное оговаривается заранее.

2. Достаточно 1-2 клиентов для сервера, но можно и больше.

3. Задания выполняются индивидуально. Нечетный вариант (1,3,5…) – реализация через именованные каналы, четный вариант (2,4,6…) – реализация через сокеты.

Задания для лабораторной работы №2:

 

Вариант 1-2.

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

 

Вариант 3-4.

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

 

Вариант 5-6.

Сервер должен получать от одного единственного клиента сообщения в формате АрифметическаяОперацияЦелоеилиДробноеЧисло (например, +322, -55, /400.54, *322 и т.д.). После каждого такого ввода на сторону клиента должно быть передано сообщение со всей формулой на данный момент. Полученные же от клиента части выражения должны приписываться справа от уже имеющегося. Так же должны быть предусмотрены такие вещи как набор служебных команд для очищения последовательности вычисления, расчета выражения, проверка корректности выражения (отсутствие в нём /0). Выражение должно считаться по правилам арифметики с соответствующим приоритетом операций.

 

Вариант 7-8.

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

 

Вариант 9-10.

Сервер должен запустить n процессов, открыть файл, имя которого вводится через консоль, и раздать все слова в этом текстовом файле между всеми клиентами по следующему правилу: 1-й клиент должен получить 1-е слово, 2-й – второе и т.д. вплоть до n процесса, после чего процесс должен повторяться вплоть до конца считываемого файла.

 

Вариант 11-12.

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

 

Вариант 13-14.

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

 

Вариант 15-16.

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

 

Вариант 17-18.

Сервер должен получать от клиента скобочную последовательность из следующих типов скобок: (), {}, [],<>. После анализа на клиент должно быть выслано сообщение, корректна ли скобочная последовательность или нет. Длина вводимой последовательности неограниченна.

 

Вариант 19-20.

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

 

Вариант 21-22.

Сервер создаёт 2 процесса клиента, которые играют через сервер, который их создал, в следующую игру: есть случайно сгенерированное число от 20 до 100. Клиенты, поочерёдно отнимают от числа от 1 до 4. Проиграет тот, кто первый обнулит число или сделает его отрицательным.

 

Вариант 23-24.

Сервер создаёт n процессов, указывая для каждого время его жизни в секундах. Должно быть предусмотрено специальное значение под бесконечное время. Клиент должен запуститься, просуществовать без учёта воздействия на него пользователя заданное ему время и завершиться.

 

Вариант 25-26

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

Теоретическая часть и примеры реализации

Каналы (pipe)

– средство межпроцессорного взаимодействия,  по средствам так называемых “каналов” – особый тип файлов, которые, как и обычные можно читать и/или модифицировать, в зависимости от указанных на это прав процесса, который подключается к каналу. Каналы в windows разделяются на 2 типа: анонимные и именованные. Нас будут интересовать только именованные каналы, т.к. через анонимные можно передавать данные в одном направлении (полудуплексный режим работы канала) и в основном используются для передачи данных от родительского процесса к дочернему. Именованные каналы являются компонентами WinAPIи, следовательно, для их использования необходимо включение библиотеки <Windows.h>. Для того, чтобы создать именованный канал, необходимо выполнить функцию:

CreateNamepPipe(

LPCTSTR lpName, // имя канала

DWORDdwOpenMode, // атрибуты канала

DWORDdwPipeMode, // режим передачи данных

DWORDnMaxInstances, // максимальное количество экземпляров канала

DWORDnOutBufferSize, // размер выходного буфера

DWORDnInBufferSize, // размер входного буфера

DWORDnDefaultTimeOut, // время ожидания связи с клиентом

LPSECURITY_ATTRIBUTES lpPipeAttributes // атрибутызащиты)

 

Где lpName - имя канала в формате L” \\\\.\\pipe\\название_канала”. \\\\. – локальная директория компьютера, на котором запускается процесс.

 

dwOpenMode – установка флага режима передачи данных между клиентом и сервером:

       PIPE_ACCESS_DUPLEX чтение и запись в канал

PIPE_ACCESS_INBOUND клиент пишет, а сервер читает данные

PIPE_ACCESS_OUTBOUND сервер пишет, а клиент читает данные

 

dwPipeMode – флаг режима передачи данных по каналу:

PIPE_TYPE_BYTE         запись данных потоком

PIPE_TYPE_MESSAGE            запись данных сообщениями

PYPE_READMODE_BYTE      чтение данных потоком

PYPE_READMODE_MESSAGE чтение данных сообщениями

PIPE_WAIT синхронная связь с каналом и обмен данными по каналу

PIPE_NOWAIT асинхронная связь с каналом и обмен данными по каналу

Замечания: 1)флаги можно комбинировать 

2)все эти флаги должны быть одинаковые для каждого экземпляра потока. 3)По умолчанию данные передаются потоком

 

nMaxInstances – максимальное количество экземпляров канала, может варьироваться от 1 до значения PIPE_UNLIMITED_INSTANCES. При попытке создания большего количества экземпляров последующие выполнения функции CreateNamedPipe вернут значение INVALID_HANDLE_VALUE

 

 

nOutBufferSize, nInBufferSize – значения являются всего лишь рекомендательными. Поэтому в дальнейшем будем пользоваться значениями, выставляемыми операционной системой (выставляем 0)

nDefaultTimeOut –время ожидания клиента связи с сервером при вызове функции WaitNamedPipe. Если требуется ждать бесконечно, то выставляем значение INFINITE.

 

lpPipeAttributes–Атрибуты защиты будем выставлять по умолчанию значением (LPSECURITY_ATTRIBUTES)NULL

 

При успешном создании канала функция возвращает дескриптор канала. Иначевозвращаетсязначение INVALID_HANDLE_VALUE или ERROR_INVALID_PARAMETR.

 

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

ConnectNamedPipe(HANDLE hNamedPipe, //дескрипторожидаемогоканала

                              LPOVERLAPPEDlpOverlapped // асинхронная связь)

Функция возвращает TRUE, если сервер дождался ответа от клиента за время, указанное при создании экземпляра канала и FALSE в противном случае.

 

Если же процессу нет необходимости больше занимать экземпляр канала, то можно отсоединиться через команду

       DisconnectNamedPipe(HANDLE hNamedPipe//дескриптор отключаемого канала)

Возвращает TRUE при удачном отключении и FALSE при иных исходах (например, если канал исходно не был присоединен к процессу)

 

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

WaitNamedPipe(LPCTSTRlpNamedPipeName, // указатель на имя канала

DWORD nTimeOut // интервал ожидания)

Где lpNamedPipeName – указатель на директорию канала в формате, ранее описанном в функции CreateNamedPipe. Функция возвращает TRUE, если канал не занят и FALSE, если за время nTimeOut не было получено ответа.

 

Чтобы подключиться к каналу (а теперь вспомним, что в самом начале говорилось о том, что канал – это своеобразный файл) используем функцию

CreateFile(LPCTSTRlpFileName,          // указатель на имя канала

DWORDdwDesiredAccess,                   // чтение или запись в канал

DWORDdwShareMode,                         // режим совместного использования

LPSECURITY_ATTRIBUTESlpSecurityAttributes, // атрибуты защиты

DWORDdwCreationDisposition,            // флаг открытия канала

DWORDdwFlagsAndAttributes,                        // флаги и атрибуты

HANDLEhTemplateFile                                      // дополнительные атрибуты)

 

Где lpFileName – указатель на имя файла в уже ранее оговорённом формате

       dwDesiredAccess - Одно из трёх значений:

                   0 разрешает получить атрибуты канала,

GENERIC_READ разрешает чтение из канала

GENERIC_WRITE разрешает запись в канал

Причем если аргумент не будет соответствовать аргументу dwOpenMode в CreateNamedPipe, то возникнет ошибка.

           

       dwShareMode – один из трёх вариантов совместного использования канала

                   FILE_SHARE_READ разрешает совместное чтение из канала,

FILE_SHARE_WRITE разрешает совместную запись в канал.

0 разрешает и то и другое

 

dwCreationDisposition –для работы с уже существующими каналами должен быть равен OPEN_EXISTING

lpSecurityAttributes - задает атрибуты защиты именованного канала.

 

dwFlagsAndAttributes - можно задается равным 0, что определяет флаги и атрибуты по умолчанию

Значение параметра hTemplateFile задается равным NULL.

 

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

       WriteFile(

                              HANDLEhNamedPipe,                           // дескриптор канала

                              LPCVOIDnData,                         // данные

                              DWORDRazmer_data_v_baitah, // размерданных

                              LPDWORD dwBytesWritten,                 // количествозаписанныхбайтов

                              LPOVERLAPPED (LPOVERLAPPED)NULL // синхроннаязапись

                  )

И

ReadFile(

                              HANDLE hNamedPipe1,                        // дескрипторканала

                              LPCVOIDnData,                         // адрес буфера для ввода данных

                              DWORDRazmer_data_v_baitah, // количество читаемых байтов

                              LPDWORDdwBytesRead,          // количество прочитанных байтов

                              LPOVERLAPPED (LPOVERLAPPED)NULL // передача данных синхронная

                    )

Пример простейшего меж процессного взаимодействия через именованные каналы:

 

Клиент:

#include<windows.h>

#include<iostream>

#include<string.h>

#include<conio.h>

#include<io.h>

#include<fcntl.h>

#include<conio.h>

#include<codecvt>

usingnamespace std;

intwmain()

{

       _setmode(_fileno(stdout), _O_U16TEXT);

       _setmode(_fileno(stdin), _O_U16TEXT);

       _setmode(_fileno(stderr), _O_U16TEXT);

       HANDLEhNamedPipe;

       wchar_tpipeName[] = L"\\\\.\\pipe\\demo_pipe";

 

       // связываемся с именованным каналом

       hNamedPipe = CreateFile(

                   pipeName, // имя канала

                   GENERIC_WRITE, // записываем в канал

                   FILE_SHARE_READ, // разрешаем только запись в канал

                   (LPSECURITY_ATTRIBUTES)NULL, // защитапоумолчанию

                   OPEN_EXISTING, // открываем существующий канал

                   0, // атрибуты по умолчанию

                   (HANDLE)NULL// дополнительных атрибутов нет

                  );

 

       // проверяемсвязьсканалом

       if (hNamedPipe == INVALID_HANDLE_VALUE)

       {

                   wcerr<<L"Ошибка при подключении к серверу"<<endl

                              <<L"Кодошибки: "<<GetLastError() <<endl;

                   wcout<<L"Нажмите любую клавишу для выхода";

                   _getch();

                   return 0;

       }

       char connect[] = "connect";

       // пишемвименованныйканал

                   DWORDdwBytesWritten;

                   if (!WriteFile(

                              hNamedPipe, // дескрипторканала

                              connect, // данные

                              strlen(connect)+1, // размер данных

                              &dwBytesWritten, // количество записанных байтов

                              (LPOVERLAPPED)NULL// синхронная запись

                             ))

                   {

                              // ошибка записи

                              wcerr<<L"Ошибка при записи в канал: "<<endl

                                          <<L"Кодошибки: "<<GetLastError() <<endl;

                              wcout<<L"Нажмите любую клавишу для выхода ";

                              _getch();

                              CloseHandle(hNamedPipe);

                              return 0;

                   }

       // закрываем дескриптор канала

       CloseHandle(hNamedPipe);

       // завершаем процесс

       wcout<<L"Данные успешно отосланы на сервер"<<endl

                   <<L"Нажмите любую клавишу для выхода ";

       _getch();

       return 0;

}

 

Сервер:

#include<windows.h>

#include<iostream>

#include<conio.h>

#include<io.h>

#include<fcntl.h>

#include<conio.h>

#include<codecvt>

usingnamespace std;

int main()

{

       HANDLEhNamedPipe;

       _setmode(_fileno(stdout), _O_U16TEXT);

       _setmode(_fileno(stdin), _O_U16TEXT);

       _setmode(_fileno(stderr), _O_U16TEXT);

       // создаемименованныйканалдлячтения

       hNamedPipe = CreateNamedPipe(

                   L"\\\\.\\pipe\\demo_pipe", // имяканала

                   PIPE_ACCESS_DUPLEX, // читаем из канала

                   PIPE_TYPE_MESSAGE | PIPE_WAIT, // синхронная передача сообщений

                   1, // максимальное количество экземпляров канала

                   0, // размер выходного буфера по умолчанию

                   0, // размер входного буфера по умолчанию

                   INFINITE, // клиент ждет связь бесконечно долго

                   (LPSECURITY_ATTRIBUTES)NULL// защитапоумолчанию

                  );

       // проверяемнауспешноесоздание

       if (hNamedPipe == INVALID_HANDLE_VALUE)

       {

                   wcerr<<L"Ошибка при создании канала"<<endl

                              <<L"Кодошибки: "<<GetLastError() <<endl;

                   wcout<<L"Нажмите любую клавишу для выхода";

                   _getch();

                   return 0;

       }

 

       // ждем пока клиент свяжется с каналом

       wcout<<L"Ожидаем подключение клиента"<<endl;

       if(!ConnectNamedPipe(

                   hNamedPipe, // дескриптор канала

                   (LPOVERLAPPED)NULL// связь синхронная

                  ))

       {

                   wcerr<<L"Не удалось дождаться клиента"<<endl

                          <<L"Кодошибки: "<<GetLastError() <<endl;

                              CloseHandle(hNamedPipe);

                   wcout<<L"Нажмителюбуюклавишудлявыхода";

                   _getch();

                   return 0;

       }

       charout[100];

       // читаем данные из канала

                   intnData;

                   DWORDdwBytesRead;

                   if (!ReadFile(

                              hNamedPipe, // дескриптор канала

                              out, // адрес буфера для ввода данных

                              100, // количество читаемых байтов

                              &dwBytesRead, // количество прочитанных байтов

                              (LPOVERLAPPED)NULL// передача данных синхронная

                             ))

                   {

                              wcerr<<L"Не удалось прочитать данные из канала"<<endl

                                          <<L"Кодошибки: "<<GetLastError() <<endl;

                              CloseHandle(hNamedPipe);

                              wcout<<L"Нажмителюбуюклавишудлявыхода";

                              _getch();

                              return 0;

                   }

                   // выводим прочитанные данные на консоль

                   wcout<<L"Получено сообщение: "<<out<<endl;

       // закрываем дескриптор канала

       CloseHandle(hNamedPipe);

       // завершаем процесс

       wcout<<L"Данные успешно доставлены"<<endl;

       wcout<<L"PНажмите любую клавишу для выхода";

       _getch();

       return 0;

}

 

Сокеты

Сокеты в windows реализованы в библиотеке winsock2.dll, поэтому перед тем как начать работу с данной библиотекой, необходимо включить её в настройках линкера проекта. Для этого заходим в свойства проекта, вкладки Linker->Input и в свойстве “AdditionalDepencies” добавляем библиотеку ws2_32.lib. После этого мы можем включить <winsock2.h>и пользоваться функционалом winsock2.dll. Для того чтобы начать пользоваться сокетами, необходимо запустить функцию

WSAStartup (WORD wVersionRequested, LPWSADATA lpWSAData)

Где wVersionRequested – старший байт: номер версии используемного WinSock, а младший: номер подверсии

lpWSAData – структура WSADATA, в которую при удачном завершении будет занесена информация о производителе.

Функция возвращает ненулевое значение при неудаче

 

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

socket (intaf, inttype, intprotocol)

Где af указывает на семейство протоколов (Для передачи по сети обычно используется IPv4 (AF_INET) или IPv6(AF_INET6))

Type – тип создаваемого сокета. Мы будет использовать только потоковые сокеты (SOCK_STREAM) ввиду простоты работы с этим типом.

Protocol – тип протокола. Рекомендуется выставлять значение по умолчанию (0), чтобы ОС сама выбрала протокол. После успешного создания функция вернёт дескриптор сокета, иначе вернётся нулевое значение.

 

После создания сокета требуется установить соединение с удалённым узлом с помощью функции

Connect(SOCKET s, conststructsockaddr FAR* name, intnamelen)

Где s – подключаемый сокет

       Name – структура, содержащая номер порта, адрес, и семейство протоколов. Существует 2 варианта этой структуры – sockaddr и sockaddr_in –рекомендуется использовать 2 вариант ввиду неудобности и устаревания первого.

Namelen – длина структуры в байтах.

 

При неправильно заданном адресе, или несуществующем узле функция возвратит ненулевое значение

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

bind (SOCKET s, conststructsockaddr FAR* name, intnamelen)

Где s – связываемый сокет

name – структура sockaddr_in в которую как и в предыдущем случае

namelen –длина структуры в байтах

 

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

listen (SOCKET s, int backlog)

Где s – сокет, который требуется перевести

Backlog- максимальное количество сообщений в очереди

В случае неудачи функция возвращает ненулевое значение

 

Далее требуется извлечь запросы на подключение к сокетам с помщью функции

accept (SOCKETs, structsockaddrFAR* addr, intFAR* addrlen), которая автоматически создает новый сокет, выполняет связывание и возвращает его дескриптор (SOCKET). В name заносятся данные о только что созданном сокете, а namelen –длина структуры name в байтах. Функция не будет возвращать значение, пока очередь запросов на подключение пуста. Если в процессе работы возникла ошибка, то возвращаемое значение будет отрицательным.

После того как соединение установлено и у нас “на руках” есть сокет, через который можно передавать данные, можно использовать функции

       send (SOCKET s, const char FAR * buf, intlen,int flags) и

       recv (SOCKET s, char FAR* buf, intlen, int flags).

Где s –Сокет, через который передаются данные

Buf - указатель наданные в виде байтового массив

Len – длина передаваемого сообщения в байтах

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

Стоит заметить, что send и recv сами по себе не гарантируют корректной передачи и получения данных. Однако это гарантирует протокол TCP/IP, но только при условии, что соединение не будет разорвано перед полной отправкой данных.

Для закрытия сокета и разрыва соединения необходимо вызвать функцию

closesocket (SOCKET s), возвращающая в случае удачного закрытия нулевое значение

Для отключения библиотеки WinSock2 и освобождением ресурсов перед закрытием программы требуется вызвать функцию

int WSACleanup (void).

Обратите внимание, что завершение процесса автоматически не высвобождает ресурсы сокетов!

 

Пример сервера для программы обмена сообщениями между клиентами и сервером

 

#include<stdio.h>

#include<winsock2.h>// Wincosk2.h долженбыть

// подключенраньшеwindows.h!

#include<windows.h>

 

#defineMY_PORT 666

// Порт, который слушает сервер

 

// макрос для печати количества активных

// пользователей

#definePRINTNUSERSif (nclients)\

printf("%d user on-line\n",nclients);\

elseprintf("No User on line\n");

 

// прототипфункции, обслуживающий

// подключившихсяпользователей

DWORDWINAPISexToClient(LPVOIDclient_socket);

 

// глобальная переменная – количество

// активных пользователей

intnclients = 0;

 

int main(intargc, char* argv[])

{

       charbuff[1024]; // Буфер для различных нужд

 

       printf("TCP SERVER DEMO\n");

 

       // Шаг 1 - Инициализация Библиотеки Сокетов

       // Т.к. возвращенная функцией информация

       // не используется ей передается указатель на

       // рабочий буфер, преобразуемый

       // к указателю на структуру WSADATA.

       // Такой прием позволяет сэкономить одну

       // переменную, однако, буфер должен быть не менее

       // полкилобайта размером (структура WSADATA

       // занимает 400 байт)

       if (WSAStartup(0x0202, (WSADATA *)&buff[0]))

       {

                   // Ошибка!

                   printf("Error WSAStartup %d\n",

                              WSAGetLastError());

                   return -1;

       }

 

       // Шаг 2 - созданиесокета

       SOCKETmysocket;

       // AF_INET - сокет Интернета

       // SOCK_STREAM - потоковый сокет (с

       // установкой соединения)

       // 0 - по умолчанию выбирается TCP протокол

       if ((mysocket = socket(AF_INET, SOCK_STREAM, 0))<0)

       {

                   // Ошибка!

                   printf("Error socket %d\n", WSAGetLastError());

                   WSACleanup();

                   // Деиницилизациябиблиотеки Winsock

                   return -1;

       }

 

       // Шаг 3 связывание сокета с локальным адресом

       sockaddr_inlocal_addr;

       local_addr.sin_family = AF_INET;

       local_addr.sin_port = htons(MY_PORT);

       // не забываем о сетевом порядке!!!

       local_addr.sin_addr.s_addr = 0;

       // сервер принимает подключения

       // на все IP-адреса

 

       // вызываем bind длясвязывания

       if (bind(mysocket, (sockaddr *)&local_addr,

                   sizeof(local_addr)))

       {

                   // Ошибка

                   printf("Error bind %d\n", WSAGetLastError());

                   closesocket(mysocket); // закрываемсокет!

                   WSACleanup();

                   return -1;

       }

 

       // Шаг 4 ожидание подключений

       // размер очереди – 0x100

       if (listen(mysocket, 0x100))

       {

                   // Ошибка

                   printf("Error listen %d\n", WSAGetLastError());

                   closesocket(mysocket);

                   WSACleanup();

                   return -1;

       }

 

       printf("Waiting for client\n");

 

       // Шаг 5 извлекаем сообщение из очереди

       SOCKETclient_socket; // сокет для клиента

       sockaddr_inclient_addr; // адресклиента

                                                                                         // (заполняется системой)

 

                                                                                         // функции accept необходимо передать размер

                                                                                         // структуры

       intclient_addr_size = sizeof(client_addr);

 

       // цикл извлечения запросов на подключение из

       // очереди

       while ((client_socket = accept(mysocket, (sockaddr *)

                   &client_addr, &client_addr_size)))

       {

                   nclients++; // увеличиваем счетчик

                                                                  // подключившихся клиентов

 

                                                                  // пытаемся получить имя хоста

                   HOSTENT *hst;

                   hst = gethostbyaddr((char *)

                              &client_addr.sin_addr.s_addr, 4, AF_INET);

 

                   // выводсведенийоклиенте

                   printf("+%s [%s] new connect!\n",

                              (hst)? hst->h_name: "",

                              inet_ntoa(client_addr.sin_addr));

                   PRINTNUSERS

 

                              // Вызов нового потока для обслуживания клиента

                              // Да, для этого рекомендуется использовать

                              // _beginthreadex но, поскольку никаких вызов

                              // функций стандартной Си библиотеки поток не

                              // делает, можно обойтись и CreateThread

                              DWORDthID;

                   CreateThread(NULL, NULL, SexToClient,

                              &client_socket, NULL, &thID);

       }

       return 0;

}

 

// Эта функция создается в отдельном потоке и

// обсуживает очередного подключившегося клиента

// независимо от остальных

DWORDWINAPISexToClient(LPVOIDclient_socket)

{

       SOCKETmy_sock;

       my_sock = ((SOCKET *)client_socket)[0];

       char buff[20 * 1024];

#definesHELLO"Hello, Sailor\r\n"

 

       // отправляем клиенту приветствие

       send(my_sock, sHELLO, sizeof(sHELLO), 0);

 

       // цикл эхо-сервера: прием строки от клиента и

       // возвращение ее клиенту

       intbytes_recv;

       while ((bytes_recv =

                   recv(my_sock, &buff[0], sizeof(buff), 0))

                   &&bytes_recv!= SOCKET_ERROR)

       {

                   printf("%s", buff);

                   send(my_sock, &buff[0], bytes_recv, 0);

       }

 

       // если мы здесь, то произошел выход из цикла по

       // причине возращения функцией recv ошибки –

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

       nclients--; // уменьшаем счетчик активных клиентов

       printf("-disconnect\n"); PRINTNUSERS

 

                   // закрываемсокет

                   closesocket(my_sock);

       return 0;

}

 

Примерклиента:

// Пример простого TCP клиента

#include<stdio.h>

#include<string.h>

#include<winsock2.h>

#include<windows.h>

 

 

#definePORT 666

#defineSERVERADDR"127.0.0.1"

 

int main(intargc, char* argv[])

{

       char buff[1024];

       printf("TCP DEMO CLIENT\n");

 

       // Шаг 1 - инициализациябиблиотеки Winsock

       if (WSAStartup(0x202, (WSADATA *)&buff[0]))

       {

                   printf("WSAStart error %d\n", WSAGetLastError());

                   return -1;

       }

 

       // Шаг 2 - созданиесокета

       SOCKETmy_sock;

       my_sock = socket(AF_INET, SOCK_STREAM, 0);

       if (my_sock< 0)

       {

                   printf("Socket() error %d\n", WSAGetLastError());

                   return -1;

       }

 

       // Шаг 3 - установка соединения

 

       // заполнение структуры sockaddr_in

       // указание адреса и порта сервера

       sockaddr_indest_addr;

       dest_addr.sin_family = AF_INET;

       dest_addr.sin_port = htons(PORT);

       HOSTENT *hst;

 

       // преобразование IP адреса из символьного в

       // сетевойформат

       if (inet_addr(SERVERADDR)!= INADDR_NONE)

                   dest_addr.sin_addr.s_addr = inet_addr(SERVERADDR);

       else

                   // попытка получить IP адрес по доменному

                   // имени сервера

                   if (hst = gethostbyname(SERVERADDR))

                              // hst->h_addr_list содержит не массив адресов,

                              // а массив указателей на адреса

                              ((unsignedlong *)&dest_addr.sin_addr)[0] =

                              ((unsignedlong **)hst->h_addr_list)[0][0];

                   else

                   {

                              printf("Invalid address %s\n", SERVERADDR);

                              closesocket(my_sock);

                              WSACleanup();

                              return -1;

                   }

 

       // адрес сервера получен – пытаемся установить

       // соединение

       if (connect(my_sock, (sockaddr *)&dest_addr,

                   sizeof(dest_addr)))

       {

                   printf("Connect error %d\n", WSAGetLastError());

                   return -1;

       }

 

       printf("Соединение с %s успешно установлено\n\

Type quit for quit\n\n", SERVERADDR);

 

       // Шаг 4 - чтение и передача сообщений

       intnsize;

       while ((nsize = recv(my_sock, &buff[0],

                   sizeof(buff) - 1, 0))

                  !=SOCKET_ERROR)

       {

                   // ставим завершающий ноль в конце строки

                   buff[nsize] = 0;

 

                   // выводимнаэкран

                   printf("S=>C:%s", buff);

 

                   // читаем пользовательский ввод с клавиатуры

                   printf("S<=C:"); fgets(&buff[0], sizeof(buff) - 1,

                              stdin);

 

                   // проверкана "quit"

                   if (!strcmp(&buff[0], "quit\n"))

                   {

                              // Корректный выход

                              printf("Exit...");

                              closesocket(my_sock);

                              WSACleanup();

                              return 0;

                   }

 

                   // передаем строку клиента серверу

                   send(my_sock, &buff[0], nsize, 0);

       }

 

       printf("Recv error %d\n", WSAGetLastError());

       closesocket(my_sock);

       WSACleanup();

       return -1;

}

 

 


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: