work in progress. adding TcpServer
This commit is contained in:
		| @ -55,6 +55,7 @@ libqsesixx_la_SOURCES = \ | ||||
| 	AppRoot.cpp \ | ||||
| 	SocketAddress.cpp \ | ||||
| 	Socket.cpp \ | ||||
| 	TcpServer.cpp \ | ||||
| 	Thread.cpp | ||||
| libqsesixx_la_LDFLAGS = -L. -L../cmn -version-info 1:0:0 -no-undefined | ||||
| libqsesixx_la_LIBADD = -lqsecmnxx -lqsecmn  | ||||
|  | ||||
| @ -162,9 +162,10 @@ libqsesi_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ | ||||
| 	$(CFLAGS) $(libqsesi_la_LDFLAGS) $(LDFLAGS) -o $@ | ||||
| libqsesixx_la_DEPENDENCIES = | ||||
| am__libqsesixx_la_SOURCES_DIST = AppRoot.cpp SocketAddress.cpp \ | ||||
| 	Socket.cpp Thread.cpp | ||||
| 	Socket.cpp TcpServer.cpp Thread.cpp | ||||
| @ENABLE_CXX_TRUE@am_libqsesixx_la_OBJECTS = AppRoot.lo \ | ||||
| @ENABLE_CXX_TRUE@	SocketAddress.lo Socket.lo Thread.lo | ||||
| @ENABLE_CXX_TRUE@	SocketAddress.lo Socket.lo TcpServer.lo \ | ||||
| @ENABLE_CXX_TRUE@	Thread.lo | ||||
| libqsesixx_la_OBJECTS = $(am_libqsesixx_la_OBJECTS) | ||||
| libqsesixx_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ | ||||
| 	$(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \ | ||||
| @ -423,7 +424,6 @@ pdfdir = @pdfdir@ | ||||
| prefix = @prefix@ | ||||
| program_transform_name = @program_transform_name@ | ||||
| psdir = @psdir@ | ||||
| runstatedir = @runstatedir@ | ||||
| sbindir = @sbindir@ | ||||
| sharedstatedir = @sharedstatedir@ | ||||
| srcdir = @srcdir@ | ||||
| @ -487,6 +487,7 @@ libqsesi_la_LIBADD = -lqsecmn $(PTHREAD_LIBS) $(SSL_LIBS) | ||||
| @ENABLE_CXX_TRUE@	AppRoot.cpp \ | ||||
| @ENABLE_CXX_TRUE@	SocketAddress.cpp \ | ||||
| @ENABLE_CXX_TRUE@	Socket.cpp \ | ||||
| @ENABLE_CXX_TRUE@	TcpServer.cpp \ | ||||
| @ENABLE_CXX_TRUE@	Thread.cpp | ||||
|  | ||||
| @ENABLE_CXX_TRUE@libqsesixx_la_LDFLAGS = -L. -L../cmn -version-info 1:0:0 -no-undefined | ||||
| @ -575,6 +576,7 @@ distclean-compile: | ||||
| @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/AppRoot.Plo@am__quote@ | ||||
| @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Socket.Plo@am__quote@ | ||||
| @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/SocketAddress.Plo@am__quote@ | ||||
| @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/TcpServer.Plo@am__quote@ | ||||
| @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Thread.Plo@am__quote@ | ||||
| @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libqsesi_la-aio-pro.Plo@am__quote@ | ||||
| @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libqsesi_la-aio-sck.Plo@am__quote@ | ||||
|  | ||||
| @ -126,6 +126,36 @@ void Socket::close () QSE_CPP_NOEXCEPT | ||||
| 	} | ||||
| } | ||||
|  | ||||
| int Socket::shutdown (int how) QSE_CPP_NOEXCEPT | ||||
| { | ||||
| 	QSE_ASSERT (this->handle != QSE_INVALID_SCKHND); | ||||
|  | ||||
| 	if (::shutdown(this->handle, how) == -1) | ||||
| 	{ | ||||
| 		this->setErrorCode (syserr_to_errnum(errno)); | ||||
| 		return -1; | ||||
| 	} | ||||
|  | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
|  | ||||
| int Socket::getOption (int level, int optname, void* optval, qse_sck_len_t* optlen) QSE_CPP_NOEXCEPT | ||||
| { | ||||
| 	QSE_ASSERT (this->handle != QSE_INVALID_SCKHND); | ||||
| 	int n = ::getsockopt (this->handle, level, optname, (char*)optval, optlen); | ||||
| 	if (n == -1) this->setErrorCode (syserr_to_errnum(errno)); | ||||
| 	return n; | ||||
| } | ||||
|  | ||||
| int Socket::setOption (int level, int optname, const void* optval, qse_sck_len_t optlen) QSE_CPP_NOEXCEPT | ||||
| { | ||||
| 	QSE_ASSERT (this->handle != QSE_INVALID_SCKHND); | ||||
| 	int n = ::setsockopt (this->handle, level, optname, (const char*)optval, optlen); | ||||
| 	if (n == -1) this->setErrorCode (syserr_to_errnum(errno)); | ||||
| 	return n; | ||||
| } | ||||
|  | ||||
| int Socket::connect (const SocketAddress& target) QSE_CPP_NOEXCEPT | ||||
| { | ||||
| 	QSE_ASSERT (this->handle != QSE_INVALID_SCKHND); | ||||
|  | ||||
							
								
								
									
										327
									
								
								qse/lib/si/TcpServer.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										327
									
								
								qse/lib/si/TcpServer.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,327 @@ | ||||
| /* | ||||
|  * $Id$ | ||||
|  * | ||||
|     Copyright (c) 2006-2014 Chung, Hyung-Hwan. All rights reserved. | ||||
|  | ||||
|     Redistribution and use in source and binary forms, with or without | ||||
|     modification, are permitted provided that the following conditions | ||||
|     are met: | ||||
|     1. Redistributions of source code must retain the above copyright | ||||
|        notice, this list of conditions and the following disclaimer. | ||||
|     2. Redistributions in binary form must reproduce the above copyright | ||||
|        notice, this list of conditions and the following disclaimer in the | ||||
|        documentation and/or other materials provided with the distribution. | ||||
|  | ||||
|     THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EQSERESS OR | ||||
|     IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | ||||
|     OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | ||||
|     IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, | ||||
|     INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | ||||
|     NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||||
|     DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||||
|     THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||
|     (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | ||||
|     THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
|  */ | ||||
|  | ||||
| #include <qse/si/TcpServer.hpp> | ||||
|  | ||||
| QSE_BEGIN_NAMESPACE(QSE) | ||||
|  | ||||
| TcpServer::Client::Client (TcpServer* server)  | ||||
| { | ||||
| 	this->server = server; | ||||
| } | ||||
|  | ||||
| // | ||||
| // NOTICE: the guarantee class below could have been placed  | ||||
| //         inside TCPServer::Client::run () without supporting  | ||||
| //         old C++ compilers. | ||||
| // | ||||
| class guarantee_tcpsocket_close { | ||||
| public: | ||||
| 	guarantee_tcpsocket_close (Socket* socket): psck (socket) {} | ||||
| 	~guarantee_tcpsocket_close () { psck->shutdown (); psck->close (); } | ||||
| 	Socket* psck; | ||||
| }; | ||||
|  | ||||
| int TcpServer::Client::run () | ||||
| { | ||||
| 	// blockAllSignals is called inside run because  | ||||
| 	// Client is instantiated in the TcpServer thread. | ||||
| 	// so if it is called in the constructor of Client,  | ||||
| 	// it would just block signals to the TcpProxy thread. | ||||
| 	blockAllSignals (); // don't care about the result. | ||||
|  | ||||
| 	guarantee_tcpsocket_close close_socket (&socket); | ||||
| 	if (server->handle_client (&socket, &address) == -1) return -1; | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| int TcpServer::Client::stop () QSE_CPP_NOEXCEPT | ||||
| { | ||||
| 	// the receiver will be notified of the end of  | ||||
| 	// the connection by the socket's closing. | ||||
| 	// therefore, handle_client() must return | ||||
| 	// when it detects the end of the connection. | ||||
| 	// | ||||
| 	// TODO: must think of a better way to do this  | ||||
| 	//       as it might not be thread-safe. | ||||
| 	//       but it is still ok because Client::stop()  | ||||
| 	//       is rarely called. | ||||
| 	socket.shutdown (); | ||||
| 	socket.close (); | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| TcpServer::TcpServer ():  | ||||
| 	stop_requested(false),  | ||||
| 	server_serving(false),  | ||||
| 	max_connections(0), | ||||
| 	thread_stack_size (0), | ||||
| 	reopen_socket_upon_error(false) | ||||
| { | ||||
| } | ||||
|  | ||||
| TcpServer::TcpServer (const SocketAddress& address):  | ||||
| 	binding_address(address), | ||||
| 	stop_requested(false),  | ||||
| 	server_serving(false),  | ||||
| 	max_connections(0),  | ||||
| 	thread_stack_size (0), | ||||
| 	reopen_socket_upon_error(false) | ||||
| { | ||||
| } | ||||
|  | ||||
| TcpServer::~TcpServer () | ||||
| { | ||||
| 	// QSE_ASSERT (server_serving == false); | ||||
| 	this->delete_all_clients (); | ||||
| } | ||||
|  | ||||
| int TcpServer::start (int* err_code) | ||||
| { | ||||
| 	return this->start(true, err_code); | ||||
| } | ||||
|  | ||||
| int TcpServer::open_tcp_socket (Socket& socket, bool winsock_inheritable, int* err_code) | ||||
| { | ||||
| 	if (socket.open(QSE_AF_INET6, QSE_SOCK_STREAM, 0) <= -1) | ||||
| 	{ | ||||
| 		if (err_code) *err_code = ERR_OPEN; | ||||
| 		return -1; | ||||
| 	} | ||||
|  | ||||
|  | ||||
|  | ||||
| #ifdef _WIN32 | ||||
| 	if (winsock_inheritable) | ||||
| 	{ | ||||
| 		SetHandleInformation ( | ||||
| 			(HANDLE)socket.handle(),  | ||||
| 			HANDLE_FLAG_INHERIT, | ||||
| 			HANDLE_FLAG_INHERIT); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		SetHandleInformation ( | ||||
| 			(HANDLE)socket.handle(),  | ||||
| 			HANDLE_FLAG_INHERIT, 0); | ||||
| 	} | ||||
| #endif | ||||
|  | ||||
| 	socket.setReuseAddr (true); | ||||
| 	//socket.setReusePort (true); | ||||
|  | ||||
| 	if (socket.bind(this->binding_address) <= -1) | ||||
| 	{ | ||||
| 		if (err_code) *err_code = ERR_BIND; | ||||
| 		return -1; | ||||
| 	} | ||||
|  | ||||
| 	if (socket.listen() <= -1)  | ||||
| 	{ | ||||
| 		if (err_code) *err_code = ERR_LISTEN; | ||||
| 		return -1; | ||||
| 	} | ||||
|  | ||||
| 	socket.enableTimeout (1000); | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| int TcpServer::start (bool winsock_inheritable, int* err_code) | ||||
| { | ||||
| 	this->server_serving = true; | ||||
| 	if (err_code != QSE_NULL) *err_code = ERR_NONE; | ||||
|  | ||||
| 	this->setStopRequested (false); | ||||
|  | ||||
| 	Client* client = QSE_NULL; | ||||
|  | ||||
| 	try  | ||||
| 	{ | ||||
| 		Socket socket; | ||||
|  | ||||
| 		if (this->open_tcp_socket(socket, winsock_inheritable, err_code) <= -1) | ||||
| 		{ | ||||
| 			this->server_serving = false; | ||||
| 			this->setStopRequested (false); | ||||
| 			return -1; | ||||
| 		} | ||||
|  | ||||
| 		while (!this->isStopRequested())  | ||||
| 		{ | ||||
| 			this->delete_dead_clients (); | ||||
|  | ||||
| 			if (this->max_connections > 0 && this->max_connections <= this->client_list.getSize())  | ||||
| 			{ | ||||
| 				Socket s; | ||||
| 				SocketAddress sa; | ||||
| 				if (socket.accept(&s, &sa, Socket::T_CLOEXEC) >= 0) s.close(); | ||||
| 				continue; | ||||
| 			} | ||||
|  | ||||
| 			if (client == QSE_NULL) | ||||
| 			{ | ||||
| 				// allocating the client object before accept is  | ||||
| 				// a bit awkward. but socket.accept() can be passed | ||||
| 				// the socket field inside the client object. | ||||
| 				try { client = new Client (this); }  | ||||
| 				catch (...) { } | ||||
| 			} | ||||
| 			if (client == QSE_NULL)  | ||||
| 			{ | ||||
| 				// memory alloc failed | ||||
| 				Socket s; | ||||
| 				SocketAddress sa; | ||||
| 				if (socket.accept(&s, &sa, Socket::T_CLOEXEC) >= 0) s.close(); | ||||
| 				continue; | ||||
| 			} | ||||
|  | ||||
| 			if (socket.accept(&client->socket, &client->address, Socket::T_CLOEXEC) <= -1)  | ||||
| 			{ | ||||
| 				// can't do much if accept fails | ||||
|  | ||||
| 				// don't "delete client" here as i want it to be reused | ||||
| 				// in the next iteration after "continue" | ||||
|  | ||||
| 				if (this->reopen_socket_upon_error) | ||||
| 				{ | ||||
| 					// closing the listeing socket causes the  | ||||
| 					// the pending connection to be dropped. | ||||
| 					// this poses the risk of reopening failure. | ||||
| 					// imagine the case of EMFILE(too many open files). | ||||
| 					// accept() will fail until an open file is closed. | ||||
| 					qse_size_t reopen_count = 0; | ||||
| 					socket.close (); | ||||
|  | ||||
| 				reopen: | ||||
| 					if (this->open_tcp_socket (socket, winsock_inheritable, err_code) <= -1) | ||||
| 					{ | ||||
| 						if (reopen_count >= 200) qse_sleep (100); | ||||
| 						else if (reopen_count >= 100) qse_sleep (10); | ||||
|  | ||||
| 						if (this->isStopRequested()) break; | ||||
| 						reopen_count++; | ||||
| 						goto reopen; | ||||
| 					} | ||||
| 				} | ||||
| 				continue; | ||||
| 			} | ||||
|  | ||||
| 			client->setStackSize (thread_stack_size); | ||||
| 		#ifdef _WIN32 | ||||
| 			if (client->start(Thread::DETACHED) == -1)  | ||||
| 		#else | ||||
| 			if (client->start(0) == -1) | ||||
| 		#endif | ||||
| 			{ | ||||
| 				delete client;  | ||||
| 				client = QSE_NULL; | ||||
| 				continue; | ||||
| 			} | ||||
|  | ||||
| 			this->client_list.append (client); | ||||
| 			client = QSE_NULL; | ||||
| 		} | ||||
|  | ||||
| 		this->delete_all_clients (); | ||||
| 		if (client != QSE_NULL) delete client; | ||||
| 	} | ||||
| 	catch (...)  | ||||
| 	{ | ||||
| 		this->delete_all_clients (); | ||||
| 		if (client != QSE_NULL) delete client; | ||||
|  | ||||
| 		if (err_code) *err_code = ERR_EXCEPTION; | ||||
| 		this->server_serving = false; | ||||
| 		this->setStopRequested (false); | ||||
|  | ||||
| 		return -1; | ||||
| 	} | ||||
|  | ||||
| 	this->server_serving = false; | ||||
| 	this->setStopRequested (false); | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| int TcpServer::stop () | ||||
| { | ||||
| 	if (server_serving) setStopRequested (true); | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| void TcpServer::delete_dead_clients () | ||||
| { | ||||
| 	ClientList::Node* np, * np2; | ||||
| 	 | ||||
| 	np = client_list.getHeadNode(); | ||||
| 	while (np)  | ||||
| 	{ | ||||
| 		Client* p = np->value; | ||||
| 		QSE_ASSERT (p != QSE_NULL); | ||||
|  | ||||
| 		if (p->getState() != Thread::RUNNING) | ||||
| 		{ | ||||
| #ifndef _WIN32 | ||||
| 			p->join (); | ||||
| #endif | ||||
| 			delete p; | ||||
| 			np2 = np; np = np->getNextNode(); | ||||
| 			client_list.remove (np2); | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		np = np->getNextNode(); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void TcpServer::delete_all_clients () | ||||
| { | ||||
| 	ClientList::Node* np, * np2; | ||||
| 	Client* p; | ||||
|  | ||||
| 	for (np = client_list.getHeadNode(); np; np = np->getNextNode())  | ||||
| 	{ | ||||
| 		p = np->value; | ||||
| 		if (p->getState() == Thread::RUNNING) p->stop(); | ||||
| 	} | ||||
|  | ||||
| 	np = client_list.getHeadNode(); | ||||
| 	while (np != QSE_NULL)  | ||||
| 	{ | ||||
| 		p = np->value; | ||||
| 		QSE_ASSERT (p != QSE_NULL); | ||||
|  | ||||
| #ifdef _WIN32 | ||||
| 		while (p->state() == Thread::RUNNING) qse_sleep (300); | ||||
| #else	 | ||||
| 		p->join (); | ||||
| #endif | ||||
| 		delete p; | ||||
| 		np2 = np; np = np->getNextNode(); | ||||
| 		client_list.remove (np2); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| QSE_END_NAMESPACE(QSE) | ||||
		Reference in New Issue
	
	Block a user