Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Кроссплатформенный https сервер с неблокирующими сокетами. Часть 3

Anna | 24.06.2014 | нет комментариев
В этой статье я продолжаю улучшить однопоточный https сервер на неблокирующих сокетах. Предыдущие статьи с ссылками на начальный код, дозволено обнаружить тут:
Примитивный кросcплатформенный сервер с помощью ssl
Кроссплатформенный https сервер с неблокирующими сокетами 
Кроссплатформенный https сервер с неблокирующими сокетами. Часть 2В конце этой статьи будет ссылка на начальный код сервера, тот, что я протестировал в Visual Studio 2012 (Windows 8 64bit), g 4.4 (Linux 32bit), g 4.6 (Linux 64bit). Сервер принимает соединения от всякого числа заказчиков и отправляет в результат заголовки запроса.
Но начну я статью вероятно, с результатов на некоторые комментарии к предыдущим.

Во-первых, получив массу отрицательных откликов о незаурядности своего кода, отныне я решил свои статьи помещать еще и в хаб «Ненормальное программирование».
Во-вторых, я решил огромнее не ставить пометку «tutorial»: кто-то обнаружит что-то новое в моих статьях, а кому-то они покажутся дилетантскими. Я не против…

Сейчас про мой жанр программирования:
1. Я продолжу писать код в заголовочных файлах по ряду причин:
а) Я хочу без дополнительных телодвижений знать полное число строк кода и следственно мне так комфортней.
б) В всякий момент я могу захотеть прикрутить к заказчику либо серверу template, и не хотелось бы ради этого переписывать каждый код.
Те кто уверен, что так как я делать невозможно — можете поучить программированию создателей stl и boost вначале, а потом переименовать файл server.h в server.cpp и будет каждому хорошо…

2. Я оставлю безмерный цикл в конструкторе по одной причине: считаю данный подход верным. Если класс не делает огромнее ничего, помимо метаморфозы своих внутренних переменных, то самым верным будет оставить у этого класса публичной одну исключительную функцию: его конструктор.
Дозволено безусловно в этом случае вообще без класса, но с классом мне как-то привычней, да и всеобщии функции на пустом месте тоже не необходимы.

3. Я не буду применять std::copy взамен memcpy по одной причине: std::copy — тормоз!

Наконец хочу поблагодарить всех, кто не поленился откомпилировать исходник и указать на некоторые ошибки. Я постарался их учесть и поправить.

Сейчас о основном. 
Дабы сервер из предыдущей статьи наконец подготовить для парсинга заголовков запроса и раздаче файлов, осталось сделать одно маленькое дополнение: начать взамен безмерного цикла применять намеренно предуготовленные для пассивного ожидания сетевых событий функции.
В Windows и Linux есть несколько таких функций, я предлагаю применять select в Windows и epoll в Linux.

Есть задача в том, что функции epoll в Windows не существует. Дабы код выглядел единообразно во всех системах, давайте напишем код сервера так, как словно epoll в Windows есть!

Простая реализация epoll для Windows с поддержкой select
1. Добавим в план Visual Studio два пустых файла из той же директории, где размещен «server.h». Файлы: «epoll.h» и «epoll.cpp».
2. Перенесем в файл epoll.h определения констант, конструкций и функций из документации по epoll:

#ifndef __linux__

enum EPOLL_EVENTS
  {
    EPOLLIN = 0x001,
#define EPOLLIN EPOLLIN
    EPOLLPRI = 0x002,
#define EPOLLPRI EPOLLPRI
    EPOLLOUT = 0x004,
#define EPOLLOUT EPOLLOUT
    EPOLLRDNORM = 0x040,
#define EPOLLRDNORM EPOLLRDNORM
    EPOLLRDBAND = 0x080,
#define EPOLLRDBAND EPOLLRDBAND
    EPOLLWRNORM = 0x100,
#define EPOLLWRNORM EPOLLWRNORM
    EPOLLWRBAND = 0x200,
#define EPOLLWRBAND EPOLLWRBAND
    EPOLLMSG = 0x400,
#define EPOLLMSG EPOLLMSG
    EPOLLERR = 0x008,
#define EPOLLERR EPOLLERR
    EPOLLHUP = 0x010,
#define EPOLLHUP EPOLLHUP
    EPOLLRDHUP = 0x2000,
#define EPOLLRDHUP EPOLLRDHUP
    EPOLLONESHOT = (1 << 30),
#define EPOLLONESHOT EPOLLONESHOT
    EPOLLET = (1 << 31)
#define EPOLLET EPOLLET
  };

/* Valid opcodes ( "op" parameter ) to issue to epoll_ctl().  */
#define EPOLL_CTL_ADD 1      /* Add a file descriptor to the interface.  */
#define EPOLL_CTL_DEL 2      /* Remove a file descriptor from the interface.  */
#define EPOLL_CTL_MOD 3      /* Change file descriptor epoll_event structure.  */

typedef union epoll_data
{
    void				*ptr;
    int					fd;
    unsigned int		u32;
    unsigned __int64    u64;
} epoll_data_t;

struct epoll_event
{
    unsigned __int64     events;      /* Epoll events */
    epoll_data_t		 data;        /* User data variable */
};

int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

#endif

3. В файл epoll.cpp добавляем заголовки, а так же глобальную переменную, в которой будут храниться сокеты и их состояния:

#include "epoll.h"
#include <map>
#ifndef WIN32
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#else
#include <io.h>
#include <Winsock2.h>
#pragma comment(lib, "ws2_32.lib")
#endif

std::map<int, epoll_event> g_mapSockets;

4. Добавляем код для первой функции:

int epoll_create(int size)
{
	return 1;
}

Что здесь происходит?
На сколько я могу судить по документации: подлинный код в линуксе при всяком вызове epoll_create создает файл, в котором хранятся состояния сокетов. Видимо это необходимо в многопоточных процессах.
У нас же процесс однопоточный и нам не необходимо больше одной конструкции для хранения сокетов. Следственно epoll_create у нас это «заглушка».

5. С поддержкой stl добавление и удаление сокетов в памяти происходит элементарно:

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
{
	switch(op)
	{
		case EPOLL_CTL_ADD:
		case EPOLL_CTL_MOD:
			g_mapSockets[fd] = *event;
			return 0;
		case EPOLL_CTL_DEL:
			if (g_mapSockets.find(fd) == g_mapSockets.end()) 
				return -1;

			g_mapSockets.erase(fd);
			return 0;
	}
	return 0;
}

6. Наконец основное: функцию ожидания реализуем через select

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
{
	if ((!events) || (!maxevents))
		return -1;

	//Создаем и обнуляем конструкции для функции select
	fd_set readfds, writefds, exceptfds;

	FD_ZERO(&readfds);
	FD_ZERO(&writefds);
	FD_ZERO(&exceptfds);

	//Заполняем конструкции сокетами
	int nFDS = 0;
	for (auto it=g_mapSockets.begin(); it != g_mapSockets.end();   it)
	{
		if (it->first == -1)
			continue;

		if (it->first > nFDS)
			nFDS = it->first;

		FD_SET(it->first, &readfds);
		FD_SET(it->first, &writefds);
		FD_SET(it->first, &exceptfds);
	}

	//Задаем промежуток ожидания
	struct timeval tv;
	tv.tv_sec = timeout/1000;
	tv.tv_usec = timeout - tv.tv_sec*1000;

	//Ждем событий
	nFDS  ;
	select(nFDS, &readfds, &writefds, &exceptfds, &tv);

	//Заполняем конструкцию для отправки программе так, как словно она вызвала epoll
	int nRetEvents = 0;
	for (auto it=g_mapSockets.begin(); (it != g_mapSockets.end() && nRetEvents < maxevents);   it)
	{
		if (it->first == -1)
			continue;
		if (!FD_ISSET(it->first, &readfds) && !FD_ISSET(it->first, &writefds) && !FD_ISSET(it->first, &exceptfds))
			continue;

		memcpy(&events[nRetEvents].data, &it->second.data, sizeof(epoll_data));

		if (FD_ISSET(it->first, &readfds))
			events[nRetEvents].events |= EPOLLIN;
		if (FD_ISSET(it->first, &writefds))
			events[nRetEvents].events |= EPOLLOUT;
		if (FD_ISSET(it->first, &exceptfds))
			events[nRetEvents].events |= EPOLLERR;

		nRetEvents  ;
	}

	return nRetEvents;
}

Вот и все. Функция epoll для Windows реализована!

Добавление epoll в сервер

1. Добавляем в заголовки:

#ifdef __linux__
#include <sys/epoll.h>
#else
#include "epoll.h"
#endif

2. В класс CServer добавляем строки:

	private:
		//События слушающего сокета
		struct epoll_event m_ListenEvent;
		//События клиентских сокетов
		vector<struct epoll_event> m_events;
		int m_epoll;

3. В конструкторе CServer все, что позже вызова функции listen меняем на:

			m_epoll = epoll_create (1);
			if (m_epoll == -1)
			{
				printf("error: epoll_create\n");
				return;
			}

			m_ListenEvent.data.fd = listen_sd;
			m_ListenEvent.events = EPOLLIN | EPOLLET;
			epoll_ctl (m_epoll, EPOLL_CTL_ADD, listen_sd, &m_ListenEvent);

			while(true)
			{
				m_events.resize(m_mapClients.size() 1);
				int n = epoll_wait (m_epoll, &m_events[0], m_events.size(), 5000);

				if (n == -1)
					continue;

				Callback(n);
			}

4. Ветхую функцию CServer::Callback меняем на новую:

		void Callback(const int nCount)
		{
			for (int i = 0; i < nCount; i  )
			{
				SOCKET hSocketIn = m_events[i].data.fd;

				if (m_ListenEvent.data.fd == (int)hSocketIn)
				{
					if (!m_events[i].events == EPOLLIN)
						continue;

					struct sockaddr_in sa_cli;  
					size_t client_len = sizeof(sa_cli);
#ifdef WIN32
					const SOCKET sd = accept (hSocketIn, (struct sockaddr*) &sa_cli, (int *)&client_len);
#else
					const SOCKET sd = accept (hSocketIn, (struct sockaddr*) &sa_cli, (socklen_t *)&client_len);
#endif  
					if (sd != INVALID_SOCKET)
					{
						//Добавляем нового заказчика в класс сервера
						m_mapClients[sd] = shared_ptr<CClient>(new CClient(sd));

						auto it = m_mapClients.find(sd);
						if (it == m_mapClients.end())
							continue;

						//Добавляем нового заказчика в epoll
						struct epoll_event ev = it->second->GetEvent();
						epoll_ctl (m_epoll, EPOLL_CTL_ADD, it->first, &ev);
					}					
					continue;
				}

				auto it = m_mapClients.find(hSocketIn); //Находим заказчика по сокету
				if (it == m_mapClients.end())
					continue;

				if (!it->second->Continue()) //Делаем что-нибудь с заказчиком
				{
					//Если заказчик вернул false, то удаляем заказчика из epoll и из класса сервера
					epoll_ctl (m_epoll, EPOLL_CTL_DEL, it->first, NULL);
					m_mapClients.erase(it);
				}
			}
		}

С классом сервера завершили, осталось разобраться с классом CClient.
Добавим в него такой код:

	private:
		//События сокета заказчика
		struct epoll_event m_ClientEvent;
	public:
		const struct epoll_event GetEvent() const {return m_ClientEvent;}

И на этом добавление кода поддержки epoll завершено!

Вот здесь находится план для Visual Studio: c0.3s3s.org
Для компиляции в Linux файлы epoll.h и epoll.cpp не необходимы, т.е все как традиционно: «скопировать в одну директорию файлы: serv.cpp, server.h, ca-cert.pem и в командной строке набрать: «g -std=c 0x -L/usr/lib -lssl -lcrypto serv.cpp» „

 

Источник: programmingmaster.ru

 

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB