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

«Boost.Asio C Network Programming». Глава 3: Эхо сервер/клиент

Anna | 24.06.2014 | нет комментариев

Продолжаю перевод книги John Torjo «Boost.Asio C Network Programming».

Оглавление:

В этой главе мы реализуем малое клиент/серверное приложение, которое, видимо, будет самым простым клиент/серверным приложением. Это приложение эхо-сервер, тот, что возвращает заказчику то, что тот ему написал, а после этого закрывает соединение заказчика. Сервер может трудиться с любым числом заказчиков. Когда подключается новейший заказчик, он шлет сообщение. Сервер получает сообщение целиком и посылает его обратно. Позже этого он закрывает соединение.
Таким образом, всякий эхо-заказчик подключается к серверу, посылает сообщение и читает то, что ответил сервер, удостоверясь, что это то же сообщение, которое он послал, заканчивает общение с сервером.
Вначале мы будем реализовывать синхронное приложение, а после этого асинхронное, так что вы можете легко их сравнить:

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

TCP эхо сервер/клиент

Для TCP мы можем иметь дополнительное преобладание, всякое сообщение заканчивается символом ‘n’. Написание синхронного эхо сервер/клиента дюже легко.
Мы приведем примеры программ таких как синхронный заказчик, синхронный сервер, асинхронный заказчик и асинхронный сервер.

TCP синхронный заказчик

В большинстве нетривиальных примеров обыкновенно код заказчика значительно проще, чем сервера (так как сервер должен иметь дело с несколькими заказчиками).
Дальнейший пример это исключение из правил:

ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 8001);
size_t read_complete(char * buf, const error_code & err, size_t bytes) 
{
	if ( err) return 0;
	bool found = std::find(buf, buf   bytes, 'n') < buf   bytes;
	// we read one-by-one until we get to enter, no buffering
	return found ? 0 : 1;
}
void sync_echo(std::string msg) 
{
	msg  = "n";
	ip::tcp::socket sock(service);
	sock.connect(ep);
	sock.write_some(buffer(msg));
	char buf[1024];
	int bytes = read(sock, buffer(buf), boost::bind(read_complete,buf,_1,_2));
	std::string copy(buf, bytes - 1);
	msg = msg.substr(0, msg.size() - 1);
	std::cout << "server echoed our " << msg << ": "<< (copy == msg ? "OK" : "FAIL") << std::endl;
	sock.close();
}
int main(int argc, char* argv[]) 
{
	char* messages[] = { "John says hi", "so does James", "Lucy just got home", "Boost.Asio is Fun!", 0 };
	boost::thread_group threads;
	for ( char ** message = messages; *message;   message)
	 {
		threads.create_thread( boost::bind(sync_echo, *message));
		boost::this_thread::sleep( boost::posix_time::millisec(100));
	}
	threads.join_all();
}

Обратите внимание на функцию sync_echo. Она содержит всю логику для подключения к серверу, отправляет ему сообщение и ожидает обратного результата.
Вы подметили, что для чтения мы используем свободную функцию read(), потому что мы хотим получать сообщение все целиком до символа ‘n’. Функции sock.read_some() будет неудовлетворительно, так как она будет читать только то, что доступно, но вовсе не непременно все сообщение целиком.
3-й довод функции read() это завершающий обработчик. Она вернет 0, если сообщение прочитано всецело. В отвратном случае возвращается наивысший размер буфера, которые может быть прочитан на дальнейшем шаге (до заключения read). В нашем случае неизменно будет возвращаться 1, потому что мы не хотим ложно читать огромнее чем нам нужно.
В main() мы создаем несколько потоков; по одному потоку для всякого сообщения, которое отправляет заказчик, и ожидаем, пока они завершатся. Если вы запустите программу, то увидите дальнейший итог:

server echoed our John says hi: OK
server echoed our so does James: OK
server echoed our Lucy just got home: OK
server echoed our Boost.Asio is Fun!: OK

Обратите внимание, что, так как мы имеем дело с синхронным заказчиком, то нет никакой необходимости вызывать service.run().

TCP синхронный сервер

Синхронный эхо сервер написать достаточно легко, как показано в дальнейшем фрагменте кода:

io_service service;
size_t read_complete(char * buff, const error_code & err, size_t bytes) 
{
	if ( err) return 0;
	bool found = std::find(buff, buff   bytes, 'n') < buff   bytes;
	// we read one-by-one until we get to enter, no buffering
	return found ? 0 : 1;
}

void handle_connections() 
{
	ip::tcp::acceptor acceptor(service, ip::tcp::endpoint(ip::tcp::v4(),8001));
	char buff[1024];
	while ( true) 
	{
		ip::tcp::socket sock(service);
		acceptor.accept(sock);
		int bytes = read(sock, buffer(buff), boost::bind(read_complete,buff,_1,_2));
		std::string msg(buff, bytes);
		sock.write_some(buffer(msg));
		sock.close();
	}
}

int main(int argc, char* argv[]) 
{
	handle_connections();
}

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

TCP асинхронный заказчик

Как только мы начинаем трудиться асинхронно, код становится немножко труднее. Мы будем моделировать класс connection, как показано во 2-й главе.
Глядя на следующие фрагменты кода в этом разделе, вы подметите, что всякая асинхронная операция запускает новую асинхронную операцию, сберегая service.run() в работе.
Первое, основные функции:

#define MEM_FN(x) boost::bind(&self_type::x, shared_from_this())
#define MEM_FN1(x,y) boost::bind(&self_type::x, shared_from_this(),y)
#define MEM_FN2(x,y,z) boost::bind(&self_type::x, shared_from_this(),y,z)
class talk_to_svr : public boost::enable_shared_from_this<talk_to_svr>,boost::noncopyable 
{
	typedef talk_to_svr self_type;
	talk_to_svr(const std::string & message) : sock_(service), started_(true),message_(message) {}
	void start(ip::tcp::endpoint ep) 
	{
		sock_.async_connect(ep, MEM_FN1(on_connect,_1));
	}
public:
	typedef boost::system::error_code error_code;
	typedef boost::shared_ptr<talk_to_svr> ptr;
	static ptr start(ip::tcp::endpoint ep, const std::string & message) 
	{
		ptr new_(new talk_to_svr(message));
		new_->start(ep);
		return new_;
	}
	void stop() 
	{
		if ( !started_) return;
		started_ = false;
		sock_.close();
	}
	bool started() { return started_; }
	...
private:
	ip::tcp::socket sock_;
	enum { max_msg = 1024 };
	char read_buffer_[max_msg];
	char write_buffer_[max_msg];
	bool started_;
	std::string message_;
};

Мы хотим неизменно применять shared pointers на talk_to_svr, для того Дабы пока есть асинхронные операции в экземпляре talk_to_svr, данный экземпляр оставался жив. Для того Дабы избежать таких ошибок как создание экземпляров talk_to_svr в стеке, мы сделали конструктор приватным и запретили конструктор копирования (наследовались от boost::noncopyable).
У нас есть основные функции, такие как start(), stop(), и started(), которые делают только то, о чем говорят их наименования. Для создания соединения легко вызовите talk_to_svr::start(endpoint, message). Так же у нас имеются буферы для чтения и записи (read_buffer_ и write_buffer_).
Как объяснялось ранее, следующие строки крепко отличаются:

// equivalent to "sock_.async_connect(ep, MEM_FN1(on_connect,_1));"
sock_.async_connect(ep, boost::bind(&talk_to_svr::on_connect,shared_ptr_from_this(),_1));
sock_.async_connect(ep, boost::bind(&talk_to_svr::on_connect,this,_1));

В первом случае мы верно создаем завершающий обработчик async_connect, он будет сберегать shared pointer на экземпляр talk_to_server пока он не вызовет завершающий обработчик, тем самым, удостоверясь, что мы все еще живы, когда это произойдет.
В последнем случае мы ненормально создаем завершающий обработчик. К тому времени, когда вызывается экземпляр talk_to_server, он может быть теснее удален!
Для чтения и записи в сокет мы будем применять дальнейший фрагмент кода:

void do_read() 
{
	async_read(sock_, buffer(read_buffer_), 
	MEM_FN2(read_complete,_1,_2), MEM_FN2(on_read,_1,_2));
}
void do_write(const std::string & msg) 
{
	if ( !started() ) return;
	std::copy(msg.begin(), msg.end(), write_buffer_);
	sock_.async_write_some( buffer(write_buffer_, msg.size()), 
	MEM_FN2(on_write,_1,_2));
}
size_t read_complete(const boost::system::error_code & err, size_t bytes) 
{
	// similar to the one shown in TCP Synchronous Client
}

Функция do_read() вначале убеждается, что мы читаем сообщение от сервера, позже чего вызываетсяon_read(). Функция do_write() вначале копирует сообщение в буфер (имеет место вероятность того, что msg может выйти за область видимости и со временем разрушится), а после этого убеждается в том, что вызов on_write() происходит позже реальной записи.
И особенно значимые функции, которые содержат основную логику класса:

void on_connect(const error_code & err) 
{
	if ( !err) do_write(message_   "n");
	else stop();
}
void on_read(const error_code & err, size_t bytes) 
{
	if ( !err) 
	{
		std::string copy(read_buffer_, bytes - 1);
		std::cout << "server echoed our " << message_ << ": "<< (copy == message_ ? "OK" : "FAIL") << std::endl;
	}
	stop();
}
void on_write(const error_code & err, size_t bytes) 
{
	do_read();
}

Позже этого мы подключаемся и посылаем сообщение на сервер, do_write(). Когда операция записи закончена, вызывается on_write(), которая инициирует функцию do_read(). Когда завершается do_read(), вызывается on_read(), тут мы легко проверяем, что сообщение от сервера то же самое, что мы ему посылали и выходим из нее.
Мы отправим три сообщения на сервер только Дабы сделать это все больше увлекательным:

int main(int argc, char* argv[]) 
{
	ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 8001);
	char* messages[] = { "John says hi", "so does James", "Lucy got home", 0 };
	for ( char ** message = messages; *message;   message) 
	{
		talk_to_svr::start( ep, *message);
		boost::this_thread::sleep( boost::posix_time::millisec(100));
	}
	service.run();
}

В предыдущем фрагменте кода будет генерироваться дальнейший код:

server echoed our John says hi: OK
server echoed our so does James: OK
server echoed our Lucy just got home: OK
TCP асинхронный сервер

Как показано ниже, основные функции дюже схожи на функции асинхронного заказчика:

class talk_to_client : public boost::enable_shared_from_this<talk_to_client>, boost::noncopyable 
{
	typedef talk_to_client self_type;
	talk_to_client() : sock_(service), started_(false) {}
public:
	typedef boost::system::error_code error_code;
	typedef boost::shared_ptr<talk_to_client> ptr;
	void start() 
	{	
		started_ = true;
		do_read();
	}
	static ptr new_() 
	{
		ptr new_(new talk_to_client);
		return new_;
	}
	void stop() 
	{
		if ( !started_) return;
		started_ = false;
	vsock_.close();
	}
	ip::tcp::socket & sock() { return sock_;}
	...
private:
	ip::tcp::socket sock_;
	enum { max_msg = 1024 };
	char read_buffer_[max_msg];
	char write_buffer_[max_msg];
	bool started_;
};

Это дюже легкой эхо-сервер, нет никакой необходимости в функции is_started(). Для всякого заказчика мы легко читаем сообщение, которое он прислал, посылаем это же сообщение обратно и закрываем соединение.
Функции do_read(), do_write() и read_complete() верно такие же как в асинхронном TCP заказчике.
Основная логика класса заключена в функциях on_read() и on_write():

void on_read(const error_code & err, size_t bytes) 
{
	if ( !err) 
	{
		std::string msg(read_buffer_, bytes);
		do_write(msg   "n");
	}
	stop();
}
void on_write(const error_code & err, size_t bytes) 
{
	do_read();
}

Работа с заказчиками происходит дальнейшим образом:

ip::tcp::acceptor acceptor(service, ip::tcp::endpoint(ip::tcp::v4(), 8001));
void handle_accept(talk_to_client::ptr client, const error_code & err) 
{
	client->start();
	talk_to_client::ptr new_client = talk_to_client::new_();
	acceptor.async_accept(new_client->sock(), boost::bind(handle_accept,new_client,_1));
}
int main(int argc, char* argv[]) 
{
	talk_to_client::ptr client = talk_to_client::new_();
	acceptor.async_accept(client->sock(), boost::bind(handle_accept,client,_1));
	service.run();
}

Всякий раз, когда заказчик подключается к серверу, вызывается handle_accept, тот, что начинает асинхронно читать от этого заказчика, а так же асинхронно ожидает нового заказчика.

UDP эхо сервер/клиент

От того что в UDP не все сообщения доходят до получателя, то у нас нет ручательства, что сообщение пришло всецело. Так как мы трудимся по UDP, то всякое сообщение, которое мы получаем, мы легко выводим, не закрывая сокет (на стороне сервера).

UDP синхронный эхо заказчик

UDP эхо-заказчик немножко проще, чем TCP эхо-заказчик:

ip::udp::endpoint ep( ip::address::from_string("127.0.0.1"), 8001);
void sync_echo(std::string msg) 
{
	ip::udp::socket sock(service, ip::udp::endpoint(ip::udp::v4(), 0) );
	sock.send_to(buffer(msg), ep);
	char buff[1024];
	ip::udp::endpoint sender_ep;
	int bytes = sock.receive_from(buffer(buff), sender_ep);
	std::string copy(buff, bytes);
	std::cout << "server echoed our " << msg << ": "<< (copy == msg ? "OK" : "FAIL") << std::endl;
	sock.close();
}
int main(int argc, char* argv[]) 
{
	char* messages[] = { "John says hi", "so does James", "Lucy got home", 0 };
	boost::thread_group threads;
	for ( char ** message = messages; *message;   message) 
	{
		threads.create_thread( boost::bind(sync_echo, *message));
		boost::this_thread::sleep( boost::posix_time::millisec(100));
	}
	threads.join_all();
}

Каждая логика заключена в функции synch_echo(); подключение к серверу, отправка сообщения, приобретение ответного сообщения от сервера и закрытие соединения.

UDP синхронный эхо-сервер

UDP эхо-сервер это самый примитивный сервер, тот, что только дозволено написать:

io_service service;
void handle_connections() 
{
	char buff[1024];
	ip::udp::socket sock(service, ip::udp::endpoint(ip::udp::v4(), 8001));
	while ( true) 
	{
		ip::udp::endpoint sender_ep;
		int bytes = sock.receive_from(buffer(buff), sender_ep);
		std::string msg(buff, bytes);
		sock.send_to(buffer(msg), sender_ep);
	}
}
int main(int argc, char* argv[]) 
{
	handle_connections();
}

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

Резюме

Мы написали несколько приложений и, наконец, приступили к работе с Boost.Asio. Эти приложения дюже классны для начала работы с этой библиотекой.
В дальнейшей главе мы будем создавать больше трудные клиент/серверные приложения, будем учиться чураться таких ошибок как утрата памяти, дедлоки и так дальше.

Каждому огромное спасибо!

Источники к этой статье: ссылка

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

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Проголосовал 1 человек. Воздержавшихся нет.

 

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

 

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