Основы сетевой передачи данных. Работа с сокетами. Обработка запросов от нескольких клиентов, страница 3

Обработка запросов от нескольких клиентов.

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

bind(s, (sockaddr*)&serv_addr, nsize));

listen(s, 2);

sender_sock = accept(s, (sockaddr*)&sender, &nsize);

recv(sender_sock, &buff[0], sizeof(buff), 0);

make(buff);

closesocket(s);

WSACleanup();

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

Чтобы сервер не завершал работу после одного запроса, необходимо задать цикл:

bind(s, (sockaddr*)&serv_addr, nsize));

while(1){

listen(s, 2);

sender_sock = accept(s, (sockaddr*)&sender, &nsize);

recv(sender_sock, &buff[0], sizeof(buff), 0);

make(buff);

if(WSAGetLastError()) break;

}

closesocket(s);

WSACleanup();

Такая программа, выполнив обработку одного запроса, возвращает управление функции listen и ожидает нового запроса.

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

Теперь вопрос: как заставить сервер слушать новые запросы, не дожидаясь обработки предыдщего?

Одним из возможных решений этой задачи является использование дочерних потоков процесса через вызов функции CreateThread:

CreateThread(

            NULL,              // аттрибуты безопасности, значчение по умолчанию.

            0,                 // рамер стека потока, значение по умолчанию.

            ThreadProc,        // функция, которая будет выполняться в потоке

            pData,             // структура, в которую заносятся параметры функции ThreadProc

            0,                 // флаги

            &ThreadId);   // идентификатор потока

В применении к нашей программе это будет выглядеть так (для выделения памяти не забываем подключать malloc.h):

typedef struct {

               сhar* buff;

               SOCKET s;

}  *my_data;

DWORD ThreadProc(LPVOID params){

               my_data  data;

               data = (my_data)params;

               buff = data->buff;

               s = data->s;

recv(sender_sock, &buff[0], sizeof(buff), 0);

make(buff);

}

void main(){

**********************************

**********************************

my_data pData = (my_data)malloc(sizeof(my_data));

DWORD ThreadId[N];

bind(s, (sockaddr*)&serv_addr, nsize));

ThreadNum = 0;

while(1){

listen(s, 2);

sender_sock = accept(s, (sockaddr*)&sender, &nsize);

pData->s = sеnder_sock;

pData->buff = buff;

CreateThread(NULL, 0 ,(LPTHREAD_START_ROUTINE)& ThreadProc, pData, 0, &ThreadId[ThreadNum]);

ThreadNum++;

if(WSAGetLastError()) break;

}

closesocket(s);

WSACleanup();

}

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

Задания.

Лабораторная работа №1.

TCP клиент, TCP сервер.

1) Клиент вводит строку с клавиатуры и отсылает ее серверу.

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

3) Клиент принимает результат и выводит его на экран.

Требования:

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

 - работа приложения должна быть видна (чтобы не было вопросов "А как мне понять, что программа работает?").

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

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

"Чат"

1) Клиент <--> Клиент

Клиенту задается адрес*, с которым он связывается.

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

Клиент в цикле вводит строки с клавиатуры. Строка "quit" (или любая другая, но пользователь должен быть предупрежден) означает выход из приложения.

К введенной строке добавляется имя в формате "имя: строка" и пересылается по адресу*.

Другой клиент принимает эту строку и выводит на экран.

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

Должны быть реализованы "приветственное" сообщение (например, "имя enter the room") и "прощальное" сообщение.

2) Сервер

Имеет структуру данных (массив) для запоминания адресов подключенных к нему клиентов.

Принимает сообщение от произвольного клиента и передает его по всему списку клиентов, кроме отправителя.

Для добавления/удаления адреса в список используются приветственные/прощальные сообщения.

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