work in progress. adding TcpServer
This commit is contained in:
parent
70c787de9f
commit
26e27e78cd
@ -341,16 +341,16 @@
|
|||||||
#else
|
#else
|
||||||
|
|
||||||
# define QSE_ASSERT(expr) (void)((expr) || \
|
# define QSE_ASSERT(expr) (void)((expr) || \
|
||||||
(qse_assert_failed (QSE_T(#expr), QSE_NULL, QSE_T(__FILE__), __LINE__), 0))
|
(QSE_ASSERT_failed (QSE_T(#expr), QSE_NULL, QSE_T(__FILE__), __LINE__), 0))
|
||||||
# define QSE_ASSERTX(expr,desc) (void)((expr) || \
|
# define QSE_ASSERTX(expr,desc) (void)((expr) || \
|
||||||
(qse_assert_failed (QSE_T(#expr), QSE_T(desc), QSE_T(__FILE__), __LINE__), 0))
|
(QSE_ASSERT_failed (QSE_T(#expr), QSE_T(desc), QSE_T(__FILE__), __LINE__), 0))
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
#if defined(__cplusplus)
|
#if defined(__cplusplus)
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
QSE_EXPORT void qse_assert_failed (
|
QSE_EXPORT void QSE_ASSERT_failed (
|
||||||
const qse_char_t* expr, const qse_char_t* desc,
|
const qse_char_t* expr, const qse_char_t* desc,
|
||||||
const qse_char_t* file, qse_size_t line);
|
const qse_char_t* file, qse_size_t line);
|
||||||
#if defined(__cplusplus)
|
#if defined(__cplusplus)
|
||||||
|
@ -32,6 +32,7 @@ pkginclude_HEADERS += \
|
|||||||
SocketAddress.hpp \
|
SocketAddress.hpp \
|
||||||
Socket.hpp \
|
Socket.hpp \
|
||||||
SpinLock.hpp \
|
SpinLock.hpp \
|
||||||
|
TcpServer.hpp \
|
||||||
Thread.hpp
|
Thread.hpp
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
@ -92,6 +92,7 @@ host_triplet = @host@
|
|||||||
@ENABLE_CXX_TRUE@ SocketAddress.hpp \
|
@ENABLE_CXX_TRUE@ SocketAddress.hpp \
|
||||||
@ENABLE_CXX_TRUE@ Socket.hpp \
|
@ENABLE_CXX_TRUE@ Socket.hpp \
|
||||||
@ENABLE_CXX_TRUE@ SpinLock.hpp \
|
@ENABLE_CXX_TRUE@ SpinLock.hpp \
|
||||||
|
@ENABLE_CXX_TRUE@ TcpServer.hpp \
|
||||||
@ENABLE_CXX_TRUE@ Thread.hpp
|
@ENABLE_CXX_TRUE@ Thread.hpp
|
||||||
|
|
||||||
subdir = include/qse/si
|
subdir = include/qse/si
|
||||||
@ -135,7 +136,7 @@ am__pkginclude_HEADERS_DIST = aio.h aio-pro.h aio-sck.h cnd.h dir.h \
|
|||||||
fio.h fs.h glob.h intr.h log.h mtx.h mux.h nwad.h nwif.h \
|
fio.h fs.h glob.h intr.h log.h mtx.h mux.h nwad.h nwif.h \
|
||||||
nwio.h pio.h rwl.h sck.h sinfo.h sio.h spl.h task.h thr.h \
|
nwio.h pio.h rwl.h sck.h sinfo.h sio.h spl.h task.h thr.h \
|
||||||
tio.h AppRoot.hpp SocketAddress.hpp Socket.hpp SpinLock.hpp \
|
tio.h AppRoot.hpp SocketAddress.hpp Socket.hpp SpinLock.hpp \
|
||||||
Thread.hpp
|
TcpServer.hpp Thread.hpp
|
||||||
am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
|
am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
|
||||||
am__vpath_adj = case $$p in \
|
am__vpath_adj = case $$p in \
|
||||||
$(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
|
$(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
|
||||||
@ -357,7 +358,6 @@ pdfdir = @pdfdir@
|
|||||||
prefix = @prefix@
|
prefix = @prefix@
|
||||||
program_transform_name = @program_transform_name@
|
program_transform_name = @program_transform_name@
|
||||||
psdir = @psdir@
|
psdir = @psdir@
|
||||||
runstatedir = @runstatedir@
|
|
||||||
sbindir = @sbindir@
|
sbindir = @sbindir@
|
||||||
sharedstatedir = @sharedstatedir@
|
sharedstatedir = @sharedstatedir@
|
||||||
srcdir = @srcdir@
|
srcdir = @srcdir@
|
||||||
|
@ -76,9 +76,15 @@ public:
|
|||||||
int open (int domain, int type, int protocol, int traits = 0) QSE_CPP_NOEXCEPT;
|
int open (int domain, int type, int protocol, int traits = 0) QSE_CPP_NOEXCEPT;
|
||||||
void close () QSE_CPP_NOEXCEPT;
|
void close () QSE_CPP_NOEXCEPT;
|
||||||
|
|
||||||
|
|
||||||
|
int shutdown (int how = 2) QSE_CPP_NOEXCEPT;
|
||||||
|
|
||||||
|
int getOption (int level, int optname, void* optval, qse_sck_len_t* optlen) QSE_CPP_NOEXCEPT;
|
||||||
|
int setOption (int level, int optname, const void* optval, qse_sck_len_t optlen) QSE_CPP_NOEXCEPT;
|
||||||
|
|
||||||
int connect (const SocketAddress& target) QSE_CPP_NOEXCEPT;
|
int connect (const SocketAddress& target) QSE_CPP_NOEXCEPT;
|
||||||
int bind (const SocketAddress& target) QSE_CPP_NOEXCEPT;
|
int bind (const SocketAddress& target) QSE_CPP_NOEXCEPT;
|
||||||
int listen (int backlog) QSE_CPP_NOEXCEPT;
|
int listen (int backlog = 128) QSE_CPP_NOEXCEPT;
|
||||||
int accept (Socket* newsck, SocketAddress* newaddr, int traits = 0) QSE_CPP_NOEXCEPT;
|
int accept (Socket* newsck, SocketAddress* newaddr, int traits = 0) QSE_CPP_NOEXCEPT;
|
||||||
|
|
||||||
// The send() functions sends data by attemping a single call to the
|
// The send() functions sends data by attemping a single call to the
|
||||||
@ -96,6 +102,7 @@ public:
|
|||||||
qse_ssize_t receive (void* buf, qse_size_t len, SocketAddress& srcaddr) QSE_CPP_NOEXCEPT;
|
qse_ssize_t receive (void* buf, qse_size_t len, SocketAddress& srcaddr) QSE_CPP_NOEXCEPT;
|
||||||
|
|
||||||
/* TODO: sendmsg, recvmsg */
|
/* TODO: sendmsg, recvmsg */
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
qse_sck_hnd_t handle;
|
qse_sck_hnd_t handle;
|
||||||
ErrorCode errcode;
|
ErrorCode errcode;
|
||||||
|
164
qse/include/qse/si/TcpServer.hpp
Normal file
164
qse/include/qse/si/TcpServer.hpp
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
/*
|
||||||
|
* $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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _QSE_SI_TCPSERVER_CLASS_
|
||||||
|
#define _QSE_SI_TCPSERVER_CLASS_
|
||||||
|
|
||||||
|
#include <qse/si/Socket.hpp>
|
||||||
|
#include <qse/si/SocketAddress.hpp>
|
||||||
|
#include <qse/si/Thread.hpp>
|
||||||
|
#include <qse/cmn/LinkedList.hpp>
|
||||||
|
#include <qse/Uncopyable.hpp>
|
||||||
|
|
||||||
|
|
||||||
|
QSE_BEGIN_NAMESPACE(QSE)
|
||||||
|
|
||||||
|
class TcpServer: public QSE::Uncopyable
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TcpServer ();
|
||||||
|
TcpServer (const SocketAddress& address);
|
||||||
|
virtual ~TcpServer ();
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
ERR_NONE = 0,
|
||||||
|
ERR_OPEN = 1,
|
||||||
|
ERR_BIND = 2,
|
||||||
|
ERR_LISTEN = 3,
|
||||||
|
ERR_EXCEPTION = 4
|
||||||
|
};
|
||||||
|
|
||||||
|
virtual int start (int* err_code = QSE_NULL);
|
||||||
|
virtual int start (bool winsock_inheritable, int* err_code = QSE_NULL);
|
||||||
|
virtual int stop ();
|
||||||
|
|
||||||
|
bool isServing () const
|
||||||
|
{
|
||||||
|
return this->server_serving;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isStopRequested () const
|
||||||
|
{
|
||||||
|
return this->stop_requested;
|
||||||
|
}
|
||||||
|
void setStopRequested (bool req)
|
||||||
|
{
|
||||||
|
this->stop_requested = req;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SocketAddress& bindingAddress () const
|
||||||
|
{
|
||||||
|
return this->binding_address;
|
||||||
|
}
|
||||||
|
void setBindingAddress (const SocketAddress& address)
|
||||||
|
{
|
||||||
|
QSE_ASSERT (this->server_serving == false);
|
||||||
|
this->binding_address = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
qse_size_t maxConnections () const
|
||||||
|
{
|
||||||
|
return this->max_connections;
|
||||||
|
}
|
||||||
|
void setMaxConnections (qse_size_t mc)
|
||||||
|
{
|
||||||
|
// don't disconnect client connections
|
||||||
|
// establised before maxConn is set.
|
||||||
|
// 0 means there's no restriction over
|
||||||
|
// the number of connection.
|
||||||
|
this->max_connections = mc;
|
||||||
|
}
|
||||||
|
|
||||||
|
qse_size_t clientCount () const
|
||||||
|
{
|
||||||
|
return this->client_list.getSize();
|
||||||
|
}
|
||||||
|
qse_size_t connectionCount () const
|
||||||
|
{
|
||||||
|
return this->client_list.getSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
qse_size_t threadStackSize () const
|
||||||
|
{
|
||||||
|
return this->thread_stack_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setThreadStackSize (qse_size_t tss)
|
||||||
|
{
|
||||||
|
this->thread_stack_size = tss;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool shouldReopenSocketUponError () const
|
||||||
|
{
|
||||||
|
return this->reopen_socket_upon_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setReopenSocketUponError (bool v)
|
||||||
|
{
|
||||||
|
this->reopen_socket_upon_error = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
class Client: public QSE::Thread
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
friend class TcpServer;
|
||||||
|
|
||||||
|
Client (TcpServer* server);
|
||||||
|
|
||||||
|
int run ();
|
||||||
|
int stop () QSE_CPP_NOEXCEPT;
|
||||||
|
|
||||||
|
private:
|
||||||
|
TcpServer* server;
|
||||||
|
QSE::Socket socket;
|
||||||
|
SocketAddress address;
|
||||||
|
};
|
||||||
|
|
||||||
|
SocketAddress binding_address;
|
||||||
|
bool stop_requested;
|
||||||
|
bool server_serving;
|
||||||
|
qse_size_t max_connections;
|
||||||
|
qse_size_t thread_stack_size;
|
||||||
|
bool reopen_socket_upon_error;
|
||||||
|
|
||||||
|
typedef QSE::LinkedList<Client*> ClientList;
|
||||||
|
ClientList client_list;
|
||||||
|
|
||||||
|
friend class TcpServer::Client;
|
||||||
|
virtual int handle_client (Socket* sock, SocketAddress* addr) = 0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void delete_dead_clients ();
|
||||||
|
void delete_all_clients ();
|
||||||
|
int open_tcp_socket (Socket& socket, bool winsock_inheritable, int* err_code);
|
||||||
|
};
|
||||||
|
|
||||||
|
QSE_END_NAMESPACE(QSE)
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -55,7 +55,7 @@
|
|||||||
# include "syscall.h"
|
# include "syscall.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void qse_assert_failed (
|
void QSE_ASSERT_failed (
|
||||||
const qse_char_t* expr, const qse_char_t* desc,
|
const qse_char_t* expr, const qse_char_t* desc,
|
||||||
const qse_char_t* file, qse_size_t line)
|
const qse_char_t* file, qse_size_t line)
|
||||||
{
|
{
|
||||||
|
@ -55,6 +55,7 @@ libqsesixx_la_SOURCES = \
|
|||||||
AppRoot.cpp \
|
AppRoot.cpp \
|
||||||
SocketAddress.cpp \
|
SocketAddress.cpp \
|
||||||
Socket.cpp \
|
Socket.cpp \
|
||||||
|
TcpServer.cpp \
|
||||||
Thread.cpp
|
Thread.cpp
|
||||||
libqsesixx_la_LDFLAGS = -L. -L../cmn -version-info 1:0:0 -no-undefined
|
libqsesixx_la_LDFLAGS = -L. -L../cmn -version-info 1:0:0 -no-undefined
|
||||||
libqsesixx_la_LIBADD = -lqsecmnxx -lqsecmn
|
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 $@
|
$(CFLAGS) $(libqsesi_la_LDFLAGS) $(LDFLAGS) -o $@
|
||||||
libqsesixx_la_DEPENDENCIES =
|
libqsesixx_la_DEPENDENCIES =
|
||||||
am__libqsesixx_la_SOURCES_DIST = AppRoot.cpp SocketAddress.cpp \
|
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@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_OBJECTS = $(am_libqsesixx_la_OBJECTS)
|
||||||
libqsesixx_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
|
libqsesixx_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
|
||||||
$(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
|
$(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
|
||||||
@ -423,7 +424,6 @@ pdfdir = @pdfdir@
|
|||||||
prefix = @prefix@
|
prefix = @prefix@
|
||||||
program_transform_name = @program_transform_name@
|
program_transform_name = @program_transform_name@
|
||||||
psdir = @psdir@
|
psdir = @psdir@
|
||||||
runstatedir = @runstatedir@
|
|
||||||
sbindir = @sbindir@
|
sbindir = @sbindir@
|
||||||
sharedstatedir = @sharedstatedir@
|
sharedstatedir = @sharedstatedir@
|
||||||
srcdir = @srcdir@
|
srcdir = @srcdir@
|
||||||
@ -487,6 +487,7 @@ libqsesi_la_LIBADD = -lqsecmn $(PTHREAD_LIBS) $(SSL_LIBS)
|
|||||||
@ENABLE_CXX_TRUE@ AppRoot.cpp \
|
@ENABLE_CXX_TRUE@ AppRoot.cpp \
|
||||||
@ENABLE_CXX_TRUE@ SocketAddress.cpp \
|
@ENABLE_CXX_TRUE@ SocketAddress.cpp \
|
||||||
@ENABLE_CXX_TRUE@ Socket.cpp \
|
@ENABLE_CXX_TRUE@ Socket.cpp \
|
||||||
|
@ENABLE_CXX_TRUE@ TcpServer.cpp \
|
||||||
@ENABLE_CXX_TRUE@ Thread.cpp
|
@ENABLE_CXX_TRUE@ Thread.cpp
|
||||||
|
|
||||||
@ENABLE_CXX_TRUE@libqsesixx_la_LDFLAGS = -L. -L../cmn -version-info 1:0:0 -no-undefined
|
@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)/AppRoot.Plo@am__quote@
|
||||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Socket.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)/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)/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-pro.Plo@am__quote@
|
||||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libqsesi_la-aio-sck.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
|
int Socket::connect (const SocketAddress& target) QSE_CPP_NOEXCEPT
|
||||||
{
|
{
|
||||||
QSE_ASSERT (this->handle != QSE_INVALID_SCKHND);
|
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)
|
@ -65,14 +65,16 @@ if ENABLE_CXX
|
|||||||
|
|
||||||
CXXLIB = -lqsesixx -lqsecmnxx
|
CXXLIB = -lqsesixx -lqsecmnxx
|
||||||
|
|
||||||
bin_PROGRAMS += sck01 spl02 thr02
|
bin_PROGRAMS += sck01 spl02 tcpsvr01 thr02
|
||||||
|
|
||||||
sck01_SOURCES = sck01.cpp
|
sck01_SOURCES = sck01.cpp
|
||||||
spl02_SOURCES = spl02.cpp
|
spl02_SOURCES = spl02.cpp
|
||||||
|
tcpsvr01_SOURCES = tcpsvr01.cpp
|
||||||
thr02_SOURCES = thr02.cpp
|
thr02_SOURCES = thr02.cpp
|
||||||
|
|
||||||
sck01_LDADD = $(CXXLIB) $(LDADD)
|
sck01_LDADD = $(CXXLIB) $(LDADD)
|
||||||
spl02_LDADD = $(CXXLIB) $(LDADD)
|
spl02_LDADD = $(CXXLIB) $(LDADD)
|
||||||
|
tcpsvr01_LDADD = $(CXXLIB) $(LDADD)
|
||||||
thr02_LDADD = $(CXXLIB) $(LDADD)
|
thr02_LDADD = $(CXXLIB) $(LDADD)
|
||||||
|
|
||||||
endif
|
endif
|
||||||
|
85
qse/samples/si/tcpsvr01.cpp
Normal file
85
qse/samples/si/tcpsvr01.cpp
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
#include <qse/si/TcpServer.hpp>
|
||||||
|
#include <qse/si/mtx.h>
|
||||||
|
#include <qse/si/sio.h>
|
||||||
|
#include <qse/cmn/mem.h>
|
||||||
|
|
||||||
|
#include <locale.h>
|
||||||
|
#if defined(_WIN32)
|
||||||
|
# include <windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
|
||||||
|
static int test1 (void)
|
||||||
|
{
|
||||||
|
QSE::TcpServer server;
|
||||||
|
|
||||||
|
server.setClientThreadStackSize (256000);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void handle_sigint (int sig, siginfo_t* siginfo, void* ctx)
|
||||||
|
{
|
||||||
|
g_stopreq = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_signal (int sig, void(*handler)(int, siginfo_t*, void*))
|
||||||
|
{
|
||||||
|
struct sigaction sa;
|
||||||
|
|
||||||
|
memset (&sa, 0, sizeof(sa));
|
||||||
|
/*sa.sa_handler = handler;*/
|
||||||
|
sa.sa_flags = SA_SIGINFO;
|
||||||
|
sa.sa_sigaction = handler;
|
||||||
|
sigemptyset (&sa.sa_mask);
|
||||||
|
|
||||||
|
sigaction (sig, &sa, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_signal_to_default (int sig)
|
||||||
|
{
|
||||||
|
struct sigaction sa;
|
||||||
|
|
||||||
|
memset (&sa, 0, sizeof(sa));
|
||||||
|
sa.sa_handler = SIG_DFL;
|
||||||
|
sa.sa_flags = 0;
|
||||||
|
sigemptyset (&sa.sa_mask);
|
||||||
|
|
||||||
|
sigaction (sig, &sa, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main ()
|
||||||
|
{
|
||||||
|
#if defined(_WIN32)
|
||||||
|
char locale[100];
|
||||||
|
UINT codepage = GetConsoleOutputCP();
|
||||||
|
if (codepage == CP_UTF8)
|
||||||
|
{
|
||||||
|
/*SetConsoleOUtputCP (CP_UTF8);*/
|
||||||
|
qse_setdflcmgrbyid (QSE_CMGR_UTF8);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sprintf (locale, ".%u", (unsigned int)codepage);
|
||||||
|
setlocale (LC_ALL, locale);
|
||||||
|
/*qse_setdflcmgrbyid (QSE_CMGR_SLMB);*/
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
setlocale (LC_ALL, "");
|
||||||
|
/*qse_setdflcmgrbyid (QSE_CMGR_SLMB);*/
|
||||||
|
#endif
|
||||||
|
|
||||||
|
set_signal (SIGINT, handle_sigint);
|
||||||
|
|
||||||
|
qse_open_stdsios ();
|
||||||
|
test1();
|
||||||
|
qse_close_stdsios ();
|
||||||
|
|
||||||
|
set_signal_to_default (SIGINT);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user