work in progress. adding TcpServer
This commit is contained in:
@ -55,7 +55,7 @@
|
||||
# include "syscall.h"
|
||||
#endif
|
||||
|
||||
void qse_assert_failed (
|
||||
void QSE_ASSERT_failed (
|
||||
const qse_char_t* expr, const qse_char_t* desc,
|
||||
const qse_char_t* file, qse_size_t line)
|
||||
{
|
||||
|
@ -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