Презентация URL

advertisement
Введение в сетевое
программирование
1
Задача

Запрограммировать службу удаленных
вычислений:

клиенты просят сервер вычислить выражение
(для начала содержащее только одну
арифметическую операцию +, -, *, / )

сервер возвращает результат
2
Разработка протокола
Calculation 1.0

Запрос клиента (12*6):
CALC * 12 6 \n

Ответы сервера:


ОК 72 \n (если все нормально)
ERR \n (если запрос неправильный)
\n – нужен, чтобы узнать где конец сообщения
Вопрос: используем TCP или UDP ?
3
Сервер (TCP)
1) запускается заранее, до подключения клиентов
2) сообщает ОС, что будет ожидать сообщений,
посланных на заранее утвержденный порт
№ 12345
3) выделяет память для очереди подключений
4) В цикле:
a) устанавливает соединение с клиентом из очереди;
если очередь пуста, то ждет подключения клиента
b) принимает/передает данные
c) закрывает соединение с клиентом
UDP
4
Клиент (TCP)
1) получает от ОС случайный номер порта
для общения с сервером
2) устанавливает соединение с сервером
3) передает/принимает данные
4) закрывает соединение с сервером
UDP
5
Сервер (UDP)
1) запускается заранее, до подключения
клиентов
2) сообщает ОС, что будет ожидать
сообщений, посланных на заранее
утвержденный порт № 12345
3) В цикле:



ждет прихода сообщения
обрабатывает данные
передает результат
TCP
6
Клиент (UDP)
1) получает от ОС случайный номер порта
для общения с сервером
2) передает/принимает данные
TCP
7
Интерфейс транспортного
уровня. Сокеты.

Socket (гнездо) - структура данных,
идентифицирующая сетевое соединение

Команды:

SOCKET – создать новый (пустой) сокет

BIND – сервер связывает свой локальный
адрес (порт) с сокетом
8
Интерфейс транспортного
уровня. Команды.

LISTEN – сервер выделяет память под очередь
подсоединений клиентов и
устанавливает сокет в состояние
«listening» (TCP)

ACCEPT – сервер ожидает подсоединения
клиента или принимает первое
подключение из очереди (TCP)
CONNECT–клиент запрашивает соединение
(TCP)

9
Интерфейс транспортного
уровня. Команды.

SEND / SEND_TO – послать данные (TCP /
UDP)

RECEIVE / RECEIVE_FROM – получить
данные
(TCP / UDP)

DISCONNECT – запросить разъединение
(TCP)
10
Реализации

Linux: sys/socket.h

Windows (основана на коде BSD) : winsock2.h

Кроссплатформенные обертки
Обертки в скипровых ЯП

11
TCP-сервер на C++ (winsock2.h)

Переменные:

SOCKET
ListenSocket, ClientSocket;

sockaddr_in ServerAddr;

int err, maxlen = 512;

char* recvbuf = new char[maxlen+1];

char* result_string = new char[maxlen];
12
TCP-сервер на C++ (winsock2.h)

ListenSocket =
socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);

ServerAddr.sin_family = AF_INET;

ServerAddr.sin_addr.s_addr =
inet_addr("127.0.0.1");

ServerAddr.sin_port=htons(12345);

bind(ListenSocket, &ServerAddr, sizeof(ServerAddr));
13
TCP-сервер на C++ (winsock2.h)


listen (ListenSocket, 50);
while (true)

{ClientSocket =
accept(ListenSocket, NULL, NULL);

err = recv (ClientSocket, recvbuf, maxlen, 0);
if (err > 0)





{recvbuf[err] = 0;
printf("Received query: %s\n", (char* )recvbuf);
// вычисляем результат
int result = ………………………………………
14
TCP-сервер на C++ (winsock2.h)
snprintf(result_string, maxlen, "OK %d\n", result);
 send( ClientSocket, result_string,
strlen(result_string), 0 );
 printf("Sent answer: %s\n", result_string);
 } // end if



closesocket(ClientSocket);
} // end while
15
TCP-сервер 2.0 на C++





ClientSocket = accept(ListenSocket, 0, 0);
string recv_string;
char lastSymb = 0;
while (lastSymb != 10) //10 = \n
{





}
err = recv(ClientSocket, recvbuf, maxlen, 0);
lastSymb = recvbuf[err-1];
recvbuf[err] = 0; //признак конца строки
recv_string += recvbuf;
16
TCP-клиент на C++

Переменные:

SOCKET
ClientSocket;

sockaddr_in ServerAddr;

int err, maxlen = 512;

char* recvbuf = new char[maxlen];

char* query = new char[maxlen];
17
TCP-клиент на C++

ConnectSocket =
socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);

ServerAddr.sin_family = AF_INET;

ServerAddr.sin_addr.s_addr =
inet_addr("127.0.0.1");

ServerAddr.sin_port=htons(12345);
18
TCP-клиент на C++

connect (ConnectSocket, (sockaddr *)
&ServerAddr, sizeof(ServerAddr));

query = "CALC * 12 6\n";

send (ConnectSocket, query,strlen(query),0);

printf("Sent: %s\n", query);
19
TCP-клиент на C++

err = recv(ConnectSocket,recvbuf,maxlen,0);

if (err > 0)





{
recvbuf[err]=0;
printf(“Result: %s\n", (char* )recvbuf);
}
closesocket(ConnectSocket);
20
Ненадежный UDP - сервер

Переменные:

SOCKET
SendRecvSocket;

sockaddr_in ServerAddr, ClientAddr;

int err, maxlen = 512,
ClientAddrSize=sizeof(ClientAddr);

char* recvbuf = new char[maxlen+1];

char* result_string = new char[maxlen];
21
Ненадежный UDP - сервер

SendRecvSocket =
socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);

ServerAddr.sin_family = AF_INET;

ServerAddr.sin_addr.s_addr =
inet_addr("127.0.0.1");

ServerAddr.sin_port=htons(12345);
22
Ненадежный UDP - сервер



bind (SendRecvSocket, ServerAddr, sizeof(ServerAddr));
// listen – нельзя !!!
while (true)

{ // accept – нельзя !!!

err = recvfrom (SendRecvSocket, recvbuf, maxlen, 0,
(sockaddr *)&ClientAddr, &ClientAddrSize);
if (err > 0)
 {recvbuf[err]=0;




printf("Received query: %s\n", (char* )recvbuf);
// вычисляем результат
int result = ………………………………………
23
Ненадежный UDP - сервер





snprintf (result_string,maxlen, "OK %d\n",result);
sendto (SendRecvSocket, result_string,
strlen(result_string), 0, (sockaddr *)&ClientAddr,
sizeof(ClientAddr));
printf("Sent answer: %s\n", result_string);
} // end if
} // end while
24
Ненадежный UDP - клиент

Переменные:

SOCKET
SendRecvSocket;

sockaddr_in ServerAddr;

int err, maxlen = 512;

char* recvbuf = new char[maxlen+1];

char* query = new char[maxlen];
25
Ненадежный UDP - клиент

SendRecvSocket =
socket (AF_INET, SOCK_DGRAM,
IPPROTO_UDP);

ServerAddr.sin_family = AF_INET;

ServerAddr.sin_addr.s_addr =
inet_addr("127.0.0.1");

ServerAddr.sin_port=htons(12345);
26
Ненадежный UDP - клиент

query="CALC * 12 6\n";

sendto (SendRecvSocket, query,
strlen(query), 0, (sockaddr *)&ServerAddr,
sizeof(ServerAddr));

printf("Sent: %s\n", query);
27
Ненадежный UDP - клиент


err = recvfrom
(SendRecvSocket,recvbuf,maxlen,0,0,0);
if (err > 0)





{
recvbuf[err]=0;
printf(“Result: %s\n", (char* )recvbuf);
}
closesocket(SendRecvSocket);
28
Вопросы




Что произойдет, если сообщения будут
большими (длинная арифметика или
клиент будет посылать конвейерные
запросы)?
А если еще будет одновременно
подключаться много клиентов?
А вдруг какой-нибудь пакет потеряется?
Как следует изменить алгоритмы?
29
TCP-клиент 2.0 на C++




string recv_string;
char lastSymb = 0;
while (lastSymb != 10) //10 = \n
{





err = recv(ConnectSocket, recvbuf, maxlen, 0);
lastSymb = recvbuf[err-1];
recvbuf[err] = 0;
recv_string += recvbuf;
}
30
UDP – клиент 2.0


err = 0;
while (err == 0)







{
sendto(SendRecvSocket, query, strlen(query), 0,
(sockaddr *)&ServerAddr, sizeof(ServerAddr));
// проверяем, получен ли результат
err = select(-1, &fds, 0, 0, &timeToWaitAnswer);
if (err == 0)
cout << "Packet was lost. Another attempt\n";
}//end while
recvfrom(SendRecvSocket,recvbuf,maxlen,0,0,0);
31
Контрольные вопросы

Почему клиент не вызывает bind, разве ему
не нужен номер порта?

Сервер ждет подключения клиентов в
функции listen?

Если мы сделаем сервер, который общается
одновременно с несколькими клиентами, как
мы будем проверять от какого клиента
получила данные функция recv?
32
Контрольные вопросы

UDP-клиент не вызывает функцию
connect, где же тогда ему присваивают
номер порта?

На какой функции сервер ждет запроса
клиента?

Что происходит если размер буфера
приема маловат?
33
Обработка запросов одновременно
подключенных клиентов









Вариант 1. Использование потоков
while (true)
{
int *clientSocket = new int[1];
*clientSocket = accept(listenSocket,NULL,NULL);
if (*clientSocket < 0) handleError("accept failed:");
pthread_t threadId;
pthread_create(&threadId, NULL, processClient,
(void*)clientSocket);
}
34
Функция processClient






static pthread_mutex_t cs_mutex =
PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
void *processClient(void *dataPtr)
{
pthread_mutex_lock( &cs_mutex );
cout << ++concurrentClientCount << "\n";
pthread_mutex_unlock( &cs_mutex );

int clientSocket = ((int*)dataPtr)[0];
recv / send …
…




}
35
Обработка запросов одновременно
подключенных клиентов


















Вариант 2. Использование неблокирующих сокетов
listenSocket = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK,IPPROTO_TCP);
vector<pollfd> allSockets;
pollfd tmpPollfd; tmpPollfd.fd = listenSocket; tmpPollfd.events = POLLIN;
allSockets.push_back(tmpPollfd);
while (true) {
poll(&allSockets[0], allSockets.size(), -1);
for (int i = 0; i < allSockets.size(); ++i) {
if (allSockets[i].revents == 0) continue; //на этот сокет ничего не пришло
if (i == 0) { //произошло подключение нового клиента
int clientSocket = accept(listenSocket, NULL, NULL);
tmpPollfd.fd = clientSocket;
allSockets.push_back(tmpPollfd);
}
else { //произошел прием данных от клиента
int clientSocket = allSockets[i].fd;
recv / send …
36
} }
Обработка запросов одновременно
подключенных клиентов

Вариант 3. Использование событийно ориентированного
фреймворка (обертки над poll), например, Node.js

var net = require('net');
var server = net.createServer(processClient);
server.listen(28563, '0.0.0.0');
function processClient(clientSocket) {
//регистрация обработчика события прихода данных
clientSocket.on('data', function (data) {
… обработка data
clientSocket.write(answer);
});
}









37
Download