目录

目录 1

1. 前言 1

2. 示例Service 1

3. 网络部分类图 2

4. 线程模式 3

4.1. IO线程 3

4.2. 工作线程 4

4.2.1. 工作线程类图 4

4.2.2. 工作线程启动过程 5

5. 一个RPC函数被调用时序图 5

5.1. 启动准备 5

5.2. 接受连接 6

5.3. 收发数据:执行调用 7

5.4. 服务端回调代码解读 7

5.5. 客户端回调代码解读 9

5.6. 服务端dispatchCall的实现 10

6. TProtocol 11

7. TTransport 12

8. TProtocol&TTransport 12

9. 数据流向关系 13

10. 取客户端IP 13

11. 日志输出 20

附:问题 20

1. 前言

分析Thrift的结构动机是为了实现服务端能取到客户端的IP,因此需要对它的结构、调用流程有些了解。另外,请注意本文针对的是TNonblockingServer,不包含TThreadPoolServer、TThreadedServer和TSimpleServer。

thrift对网络连接没有使用内存池,最直接简单的性能优化是绑定Google gperftools中的TCMalloc。

2. 示例Service

service EchoService

{

string hello(1: string greetings);

}

class EchoHandler: public EchoServiceIf

{

private:

virtual void hello(std::string& _return, const std::string& greetings);

};

3. 网络部分类图

Thrift线程模型为若干IO线程TNonblockingIOThread(负责收发TCP连接的数据),以及主线程(负责监听TCP连接及接受连接请求)组成。

主线程不一定就是进程的主线程,哪个线程调用了TServer::run()或TServer::serve()就是本文所说的主线程。就当前最新版本(0.9.2)的Thrift而言,调用TServer::run()或TServer::serve()均可以,原因是TServer::run()除无条件的调用外TServer::serve(),没有做任何其它事。对TServer::serve()的调用实际是对TServer的实现类TNonblockingServer的serve()的调用。

简而言之,TNonblockingIOThread负责数据的收发,而TNonblockingServer负责接受连接请求。

在使用中需要注意,调用TServer::run()或TServer::serve()的线程或进程会被阻塞,阻塞进入libevent的死循环,Linux上是死循环调用epoll_wait()。

4. 线程模式

Thrift将线程分成两类:

4.1. IO线程

IO线程负责监听和接受连接请求,和接收客户端发送过来的数据,收到完整请求后,以Task方式传递给工作线程,由工作线程回调。

IO线程针对TNonblockingServer,TNonblockingServer提供方法setNumIOThreads()来设置IO线程个数。第一个IO线程总是独占调用TServer::server()或TServer::run()的线程。

IO线程在accept一个连接后,会创建一个TConnection实例(在TNonblockingServer::TConnection::transition()中),而TConnection会创建一个Task(在TNonblockingServer::TConnection::transition()中完成),由TNonblockingServer将Task传递给ThreadManager。

纠正:上图中的TNonblockingServer应当为TNonblockingIOThread。

注意函数TNonblockingServer::handleEvent()的下小段代码,getIOThreadNumber()并不是表示取得IO线程个数,而是该线程在线程组中的ID,可以这么认为等于0时表示0号线程:

void TNonblockingServer::handleEvent(int fd, short which) {

if (clientConnection->getIOThreadNumber() == 0) {

clientConnection->transition();

} else {

clientConnection->notifyIOThread(); // 最终也会调用transition()

}

}

4.2. 工作线程

工作线程负责回调和对客户端响应。

4.2.1. 工作线程类图

4.2.2. 工作线程启动过程

5. 一个RPC函数被调用时序图

5.1. 启动准备

准备的工作包括:

1) 启动监听连接

2) 启动收发数据线程

3) 初始化运行环境

在这里,可以看到第一次对TServerEventHandler的回调:

5.2. 接受连接

从接受连接的时序过程可以看出:在该连接TConnection接收数据之前,先调用了TServerEventHandler::createContext(),这个就是获取客户端IP的机会之一,但是当前的实现没有将相关的信息作为参数传递给TServerEventHandler::createContext()。

5.3. 收发数据:执行调用

这过程中对TServerEventHandler::processContext(connectionContext_, getTSocket())进行了回调,并传递了TSocket。

5.4. 服务端回调代码解读

下面是thrift编译生成的代码片段,为服务端的代码:

// TProtocol为协议接口,常用实现类为TBinaryProtocol等

void EchoServiceProcessor::process_hello(int32_t seqid, // 消息序列号

::apache::thrift::protocol::TProtocol* iprot, // 输入参数

::apache::thrift::protocol::TProtocol* oprot, // 输出参数

void* callContext)

{

// eventHandler_类型为TProcessorEventHandler,是一个被回调对象

void* ctx = NULL;

if (this->eventHandler_.get() != NULL) {

ctx = this->eventHandler_->getContext("EchoService.hello", callContext);

}

::apache::thrift::TProcessorContextFreer freer(this->eventHandler_.get(), ctx, "EchoService.hello");

if (this->eventHandler_.get() != NULL) {

this->eventHandler_->preRead(ctx, "HelloService.hello"); // 回调TProcessorEventHandler

}

EchoService_hello_args args; // 输入参数

args.read(iprot); // 反序列化输入参数

iprot->readMessageEnd();

uint32_t bytes = iprot->getTransport()->readEnd();

if (this->eventHandler_.get() != NULL) {

this->eventHandler_->postRead(ctx, "EchoService.hello", bytes); // 回调TProcessorEventHandler

}

// EchoService_hello_result是thrift编译生成的类

EchoService_hello_result result; // 输出参数,也就是thrift文件中定义的返回值

try {

iface_->hello(result.success, args.greetings); // 这里就是回调用户自己写的代码了

result.__isset.success = true;

} catch (const std::exception& e) {

if (this->eventHandler_.get() != NULL) {

this->eventHandler_->handlerError(ctx, "EchoService.hello"); // 回调TProcessorEventHandler

}

// 下段是异常时的返回,客户端应当catch它

::apache::thrift::TApplicationException x(e.what());

// writeMessageBegin序列化消息头

oprot->writeMessageBegin("hello", ::apache::thrift::protocol::T_EXCEPTION, seqid);

x.write(oprot); // 将x序列化到oprot

oprot->writeMessageEnd();

oprot->getTransport()->writeEnd();

oprot->getTransport()->flush();

return;

}

if (this->eventHandler_.get() != NULL) {

this->eventHandler_->preWrite(ctx, "EchoService.hello"); // 回调TProcessorEventHandler

}

// 下段为序列化输出参数,也注是返回值啦

oprot->writeMessageBegin("hello", ::apache::thrift::protocol::T_REPLY, seqid); // 序列化消息头

result.write(oprot); // 序列化result到oprot

oprot->writeMessageEnd();

bytes = oprot->getTransport()->writeEnd();

oprot->getTransport()->flush();

if (this->eventHandler_.get() != NULL) {

this->eventHandler_->postWrite(ctx, "EchoService.hello", bytes); // 回调TProcessorEventHandler

}

}

5.5. 客户端回调代码解读

下面是thrift编译生成的代码片段,为客户端的代码:

// 同步调用实现

// hello就是客户端直接调用的

void EchoServiceClient::hello(std::string& _return, const std::string& greetings)

{

send_hello(greetings); // 序列化输入参数,并发送给服务端

recv_hello(_return); // 接收服务端的返回,并反序列化

}

// 向服务端发起调用

void EchoServiceClient::send_hello(const std::string& greetings)

{

int32_t cseqid = 0;

oprot_->writeMessageBegin("hello", ::apache::thrift::protocol::T_CALL, cseqid);

// 类EchoService_hello_pargs也是thrift编译生成的类,所有的参数都是它的数据成员

EchoService_hello_pargs args;

args.greetings = &greetings;

args.write(oprot_); // 序列化

oprot_->writeMessageEnd();

oprot_->getTransport()->writeEnd();

oprot_->getTransport()->flush();

}

// 接收服务端的响应

void EchoServiceClient::recv_hello(std::string& _return)

{

int32_t rseqid = 0;

std::string fname; // 函数名

::apache::thrift::protocol::TMessageType mtype;

iprot_->readMessageBegin(fname, mtype, rseqid);

if (mtype == ::apache::thrift::protocol::T_EXCEPTION) {

::apache::thrift::TApplicationException x;

x.read(iprot_);

iprot_->readMessageEnd();

iprot_->getTransport()->readEnd();

throw x; // 抛出异常

}

if (mtype != ::apache::thrift::protocol::T_REPLY) {

iprot_->skip(::apache::thrift::protocol::T_STRUCT);

iprot_->readMessageEnd();

iprot_->getTransport()->readEnd();

}

if (fname.compare("hello") != 0) {

iprot_->skip(::apache::thrift::protocol::T_STRUCT);

iprot_->readMessageEnd();

iprot_->getTransport()->readEnd();

}

EchoService_hello_presult result;

result.success = &_return;

result.read(iprot_); // 反序列化

iprot_->readMessageEnd();

iprot_->getTransport()->readEnd();

if (result.__isset.success) {

// _return pointer has now been filled

return;

}

throw ::apache::thrift::TApplicationException(::apache::thrift::TApplicationException::MISSING_RESULT, "hello failed: unknown result");

}

5.6. 服务端dispatchCall的实现

thrift编译生成的类Ec hoServiceProcessor,实现了接口apache::thrift::TDispatchProcessor的dispatchCall()方法:

bool EchoServiceProcessor::dispatchCall(::apache::thrift::protocol::TProtocol* iprot, // 输入参数

::apache::thrift::protocol::TProtocol* oprot, // 输出参数

const std::string& fname, // 被调用的函数名

int32_t seqid, // 序列号

void* callContext)

{

ProcessMap::iterator pfn;

// typedef  void (EchoServiceProcessor::*ProcessFunction)(int32_t,

//                                                         ::apache::thrift::protocol::TProtocol*,

//                                                         ::apache::thrift::protocol::TProtocol*,

//                                                         void*);

// typedef std::map<std::string, ProcessFunction> ProcessMap;

pfn = processMap_.find(fname); // 根据函数名,找到函数(ProcessMap processMap_;)

if (pfn == processMap_.end()) {

// 没有找到时,抛出异常

iprot->skip(::apache::thrift::protocol::T_STRUCT);

iprot->readMessageEnd();

iprot->getTransport()->readEnd();

::apache::thrift::TApplicationException x(::apache::thrift::TApplicationException::UNKNOWN_METHOD, "Invalid method name: '"+fname+"'");

oprot->writeMessageBegin(fname, ::apache::thrift::protocol::T_EXCEPTION, seqid);

x.write(oprot);

oprot->writeMessageEnd(); // 序列化后,调用了Transport,而Transport调用了网络send

oprot->getTransport()->writeEnd();

oprot->getTransport()->flush();

return true;

}

// 找到,则进行回调

(this->*(pfn->second))(seqid, iprot, oprot, callContext);

return true;

}

6. TProtocol

TProtocol提供序列化和反序列化能力,定义了消息包的编码和解码协议,它的实现有以下几种:

1) TBinaryProtocol 二进制编解码

2) TDebugProtocol 用于调试的,可读的文本编解码

3) TJSONProtocol 基于json的编解码

4) TCompactProtocol 压缩的二进制编解码

如果需要为thrift增加一种数据类型,则需要修改TProtocol,增加对新数据类型的序列化和反序列化实现。

7. TTransport

TTransport负责收发数据,可以简单的是对Socket的包装,但是也支持非Socket,比如Pipe。其中TSocket为TServerSocket使用的Transport。

8. TProtocol&TTransport

对于TNonblockingServer默认使用的是输入和输出Transport,都是以TMemoryBuffer为TTransport。

TProtocol本身没有缓冲区等,它只是序列化和反序列化。然而它依赖于TTransport,通过TTransport发送数据。以TBinaryProtocol为例:

// 序列化int16_t值

template <class Transport_>

uint32_t TBinary(const int16_t i16) {

int16_t net = (int16_t)htons(i16);

this->trans_->write((uint8_t*)&net, 2); // 看到没?这里调用的是TTransport

return 2;

}

对比看下TTransport::write的实现:

// TSocket是一种TTransport

void TSocket::write(const uint8_t* buf, uint32_t len) {

uint32_t sent = 0;

// 从下面的实现可以看出发送是同步的

while (sent < len) {

uint32_t b = write_partial(buf + sent, len - sent); // 这里实际调用的是系统的send()

if (b == 0) {

// This should only happen if the timeout set with SO_SNDTIMEO expired.

// Raise an exception.

throw TTransportException(TTransportException::TIMED_OUT,

"send timeout expired");

}

sent += b;

}

}

9. 数据流向关系

客户端发送数据时,会触发libevent事件,然后调用Transport收数据。包完整后,调用Protocol反序列化,接着就调用服务端的代码。

前半部分在IO线程中完成,后半部分在工作线程中完成。

10. 取客户端IP

为取得客户端的IP,有三个办法:

1) 网上博文http://blog.csdn.net/hbuxiaoshe/article/details/38942869介绍的方法也是可行的,不过让人有些纠结;

2) 修改Thrift的实现,为TServerEventHandler::createContext()增加一个参数,将TSocket作为参数传递,这样就可以非常轻易的取得客户端的IP了。最简单的修改为:

class TServerEventHandler {

public:

virtual void* createContext(boost::shared_ptr<TProtocol> input,

boost::shared_ptr<TProtocol> output,

TTransport* transport); // 对于TNonblockingServer实际传递为TSocket

};

3) 不修改Thrift的实现。

在“收发数据:执行调用”的流程中,可以发现有对TServerEventHandler::processContext()的调用,而这里真好将TSocket作为第二个参数进行了传递,因此可以直接利用。

TServerEventHandler::createContext()和TServerEventHandler::processContext()的不同在于:前者只在建立连接时被调用一次,而后者每一个RPC调用时都会调用一次。

#ifndef MOOON_NET_THRIFT_HELPER_H

#define MOOON_NET_THRIFT_HELPER_H

#include <mooon/net/config.h>

#include <mooon/sys/log.h>

#include <mooon/utils/scoped_ptr.h>

#include <arpa/inet.h>

#include <boost/scoped_ptr.hpp>

#include <thrift/concurrency/PosixThreadFactory.h>

#include <thrift/concurrency/ThreadManager.h>

#include <thrift/protocol/TBinaryProtocol.h>

#include <thrift/server/TNonblockingServer.h>

#include <thrift/transport/TSocketPool.h>

#include <thrift/transport/TTransportException.h>

NET_NAMESPACE_BEGIN

// 用来判断thrift是否已经连接,包括两种情况:

// 1.从未连接过,也就是还未打开过连接

// 2.连接被对端关闭了

inline bool thrift_not_connected(

apache::thrift::transport::TTransportException::TTransportExceptionType type)

{

return (apache::thrift::transport::TTransportException::NOT_OPEN == type)

|| (apache::thrift::transport::TTransportException::END_OF_FILE == type);

}

inline bool thrift_not_connected(

apache::thrift::transport::TTransportException& ex)

{

apache::thrift::transport::TTransportException::TTransportExceptionType type = ex.getType();

return thrift_not_connected(type);

}

// thrift客户端辅助类

//

// 使用示例:

// mooon::net::CThriftClientHelper<ExampleServiceClient> client(rpc_server_ip, rpc_server_port);

// try

// {

//     client.connect();

//     client->foo();

// }

// catch (apache::thrift::transport::TTransportException& transport_ex)

// {

//     MYLOG_ERROR("thrift exception: %s\n", transport_ex.what());

// }

// catch (apache::thrift::transport::TApplicationException& app_ex)

// {

//     MYLOG_ERROR("thrift exception: %s\n", app_ex.what());

// }

// catch (apache::thrift::TException& tx)

// {

//     MYLOG_ERROR("thrift exception: %s\n", tx.what());

// }

// Transport除默认的TFramedTransport (TBufferTransports.h),还可选择:

// TBufferedTransport (TBufferTransports.h)

// THttpTransport

// TZlibTransport

// TFDTransport (TSimpleFileTransport)

//

// Protocol除默认的apache::thrift::protocol::TBinaryProtocol,还可选择:

// TCompactProtocol

// TJSONProtocol

// TDebugProtocol

template <class ThriftClient,

class Protocol=apache::thrift::protocol::TBinaryProtocol,

class Transport=apache::thrift::transport::TFramedTransport>

class CThriftClientHelper

{

public:

// host thrift服务端的IP地址

// port thrift服务端的端口号

// connect_timeout_milliseconds 连接thrift服务端的超时毫秒数

// receive_timeout_milliseconds 接收thrift服务端发过来的数据的超时毫秒数

// send_timeout_milliseconds 向thrift服务端发送数据时的超时毫秒数

CThriftClientHelper(const std::string &host, uint16_t port,

int connect_timeout_milliseconds=2000,

int receive_timeout_milliseconds=2000,

int send_timeout_milliseconds=2000);

~CThriftClientHelper();

// 连接thrift服务端

//

// 出错时,可抛出以下几个thrift异常:

// apache::thrift::transport::TTransportException

// apache::thrift::TApplicationException

// apache::thrift::TException

void connect();

// 断开与thrift服务端的连接

//

// 出错时,可抛出以下几个thrift异常:

// apache::thrift::transport::TTransportException

// apache::thrift::TApplicationException

// apache::thrift::TException

void close();

ThriftClient* get() { return _client.get(); }

ThriftClient* get() const { return _client.get(); }

ThriftClient* operator ->() { return get(); }

ThriftClient* operator ->() const { return get(); }

const std::string& get_host() const { return _host; }

uint16_t get_port() const { return _port; }

private:

std::string _host;

uint16_t _port;

boost::shared_ptr<apache::thrift::transport::TSocketPool> _sock_pool;

boost::shared_ptr<apache::thrift::transport::TTransport> _socket;

boost::shared_ptr<apache::thrift::transport::TFramedTransport> _transport;

boost::shared_ptr<apache::thrift::protocol::TProtocol> _protocol;

boost::shared_ptr<ThriftClient> _client;

};

////////////////////////////////////////////////////////////////////////////////

// thrift服务端辅助类

//

// 使用示例:

// mooon::net::CThriftServerHelper<CExampleHandler, ExampleServiceProcessor> _thrift_server;

// try

// {

//     _thrift_server.serve(listen_port);

// }

// catch (apache::thrift::TException& tx)

// {

//     MYLOG_ERROR("thrift exception: %s\n", tx.what());

// }

// ProtocolFactory除了默认的TBinaryProtocolFactory,还可选择:

// TCompactProtocolFactory

// TJSONProtocolFactory

// TDebugProtocolFactory

//

// Server除默认的TNonblockingServer外,还可选择:

// TSimpleServer

// TThreadedServer

// TThreadPoolServer

template <class ThriftHandler,

class ServiceProcessor,

class ProtocolFactory=apache::thrift::protocol::TBinaryProtocolFactory,

class Server=apache::thrift::server::TNonblockingServer>

class CThriftServerHelper

{

public:

// 启动rpc服务,请注意该调用是同步阻塞的,所以需放最后调用

// port thrift服务端的监听端口号

// num_threads thrift服务端开启的线程数

//

// 出错时,可抛出以下几个thrift异常:

// apache::thrift::transport::TTransportException

// apache::thrift::TApplicationException

// apache::thrift::TException

// 参数num_io_threads,只有当Server为TNonblockingServer才有效

void serve(uint16_t port, uint8_t num_worker_threads=1, uint8_t num_io_threads=1);

void serve(const std::string &ip, uint16_t port, uint8_t num_worker_threads, uint8_t num_io_threads=1);

void stop();

private:

boost::shared_ptr<ThriftHandler> _handler;

boost::shared_ptr<apache::thrift::TProcessor> _processor;

boost::shared_ptr<apache::thrift::protocol::TProtocolFactory> _protocol_factory;

boost::shared_ptr<apache::thrift::server::ThreadManager> _thread_manager;

boost::shared_ptr<apache::thrift::concurrency::PosixThreadFactory> _thread_factory;

boost::shared_ptr<apache::thrift::server::TServer> _server;

};

////////////////////////////////////////////////////////////////////////////////

// 被thrift回调的写日志函数,由set_thrift_log_write_function()调用它

inline void write_log_function(const char* log)

{

MYLOG_INFO("%s", log);

}

// 将thrift输出写入到日志文件中

inline void set_thrift_log_write_function()

{

if (log != NULL)

{

apache::thrift::GlobalOutput.setOutputFunction(write_log_function);

}

}

////////////////////////////////////////////////////////////////////////////////

template <class ThriftClient, class Protocol, class Transport>

CThriftClientHelper<ThriftClient, Protocol, Transport>::CThriftClientHelper(

const std::string &host, uint16_t port,

int connect_timeout_milliseconds, int receive_timeout_milliseconds, int send_timeout_milliseconds)

: _host(host)

, _port(port)

{

set_thrift_log_write_function();

_sock_pool.reset(new apache::thrift::transport::TSocketPool());

_sock_pool->addServer(host, (int)port);

_sock_pool->setConnTimeout(connect_timeout_milliseconds);

_sock_pool->setRecvTimeout(receive_timeout_milliseconds);

_sock_pool->setSendTimeout(send_timeout_milliseconds);

_socket = _sock_pool;

// Transport默认为apache::thrift::transport::TFramedTransport

_transport.reset(new Transport(_socket));

// Protocol默认为apache::thrift::protocol::TBinaryProtocol

_protocol.reset(new Protocol(_transport));

_client.reset(new ThriftClient(_protocol));

}

template <class ThriftClient, class Protocol, class Transport>

CThriftClientHelper<ThriftClient, Protocol, Transport>::~CThriftClientHelper()

{

close();

}

template <class ThriftClient, class Protocol, class Transport>

void CThriftClientHelper<ThriftClient, Protocol, Transport>::connect()

{

if (!_transport->isOpen())

{

_transport->open();

}

}

template <class ThriftClient, class Protocol, class Transport>

void CThriftClientHelper<ThriftClient, Protocol, Transport>::close()

{

if (_transport->isOpen())

{

_transport->close();

}

}

////////////////////////////////////////////////////////////////////////////////

template <class ThriftHandler, class ServiceProcessor, class ProtocolFactory, class Server>

void CThriftServerHelper<ThriftHandler, ServiceProcessor, ProtocolFactory, Server>::serve(uint16_t port, uint8_t num_worker_threads, uint8_t num_io_threads)

{

serve("0.0.0.0", port, num_worker_threads, num_io_threads);

}

template <class ThriftHandler, class ServiceProcessor, class ProtocolFactory, class Server>

void CThriftServerHelper<ThriftHandler, ServiceProcessor, ProtocolFactory, Server>::serve(const std::string &ip, uint16_t port, uint8_t num_worker_threads, uint8_t num_io_threads)

{

set_thrift_log_write_function();

_handler.reset(new ThriftHandler);

_processor.reset(new ServiceProcessor(_handler));

// ProtocolFactory默认为apache::thrift::protocol::TBinaryProtocolFactory

_protocol_factory.reset(new ProtocolFactory());

_thread_manager = apache::thrift::server::ThreadManager::newSimpleThreadManager(num_worker_threads);

_thread_factory.reset(new apache::thrift::concurrency::PosixThreadFactory());

_thread_manager->threadFactory(_thread_factory);

_thread_manager->start();

// Server默认为apache::thrift::server::TNonblockingServer

Server* server = new Server(_processor, _protocol_factory, port, _thread_manager);

if (sizeof(Server) == sizeof(apache::thrift::server::TNonblockingServer))

server->setNumIOThreads(num_io_threads);

_server.reset(server);

_server->run(); // 这里也可直接调用serve(),但推荐run()

}

template <class ThriftHandler, class ServiceProcessor, class ProtocolFactory, class Server>

void CThriftServerHelper<ThriftHandler, ServiceProcessor, ProtocolFactory, Server>::stop()

{

_server->stop();

}

NET_NAMESPACE_END

#endif // MOOON_NET_THRIFT_HELPER_H

11. 日志输出

默认thrift日志打屏,但其实可以让它输出到自己的日志文件中。这个功能通过全局对象apache::thrift::GlobalOutput来实现,在Thrift.h中声明了GlobalOutput,它的定义在Thrift.cpp文件中。

类TOutput提供了方法setOutputFunction()用来设置日志输出函数:

class TOutput{

public:

inline void setOutputFunction(void (*function)(const char *));

};

调用setOutputFunction()设置回调函数,即可将日志输出到自己的日志文件中,遗憾的是不能自动区分日志级别。更佳的做法是定义一个抽象接口,然后让使用者注入接口实现,如mooon中ILogger:

https://github.com/eyjian/mooon/blob/master/common_library/include/mooon/sys/log.h

具体做法,可以参考:https://github.com/eyjian/mooon/blob/master/common_library/include/mooon/net/thrift_helper.h

附:问题

如何让Thrift只在指定的IP上监听,而不是监听0.0.0.0?

Thrift结构分析及增加取客户端IP功能实现的更多相关文章

  1. Nginx作为反向代理时传递客户端IP的设置方法

    因为nginx的优越性,现在越来越多的用户在生产环境中使用nginx作为前端,不管nginx在前端是做负载均衡还是只做简单的反向代理,都需要把日志转发到后端real server,以方便我们检查程序的 ...

  2. NGINX前端代理TOMCAT取真实客户端IP

    nginx前端代理tomcat取真实客户端IP 使用Nginx作为反向代理时,Tomcat的日志记录的客户端IP就不在是真实的客户端IP,而是Nginx代理的IP.要解决这个问题可以在Nginx配置一 ...

  3. C#服务器获取客户端IP地址以及归属地探秘

    背景:博主本是一位Windows桌面应用程序开发工程师,对网络通信一知半解.一日老婆逛完某宝,问:"为什么他们知道我的地址呢,他们是怎么获取我的地址的呢?" 顺着这个问题我们的探秘 ...

  4. JAVA获取客户端IP地址

    在JSP里,获取客户端的IP地址的方法是:request.getRemoteAddr(),这种方法在大部分情况下都是有效的.但是在通过了Apache,Squid等反向代理软件就不能获取到客户端的真实I ...

  5. java服务器获取客户端ip

    在写服务端代码时,有时需要对客户端ip做认证,比如限制只有某些ip能访问,或者1个ip1天只能访问几次.最近就碰到个需要限制ip的情况,从网上找了一些服务器获取客户端ip的方法,说的都不太完善,这里整 ...

  6. 根据Request获取客户端IP

    转自: http://www.cnblogs.com/icerainsoft/p/3584532.html http://www.cnblogs.com/bingya/articles/3134227 ...

  7. Struts如何获取客户端ip地址

    在JSP里,获取客户端的IP地址的方法是:request.getRemoteAddr(),这种方法在大部分情况下都是有效的.但是在通过了Apache,Squid等反向代理软件就不能获取到客户端的真实I ...

  8. python中通过客户端IP拿到所在城市和当地天气信息—附带项目案例

    熟悉老一代QQ的小伙伴可能都知道,很早以前的QQ,鼠标滑到头像的位置,你的位置和IP会在详情页显示,那么这个是如何做到的呢?下面我们就来玩一玩这个东西 首先,需求分析: 1.拿到客户端IP 2.通过I ...

  9. Tawk.to一键给自己的网站增加在线客服功能

    Tawk.to一键给自己的网站增加在线客服功能 很多外贸网站只有contact页面,留下邮箱.电话等联系方式,而在国际贸易当中能够及时在线交流沟通,能给客户留下更好的印象.接下来,就让我们一起来了解一 ...

随机推荐

  1. CentOS7修复python拯救yum - 转载

    原文:http://blog.51cto.com/welcomeweb/2132654 本人正在吹着空调,喝着茶水,然后qq头像抖了两下,业务开发同学给我打了个招呼,“忙么?帮个忙可以不?” 这很明显 ...

  2. RabbitMQ内存爆出问题解决思路

    http://www.bubuko.com/infodetail-2121050.html RabbitMQ升级到3.6.1版本后,随着业务和系统功能的增加,出现RabbitMQ内存陡增直至服务宕掉的 ...

  3. Dubbo 基础教程

    原文地址:Dubbo 基础教程 博客地址:http://www.extlight.com 一.前言 当服务越来越多时,容量的评估,小服务资源的浪费等问题逐渐显现,此时需要增加一个调度中心基于访问压力实 ...

  4. SpringCloud初体验:四、API GateWay 服务网关

    网关服务很多,比如:Zuul.Kong.spring cloud gateway ……, 这里不纠结哪种性能好,本次体验是用的 spring cloud gateway 更多网关比较可以了解这篇文章: ...

  5. PHP 通过实现 Iterator(迭代器)接口来读取大文件文本

    读了NGINX的access日志,bnb_manage_access.log(31M) 和  bnb_wechat_access.log(50M) 附上代码: <?php /** * User: ...

  6. HR-人力资源管理系统(Human Resources Management System,HRMS)

    人力资源管理系统(Human Resources Management System,HRMS),是指组织或社会团体运用系统学理论方法,对企业的人力资源管理方方面面进行分析.规划.实施.调整,提高企业 ...

  7. CentOS查看显卡及GPU相关信息

    lspci  | grep -i vga 这样就可以显示机器上的显卡信息,比如 [root@localhost conf]# lspci | grep -i vga01:00.0 VGA compat ...

  8. 转载 关于restTemplate 内部实现

    2016-12-28 by 安静的下雪天  http://www.cnblogs.com/quiet-snowy-day/p/6228198.html  本篇概要 RestTemplate 类图 po ...

  9. (1/24) 认识webpack

    1.什么是webpack (1)webpack是一个模块打包工具,它做的事情是,分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript ...

  10. 什么是“堆”,"栈","堆栈","队列",它们的区别?

    堆:什么是堆?又该怎么理解呢? ①堆通常是一个可以被看做一棵树的数组对象.堆总是满足下列性质: ·堆中某个节点的值总是不大于或不小于其父节点的值: ·堆总是一棵完全二叉树. 将根节点最大的堆叫做最大堆 ...