initial commit
This commit is contained in:
15
third_party/libhv/evpp/Buffer.h
vendored
Executable file
15
third_party/libhv/evpp/Buffer.h
vendored
Executable file
@@ -0,0 +1,15 @@
|
||||
#ifndef HV_BUFFER_HPP_
|
||||
#define HV_BUFFER_HPP_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "hbuf.h"
|
||||
|
||||
namespace hv {
|
||||
|
||||
typedef HBuf Buffer;
|
||||
typedef std::shared_ptr<Buffer> BufferPtr;
|
||||
|
||||
}
|
||||
|
||||
#endif // HV_BUFFER_HPP_
|
||||
377
third_party/libhv/evpp/Channel.h
vendored
Executable file
377
third_party/libhv/evpp/Channel.h
vendored
Executable file
@@ -0,0 +1,377 @@
|
||||
#ifndef HV_CHANNEL_HPP_
|
||||
#define HV_CHANNEL_HPP_
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <atomic>
|
||||
|
||||
#include "hloop.h"
|
||||
#include "hsocket.h"
|
||||
|
||||
#include "Buffer.h"
|
||||
|
||||
namespace hv {
|
||||
|
||||
class Channel {
|
||||
public:
|
||||
Channel(hio_t* io = NULL) {
|
||||
io_ = io;
|
||||
fd_ = -1;
|
||||
id_ = 0;
|
||||
ctx_ = NULL;
|
||||
status = CLOSED;
|
||||
if (io) {
|
||||
fd_ = hio_fd(io);
|
||||
id_ = hio_id(io);
|
||||
ctx_ = hio_context(io);
|
||||
hio_set_context(io, this);
|
||||
if (hio_is_opened(io)) {
|
||||
status = OPENED;
|
||||
}
|
||||
if (hio_getcb_read(io) == NULL) {
|
||||
hio_setcb_read(io_, on_read);
|
||||
}
|
||||
if (hio_getcb_write(io) == NULL) {
|
||||
hio_setcb_write(io_, on_write);
|
||||
}
|
||||
if (hio_getcb_close(io) == NULL) {
|
||||
hio_setcb_close(io_, on_close);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual ~Channel() {
|
||||
if (isOpened()) {
|
||||
close();
|
||||
// NOTE: Detach after destructor to avoid triggering onclose
|
||||
if (io_ && id_ == hio_id(io_)) {
|
||||
hio_set_context(io_, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hio_t* io() { return io_; }
|
||||
int fd() { return fd_; }
|
||||
uint32_t id() { return id_; }
|
||||
int error() { return hio_error(io_); }
|
||||
|
||||
// context
|
||||
void* context() {
|
||||
return ctx_;
|
||||
}
|
||||
void setContext(void* ctx) {
|
||||
ctx_ = ctx;
|
||||
}
|
||||
template<class T>
|
||||
T* newContext() {
|
||||
ctx_ = new T;
|
||||
return (T*)ctx_;
|
||||
}
|
||||
template<class T>
|
||||
T* getContext() {
|
||||
return (T*)ctx_;
|
||||
}
|
||||
template<class T>
|
||||
void deleteContext() {
|
||||
if (ctx_) {
|
||||
delete (T*)ctx_;
|
||||
ctx_ = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// contextPtr
|
||||
std::shared_ptr<void> contextPtr() {
|
||||
return contextPtr_;
|
||||
}
|
||||
void setContextPtr(const std::shared_ptr<void>& ctx) {
|
||||
contextPtr_ = ctx;
|
||||
}
|
||||
void setContextPtr(std::shared_ptr<void>&& ctx) {
|
||||
contextPtr_ = std::move(ctx);
|
||||
}
|
||||
template<class T>
|
||||
std::shared_ptr<T> newContextPtr() {
|
||||
contextPtr_ = std::make_shared<T>();
|
||||
return std::static_pointer_cast<T>(contextPtr_);
|
||||
}
|
||||
template<class T>
|
||||
std::shared_ptr<T> getContextPtr() {
|
||||
return std::static_pointer_cast<T>(contextPtr_);
|
||||
}
|
||||
void deleteContextPtr() {
|
||||
contextPtr_.reset();
|
||||
}
|
||||
|
||||
bool isOpened() {
|
||||
if (io_ == NULL || status >= DISCONNECTED) return false;
|
||||
return id_ == hio_id(io_) && hio_is_opened(io_);
|
||||
}
|
||||
bool isClosed() {
|
||||
return !isOpened();
|
||||
}
|
||||
|
||||
int startRead() {
|
||||
if (!isOpened()) return -1;
|
||||
return hio_read_start(io_);
|
||||
}
|
||||
|
||||
int stopRead() {
|
||||
if (!isOpened()) return -1;
|
||||
return hio_read_stop(io_);
|
||||
}
|
||||
|
||||
int readOnce() {
|
||||
if (!isOpened()) return -1;
|
||||
return hio_read_once(io_);
|
||||
}
|
||||
|
||||
int readString() {
|
||||
if (!isOpened()) return -1;
|
||||
return hio_readstring(io_);
|
||||
}
|
||||
|
||||
int readLine() {
|
||||
if (!isOpened()) return -1;
|
||||
return hio_readline(io_);
|
||||
}
|
||||
|
||||
int readBytes(int len) {
|
||||
if (!isOpened() || len <= 0) return -1;
|
||||
return hio_readbytes(io_, len);
|
||||
}
|
||||
|
||||
// write thread-safe
|
||||
int write(const void* data, int size) {
|
||||
if (!isOpened()) return -1;
|
||||
return hio_write(io_, data, size);
|
||||
}
|
||||
|
||||
int write(Buffer* buf) {
|
||||
return write(buf->data(), buf->size());
|
||||
}
|
||||
|
||||
int write(const std::string& str) {
|
||||
return write(str.data(), str.size());
|
||||
}
|
||||
|
||||
// iobuf setting
|
||||
void setReadBuf(void* buf, size_t len) {
|
||||
if (io_ == NULL) return;
|
||||
hio_set_readbuf(io_, buf, len);
|
||||
}
|
||||
void setMaxReadBufsize(uint32_t size) {
|
||||
if (io_ == NULL) return;
|
||||
hio_set_max_read_bufsize(io_, size);
|
||||
}
|
||||
void setMaxWriteBufsize(uint32_t size) {
|
||||
if (io_ == NULL) return;
|
||||
hio_set_max_write_bufsize(io_, size);
|
||||
}
|
||||
size_t writeBufsize() {
|
||||
if (io_ == NULL) return 0;
|
||||
return hio_write_bufsize(io_);
|
||||
}
|
||||
bool isWriteComplete() {
|
||||
return writeBufsize() == 0;
|
||||
}
|
||||
|
||||
// close thread-safe
|
||||
int close(bool async = false) {
|
||||
if (isClosed()) return -1;
|
||||
status = CLOSED;
|
||||
return async ? hio_close_async(io_) : hio_close(io_);
|
||||
}
|
||||
|
||||
public:
|
||||
hio_t* io_;
|
||||
int fd_;
|
||||
uint32_t id_;
|
||||
void* ctx_;
|
||||
enum Status {
|
||||
OPENED,
|
||||
CONNECTING,
|
||||
CONNECTED,
|
||||
DISCONNECTED,
|
||||
CLOSED,
|
||||
};
|
||||
std::atomic<Status> status;
|
||||
std::function<void(Buffer*)> onread;
|
||||
// NOTE: Use Channel::isWriteComplete in onwrite callback to determine whether all data has been written.
|
||||
std::function<void(Buffer*)> onwrite;
|
||||
std::function<void()> onclose;
|
||||
std::shared_ptr<void> contextPtr_;
|
||||
|
||||
private:
|
||||
static void on_read(hio_t* io, void* data, int readbytes) {
|
||||
Channel* channel = (Channel*)hio_context(io);
|
||||
if (channel && channel->onread) {
|
||||
Buffer buf(data, readbytes);
|
||||
channel->onread(&buf);
|
||||
}
|
||||
}
|
||||
|
||||
static void on_write(hio_t* io, const void* data, int writebytes) {
|
||||
Channel* channel = (Channel*)hio_context(io);
|
||||
if (channel && channel->onwrite) {
|
||||
Buffer buf((void*)data, writebytes);
|
||||
channel->onwrite(&buf);
|
||||
}
|
||||
}
|
||||
|
||||
static void on_close(hio_t* io) {
|
||||
Channel* channel = (Channel*)hio_context(io);
|
||||
if (channel) {
|
||||
channel->status = CLOSED;
|
||||
if (channel->onclose) {
|
||||
channel->onclose();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class SocketChannel : public Channel {
|
||||
public:
|
||||
std::function<void()> onconnect; // only for TcpClient
|
||||
std::function<void()> heartbeat;
|
||||
|
||||
SocketChannel(hio_t* io) : Channel(io) {
|
||||
}
|
||||
virtual ~SocketChannel() {}
|
||||
|
||||
// SSL/TLS
|
||||
int enableSSL() {
|
||||
if (io_ == NULL) return -1;
|
||||
return hio_enable_ssl(io_);
|
||||
}
|
||||
bool isSSL() {
|
||||
if (io_ == NULL) return false;
|
||||
return hio_is_ssl(io_);
|
||||
}
|
||||
int setSSL(hssl_t ssl) {
|
||||
if (io_ == NULL) return -1;
|
||||
return hio_set_ssl(io_, ssl);
|
||||
}
|
||||
int setSslCtx(hssl_ctx_t ssl_ctx) {
|
||||
if (io_ == NULL) return -1;
|
||||
return hio_set_ssl_ctx(io_, ssl_ctx);
|
||||
}
|
||||
int newSslCtx(hssl_ctx_opt_t* opt) {
|
||||
if (io_ == NULL) return -1;
|
||||
return hio_new_ssl_ctx(io_, opt);
|
||||
}
|
||||
// for hssl_set_sni_hostname
|
||||
int setHostname(const std::string& hostname) {
|
||||
if (io_ == NULL) return -1;
|
||||
return hio_set_hostname(io_, hostname.c_str());
|
||||
}
|
||||
|
||||
// timeout
|
||||
void setConnectTimeout(int timeout_ms) {
|
||||
if (io_ == NULL) return;
|
||||
hio_set_connect_timeout(io_, timeout_ms);
|
||||
}
|
||||
void setCloseTimeout(int timeout_ms) {
|
||||
if (io_ == NULL) return;
|
||||
hio_set_close_timeout(io_, timeout_ms);
|
||||
}
|
||||
void setReadTimeout(int timeout_ms) {
|
||||
if (io_ == NULL) return;
|
||||
hio_set_read_timeout(io_, timeout_ms);
|
||||
}
|
||||
void setWriteTimeout(int timeout_ms) {
|
||||
if (io_ == NULL) return;
|
||||
hio_set_write_timeout(io_, timeout_ms);
|
||||
}
|
||||
void setKeepaliveTimeout(int timeout_ms) {
|
||||
if (io_ == NULL) return;
|
||||
hio_set_keepalive_timeout(io_, timeout_ms);
|
||||
}
|
||||
|
||||
// heartbeat
|
||||
// NOTE: Beware of circular reference problems caused by passing SocketChannelPtr by value.
|
||||
void setHeartbeat(int interval_ms, std::function<void()> fn) {
|
||||
if (io_ == NULL) return;
|
||||
heartbeat = std::move(fn);
|
||||
hio_set_heartbeat(io_, interval_ms, send_heartbeat);
|
||||
}
|
||||
|
||||
/*
|
||||
* unpack
|
||||
*
|
||||
* NOTE: unpack_setting_t of multiple IOs of the same function also are same,
|
||||
* so only the pointer of unpack_setting_t is stored in hio_t,
|
||||
* the life time of unpack_setting_t shoud be guaranteed by caller.
|
||||
*/
|
||||
void setUnpack(unpack_setting_t* setting) {
|
||||
if (io_ == NULL) return;
|
||||
hio_set_unpack(io_, setting);
|
||||
}
|
||||
|
||||
int startConnect(int port, const char* host = "127.0.0.1") {
|
||||
sockaddr_u peeraddr;
|
||||
memset(&peeraddr, 0, sizeof(peeraddr));
|
||||
int ret = sockaddr_set_ipport(&peeraddr, host, port);
|
||||
if (ret != 0) {
|
||||
// hloge("unknown host %s", host);
|
||||
return ret;
|
||||
}
|
||||
return startConnect(&peeraddr.sa);
|
||||
}
|
||||
|
||||
int startConnect(struct sockaddr* peeraddr) {
|
||||
if (io_ == NULL) return -1;
|
||||
hio_set_peeraddr(io_, peeraddr, SOCKADDR_LEN(peeraddr));
|
||||
return startConnect();
|
||||
}
|
||||
|
||||
int startConnect() {
|
||||
if (io_ == NULL) return -1;
|
||||
status = CONNECTING;
|
||||
hio_setcb_connect(io_, on_connect);
|
||||
return hio_connect(io_);
|
||||
}
|
||||
|
||||
bool isConnected() {
|
||||
return status == CONNECTED && isOpened();
|
||||
}
|
||||
|
||||
std::string localaddr() {
|
||||
if (io_ == NULL) return "";
|
||||
struct sockaddr* addr = hio_localaddr(io_);
|
||||
char buf[SOCKADDR_STRLEN] = {0};
|
||||
return SOCKADDR_STR(addr, buf);
|
||||
}
|
||||
|
||||
std::string peeraddr() {
|
||||
if (io_ == NULL) return "";
|
||||
struct sockaddr* addr = hio_peeraddr(io_);
|
||||
char buf[SOCKADDR_STRLEN] = {0};
|
||||
return SOCKADDR_STR(addr, buf);
|
||||
}
|
||||
|
||||
private:
|
||||
static void on_connect(hio_t* io) {
|
||||
SocketChannel* channel = (SocketChannel*)hio_context(io);
|
||||
if (channel) {
|
||||
channel->status = CONNECTED;
|
||||
if (channel->onconnect) {
|
||||
channel->onconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void send_heartbeat(hio_t* io) {
|
||||
SocketChannel* channel = (SocketChannel*)hio_context(io);
|
||||
if (channel && channel->heartbeat) {
|
||||
channel->heartbeat();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<Channel> ChannelPtr;
|
||||
typedef std::shared_ptr<SocketChannel> SocketChannelPtr;
|
||||
|
||||
}
|
||||
|
||||
#endif // HV_CHANNEL_HPP_
|
||||
47
third_party/libhv/evpp/Event.h
vendored
Executable file
47
third_party/libhv/evpp/Event.h
vendored
Executable file
@@ -0,0 +1,47 @@
|
||||
#ifndef HV_EVENT_HPP_
|
||||
#define HV_EVENT_HPP_
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include "hloop.h"
|
||||
|
||||
namespace hv {
|
||||
|
||||
struct Event;
|
||||
struct Timer;
|
||||
|
||||
typedef uint64_t TimerID;
|
||||
#define INVALID_TIMER_ID ((hv::TimerID)-1)
|
||||
|
||||
typedef std::function<void(Event*)> EventCallback;
|
||||
typedef std::function<void(TimerID)> TimerCallback;
|
||||
|
||||
struct Event {
|
||||
hevent_t event;
|
||||
EventCallback cb;
|
||||
|
||||
Event(EventCallback cb = NULL) {
|
||||
memset(&event, 0, sizeof(hevent_t));
|
||||
this->cb = std::move(cb);
|
||||
}
|
||||
};
|
||||
|
||||
struct Timer {
|
||||
htimer_t* timer;
|
||||
TimerCallback cb;
|
||||
uint32_t repeat;
|
||||
|
||||
Timer(htimer_t* timer = NULL, TimerCallback cb = NULL, uint32_t repeat = INFINITE) {
|
||||
this->timer = timer;
|
||||
this->cb = std::move(cb);
|
||||
this->repeat = repeat;
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<Event> EventPtr;
|
||||
typedef std::shared_ptr<Timer> TimerPtr;
|
||||
|
||||
}
|
||||
|
||||
#endif // HV_EVENT_HPP_
|
||||
277
third_party/libhv/evpp/EventLoop.h
vendored
Executable file
277
third_party/libhv/evpp/EventLoop.h
vendored
Executable file
@@ -0,0 +1,277 @@
|
||||
#ifndef HV_EVENT_LOOP_HPP_
|
||||
#define HV_EVENT_LOOP_HPP_
|
||||
|
||||
#include <functional>
|
||||
#include <queue>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
|
||||
#include "hloop.h"
|
||||
#include "hthread.h"
|
||||
|
||||
#include "Status.h"
|
||||
#include "Event.h"
|
||||
#include "ThreadLocalStorage.h"
|
||||
|
||||
namespace hv {
|
||||
|
||||
class EventLoop : public Status {
|
||||
public:
|
||||
|
||||
typedef std::function<void()> Functor;
|
||||
|
||||
// New an EventLoop using an existing hloop_t object,
|
||||
// so we can embed an EventLoop object into the old application based on hloop.
|
||||
// NOTE: Be careful to deal with destroy of hloop_t.
|
||||
EventLoop(hloop_t* loop = NULL) {
|
||||
setStatus(kInitializing);
|
||||
if (loop) {
|
||||
loop_ = loop;
|
||||
is_loop_owner = false;
|
||||
} else {
|
||||
loop_ = hloop_new(HLOOP_FLAG_AUTO_FREE);
|
||||
is_loop_owner = true;
|
||||
}
|
||||
connectionNum = 0;
|
||||
nextTimerID = 0;
|
||||
setStatus(kInitialized);
|
||||
}
|
||||
|
||||
~EventLoop() {
|
||||
stop();
|
||||
}
|
||||
|
||||
hloop_t* loop() {
|
||||
return loop_;
|
||||
}
|
||||
|
||||
// @brief Run loop forever
|
||||
void run() {
|
||||
if (loop_ == NULL) return;
|
||||
if (status() == kRunning) return;
|
||||
ThreadLocalStorage::set(ThreadLocalStorage::EVENT_LOOP, this);
|
||||
setStatus(kRunning);
|
||||
hloop_run(loop_);
|
||||
setStatus(kStopped);
|
||||
}
|
||||
|
||||
// stop thread-safe
|
||||
void stop() {
|
||||
if (loop_ == NULL) return;
|
||||
if (status() < kRunning) {
|
||||
if (is_loop_owner) {
|
||||
hloop_free(&loop_);
|
||||
}
|
||||
loop_ = NULL;
|
||||
return;
|
||||
}
|
||||
setStatus(kStopping);
|
||||
hloop_stop(loop_);
|
||||
loop_ = NULL;
|
||||
}
|
||||
|
||||
void pause() {
|
||||
if (loop_ == NULL) return;
|
||||
if (isRunning()) {
|
||||
hloop_pause(loop_);
|
||||
setStatus(kPause);
|
||||
}
|
||||
}
|
||||
|
||||
void resume() {
|
||||
if (loop_ == NULL) return;
|
||||
if (isPause()) {
|
||||
hloop_resume(loop_);
|
||||
setStatus(kRunning);
|
||||
}
|
||||
}
|
||||
|
||||
// Timer interfaces: setTimer, killTimer, resetTimer
|
||||
TimerID setTimer(int timeout_ms, TimerCallback cb, uint32_t repeat = INFINITE, TimerID timerID = INVALID_TIMER_ID) {
|
||||
if (loop_ == NULL) return INVALID_TIMER_ID;
|
||||
assertInLoopThread();
|
||||
htimer_t* htimer = htimer_add(loop_, onTimer, timeout_ms, repeat);
|
||||
assert(htimer != NULL);
|
||||
if (timerID == INVALID_TIMER_ID) {
|
||||
timerID = generateTimerID();
|
||||
}
|
||||
hevent_set_id(htimer, timerID);
|
||||
hevent_set_userdata(htimer, this);
|
||||
|
||||
timers[timerID] = std::make_shared<Timer>(htimer, cb, repeat);
|
||||
return timerID;
|
||||
}
|
||||
|
||||
// setTimerInLoop thread-safe
|
||||
TimerID setTimerInLoop(int timeout_ms, TimerCallback cb, uint32_t repeat = INFINITE, TimerID timerID = INVALID_TIMER_ID) {
|
||||
if (timerID == INVALID_TIMER_ID) {
|
||||
timerID = generateTimerID();
|
||||
}
|
||||
runInLoop(std::bind(&EventLoop::setTimer, this, timeout_ms, cb, repeat, timerID));
|
||||
return timerID;
|
||||
}
|
||||
|
||||
// alias javascript setTimeout, setInterval
|
||||
// setTimeout thread-safe
|
||||
TimerID setTimeout(int timeout_ms, TimerCallback cb) {
|
||||
return setTimerInLoop(timeout_ms, cb, 1);
|
||||
}
|
||||
// setInterval thread-safe
|
||||
TimerID setInterval(int interval_ms, TimerCallback cb) {
|
||||
return setTimerInLoop(interval_ms, cb, INFINITE);
|
||||
}
|
||||
|
||||
// killTimer thread-safe
|
||||
void killTimer(TimerID timerID) {
|
||||
runInLoop([timerID, this](){
|
||||
auto iter = timers.find(timerID);
|
||||
if (iter != timers.end()) {
|
||||
htimer_del(iter->second->timer);
|
||||
timers.erase(iter);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// resetTimer thread-safe
|
||||
void resetTimer(TimerID timerID, int timeout_ms = 0) {
|
||||
runInLoop([timerID, timeout_ms, this](){
|
||||
auto iter = timers.find(timerID);
|
||||
if (iter != timers.end()) {
|
||||
htimer_reset(iter->second->timer, timeout_ms);
|
||||
if (iter->second->repeat == 0) {
|
||||
iter->second->repeat = 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
long tid() {
|
||||
if (loop_ == NULL) return hv_gettid();
|
||||
return hloop_tid(loop_);
|
||||
}
|
||||
|
||||
bool isInLoopThread() {
|
||||
if (loop_ == NULL) return false;
|
||||
return hv_gettid() == hloop_tid(loop_);
|
||||
}
|
||||
|
||||
void assertInLoopThread() {
|
||||
assert(isInLoopThread());
|
||||
}
|
||||
|
||||
void runInLoop(Functor fn) {
|
||||
if (isRunning() && isInLoopThread()) {
|
||||
if (fn) fn();
|
||||
} else {
|
||||
queueInLoop(std::move(fn));
|
||||
}
|
||||
}
|
||||
|
||||
void queueInLoop(Functor fn) {
|
||||
postEvent([fn](Event* ev) {
|
||||
if (fn) fn();
|
||||
});
|
||||
}
|
||||
|
||||
void postEvent(EventCallback cb) {
|
||||
if (loop_ == NULL) return;
|
||||
|
||||
EventPtr ev = std::make_shared<Event>(cb);
|
||||
hevent_set_userdata(&ev->event, this);
|
||||
ev->event.cb = onCustomEvent;
|
||||
|
||||
mutex_.lock();
|
||||
customEvents.push(ev);
|
||||
mutex_.unlock();
|
||||
|
||||
hloop_post_event(loop_, &ev->event);
|
||||
}
|
||||
|
||||
private:
|
||||
TimerID generateTimerID() {
|
||||
return (((TimerID)tid() & 0xFFFFFFFF) << 32) | ++nextTimerID;
|
||||
}
|
||||
|
||||
static void onTimer(htimer_t* htimer) {
|
||||
EventLoop* loop = (EventLoop*)hevent_userdata(htimer);
|
||||
|
||||
TimerID timerID = hevent_id(htimer);
|
||||
TimerPtr timer = NULL;
|
||||
|
||||
auto iter = loop->timers.find(timerID);
|
||||
if (iter != loop->timers.end()) {
|
||||
timer = iter->second;
|
||||
if (timer->repeat != INFINITE) --timer->repeat;
|
||||
}
|
||||
|
||||
if (timer) {
|
||||
if (timer->cb) timer->cb(timerID);
|
||||
if (timer->repeat == 0) {
|
||||
// htimer_t alloc and free by hloop, but timers[timerID] managed by EventLoop.
|
||||
loop->timers.erase(timerID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void onCustomEvent(hevent_t* hev) {
|
||||
EventLoop* loop = (EventLoop*)hevent_userdata(hev);
|
||||
|
||||
loop->mutex_.lock();
|
||||
EventPtr ev = loop->customEvents.front();
|
||||
loop->customEvents.pop();
|
||||
loop->mutex_.unlock();
|
||||
|
||||
if (ev && ev->cb) ev->cb(ev.get());
|
||||
}
|
||||
|
||||
public:
|
||||
std::atomic<uint32_t> connectionNum; // for LB_LeastConnections
|
||||
private:
|
||||
hloop_t* loop_;
|
||||
bool is_loop_owner;
|
||||
std::mutex mutex_;
|
||||
std::queue<EventPtr> customEvents; // GUAREDE_BY(mutex_)
|
||||
std::map<TimerID, TimerPtr> timers;
|
||||
std::atomic<TimerID> nextTimerID;
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<EventLoop> EventLoopPtr;
|
||||
|
||||
// ThreadLocalStorage
|
||||
static inline EventLoop* tlsEventLoop() {
|
||||
return (EventLoop*)ThreadLocalStorage::get(ThreadLocalStorage::EVENT_LOOP);
|
||||
}
|
||||
#define currentThreadEventLoop ::hv::tlsEventLoop()
|
||||
|
||||
static inline TimerID setTimer(int timeout_ms, TimerCallback cb, uint32_t repeat = INFINITE) {
|
||||
EventLoop* loop = tlsEventLoop();
|
||||
assert(loop != NULL);
|
||||
if (loop == NULL) return INVALID_TIMER_ID;
|
||||
return loop->setTimer(timeout_ms, cb, repeat);
|
||||
}
|
||||
|
||||
static inline void killTimer(TimerID timerID) {
|
||||
EventLoop* loop = tlsEventLoop();
|
||||
assert(loop != NULL);
|
||||
if (loop == NULL) return;
|
||||
loop->killTimer(timerID);
|
||||
}
|
||||
|
||||
static inline void resetTimer(TimerID timerID, int timeout_ms) {
|
||||
EventLoop* loop = tlsEventLoop();
|
||||
assert(loop != NULL);
|
||||
if (loop == NULL) return;
|
||||
loop->resetTimer(timerID, timeout_ms);
|
||||
}
|
||||
|
||||
static inline TimerID setTimeout(int timeout_ms, TimerCallback cb) {
|
||||
return setTimer(timeout_ms, cb, 1);
|
||||
}
|
||||
|
||||
static inline TimerID setInterval(int interval_ms, TimerCallback cb) {
|
||||
return setTimer(interval_ms, cb, INFINITE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // HV_EVENT_LOOP_HPP_
|
||||
116
third_party/libhv/evpp/EventLoopThread.h
vendored
Executable file
116
third_party/libhv/evpp/EventLoopThread.h
vendored
Executable file
@@ -0,0 +1,116 @@
|
||||
#ifndef HV_EVENT_LOOP_THREAD_HPP_
|
||||
#define HV_EVENT_LOOP_THREAD_HPP_
|
||||
|
||||
#include <thread>
|
||||
|
||||
#include "hlog.h"
|
||||
|
||||
#include "EventLoop.h"
|
||||
|
||||
namespace hv {
|
||||
|
||||
class EventLoopThread : public Status {
|
||||
public:
|
||||
// Return 0 means OK, other failed.
|
||||
typedef std::function<int()> Functor;
|
||||
|
||||
EventLoopThread(EventLoopPtr loop = NULL) {
|
||||
setStatus(kInitializing);
|
||||
loop_ = loop ? loop : std::make_shared<EventLoop>();
|
||||
setStatus(kInitialized);
|
||||
}
|
||||
|
||||
~EventLoopThread() {
|
||||
stop();
|
||||
join();
|
||||
}
|
||||
|
||||
const EventLoopPtr& loop() {
|
||||
return loop_;
|
||||
}
|
||||
|
||||
hloop_t* hloop() {
|
||||
return loop_->loop();
|
||||
}
|
||||
|
||||
bool isRunning() {
|
||||
return loop_->isRunning();
|
||||
}
|
||||
|
||||
// @param wait_thread_started: if ture this method will block until loop_thread started.
|
||||
// @param pre: This functor will be executed when loop_thread started.
|
||||
// @param post:This Functor will be executed when loop_thread stopped.
|
||||
void start(bool wait_thread_started = true,
|
||||
Functor pre = Functor(),
|
||||
Functor post = Functor()) {
|
||||
if (status() >= kStarting && status() < kStopped) return;
|
||||
if (isRunning()) return;
|
||||
setStatus(kStarting);
|
||||
|
||||
thread_ = std::make_shared<std::thread>(&EventLoopThread::loop_thread, this, pre, post);
|
||||
|
||||
if (wait_thread_started) {
|
||||
while (loop_->status() < kRunning) {
|
||||
hv_delay(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @param wait_thread_started: if ture this method will block until loop_thread stopped.
|
||||
// stop thread-safe
|
||||
void stop(bool wait_thread_stopped = false) {
|
||||
if (status() < kStarting || status() >= kStopping) return;
|
||||
setStatus(kStopping);
|
||||
|
||||
long loop_tid = loop_->tid();
|
||||
loop_->stop();
|
||||
|
||||
if (wait_thread_stopped) {
|
||||
if (hv_gettid() == loop_tid) return;
|
||||
join();
|
||||
}
|
||||
}
|
||||
|
||||
// @brief join loop_thread
|
||||
// @note destructor will join loop_thread if you forget to call this method.
|
||||
void join() {
|
||||
if (thread_ && thread_->joinable()) {
|
||||
thread_->join();
|
||||
thread_ = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void loop_thread(const Functor& pre, const Functor& post) {
|
||||
hlogi("EventLoopThread started, tid=%ld", hv_gettid());
|
||||
setStatus(kStarted);
|
||||
|
||||
if (pre) {
|
||||
loop_->queueInLoop([this, pre]{
|
||||
if (pre() != 0) {
|
||||
loop_->stop();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
loop_->run();
|
||||
assert(loop_->isStopped());
|
||||
|
||||
if (post) {
|
||||
post();
|
||||
}
|
||||
|
||||
setStatus(kStopped);
|
||||
hlogi("EventLoopThread stopped, tid=%ld", hv_gettid());
|
||||
}
|
||||
|
||||
private:
|
||||
EventLoopPtr loop_;
|
||||
std::shared_ptr<std::thread> thread_;
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<EventLoopThread> EventLoopThreadPtr;
|
||||
|
||||
}
|
||||
|
||||
#endif // HV_EVENT_LOOP_THREAD_HPP_
|
||||
138
third_party/libhv/evpp/EventLoopThreadPool.h
vendored
Executable file
138
third_party/libhv/evpp/EventLoopThreadPool.h
vendored
Executable file
@@ -0,0 +1,138 @@
|
||||
#ifndef HV_EVENT_LOOP_THREAD_POOL_HPP_
|
||||
#define HV_EVENT_LOOP_THREAD_POOL_HPP_
|
||||
|
||||
#include "EventLoopThread.h"
|
||||
#include "hbase.h"
|
||||
|
||||
namespace hv {
|
||||
|
||||
class EventLoopThreadPool : public Status {
|
||||
public:
|
||||
EventLoopThreadPool(int thread_num = std::thread::hardware_concurrency()) {
|
||||
setStatus(kInitializing);
|
||||
thread_num_ = thread_num;
|
||||
next_loop_idx_ = 0;
|
||||
setStatus(kInitialized);
|
||||
}
|
||||
|
||||
~EventLoopThreadPool() {
|
||||
stop();
|
||||
join();
|
||||
}
|
||||
|
||||
int threadNum() {
|
||||
return thread_num_;
|
||||
}
|
||||
|
||||
void setThreadNum(int num) {
|
||||
thread_num_ = num;
|
||||
}
|
||||
|
||||
EventLoopPtr nextLoop(load_balance_e lb = LB_RoundRobin) {
|
||||
size_t numLoops = loop_threads_.size();
|
||||
if (numLoops == 0) return NULL;
|
||||
size_t idx = 0;
|
||||
if (lb == LB_RoundRobin) {
|
||||
if (++next_loop_idx_ >= numLoops) next_loop_idx_ = 0;
|
||||
idx = next_loop_idx_ % numLoops;
|
||||
} else if (lb == LB_Random) {
|
||||
idx = hv_rand(0, numLoops - 1);
|
||||
} else if (lb == LB_LeastConnections) {
|
||||
for (size_t i = 1; i < numLoops; ++i) {
|
||||
if (loop_threads_[i]->loop()->connectionNum < loop_threads_[idx]->loop()->connectionNum) {
|
||||
idx = i;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Not Implemented
|
||||
}
|
||||
return loop_threads_[idx]->loop();
|
||||
}
|
||||
|
||||
EventLoopPtr loop(int idx = -1) {
|
||||
if (idx >= 0 && idx < (int)loop_threads_.size()) {
|
||||
return loop_threads_[idx]->loop();
|
||||
}
|
||||
return nextLoop();
|
||||
}
|
||||
|
||||
hloop_t* hloop(int idx = -1) {
|
||||
EventLoopPtr ptr = loop(idx);
|
||||
return ptr ? ptr->loop() : NULL;
|
||||
}
|
||||
|
||||
// @param wait_threads_started: if ture this method will block until all loop_threads started.
|
||||
// @param pre: This functor will be executed when loop_thread started.
|
||||
// @param post:This Functor will be executed when loop_thread stopped.
|
||||
void start(bool wait_threads_started = false,
|
||||
std::function<void(const EventLoopPtr&)> pre = NULL,
|
||||
std::function<void(const EventLoopPtr&)> post = NULL) {
|
||||
if (thread_num_ == 0) return;
|
||||
if (status() >= kStarting && status() < kStopped) return;
|
||||
setStatus(kStarting);
|
||||
|
||||
auto started_cnt = std::make_shared<std::atomic<int>>(0);
|
||||
auto exited_cnt = std::make_shared<std::atomic<int>>(0);
|
||||
|
||||
loop_threads_.clear();
|
||||
for (int i = 0; i < thread_num_; ++i) {
|
||||
auto loop_thread = std::make_shared<EventLoopThread>();
|
||||
const EventLoopPtr& loop = loop_thread->loop();
|
||||
loop_thread->start(false,
|
||||
[this, started_cnt, pre, &loop]() {
|
||||
if (++(*started_cnt) == thread_num_) {
|
||||
setStatus(kRunning);
|
||||
}
|
||||
if (pre) pre(loop);
|
||||
return 0;
|
||||
},
|
||||
[this, exited_cnt, post, &loop]() {
|
||||
if (post) post(loop);
|
||||
if (++(*exited_cnt) == thread_num_) {
|
||||
setStatus(kStopped);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
);
|
||||
loop_threads_.push_back(loop_thread);
|
||||
}
|
||||
|
||||
if (wait_threads_started) {
|
||||
while (status() < kRunning) {
|
||||
hv_delay(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @param wait_threads_started: if ture this method will block until all loop_threads stopped.
|
||||
// stop thread-safe
|
||||
void stop(bool wait_threads_stopped = false) {
|
||||
if (status() < kStarting || status() >= kStopping) return;
|
||||
setStatus(kStopping);
|
||||
|
||||
for (auto& loop_thread : loop_threads_) {
|
||||
loop_thread->stop(false);
|
||||
}
|
||||
|
||||
if (wait_threads_stopped) {
|
||||
join();
|
||||
}
|
||||
}
|
||||
|
||||
// @brief join all loop_threads
|
||||
// @note destructor will join loop_threads if you forget to call this method.
|
||||
void join() {
|
||||
for (auto& loop_thread : loop_threads_) {
|
||||
loop_thread->join();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
int thread_num_;
|
||||
std::vector<EventLoopThreadPtr> loop_threads_;
|
||||
std::atomic<unsigned int> next_loop_idx_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // HV_EVENT_LOOP_THREAD_POOL_HPP_
|
||||
55
third_party/libhv/evpp/EventLoopThreadPool_test.cpp
vendored
Executable file
55
third_party/libhv/evpp/EventLoopThreadPool_test.cpp
vendored
Executable file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* EventLoopThreadPool_test.cpp
|
||||
*
|
||||
* @build: make evpp
|
||||
*
|
||||
*/
|
||||
|
||||
#include "hv.h"
|
||||
|
||||
#include "EventLoopThreadPool.h"
|
||||
|
||||
using namespace hv;
|
||||
|
||||
static void onTimer(TimerID timerID, int n) {
|
||||
printf("tid=%ld timerID=%lu time=%lus n=%d\n", hv_gettid(), (unsigned long)timerID, (unsigned long)time(NULL), n);
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
HV_MEMCHECK;
|
||||
hlog_set_level(LOG_LEVEL_DEBUG);
|
||||
|
||||
printf("main tid=%ld\n", hv_gettid());
|
||||
|
||||
EventLoopThreadPool loop_threads(4);
|
||||
loop_threads.start(true);
|
||||
|
||||
int thread_num = loop_threads.threadNum();
|
||||
for (int i = 0; i < thread_num; ++i) {
|
||||
EventLoopPtr loop = loop_threads.nextLoop();
|
||||
printf("worker[%d] tid=%ld\n", i, loop->tid());
|
||||
|
||||
loop->runInLoop([loop](){
|
||||
// runEvery 1s
|
||||
loop->setInterval(1000, std::bind(onTimer, std::placeholders::_1, 100));
|
||||
});
|
||||
|
||||
loop->queueInLoop([](){
|
||||
printf("queueInLoop tid=%ld\n", hv_gettid());
|
||||
});
|
||||
|
||||
loop->runInLoop([](){
|
||||
printf("runInLoop tid=%ld\n", hv_gettid());
|
||||
});
|
||||
}
|
||||
|
||||
// runAfter 10s
|
||||
loop_threads.loop()->setTimeout(10000, [&loop_threads](TimerID timerID){
|
||||
loop_threads.stop(false);
|
||||
});
|
||||
|
||||
// wait loop_threads exit
|
||||
loop_threads.join();
|
||||
|
||||
return 0;
|
||||
}
|
||||
48
third_party/libhv/evpp/EventLoopThread_test.cpp
vendored
Executable file
48
third_party/libhv/evpp/EventLoopThread_test.cpp
vendored
Executable file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* EventLoopThread_test.cpp
|
||||
*
|
||||
* @build: make evpp
|
||||
*
|
||||
*/
|
||||
|
||||
#include "hv.h"
|
||||
|
||||
#include "EventLoopThread.h"
|
||||
|
||||
using namespace hv;
|
||||
|
||||
static void onTimer(TimerID timerID, int n) {
|
||||
printf("tid=%ld timerID=%lu time=%lus n=%d\n", hv_gettid(), (unsigned long)timerID, (unsigned long)time(NULL), n);
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
HV_MEMCHECK;
|
||||
|
||||
printf("main tid=%ld\n", hv_gettid());
|
||||
|
||||
EventLoopThread loop_thread;
|
||||
const EventLoopPtr& loop = loop_thread.loop();
|
||||
|
||||
// runEvery 1s
|
||||
loop->setInterval(1000, std::bind(onTimer, std::placeholders::_1, 100));
|
||||
|
||||
// runAfter 10s
|
||||
loop->setTimeout(10000, [&loop](TimerID timerID){
|
||||
loop->stop();
|
||||
});
|
||||
|
||||
loop_thread.start();
|
||||
|
||||
loop->queueInLoop([](){
|
||||
printf("queueInLoop tid=%ld\n", hv_gettid());
|
||||
});
|
||||
|
||||
loop->runInLoop([](){
|
||||
printf("runInLoop tid=%ld\n", hv_gettid());
|
||||
});
|
||||
|
||||
// wait loop_thread exit
|
||||
loop_thread.join();
|
||||
|
||||
return 0;
|
||||
}
|
||||
45
third_party/libhv/evpp/EventLoop_test.cpp
vendored
Executable file
45
third_party/libhv/evpp/EventLoop_test.cpp
vendored
Executable file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* EventLoop_test.cpp
|
||||
*
|
||||
* @build: make evpp
|
||||
*
|
||||
*/
|
||||
|
||||
#include "hv.h"
|
||||
|
||||
#include "EventLoop.h"
|
||||
|
||||
using namespace hv;
|
||||
|
||||
static void onTimer(TimerID timerID, int n) {
|
||||
printf("tid=%ld timerID=%lu time=%lus n=%d\n", hv_gettid(), (unsigned long)timerID, (unsigned long)time(NULL), n);
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
HV_MEMCHECK;
|
||||
|
||||
printf("main tid=%ld\n", hv_gettid());
|
||||
|
||||
auto loop = std::make_shared<EventLoop>();
|
||||
|
||||
// runEvery 1s
|
||||
loop->setInterval(1000, std::bind(onTimer, std::placeholders::_1, 100));
|
||||
|
||||
// runAfter 10s
|
||||
loop->setTimeout(10000, [&loop](TimerID timerID){
|
||||
loop->stop();
|
||||
});
|
||||
|
||||
loop->queueInLoop([](){
|
||||
printf("queueInLoop tid=%ld\n", hv_gettid());
|
||||
});
|
||||
|
||||
loop->runInLoop([](){
|
||||
printf("runInLoop tid=%ld\n", hv_gettid());
|
||||
});
|
||||
|
||||
// run until loop stopped
|
||||
loop->run();
|
||||
|
||||
return 0;
|
||||
}
|
||||
24
third_party/libhv/evpp/README.md
vendored
Executable file
24
third_party/libhv/evpp/README.md
vendored
Executable file
@@ -0,0 +1,24 @@
|
||||
The evpp module is designed to be header-only and does not participate in compilation.
|
||||
hloop.h is encapsulated into c++ classes, referring to muduo and evpp.
|
||||
You can modify and use evpp classes according to your own business.
|
||||
|
||||
evpp模块被设计成只包含头文件,不参与编译。
|
||||
hloop.h中的c接口被封装成了c++的类,参考了muduo和evpp。
|
||||
你能修改和使用这些类根据你自己的业务。
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
.
|
||||
├── Buffer.h 缓存类
|
||||
├── Channel.h 通道类,封装了hio_t
|
||||
├── Event.h 事件类,封装了hevent_t、htimer_t
|
||||
├── EventLoop.h 事件循环类,封装了hloop_t
|
||||
├── EventLoopThread.h 事件循环线程类,组合了EventLoop和thread
|
||||
├── EventLoopThreadPool.h 事件循环线程池类,组合了EventLoop和ThreadPool
|
||||
├── TcpClient.h TCP客户端类
|
||||
├── TcpServer.h TCP服务端类
|
||||
├── UdpClient.h UDP客户端类
|
||||
└── UdpServer.h UDP服务端类
|
||||
|
||||
```
|
||||
56
third_party/libhv/evpp/Status.h
vendored
Executable file
56
third_party/libhv/evpp/Status.h
vendored
Executable file
@@ -0,0 +1,56 @@
|
||||
#ifndef HV_STATUS_HPP_
|
||||
#define HV_STATUS_HPP_
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace hv {
|
||||
|
||||
class Status {
|
||||
public:
|
||||
enum KStatus {
|
||||
kNull = 0,
|
||||
kInitializing = 1,
|
||||
kInitialized = 2,
|
||||
kStarting = 3,
|
||||
kStarted = 4,
|
||||
kRunning = 5,
|
||||
kPause = 6,
|
||||
kStopping = 7,
|
||||
kStopped = 8,
|
||||
kDestroyed = 9,
|
||||
};
|
||||
|
||||
Status() {
|
||||
status_ = kNull;
|
||||
}
|
||||
~Status() {
|
||||
status_ = kDestroyed;
|
||||
}
|
||||
|
||||
KStatus status() {
|
||||
return status_;
|
||||
}
|
||||
|
||||
void setStatus(KStatus status) {
|
||||
status_ = status;
|
||||
}
|
||||
|
||||
bool isRunning() {
|
||||
return status_ == kRunning;
|
||||
}
|
||||
|
||||
bool isPause() {
|
||||
return status_ == kPause;
|
||||
}
|
||||
|
||||
bool isStopped() {
|
||||
return status_ == kStopped;
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<KStatus> status_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // HV_STATUS_HPP_
|
||||
308
third_party/libhv/evpp/TcpClient.h
vendored
Executable file
308
third_party/libhv/evpp/TcpClient.h
vendored
Executable file
@@ -0,0 +1,308 @@
|
||||
#ifndef HV_TCP_CLIENT_HPP_
|
||||
#define HV_TCP_CLIENT_HPP_
|
||||
|
||||
#include "hsocket.h"
|
||||
#include "hssl.h"
|
||||
#include "hlog.h"
|
||||
|
||||
#include "EventLoopThread.h"
|
||||
#include "Channel.h"
|
||||
|
||||
namespace hv {
|
||||
|
||||
template<class TSocketChannel = SocketChannel>
|
||||
class TcpClientEventLoopTmpl {
|
||||
public:
|
||||
typedef std::shared_ptr<TSocketChannel> TSocketChannelPtr;
|
||||
|
||||
TcpClientEventLoopTmpl(EventLoopPtr loop = NULL) {
|
||||
loop_ = loop ? loop : std::make_shared<EventLoop>();
|
||||
remote_port = 0;
|
||||
connect_timeout = HIO_DEFAULT_CONNECT_TIMEOUT;
|
||||
tls = false;
|
||||
tls_setting = NULL;
|
||||
reconn_setting = NULL;
|
||||
unpack_setting = NULL;
|
||||
}
|
||||
|
||||
virtual ~TcpClientEventLoopTmpl() {
|
||||
HV_FREE(tls_setting);
|
||||
HV_FREE(reconn_setting);
|
||||
HV_FREE(unpack_setting);
|
||||
}
|
||||
|
||||
const EventLoopPtr& loop() {
|
||||
return loop_;
|
||||
}
|
||||
|
||||
// delete thread-safe
|
||||
void deleteInLoop() {
|
||||
loop_->runInLoop([this](){
|
||||
delete this;
|
||||
});
|
||||
}
|
||||
|
||||
// NOTE: By default, not bind local port. If necessary, you can call bind() after createsocket().
|
||||
// @retval >=0 connfd, <0 error
|
||||
int createsocket(int remote_port, const char* remote_host = "127.0.0.1") {
|
||||
memset(&remote_addr, 0, sizeof(remote_addr));
|
||||
int ret = sockaddr_set_ipport(&remote_addr, remote_host, remote_port);
|
||||
if (ret != 0) {
|
||||
return NABS(ret);
|
||||
}
|
||||
this->remote_host = remote_host;
|
||||
this->remote_port = remote_port;
|
||||
return createsocket(&remote_addr.sa);
|
||||
}
|
||||
|
||||
int createsocket(struct sockaddr* remote_addr) {
|
||||
int connfd = ::socket(remote_addr->sa_family, SOCK_STREAM, 0);
|
||||
// SOCKADDR_PRINT(remote_addr);
|
||||
if (connfd < 0) {
|
||||
perror("socket");
|
||||
return -2;
|
||||
}
|
||||
|
||||
hio_t* io = hio_get(loop_->loop(), connfd);
|
||||
assert(io != NULL);
|
||||
hio_set_peeraddr(io, remote_addr, SOCKADDR_LEN(remote_addr));
|
||||
channel = std::make_shared<TSocketChannel>(io);
|
||||
return connfd;
|
||||
}
|
||||
|
||||
int bind(int local_port, const char* local_host = "0.0.0.0") {
|
||||
sockaddr_u local_addr;
|
||||
memset(&local_addr, 0, sizeof(local_addr));
|
||||
int ret = sockaddr_set_ipport(&local_addr, local_host, local_port);
|
||||
if (ret != 0) {
|
||||
return NABS(ret);
|
||||
}
|
||||
return bind(&local_addr.sa);
|
||||
}
|
||||
|
||||
int bind(struct sockaddr* local_addr) {
|
||||
if (channel == NULL || channel->isClosed()) {
|
||||
return -1;
|
||||
}
|
||||
int ret = ::bind(channel->fd(), local_addr, SOCKADDR_LEN(local_addr));
|
||||
if (ret != 0) {
|
||||
perror("bind");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// closesocket thread-safe
|
||||
void closesocket() {
|
||||
if (channel && channel->status != SocketChannel::CLOSED) {
|
||||
loop_->runInLoop([this](){
|
||||
if (channel) {
|
||||
setReconnect(NULL);
|
||||
channel->close();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
int startConnect() {
|
||||
if (channel == NULL || channel->isClosed()) {
|
||||
int connfd = createsocket(&remote_addr.sa);
|
||||
if (connfd < 0) {
|
||||
hloge("createsocket %s:%d return %d!\n", remote_host.c_str(), remote_port, connfd);
|
||||
return connfd;
|
||||
}
|
||||
}
|
||||
if (channel == NULL || channel->status >= SocketChannel::CONNECTING) {
|
||||
return -1;
|
||||
}
|
||||
if (connect_timeout) {
|
||||
channel->setConnectTimeout(connect_timeout);
|
||||
}
|
||||
if (tls) {
|
||||
channel->enableSSL();
|
||||
if (tls_setting) {
|
||||
int ret = channel->newSslCtx(tls_setting);
|
||||
if (ret != 0) {
|
||||
hloge("new SSL_CTX failed: %d", ret);
|
||||
closesocket();
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
if (!is_ipaddr(remote_host.c_str())) {
|
||||
channel->setHostname(remote_host);
|
||||
}
|
||||
}
|
||||
channel->onconnect = [this]() {
|
||||
if (unpack_setting) {
|
||||
channel->setUnpack(unpack_setting);
|
||||
}
|
||||
channel->startRead();
|
||||
if (onConnection) {
|
||||
onConnection(channel);
|
||||
}
|
||||
if (reconn_setting) {
|
||||
reconn_setting_reset(reconn_setting);
|
||||
}
|
||||
};
|
||||
channel->onread = [this](Buffer* buf) {
|
||||
if (onMessage) {
|
||||
onMessage(channel, buf);
|
||||
}
|
||||
};
|
||||
channel->onwrite = [this](Buffer* buf) {
|
||||
if (onWriteComplete) {
|
||||
onWriteComplete(channel, buf);
|
||||
}
|
||||
};
|
||||
channel->onclose = [this]() {
|
||||
bool reconnect = reconn_setting != NULL;
|
||||
if (onConnection) {
|
||||
onConnection(channel);
|
||||
}
|
||||
if (reconnect) {
|
||||
startReconnect();
|
||||
}
|
||||
};
|
||||
return channel->startConnect();
|
||||
}
|
||||
|
||||
int startReconnect() {
|
||||
if (!reconn_setting) return -1;
|
||||
if (!reconn_setting_can_retry(reconn_setting)) return -2;
|
||||
uint32_t delay = reconn_setting_calc_delay(reconn_setting);
|
||||
hlogi("reconnect... cnt=%d, delay=%d", reconn_setting->cur_retry_cnt, reconn_setting->cur_delay);
|
||||
loop_->setTimeout(delay, [this](TimerID timerID){
|
||||
startConnect();
|
||||
});
|
||||
return 0;
|
||||
}
|
||||
|
||||
// start thread-safe
|
||||
void start() {
|
||||
loop_->runInLoop(std::bind(&TcpClientEventLoopTmpl::startConnect, this));
|
||||
}
|
||||
|
||||
bool isConnected() {
|
||||
if (channel == NULL) return false;
|
||||
return channel->isConnected();
|
||||
}
|
||||
|
||||
// send thread-safe
|
||||
int send(const void* data, int size) {
|
||||
if (!isConnected()) return -1;
|
||||
return channel->write(data, size);
|
||||
}
|
||||
int send(Buffer* buf) {
|
||||
return send(buf->data(), buf->size());
|
||||
}
|
||||
int send(const std::string& str) {
|
||||
return send(str.data(), str.size());
|
||||
}
|
||||
|
||||
int withTLS(hssl_ctx_opt_t* opt = NULL) {
|
||||
tls = true;
|
||||
if (opt) {
|
||||
if (tls_setting == NULL) {
|
||||
HV_ALLOC_SIZEOF(tls_setting);
|
||||
}
|
||||
opt->endpoint = HSSL_CLIENT;
|
||||
*tls_setting = *opt;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void setConnectTimeout(int ms) {
|
||||
connect_timeout = ms;
|
||||
}
|
||||
|
||||
void setReconnect(reconn_setting_t* setting) {
|
||||
if (setting == NULL) {
|
||||
HV_FREE(reconn_setting);
|
||||
return;
|
||||
}
|
||||
if (reconn_setting == NULL) {
|
||||
HV_ALLOC_SIZEOF(reconn_setting);
|
||||
}
|
||||
*reconn_setting = *setting;
|
||||
}
|
||||
bool isReconnect() {
|
||||
return reconn_setting && reconn_setting->cur_retry_cnt > 0;
|
||||
}
|
||||
|
||||
void setUnpack(unpack_setting_t* setting) {
|
||||
if (setting == NULL) {
|
||||
HV_FREE(unpack_setting);
|
||||
return;
|
||||
}
|
||||
if (unpack_setting == NULL) {
|
||||
HV_ALLOC_SIZEOF(unpack_setting);
|
||||
}
|
||||
*unpack_setting = *setting;
|
||||
}
|
||||
|
||||
public:
|
||||
TSocketChannelPtr channel;
|
||||
|
||||
std::string remote_host;
|
||||
int remote_port;
|
||||
sockaddr_u remote_addr;
|
||||
int connect_timeout;
|
||||
bool tls;
|
||||
hssl_ctx_opt_t* tls_setting;
|
||||
reconn_setting_t* reconn_setting;
|
||||
unpack_setting_t* unpack_setting;
|
||||
|
||||
// Callback
|
||||
std::function<void(const TSocketChannelPtr&)> onConnection;
|
||||
std::function<void(const TSocketChannelPtr&, Buffer*)> onMessage;
|
||||
// NOTE: Use Channel::isWriteComplete in onWriteComplete callback to determine whether all data has been written.
|
||||
std::function<void(const TSocketChannelPtr&, Buffer*)> onWriteComplete;
|
||||
|
||||
private:
|
||||
EventLoopPtr loop_;
|
||||
};
|
||||
|
||||
template<class TSocketChannel = SocketChannel>
|
||||
class TcpClientTmpl : private EventLoopThread, public TcpClientEventLoopTmpl<TSocketChannel> {
|
||||
public:
|
||||
TcpClientTmpl(EventLoopPtr loop = NULL)
|
||||
: EventLoopThread(loop)
|
||||
, TcpClientEventLoopTmpl<TSocketChannel>(EventLoopThread::loop())
|
||||
, is_loop_owner(loop == NULL)
|
||||
{}
|
||||
virtual ~TcpClientTmpl() {
|
||||
stop(true);
|
||||
}
|
||||
|
||||
const EventLoopPtr& loop() {
|
||||
return EventLoopThread::loop();
|
||||
}
|
||||
|
||||
// start thread-safe
|
||||
void start(bool wait_threads_started = true) {
|
||||
if (isRunning()) {
|
||||
TcpClientEventLoopTmpl<TSocketChannel>::start();
|
||||
} else {
|
||||
EventLoopThread::start(wait_threads_started, [this]() {
|
||||
TcpClientTmpl::startConnect();
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// stop thread-safe
|
||||
void stop(bool wait_threads_stopped = true) {
|
||||
TcpClientEventLoopTmpl<TSocketChannel>::closesocket();
|
||||
if (is_loop_owner) {
|
||||
EventLoopThread::stop(wait_threads_stopped);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool is_loop_owner;
|
||||
};
|
||||
|
||||
typedef TcpClientTmpl<SocketChannel> TcpClient;
|
||||
|
||||
}
|
||||
|
||||
#endif // HV_TCP_CLIENT_HPP_
|
||||
131
third_party/libhv/evpp/TcpClientEventLoop_test.cpp
vendored
Executable file
131
third_party/libhv/evpp/TcpClientEventLoop_test.cpp
vendored
Executable file
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* TcpClientEventLoop_test.cpp
|
||||
*
|
||||
* @build make evpp
|
||||
* @server bin/TcpServer_test 1234
|
||||
* @client bin/TcpClientEventLoop_test 1234
|
||||
*
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "TcpClient.h"
|
||||
#include "htime.h"
|
||||
|
||||
#define TEST_RECONNECT 1
|
||||
#define TEST_TLS 0
|
||||
|
||||
using namespace hv;
|
||||
|
||||
class MyTcpClient : public TcpClient {
|
||||
public:
|
||||
MyTcpClient(EventLoopPtr loop = NULL) : TcpClient(loop) {
|
||||
onConnection = [this](const SocketChannelPtr& channel) {
|
||||
std::string peeraddr = channel->peeraddr();
|
||||
if (channel->isConnected()) {
|
||||
printf("connected to %s! connfd=%d\n", peeraddr.c_str(), channel->fd());
|
||||
// send(time) every 3s
|
||||
setInterval(3000, [channel](TimerID timerID){
|
||||
if (channel->isConnected()) {
|
||||
if (channel->isWriteComplete()) {
|
||||
char str[DATETIME_FMT_BUFLEN] = {0};
|
||||
datetime_t dt = datetime_now();
|
||||
datetime_fmt(&dt, str);
|
||||
channel->write(str);
|
||||
}
|
||||
} else {
|
||||
killTimer(timerID);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
printf("disconnected to %s! connfd=%d\n", peeraddr.c_str(), channel->fd());
|
||||
}
|
||||
if (isReconnect()) {
|
||||
printf("reconnect cnt=%d, delay=%d\n", reconn_setting->cur_retry_cnt, reconn_setting->cur_delay);
|
||||
}
|
||||
};
|
||||
|
||||
onMessage = [](const SocketChannelPtr& channel, Buffer* buf) {
|
||||
printf("< %.*s\n", (int)buf->size(), (char*)buf->data());
|
||||
};
|
||||
}
|
||||
|
||||
int connect(int port) {
|
||||
int connfd = createsocket(port);
|
||||
if (connfd < 0) {
|
||||
return connfd;
|
||||
}
|
||||
#if TEST_RECONNECT
|
||||
// reconnect: 1,2,4,8,10,10,10...
|
||||
reconn_setting_t reconn;
|
||||
reconn_setting_init(&reconn);
|
||||
reconn.min_delay = 1000;
|
||||
reconn.max_delay = 10000;
|
||||
reconn.delay_policy = 2;
|
||||
setReconnect(&reconn);
|
||||
#endif
|
||||
|
||||
#if TEST_TLS
|
||||
withTLS();
|
||||
#endif
|
||||
printf("client connect to port %d, connfd=%d ...\n", port, connfd);
|
||||
start();
|
||||
return connfd;
|
||||
}
|
||||
};
|
||||
typedef std::shared_ptr<MyTcpClient> MyTcpClientPtr;
|
||||
|
||||
int TestMultiClientsRunInOneEventLoop(int port, int nclients) {
|
||||
auto loop_thread = std::make_shared<EventLoopThread>();
|
||||
loop_thread->start();
|
||||
|
||||
std::map<int, MyTcpClient*> clients;
|
||||
for (int i = 0; i < nclients; ++i) {
|
||||
MyTcpClient* client = new MyTcpClient(loop_thread->loop());
|
||||
client->connect(port);
|
||||
clients[i] = client;
|
||||
}
|
||||
|
||||
std::string str;
|
||||
while (std::getline(std::cin, str)) {
|
||||
if (str == "close") {
|
||||
for (auto& pair : clients) {
|
||||
MyTcpClient* client = pair.second;
|
||||
client->closesocket();
|
||||
}
|
||||
} else if (str == "delete") {
|
||||
for (auto& pair : clients) {
|
||||
MyTcpClient* client = pair.second;
|
||||
client->deleteInLoop();
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
for (auto& pair : clients) {
|
||||
MyTcpClient* client = pair.second;
|
||||
client->send(str);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
printf("Press Enter key to exit loop.\n");
|
||||
while (getchar() != '\n');
|
||||
loop_thread->stop();
|
||||
loop_thread->join();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc < 2) {
|
||||
printf("Usage: %s port [nclients]\n", argv[0]);
|
||||
return -10;
|
||||
}
|
||||
int port = atoi(argv[1]);
|
||||
|
||||
int nclients = 100;
|
||||
if (argc > 2) {
|
||||
nclients = atoi(argv[2]);
|
||||
}
|
||||
|
||||
return TestMultiClientsRunInOneEventLoop(port, nclients);
|
||||
}
|
||||
97
third_party/libhv/evpp/TcpClient_test.cpp
vendored
Executable file
97
third_party/libhv/evpp/TcpClient_test.cpp
vendored
Executable file
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* TcpClient_test.cpp
|
||||
*
|
||||
* @build make evpp
|
||||
* @server bin/TcpServer_test 1234
|
||||
* @client bin/TcpClient_test 1234
|
||||
*
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "TcpClient.h"
|
||||
#include "htime.h"
|
||||
|
||||
#define TEST_RECONNECT 1
|
||||
#define TEST_TLS 0
|
||||
|
||||
using namespace hv;
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc < 2) {
|
||||
printf("Usage: %s remote_port [remote_host]\n", argv[0]);
|
||||
return -10;
|
||||
}
|
||||
int remote_port = atoi(argv[1]);
|
||||
const char* remote_host = "127.0.0.1";
|
||||
if (argc > 2) {
|
||||
remote_host = argv[2];
|
||||
}
|
||||
|
||||
TcpClient cli;
|
||||
int connfd = cli.createsocket(remote_port, remote_host);
|
||||
if (connfd < 0) {
|
||||
return -20;
|
||||
}
|
||||
printf("client connect to port %d, connfd=%d ...\n", remote_port, connfd);
|
||||
cli.onConnection = [&cli](const SocketChannelPtr& channel) {
|
||||
std::string peeraddr = channel->peeraddr();
|
||||
if (channel->isConnected()) {
|
||||
printf("connected to %s! connfd=%d\n", peeraddr.c_str(), channel->fd());
|
||||
// send(time) every 3s
|
||||
setInterval(3000, [channel](TimerID timerID){
|
||||
if (channel->isConnected()) {
|
||||
if (channel->isWriteComplete()) {
|
||||
char str[DATETIME_FMT_BUFLEN] = {0};
|
||||
datetime_t dt = datetime_now();
|
||||
datetime_fmt(&dt, str);
|
||||
channel->write(str);
|
||||
}
|
||||
} else {
|
||||
killTimer(timerID);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
printf("disconnected to %s! connfd=%d\n", peeraddr.c_str(), channel->fd());
|
||||
}
|
||||
if (cli.isReconnect()) {
|
||||
printf("reconnect cnt=%d, delay=%d\n", cli.reconn_setting->cur_retry_cnt, cli.reconn_setting->cur_delay);
|
||||
}
|
||||
};
|
||||
cli.onMessage = [](const SocketChannelPtr& channel, Buffer* buf) {
|
||||
printf("< %.*s\n", (int)buf->size(), (char*)buf->data());
|
||||
};
|
||||
|
||||
#if TEST_RECONNECT
|
||||
// reconnect: 1,2,4,8,10,10,10...
|
||||
reconn_setting_t reconn;
|
||||
reconn_setting_init(&reconn);
|
||||
reconn.min_delay = 1000;
|
||||
reconn.max_delay = 10000;
|
||||
reconn.delay_policy = 2;
|
||||
cli.setReconnect(&reconn);
|
||||
#endif
|
||||
|
||||
#if TEST_TLS
|
||||
cli.withTLS();
|
||||
#endif
|
||||
|
||||
cli.start();
|
||||
|
||||
std::string str;
|
||||
while (std::getline(std::cin, str)) {
|
||||
if (str == "close") {
|
||||
cli.closesocket();
|
||||
} else if (str == "start") {
|
||||
cli.start();
|
||||
} else if (str == "stop") {
|
||||
cli.stop();
|
||||
break;
|
||||
} else {
|
||||
if (!cli.isConnected()) break;
|
||||
cli.send(str);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
322
third_party/libhv/evpp/TcpServer.h
vendored
Executable file
322
third_party/libhv/evpp/TcpServer.h
vendored
Executable file
@@ -0,0 +1,322 @@
|
||||
#ifndef HV_TCP_SERVER_HPP_
|
||||
#define HV_TCP_SERVER_HPP_
|
||||
|
||||
#include "hsocket.h"
|
||||
#include "hssl.h"
|
||||
#include "hlog.h"
|
||||
|
||||
#include "EventLoopThreadPool.h"
|
||||
#include "Channel.h"
|
||||
|
||||
namespace hv {
|
||||
|
||||
template<class TSocketChannel = SocketChannel>
|
||||
class TcpServerEventLoopTmpl {
|
||||
public:
|
||||
typedef std::shared_ptr<TSocketChannel> TSocketChannelPtr;
|
||||
|
||||
TcpServerEventLoopTmpl(EventLoopPtr loop = NULL) {
|
||||
acceptor_loop = loop ? loop : std::make_shared<EventLoop>();
|
||||
port = 0;
|
||||
listenfd = -1;
|
||||
tls = false;
|
||||
tls_setting = NULL;
|
||||
unpack_setting = NULL;
|
||||
max_connections = 0xFFFFFFFF;
|
||||
load_balance = LB_RoundRobin;
|
||||
}
|
||||
|
||||
virtual ~TcpServerEventLoopTmpl() {
|
||||
HV_FREE(tls_setting);
|
||||
HV_FREE(unpack_setting);
|
||||
}
|
||||
|
||||
EventLoopPtr loop(int idx = -1) {
|
||||
EventLoopPtr worker_loop = worker_threads.loop(idx);
|
||||
if (worker_loop == NULL) {
|
||||
worker_loop = acceptor_loop;
|
||||
}
|
||||
return worker_loop;
|
||||
}
|
||||
|
||||
//@retval >=0 listenfd, <0 error
|
||||
int createsocket(int port, const char* host = "0.0.0.0") {
|
||||
listenfd = Listen(port, host);
|
||||
if (listenfd < 0) return listenfd;
|
||||
this->host = host;
|
||||
this->port = port;
|
||||
return listenfd;
|
||||
}
|
||||
// closesocket thread-safe
|
||||
void closesocket() {
|
||||
if (listenfd >= 0) {
|
||||
hloop_t* loop = acceptor_loop->loop();
|
||||
if (loop) {
|
||||
hio_t* listenio = hio_get(loop, listenfd);
|
||||
assert(listenio != NULL);
|
||||
hio_close_async(listenio);
|
||||
}
|
||||
listenfd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void setMaxConnectionNum(uint32_t num) {
|
||||
max_connections = num;
|
||||
}
|
||||
|
||||
void setLoadBalance(load_balance_e lb) {
|
||||
load_balance = lb;
|
||||
}
|
||||
|
||||
// NOTE: totalThreadNum = 1 acceptor_thread + N worker_threads (N can be 0)
|
||||
void setThreadNum(int num) {
|
||||
worker_threads.setThreadNum(num);
|
||||
}
|
||||
|
||||
int startAccept() {
|
||||
if (listenfd < 0) {
|
||||
listenfd = createsocket(port, host.c_str());
|
||||
if (listenfd < 0) {
|
||||
hloge("createsocket %s:%d return %d!\n", host.c_str(), port, listenfd);
|
||||
return listenfd;
|
||||
}
|
||||
}
|
||||
hloop_t* loop = acceptor_loop->loop();
|
||||
if (loop == NULL) return -2;
|
||||
hio_t* listenio = haccept(loop, listenfd, onAccept);
|
||||
assert(listenio != NULL);
|
||||
hevent_set_userdata(listenio, this);
|
||||
if (tls) {
|
||||
hio_enable_ssl(listenio);
|
||||
if (tls_setting) {
|
||||
int ret = hio_new_ssl_ctx(listenio, tls_setting);
|
||||
if (ret != 0) {
|
||||
hloge("new SSL_CTX failed: %d", ret);
|
||||
closesocket();
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int stopAccept() {
|
||||
if (listenfd < 0) return -1;
|
||||
hloop_t* loop = acceptor_loop->loop();
|
||||
if (loop == NULL) return -2;
|
||||
hio_t* listenio = hio_get(loop, listenfd);
|
||||
assert(listenio != NULL);
|
||||
return hio_del(listenio, HV_READ);
|
||||
}
|
||||
|
||||
// start thread-safe
|
||||
void start(bool wait_threads_started = true) {
|
||||
if (worker_threads.threadNum() > 0) {
|
||||
worker_threads.start(wait_threads_started);
|
||||
}
|
||||
acceptor_loop->runInLoop(std::bind(&TcpServerEventLoopTmpl::startAccept, this));
|
||||
}
|
||||
// stop thread-safe
|
||||
void stop(bool wait_threads_stopped = true) {
|
||||
closesocket();
|
||||
if (worker_threads.threadNum() > 0) {
|
||||
worker_threads.stop(wait_threads_stopped);
|
||||
}
|
||||
}
|
||||
|
||||
int withTLS(hssl_ctx_opt_t* opt = NULL) {
|
||||
tls = true;
|
||||
if (opt) {
|
||||
if (tls_setting == NULL) {
|
||||
HV_ALLOC_SIZEOF(tls_setting);
|
||||
}
|
||||
opt->endpoint = HSSL_SERVER;
|
||||
*tls_setting = *opt;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void setUnpack(unpack_setting_t* setting) {
|
||||
if (setting == NULL) {
|
||||
HV_FREE(unpack_setting);
|
||||
return;
|
||||
}
|
||||
if (unpack_setting == NULL) {
|
||||
HV_ALLOC_SIZEOF(unpack_setting);
|
||||
}
|
||||
*unpack_setting = *setting;
|
||||
}
|
||||
|
||||
// channel
|
||||
const TSocketChannelPtr& addChannel(hio_t* io) {
|
||||
uint32_t id = hio_id(io);
|
||||
auto channel = std::make_shared<TSocketChannel>(io);
|
||||
std::lock_guard<std::mutex> locker(mutex_);
|
||||
channels[id] = channel;
|
||||
return channels[id];
|
||||
}
|
||||
|
||||
TSocketChannelPtr getChannelById(uint32_t id) {
|
||||
std::lock_guard<std::mutex> locker(mutex_);
|
||||
auto iter = channels.find(id);
|
||||
return iter != channels.end() ? iter->second : NULL;
|
||||
}
|
||||
|
||||
void removeChannel(const TSocketChannelPtr& channel) {
|
||||
uint32_t id = channel->id();
|
||||
std::lock_guard<std::mutex> locker(mutex_);
|
||||
channels.erase(id);
|
||||
}
|
||||
|
||||
size_t connectionNum() {
|
||||
std::lock_guard<std::mutex> locker(mutex_);
|
||||
return channels.size();
|
||||
}
|
||||
|
||||
int foreachChannel(std::function<void(const TSocketChannelPtr& channel)> fn) {
|
||||
std::lock_guard<std::mutex> locker(mutex_);
|
||||
for (auto& pair : channels) {
|
||||
fn(pair.second);
|
||||
}
|
||||
return channels.size();
|
||||
}
|
||||
|
||||
// broadcast thread-safe
|
||||
int broadcast(const void* data, int size) {
|
||||
return foreachChannel([data, size](const TSocketChannelPtr& channel) {
|
||||
channel->write(data, size);
|
||||
});
|
||||
}
|
||||
|
||||
int broadcast(const std::string& str) {
|
||||
return broadcast(str.data(), str.size());
|
||||
}
|
||||
|
||||
private:
|
||||
static void newConnEvent(hio_t* connio) {
|
||||
TcpServerEventLoopTmpl* server = (TcpServerEventLoopTmpl*)hevent_userdata(connio);
|
||||
if (server->connectionNum() >= server->max_connections) {
|
||||
hlogw("over max_connections");
|
||||
hio_close(connio);
|
||||
return;
|
||||
}
|
||||
|
||||
// NOTE: attach to worker loop
|
||||
EventLoop* worker_loop = currentThreadEventLoop;
|
||||
assert(worker_loop != NULL);
|
||||
hio_attach(worker_loop->loop(), connio);
|
||||
|
||||
const TSocketChannelPtr& channel = server->addChannel(connio);
|
||||
channel->status = SocketChannel::CONNECTED;
|
||||
|
||||
channel->onread = [server, &channel](Buffer* buf) {
|
||||
if (server->onMessage) {
|
||||
server->onMessage(channel, buf);
|
||||
}
|
||||
};
|
||||
channel->onwrite = [server, &channel](Buffer* buf) {
|
||||
if (server->onWriteComplete) {
|
||||
server->onWriteComplete(channel, buf);
|
||||
}
|
||||
};
|
||||
channel->onclose = [server, &channel]() {
|
||||
EventLoop* worker_loop = currentThreadEventLoop;
|
||||
assert(worker_loop != NULL);
|
||||
--worker_loop->connectionNum;
|
||||
|
||||
channel->status = SocketChannel::CLOSED;
|
||||
if (server->onConnection) {
|
||||
server->onConnection(channel);
|
||||
}
|
||||
server->removeChannel(channel);
|
||||
// NOTE: After removeChannel, channel may be destroyed,
|
||||
// so in this lambda function, no code should be added below.
|
||||
};
|
||||
|
||||
if (server->unpack_setting) {
|
||||
channel->setUnpack(server->unpack_setting);
|
||||
}
|
||||
channel->startRead();
|
||||
if (server->onConnection) {
|
||||
server->onConnection(channel);
|
||||
}
|
||||
}
|
||||
|
||||
static void onAccept(hio_t* connio) {
|
||||
TcpServerEventLoopTmpl* server = (TcpServerEventLoopTmpl*)hevent_userdata(connio);
|
||||
// NOTE: detach from acceptor loop
|
||||
hio_detach(connio);
|
||||
EventLoopPtr worker_loop = server->worker_threads.nextLoop(server->load_balance);
|
||||
if (worker_loop == NULL) {
|
||||
worker_loop = server->acceptor_loop;
|
||||
}
|
||||
++worker_loop->connectionNum;
|
||||
worker_loop->runInLoop(std::bind(&TcpServerEventLoopTmpl::newConnEvent, connio));
|
||||
}
|
||||
|
||||
public:
|
||||
std::string host;
|
||||
int port;
|
||||
int listenfd;
|
||||
bool tls;
|
||||
hssl_ctx_opt_t* tls_setting;
|
||||
unpack_setting_t* unpack_setting;
|
||||
// Callback
|
||||
std::function<void(const TSocketChannelPtr&)> onConnection;
|
||||
std::function<void(const TSocketChannelPtr&, Buffer*)> onMessage;
|
||||
// NOTE: Use Channel::isWriteComplete in onWriteComplete callback to determine whether all data has been written.
|
||||
std::function<void(const TSocketChannelPtr&, Buffer*)> onWriteComplete;
|
||||
|
||||
uint32_t max_connections;
|
||||
load_balance_e load_balance;
|
||||
|
||||
private:
|
||||
// id => TSocketChannelPtr
|
||||
std::map<uint32_t, TSocketChannelPtr> channels; // GUAREDE_BY(mutex_)
|
||||
std::mutex mutex_;
|
||||
|
||||
EventLoopPtr acceptor_loop;
|
||||
EventLoopThreadPool worker_threads;
|
||||
};
|
||||
|
||||
template<class TSocketChannel = SocketChannel>
|
||||
class TcpServerTmpl : private EventLoopThread, public TcpServerEventLoopTmpl<TSocketChannel> {
|
||||
public:
|
||||
TcpServerTmpl(EventLoopPtr loop = NULL)
|
||||
: EventLoopThread(loop)
|
||||
, TcpServerEventLoopTmpl<TSocketChannel>(EventLoopThread::loop())
|
||||
, is_loop_owner(loop == NULL)
|
||||
{}
|
||||
virtual ~TcpServerTmpl() {
|
||||
stop(true);
|
||||
}
|
||||
|
||||
EventLoopPtr loop(int idx = -1) {
|
||||
return TcpServerEventLoopTmpl<TSocketChannel>::loop(idx);
|
||||
}
|
||||
|
||||
// start thread-safe
|
||||
void start(bool wait_threads_started = true) {
|
||||
TcpServerEventLoopTmpl<TSocketChannel>::start(wait_threads_started);
|
||||
if (!isRunning()) {
|
||||
EventLoopThread::start(wait_threads_started);
|
||||
}
|
||||
}
|
||||
|
||||
// stop thread-safe
|
||||
void stop(bool wait_threads_stopped = true) {
|
||||
if (is_loop_owner) {
|
||||
EventLoopThread::stop(wait_threads_stopped);
|
||||
}
|
||||
TcpServerEventLoopTmpl<TSocketChannel>::stop(wait_threads_stopped);
|
||||
}
|
||||
|
||||
private:
|
||||
bool is_loop_owner;
|
||||
};
|
||||
|
||||
typedef TcpServerTmpl<SocketChannel> TcpServer;
|
||||
|
||||
}
|
||||
|
||||
#endif // HV_TCP_SERVER_HPP_
|
||||
75
third_party/libhv/evpp/TcpServer_test.cpp
vendored
Executable file
75
third_party/libhv/evpp/TcpServer_test.cpp
vendored
Executable file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* TcpServer_test.cpp
|
||||
*
|
||||
* @build make evpp
|
||||
* @server bin/TcpServer_test 1234
|
||||
* @client bin/TcpClient_test 1234
|
||||
*
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "TcpServer.h"
|
||||
|
||||
using namespace hv;
|
||||
|
||||
#define TEST_TLS 0
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc < 2) {
|
||||
printf("Usage: %s port\n", argv[0]);
|
||||
return -10;
|
||||
}
|
||||
int port = atoi(argv[1]);
|
||||
|
||||
hlog_set_level(LOG_LEVEL_DEBUG);
|
||||
|
||||
TcpServer srv;
|
||||
int listenfd = srv.createsocket(port);
|
||||
if (listenfd < 0) {
|
||||
return -20;
|
||||
}
|
||||
printf("server listen on port %d, listenfd=%d ...\n", port, listenfd);
|
||||
srv.onConnection = [](const SocketChannelPtr& channel) {
|
||||
std::string peeraddr = channel->peeraddr();
|
||||
if (channel->isConnected()) {
|
||||
printf("%s connected! connfd=%d id=%d tid=%ld\n", peeraddr.c_str(), channel->fd(), channel->id(), currentThreadEventLoop->tid());
|
||||
} else {
|
||||
printf("%s disconnected! connfd=%d id=%d tid=%ld\n", peeraddr.c_str(), channel->fd(), channel->id(), currentThreadEventLoop->tid());
|
||||
}
|
||||
};
|
||||
srv.onMessage = [](const SocketChannelPtr& channel, Buffer* buf) {
|
||||
// echo
|
||||
printf("< %.*s\n", (int)buf->size(), (char*)buf->data());
|
||||
channel->write(buf);
|
||||
};
|
||||
srv.setThreadNum(4);
|
||||
srv.setLoadBalance(LB_LeastConnections);
|
||||
|
||||
#if TEST_TLS
|
||||
hssl_ctx_opt_t ssl_opt;
|
||||
memset(&ssl_opt, 0, sizeof(hssl_ctx_opt_t));
|
||||
ssl_opt.crt_file = "cert/server.crt";
|
||||
ssl_opt.key_file = "cert/server.key";
|
||||
ssl_opt.verify_peer = 0;
|
||||
srv.withTLS(&ssl_opt);
|
||||
#endif
|
||||
|
||||
srv.start();
|
||||
|
||||
std::string str;
|
||||
while (std::getline(std::cin, str)) {
|
||||
if (str == "close") {
|
||||
srv.closesocket();
|
||||
} else if (str == "start") {
|
||||
srv.start();
|
||||
} else if (str == "stop") {
|
||||
srv.stop();
|
||||
break;
|
||||
} else {
|
||||
srv.broadcast(str.data(), str.size());
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
47
third_party/libhv/evpp/TimerThread.h
vendored
Executable file
47
third_party/libhv/evpp/TimerThread.h
vendored
Executable file
@@ -0,0 +1,47 @@
|
||||
#ifndef HV_TIMER_THREAD_HPP_
|
||||
#define HV_TIMER_THREAD_HPP_
|
||||
|
||||
#include "EventLoopThread.h"
|
||||
|
||||
namespace hv {
|
||||
|
||||
class TimerThread : public EventLoopThread {
|
||||
public:
|
||||
std::atomic<TimerID> nextTimerID;
|
||||
TimerThread() : EventLoopThread() {
|
||||
nextTimerID = 0;
|
||||
start();
|
||||
}
|
||||
|
||||
virtual ~TimerThread() {
|
||||
stop();
|
||||
join();
|
||||
}
|
||||
|
||||
public:
|
||||
// setTimer, setTimeout, killTimer, resetTimer thread-safe
|
||||
TimerID setTimer(int timeout_ms, TimerCallback cb, uint32_t repeat = INFINITE) {
|
||||
TimerID timerID = ++nextTimerID;
|
||||
loop()->setTimerInLoop(timeout_ms, cb, repeat, timerID);
|
||||
return timerID;
|
||||
}
|
||||
// alias javascript setTimeout, setInterval
|
||||
TimerID setTimeout(int timeout_ms, TimerCallback cb) {
|
||||
return setTimer(timeout_ms, cb, 1);
|
||||
}
|
||||
TimerID setInterval(int interval_ms, TimerCallback cb) {
|
||||
return setTimer(interval_ms, cb, INFINITE);
|
||||
}
|
||||
|
||||
void killTimer(TimerID timerID) {
|
||||
loop()->killTimer(timerID);
|
||||
}
|
||||
|
||||
void resetTimer(TimerID timerID, int timeout_ms = 0) {
|
||||
loop()->resetTimer(timerID, timeout_ms);
|
||||
}
|
||||
};
|
||||
|
||||
} // end namespace hv
|
||||
|
||||
#endif // HV_TIMER_THREAD_HPP_
|
||||
55
third_party/libhv/evpp/TimerThread_test.cpp
vendored
Executable file
55
third_party/libhv/evpp/TimerThread_test.cpp
vendored
Executable file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* TimerThread_test.cpp
|
||||
*
|
||||
* @build: make evpp
|
||||
*
|
||||
*/
|
||||
|
||||
#include "TimerThread.h"
|
||||
#include "singleton.h"
|
||||
|
||||
namespace hv {
|
||||
|
||||
class GlobalTimerThread : public TimerThread {
|
||||
SINGLETON_DECL(GlobalTimerThread)
|
||||
protected:
|
||||
GlobalTimerThread() : TimerThread() {}
|
||||
~GlobalTimerThread() {}
|
||||
|
||||
public:
|
||||
static TimerID setTimeout(int timeout_ms, TimerCallback cb) {
|
||||
return GlobalTimerThread::instance()->setTimer(timeout_ms, cb, 1);
|
||||
}
|
||||
|
||||
static void clearTimeout(TimerID timerID) {
|
||||
GlobalTimerThread::instance()->killTimer(timerID);
|
||||
}
|
||||
|
||||
static TimerID setInterval(int interval_ms, TimerCallback cb) {
|
||||
return GlobalTimerThread::instance()->setTimer(interval_ms, cb, INFINITE);
|
||||
}
|
||||
|
||||
static void clearInterval(TimerID timerID) {
|
||||
GlobalTimerThread::instance()->killTimer(timerID);
|
||||
}
|
||||
};
|
||||
|
||||
SINGLETON_IMPL(GlobalTimerThread)
|
||||
|
||||
} // end namespace hv
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
hv::GlobalTimerThread::setTimeout(3000, [](hv::TimerID timerID) {
|
||||
printf("setTimeout timerID=%lu time=%lus\n", (unsigned long)timerID, (unsigned long)time(NULL));
|
||||
});
|
||||
|
||||
hv::GlobalTimerThread::setInterval(1000, [](hv::TimerID timerID) {
|
||||
printf("setInterval timerID=%lu time=%lus\n", (unsigned long)timerID, (unsigned long)time(NULL));
|
||||
});
|
||||
|
||||
// press Enter to stop
|
||||
while (getchar() != '\n');
|
||||
|
||||
hv::GlobalTimerThread::exitInstance();
|
||||
return 0;
|
||||
}
|
||||
200
third_party/libhv/evpp/UdpClient.h
vendored
Executable file
200
third_party/libhv/evpp/UdpClient.h
vendored
Executable file
@@ -0,0 +1,200 @@
|
||||
#ifndef HV_UDP_CLIENT_HPP_
|
||||
#define HV_UDP_CLIENT_HPP_
|
||||
|
||||
#include "hsocket.h"
|
||||
|
||||
#include "EventLoopThread.h"
|
||||
#include "Channel.h"
|
||||
|
||||
namespace hv {
|
||||
|
||||
template<class TSocketChannel = SocketChannel>
|
||||
class UdpClientEventLoopTmpl {
|
||||
public:
|
||||
typedef std::shared_ptr<TSocketChannel> TSocketChannelPtr;
|
||||
|
||||
UdpClientEventLoopTmpl(EventLoopPtr loop = NULL) {
|
||||
loop_ = loop ? loop : std::make_shared<EventLoop>();
|
||||
remote_port = 0;
|
||||
#if WITH_KCP
|
||||
kcp_setting = NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
virtual ~UdpClientEventLoopTmpl() {
|
||||
#if WITH_KCP
|
||||
HV_FREE(kcp_setting);
|
||||
#endif
|
||||
}
|
||||
|
||||
const EventLoopPtr& loop() {
|
||||
return loop_;
|
||||
}
|
||||
|
||||
// NOTE: By default, not bind local port. If necessary, you can call bind() after createsocket().
|
||||
// @retval >=0 sockfd, <0 error
|
||||
int createsocket(int remote_port, const char* remote_host = "127.0.0.1") {
|
||||
hio_t* io = hloop_create_udp_client(loop_->loop(), remote_host, remote_port);
|
||||
if (io == NULL) return -1;
|
||||
this->remote_host = remote_host;
|
||||
this->remote_port = remote_port;
|
||||
channel = std::make_shared<TSocketChannel>(io);
|
||||
int sockfd = channel->fd();
|
||||
if (hv_strendswith(remote_host, ".255")) {
|
||||
udp_broadcast(sockfd, 1);
|
||||
}
|
||||
return sockfd;
|
||||
}
|
||||
|
||||
int bind(int local_port, const char* local_host = "0.0.0.0") {
|
||||
if (channel == NULL || channel->isClosed()) {
|
||||
return -1;
|
||||
}
|
||||
sockaddr_u local_addr;
|
||||
memset(&local_addr, 0, sizeof(local_addr));
|
||||
int ret = sockaddr_set_ipport(&local_addr, local_host, local_port);
|
||||
if (ret != 0) {
|
||||
return NABS(ret);
|
||||
}
|
||||
ret = ::bind(channel->fd(), &local_addr.sa, SOCKADDR_LEN(&local_addr));
|
||||
if (ret != 0) {
|
||||
perror("bind");
|
||||
}
|
||||
hio_set_localaddr(channel->io(), &local_addr.sa, SOCKADDR_LEN(&local_addr));
|
||||
return ret;
|
||||
}
|
||||
|
||||
// closesocket thread-safe
|
||||
void closesocket() {
|
||||
if (channel) {
|
||||
channel->close(true);
|
||||
}
|
||||
}
|
||||
|
||||
int startRecv() {
|
||||
if (channel == NULL || channel->isClosed()) {
|
||||
int sockfd = createsocket(remote_port, remote_host.c_str());
|
||||
if (sockfd < 0) {
|
||||
hloge("createsocket %s:%d return %d!\n", remote_host.c_str(), remote_port, sockfd);
|
||||
return sockfd;
|
||||
}
|
||||
}
|
||||
if (channel == NULL || channel->isClosed()) {
|
||||
return -1;
|
||||
}
|
||||
channel->onread = [this](Buffer* buf) {
|
||||
if (onMessage) {
|
||||
onMessage(channel, buf);
|
||||
}
|
||||
};
|
||||
channel->onwrite = [this](Buffer* buf) {
|
||||
if (onWriteComplete) {
|
||||
onWriteComplete(channel, buf);
|
||||
}
|
||||
};
|
||||
#if WITH_KCP
|
||||
if (kcp_setting) {
|
||||
hio_set_kcp(channel->io(), kcp_setting);
|
||||
}
|
||||
#endif
|
||||
return channel->startRead();
|
||||
}
|
||||
|
||||
int stopRecv() {
|
||||
if (channel == NULL) return -1;
|
||||
return channel->stopRead();
|
||||
}
|
||||
|
||||
// start thread-safe
|
||||
void start() {
|
||||
loop_->runInLoop(std::bind(&UdpClientEventLoopTmpl::startRecv, this));
|
||||
}
|
||||
|
||||
// sendto thread-safe
|
||||
int sendto(const void* data, int size, struct sockaddr* peeraddr = NULL) {
|
||||
if (channel == NULL) return -1;
|
||||
std::lock_guard<std::mutex> locker(sendto_mutex);
|
||||
if (peeraddr) hio_set_peeraddr(channel->io(), peeraddr, SOCKADDR_LEN(peeraddr));
|
||||
return channel->write(data, size);
|
||||
}
|
||||
int sendto(Buffer* buf, struct sockaddr* peeraddr = NULL) {
|
||||
return sendto(buf->data(), buf->size(), peeraddr);
|
||||
}
|
||||
int sendto(const std::string& str, struct sockaddr* peeraddr = NULL) {
|
||||
return sendto(str.data(), str.size(), peeraddr);
|
||||
}
|
||||
|
||||
#if WITH_KCP
|
||||
void setKcp(kcp_setting_t* setting) {
|
||||
if (setting == NULL) {
|
||||
HV_FREE(kcp_setting);
|
||||
return;
|
||||
}
|
||||
if (kcp_setting == NULL) {
|
||||
HV_ALLOC_SIZEOF(kcp_setting);
|
||||
}
|
||||
*kcp_setting = *setting;
|
||||
}
|
||||
#endif
|
||||
|
||||
public:
|
||||
TSocketChannelPtr channel;
|
||||
|
||||
std::string remote_host;
|
||||
int remote_port;
|
||||
|
||||
#if WITH_KCP
|
||||
kcp_setting_t* kcp_setting;
|
||||
#endif
|
||||
// Callback
|
||||
std::function<void(const TSocketChannelPtr&, Buffer*)> onMessage;
|
||||
// NOTE: Use Channel::isWriteComplete in onWriteComplete callback to determine whether all data has been written.
|
||||
std::function<void(const TSocketChannelPtr&, Buffer*)> onWriteComplete;
|
||||
|
||||
private:
|
||||
std::mutex sendto_mutex;
|
||||
EventLoopPtr loop_;
|
||||
};
|
||||
|
||||
template<class TSocketChannel = SocketChannel>
|
||||
class UdpClientTmpl : private EventLoopThread, public UdpClientEventLoopTmpl<TSocketChannel> {
|
||||
public:
|
||||
UdpClientTmpl(EventLoopPtr loop = NULL)
|
||||
: EventLoopThread(loop)
|
||||
, UdpClientEventLoopTmpl<TSocketChannel>(EventLoopThread::loop())
|
||||
, is_loop_owner(loop == NULL)
|
||||
{}
|
||||
virtual ~UdpClientTmpl() {
|
||||
stop(true);
|
||||
}
|
||||
|
||||
const EventLoopPtr& loop() {
|
||||
return EventLoopThread::loop();
|
||||
}
|
||||
|
||||
// start thread-safe
|
||||
void start(bool wait_threads_started = true) {
|
||||
if (isRunning()) {
|
||||
UdpClientEventLoopTmpl<TSocketChannel>::start();
|
||||
} else {
|
||||
EventLoopThread::start(wait_threads_started, std::bind(&UdpClientTmpl::startRecv, this));
|
||||
}
|
||||
}
|
||||
|
||||
// stop thread-safe
|
||||
void stop(bool wait_threads_stopped = true) {
|
||||
UdpClientEventLoopTmpl<TSocketChannel>::closesocket();
|
||||
if (is_loop_owner) {
|
||||
EventLoopThread::stop(wait_threads_stopped);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool is_loop_owner;
|
||||
};
|
||||
|
||||
typedef UdpClientTmpl<SocketChannel> UdpClient;
|
||||
|
||||
}
|
||||
|
||||
#endif // HV_UDP_CLIENT_HPP_
|
||||
62
third_party/libhv/evpp/UdpClient_test.cpp
vendored
Executable file
62
third_party/libhv/evpp/UdpClient_test.cpp
vendored
Executable file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* UdpClient_test.cpp
|
||||
*
|
||||
* @build make evpp
|
||||
* @server bin/UdpServer_test 1234
|
||||
* @client bin/UdpClient_test 1234
|
||||
*
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "UdpClient.h"
|
||||
#include "htime.h"
|
||||
|
||||
using namespace hv;
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc < 2) {
|
||||
printf("Usage: %s remote_port [remote_host]\n", argv[0]);
|
||||
return -10;
|
||||
}
|
||||
int remote_port = atoi(argv[1]);
|
||||
const char* remote_host = "127.0.0.1";
|
||||
if (argc > 2) {
|
||||
remote_host = argv[2];
|
||||
}
|
||||
|
||||
UdpClient cli;
|
||||
int sockfd = cli.createsocket(remote_port, remote_host);
|
||||
if (sockfd < 0) {
|
||||
return -20;
|
||||
}
|
||||
printf("client sendto port %d, sockfd=%d ...\n", remote_port, sockfd);
|
||||
cli.onMessage = [](const SocketChannelPtr& channel, Buffer* buf) {
|
||||
printf("< %.*s\n", (int)buf->size(), (char*)buf->data());
|
||||
};
|
||||
cli.start();
|
||||
|
||||
// sendto(time) every 3s
|
||||
cli.loop()->setInterval(3000, [&cli](TimerID timerID) {
|
||||
char str[DATETIME_FMT_BUFLEN] = {0};
|
||||
datetime_t dt = datetime_now();
|
||||
datetime_fmt(&dt, str);
|
||||
cli.sendto(str);
|
||||
});
|
||||
|
||||
std::string str;
|
||||
while (std::getline(std::cin, str)) {
|
||||
if (str == "close") {
|
||||
cli.closesocket();
|
||||
} else if (str == "start") {
|
||||
cli.start();
|
||||
} else if (str == "stop") {
|
||||
cli.stop();
|
||||
break;
|
||||
} else {
|
||||
cli.sendto(str);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
174
third_party/libhv/evpp/UdpServer.h
vendored
Executable file
174
third_party/libhv/evpp/UdpServer.h
vendored
Executable file
@@ -0,0 +1,174 @@
|
||||
#ifndef HV_UDP_SERVER_HPP_
|
||||
#define HV_UDP_SERVER_HPP_
|
||||
|
||||
#include "hsocket.h"
|
||||
|
||||
#include "EventLoopThreadPool.h"
|
||||
#include "Channel.h"
|
||||
|
||||
namespace hv {
|
||||
|
||||
template<class TSocketChannel = SocketChannel>
|
||||
class UdpServerEventLoopTmpl {
|
||||
public:
|
||||
typedef std::shared_ptr<TSocketChannel> TSocketChannelPtr;
|
||||
|
||||
UdpServerEventLoopTmpl(EventLoopPtr loop = NULL) {
|
||||
loop_ = loop ? loop : std::make_shared<EventLoop>();
|
||||
port = 0;
|
||||
#if WITH_KCP
|
||||
kcp_setting = NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
virtual ~UdpServerEventLoopTmpl() {
|
||||
#if WITH_KCP
|
||||
HV_FREE(kcp_setting);
|
||||
#endif
|
||||
}
|
||||
|
||||
const EventLoopPtr& loop() {
|
||||
return loop_;
|
||||
}
|
||||
|
||||
//@retval >=0 bindfd, <0 error
|
||||
int createsocket(int port, const char* host = "0.0.0.0") {
|
||||
hio_t* io = hloop_create_udp_server(loop_->loop(), host, port);
|
||||
if (io == NULL) return -1;
|
||||
this->host = host;
|
||||
this->port = port;
|
||||
channel = std::make_shared<TSocketChannel>(io);
|
||||
return channel->fd();
|
||||
}
|
||||
// closesocket thread-safe
|
||||
void closesocket() {
|
||||
if (channel) {
|
||||
channel->close(true);
|
||||
}
|
||||
}
|
||||
|
||||
int startRecv() {
|
||||
if (channel == NULL || channel->isClosed()) {
|
||||
int bindfd = createsocket(port, host.c_str());
|
||||
if (bindfd < 0) {
|
||||
hloge("createsocket %s:%d return %d!\n", host.c_str(), port, bindfd);
|
||||
return bindfd;
|
||||
}
|
||||
}
|
||||
if (channel == NULL || channel->isClosed()) {
|
||||
return -1;
|
||||
}
|
||||
channel->onread = [this](Buffer* buf) {
|
||||
if (onMessage) {
|
||||
onMessage(channel, buf);
|
||||
}
|
||||
};
|
||||
channel->onwrite = [this](Buffer* buf) {
|
||||
if (onWriteComplete) {
|
||||
onWriteComplete(channel, buf);
|
||||
}
|
||||
};
|
||||
#if WITH_KCP
|
||||
if (kcp_setting) {
|
||||
hio_set_kcp(channel->io(), kcp_setting);
|
||||
}
|
||||
#endif
|
||||
return channel->startRead();
|
||||
}
|
||||
|
||||
int stopRecv() {
|
||||
if (channel == NULL) return -1;
|
||||
return channel->stopRead();
|
||||
}
|
||||
|
||||
// start thread-safe
|
||||
void start() {
|
||||
loop_->runInLoop(std::bind(&UdpServerEventLoopTmpl::startRecv, this));
|
||||
}
|
||||
|
||||
// sendto thread-safe
|
||||
int sendto(const void* data, int size, struct sockaddr* peeraddr = NULL) {
|
||||
if (channel == NULL) return -1;
|
||||
std::lock_guard<std::mutex> locker(sendto_mutex);
|
||||
if (peeraddr) hio_set_peeraddr(channel->io(), peeraddr, SOCKADDR_LEN(peeraddr));
|
||||
return channel->write(data, size);
|
||||
}
|
||||
int sendto(Buffer* buf, struct sockaddr* peeraddr = NULL) {
|
||||
return sendto(buf->data(), buf->size(), peeraddr);
|
||||
}
|
||||
int sendto(const std::string& str, struct sockaddr* peeraddr = NULL) {
|
||||
return sendto(str.data(), str.size(), peeraddr);
|
||||
}
|
||||
|
||||
#if WITH_KCP
|
||||
void setKcp(kcp_setting_t* setting) {
|
||||
if (setting == NULL) {
|
||||
HV_FREE(kcp_setting);
|
||||
return;
|
||||
}
|
||||
if (kcp_setting == NULL) {
|
||||
HV_ALLOC_SIZEOF(kcp_setting);
|
||||
}
|
||||
*kcp_setting = *setting;
|
||||
}
|
||||
#endif
|
||||
|
||||
public:
|
||||
std::string host;
|
||||
int port;
|
||||
TSocketChannelPtr channel;
|
||||
#if WITH_KCP
|
||||
kcp_setting_t* kcp_setting;
|
||||
#endif
|
||||
// Callback
|
||||
std::function<void(const TSocketChannelPtr&, Buffer*)> onMessage;
|
||||
// NOTE: Use Channel::isWriteComplete in onWriteComplete callback to determine whether all data has been written.
|
||||
std::function<void(const TSocketChannelPtr&, Buffer*)> onWriteComplete;
|
||||
|
||||
private:
|
||||
std::mutex sendto_mutex;
|
||||
EventLoopPtr loop_;
|
||||
};
|
||||
|
||||
template<class TSocketChannel = SocketChannel>
|
||||
class UdpServerTmpl : private EventLoopThread, public UdpServerEventLoopTmpl<TSocketChannel> {
|
||||
public:
|
||||
UdpServerTmpl(EventLoopPtr loop = NULL)
|
||||
: EventLoopThread(loop)
|
||||
, UdpServerEventLoopTmpl<TSocketChannel>(EventLoopThread::loop())
|
||||
, is_loop_owner(loop == NULL)
|
||||
{}
|
||||
virtual ~UdpServerTmpl() {
|
||||
stop(true);
|
||||
}
|
||||
|
||||
const EventLoopPtr& loop() {
|
||||
return EventLoopThread::loop();
|
||||
}
|
||||
|
||||
// start thread-safe
|
||||
void start(bool wait_threads_started = true) {
|
||||
if (isRunning()) {
|
||||
UdpServerEventLoopTmpl<TSocketChannel>::start();
|
||||
} else {
|
||||
EventLoopThread::start(wait_threads_started, std::bind(&UdpServerTmpl::startRecv, this));
|
||||
}
|
||||
}
|
||||
|
||||
// stop thread-safe
|
||||
void stop(bool wait_threads_stopped = true) {
|
||||
UdpServerEventLoopTmpl<TSocketChannel>::closesocket();
|
||||
if (is_loop_owner) {
|
||||
EventLoopThread::stop(wait_threads_stopped);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool is_loop_owner;
|
||||
};
|
||||
|
||||
typedef UdpServerTmpl<SocketChannel> UdpServer;
|
||||
|
||||
}
|
||||
|
||||
#endif // HV_UDP_SERVER_HPP_
|
||||
51
third_party/libhv/evpp/UdpServer_test.cpp
vendored
Executable file
51
third_party/libhv/evpp/UdpServer_test.cpp
vendored
Executable file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* UdpServer_test.cpp
|
||||
*
|
||||
* @build make evpp
|
||||
* @server bin/UdpServer_test 1234
|
||||
* @client bin/UdpClient_test 1234
|
||||
*
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "UdpServer.h"
|
||||
|
||||
using namespace hv;
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc < 2) {
|
||||
printf("Usage: %s port\n", argv[0]);
|
||||
return -10;
|
||||
}
|
||||
int port = atoi(argv[1]);
|
||||
|
||||
UdpServer srv;
|
||||
int bindfd = srv.createsocket(port);
|
||||
if (bindfd < 0) {
|
||||
return -20;
|
||||
}
|
||||
printf("server bind on port %d, bindfd=%d ...\n", port, bindfd);
|
||||
srv.onMessage = [](const SocketChannelPtr& channel, Buffer* buf) {
|
||||
// echo
|
||||
printf("< %.*s\n", (int)buf->size(), (char*)buf->data());
|
||||
channel->write(buf);
|
||||
};
|
||||
srv.start();
|
||||
|
||||
std::string str;
|
||||
while (std::getline(std::cin, str)) {
|
||||
if (str == "close") {
|
||||
srv.closesocket();
|
||||
} else if (str == "start") {
|
||||
srv.start();
|
||||
} else if (str == "stop") {
|
||||
srv.stop();
|
||||
break;
|
||||
} else {
|
||||
srv.sendto(str);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
7
third_party/libhv/evpp/build_test.sh
vendored
Executable file
7
third_party/libhv/evpp/build_test.sh
vendored
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
SCRIPT_DIR=$(cd `dirname $0`; pwd)
|
||||
ROOT_DIR=${SCRIPT_DIR}/..
|
||||
|
||||
cd ${ROOT_DIR}
|
||||
make evpp
|
||||
Reference in New Issue
Block a user