Введение в сетевое программирование
Скачать 1.2 Mb.
|
Введение в сетевое программированиеЗадачаЗапрограммировать службу удаленных вычислений:
сервер возвращает результат Разработка протокола Calculation 1.0Запрос клиента (12*6):
Ответы сервера: ОК 72 \n (если все нормально) ERR \n (если запрос неправильный) \n – нужен, чтобы узнать где конец сообщения Следующий запрос клиент может посылать только после получения ответа на предыдущий! Вопрос: используем TCP или UDP ? Сервер (TCP)запускается заранее, до подключения клиентов сообщает ОС, что будет ожидать сообщений, посланных на заранее утвержденный порт № 12345 выделяет память для очереди подключений В цикле:
Клиент (TCP)получает от ОС случайный номер порта для общения с сервером устанавливает соединение с сервером передает/принимает данные закрывает соединение с сервером Сервер (UDP)запускается заранее, до подключения клиентов сообщает ОС, что будет ожидать сообщений, посланных на заранее утвержденный порт № 12345 В цикле:
Клиент (UDP)получает от ОС случайный номер порта для общения с сервером передает/принимает данные Интерфейс транспортного уровня. Сокеты.Socket (гнездо) - структура данных, идентифицирующая сетевое соединение Команды:
BIND – сервер связывает свой локальный адрес (порт) с сокетом LISTEN – сервер выделяет память под очередь подсоединений клиентов и устанавливает сокет в состояние «listening» (TCP) ACCEPT – сервер ожидает подсоединения клиента или принимает первое подключение из очереди (TCP) CONNECT–клиент запрашивает соединение (TCP) SEND / SEND_TO – послать данные (TCP / UDP) RECEIVE / RECEIVE_FROM – получить данные (TCP / UDP) DISCONNECT – запросить разъединение (TCP) РеализацииLinux: sys/socket.h Windows (основана на коде BSD) : winsock2.h Кроссплатформенные обертки на C, C++ Обертки в скипровых ЯП TCP организует «виртуальное» потоковое соединение UDP передает отдельные пакеты На практике наивные программисты думают, что одному вызову send будет соответствовать один вызов recv
В случае использования TCP возможны ситуации:
отправка одним send и получение всех данных по частям несколькими recv (если размер отправляемых данных > MTU) При вызове recv, если размер параметра «буфер» меньше объема полученных данных, то в случае TCP следующие вызовы recv получат оставшиеся данные (часто удобно получать по 1 байту) в случае UDP – не поместившиеся в буфер данные удаляются Переменные:
sockaddr_in ServerAddr; int err, maxlen = 512; string recvbuf(maxlen,’=’); 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)); listen (ListenSocket, 50); while (true) {
while (err > 0) {
if (err <= 0) break; string result = …… вычисляем результат …………… send(ClientSocket, &result[0], result.size(),0); } closesocket(ClientSocket); } TCP-клиент на C++Переменные:
sockaddr_in ServerAddr; int err, maxlen = 512; string recvbuf(maxlen,’=‘); 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); connect (ConnectSocket, (sockaddr *) &ServerAddr, sizeof(ServerAddr)); TCP-клиент на C++for (int i = 0; i < 1000; i++) {
send (ConnectSocket, &query[0], query.size(),0); err = recv(ConnectSocket,&recvbuf[0],maxlen,0); if (err <= 0) break; cout << recvbuf.substr(0,err); } closesocket(ConnectSocket); Ненадежный UDP - серверПеременные:
sockaddr_in ServerAddr, ClientAddr; int err, maxlen = 512, ClientAddrSize=sizeof(ClientAddr); string recvbuf(maxlen,’=‘); Ненадежный 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); bind (SendRecvSocket, ServerAddr, sizeof(ServerAddr)); // listen – нельзя !!! Ненадежный UDP - серверwhile (true) {
err = recvfrom (SendRecvSocket, recvbuf, maxlen, 0, (sockaddr *)&ClientAddr, &ClientAddrSize); if (err <= 0) continue; cout << recvbuf.substr(0,err) << “\n”; string result = ……………………………………. sendto (SendRecvSocket, &result[0], result.size(), 0, (sockaddr *)&ClientAddr, sizeof(ClientAddr)); } Ненадежный UDP - клиентПеременные:
sockaddr_in ServerAddr; int err, maxlen = 512; string recvbuf(maxlen,’=‘); Ненадежный 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); Ненадежный UDP - клиентfor (int i = 0; i < 1000; i++) {
sendto (SendRecvSocket, &query[0], query.size(), 0, (sockaddr *)&ServerAddr, sizeof(ServerAddr)); err = recvfrom (SendRecvSocket,&recvbuf[0],maxlen,0,0,0); if (err > 0) cout << recvbuf.substr(0,err); } closesocket(SendRecvSocket); Вопросы для самоконтроляПочему клиент не вызывает bind, разве ему не нужен номер порта? Сервер ждет подключения клиентов в функции listen? Если мы сделаем сервер, который общается одновременно с несколькими клиентами, как мы будем проверять от какого клиента получила данные функция recv? Вопросы для самоконтроляUDP-клиент не вызывает функцию connect, где же тогда ему присваивают номер порта? На какой функции сервер ждет запроса клиента? Что происходит если размер буфера приема маловат? UpgradeЧто произойдет, если сообщения будут большими (длинная арифметика)? Как изменить программы, чтобы клиент смог посылать конвейерные запросы? А если одновременно подключатся много клиентов? Если это приведет к неудобствам, то как их исправить? А вдруг какой-нибудь пакет потеряется? TCP-сервер 2.0 на C++ (длинная арифметика)Прием одного запроса от клиента разбивается на несколько recv: string recv_string; char lastSymb = 0; while (lastSymb != 10) //10 = \n {
lastSymb = recvbuf[err-1]; recv_string += recvbuf.substr(0,err); } TCP-клиент 2.0 на C++ (длинная арифметика)Прием ответа сервера разбивается на несколько recv: string recv_string; char lastSymb = 0; while (lastSymb != 10) //10 = \n {
lastSymb = recvbuf[err-1]; recv_string += recvbuf.substr(0,err); } Надежный UDP – клиентОтправляем одинаковые запросы, пока не получим ответ! while (err == 0) {
// проверяем, получен ли результат err = select(-1, &fds, 0, 0, &timeToWaitAnswer); if (err == 0) cout << «Пакет потерян. Попытаемся еще раз\n"; } recvfrom(SendRecvSocket,&recvbuf[0],maxlen,0,0,0); Вариант 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); } Функция processClientstatic 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 … … } Вариант 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) { // цикл не нужен для epoll 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 … } } listenSocket = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP); //создаем специальный файловый дескриптор для работы с epoll int epfd = epoll_create(MAX_CLIENT_COUNT); //добавляем впускающий сокет в общий массив для функции epoll ev.data.fd = listenSocket; ev.events = EPOLLIN; epoll_ctl(epfd, EPOLL_CTL_ADD, listenSocket, &ev); //резервируем ресурсы для максимального количества клиентов events.reserve(MAX_CLIENT_COUNT); //добавляем событие, соответствующее подключению нового клиента в общий массив событий epoll events.push_back(ev); listen(listenSocket, 1000) while (true) { //ждем подключения нового клиента или прихода запроса от какого-нибудь из уже подключенных клиентов int readyToReadfdCount = epoll_wait(epfd, &events[0], MAX_CLIENT_COUNT,-1); for (int index = 0; index < readyToReadfdCount; index++) { //подключился новый клиент if (events[index].data.fd== listenSocket) { clientSocket = accept(listenSocket, (struct sockaddr *)&clientAddress, &addrlen); //добавляем сокет подключившегося клиента в массив событий epoll ev.data.fd = clientSocket; epoll_ctl(epfd, EPOLL_CTL_ADD, clientSocket, &ev); events.push_back(ev); } else { Вариант 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); }); } |