02-Thrift研究

来源:互联网 发布:java 反射找不到注解 编辑:程序博客网 时间:2024/06/05 15:59

  • Thrift研究
  • Thrift网络库
  • 阻塞IO服务器
    • 1 TSimpleServer服务器
    • 2 TThreadServer服务器
    • 3 TThreadPoolServer服务器
  • 非阻塞IO服务器
  • 其他语言
    • 1 纯go的服务器和客户端

Thrift研究

主要研究单线程服务器、多线程服务器、线程池服务器,以及基于libevent的非阻塞模型的服务器。

1 Thrift网络库

与传输协议一样,Thrift 也支持几种不同的传输方式。
1. TSocket:阻塞型 socket,用于客户端,采用系统函数 read 和 write 进行读写数据。
2. TServerSocket:非阻塞型 socket,用于服务器端,accecpt 到的 socket 类型都是 TSocket(即阻塞型 socket)。
3. TBufferedTransport 和 TFramedTransport 都是有缓存的,均继承TBufferBase,调用下一层 TTransport 类进行读写操作吗,结构极为相似。其中 TFramedTransport 以帧为传输单位,帧结构为:4个字节(int32_t)+传输字节串,头4个字节是存储后面字节串的长度,该字节串才是正确需要传输的数据,因此 TFramedTransport 每传一帧要比 TBufferedTransport 和 TSocket 多传4个字节。
4. TMemoryBuffer 继承 TBufferBase,用于程序内部通信用,不涉及任何网络I/O,可用于三种模式:(1)OBSERVE模式,不可写数据到缓存;(2)TAKE_OWNERSHIP模式,需负责释放缓存;(3)COPY模式,拷贝外面的内存块到TMemoryBuffer。
5. TFileTransport 直接继承 TTransport,用于写数据到文件。对事件的形式写数据,主线程负责将事件入列,写线程将事件入列,并将事件里的数据写入磁盘。这里面用到了两个队列,类型为 TFileTransportBuffer,一个用于主线程写事件,另一个用于写线程读事件,这就避免了线程竞争。在读完队列事件后,就会进行队列交换,由于由两个指针指向这两个队列,交换只要交换指针即可。它还支持以 chunk(块)的形式写数据到文件。
6. TFDTransport 是非常简单地写数据到文件和从文件读数据,它的 write 和 read 函数都是直接调用系统函数 write 和 read 进行写和读文件。
7. TSimpleFileTransport 直接继承 TFDTransport,没有添加任何成员函数和成员变量,不同的是构造函数的参数和在 TSimpleFileTransport 构造函数里对父类进行了初始化(打开指定文件并将fd传给父类和设置父类的close_policy为CLOSE_ON_DESTROY)。
8. TZlibTransport 跟 TBufferedTransport 和 TFramedTransport一样,调用下一层 TTransport 类进行读写操作。它采用zlib.h提供的 zlib 压缩和解压缩库函数来进行压解缩,写时先压缩再调用底层 TTransport 类发送数据,读时先调用 TTransport 类接收数据再进行解压,最后供上层处理。
9. TSSLSocket 继承 TSocket,阻塞型 socket,用于客户端。采用 openssl 的接口进行读写数据。checkHandshake()函数调用 SSL_set_fd 将 fd 和 ssl 绑定在一起,之后就可以通过 ssl 的 SSL_read和SSL_write 接口进行读写网络数据。
10. TSSLServerSocket 继承 TServerSocket,非阻塞型 socket, 用于服务器端。accecpt 到的 socket 类型都是 TSSLSocket 类型。
11. THttpClient 和 THttpServer 是基于 Http1.1 协议的继承 Transport 类型,均继承 THttpTransport,其中 THttpClient 用于客户端,THttpServer 用于服务器端。两者都调用下一层 TTransport 类进行读写操作,均用到TMemoryBuffer 作为读写缓存,只有调用 flush() 函数才会将真正调用网络 I/O 接口发送数据。

TTransport 是所有 Transport 类的父类,为上层提供了统一的接口而且通过 TTransport 即可访问各个子类不同实现,类似多态。

2 阻塞I/O服务器

2.1 TSimpleServer服务器

在C++中,体现的代码是:

// 服务器shared_ptr<TServerTransport> serverTransport(new TServerSocket(port));TSimpleServer _server(processor, serverTransport, transportFactory, protocolFactory);// 客户端  shared_ptr<TTransport> clientSocket(new TSocket("127.0.0.1", port));

编译后运行,可以看到,线程数确实是1个。

如果开启多个Client,只有当首先占用那个关闭后,其他的客户端才有机会执行,否则阻塞。

2.2 TThreadServer服务器

在C++中,体现的代码是:

// 服务器shared_ptr<TServerTransport> serverTransport(new TServerSocket(port));TThreadedServer _server(processor, serverTransport, transportFactory, protocolFactory); // 每个客户端一个线程// 客户端  shared_ptr<TTransport> clientSocket(new TSocket("127.0.0.1", port));

编译后运行,可以看到,服务器原始线程数为4,客户端每连接一个,线程数增加1。但是客户端可以并行调用。

2.3 TThreadPoolServer服务器

在C++中,体现的代码是:

// 服务器shared_ptr<TServerTransport> serverTransport(new TServerSocket(port));TThreadPoolServer _server(processor, serverTransport, transportFactory, protocolFactory); //默认4个线程// 客户端  shared_ptr<TTransport> clientSocket(new TSocket("127.0.0.1", port));

编译后运行,可以看到,线程数确实是4个。

额,这个C++服务器会崩溃掉,不知道为啥~~

3 非阻塞I/O服务器

非阻塞模型(TNonblockingServer),官方使用libevent作为基础库,可以想象,性能一定很不错。

值得注意的是,非阻塞服务器必须使用TFramedTransport, 不能是其他。同样,客户端也必须使用这个模式。

使用C++代码:

// 服务器  TNonblockingServer _server(processor, protocolFactory, 9090); // 默认使用一个线程处理   // 手工指定线程管理对象  // using thread pool with maximum 8 threads to handle incoming requests  shared_ptr<ThreadManager> threadManager = ThreadManager::newSimpleThreadManager(8);shared_ptr<PlatformThreadFactory> threadFactory = shared_ptr<PlatformThreadFactory>(new PlatformThreadFactory());threadManager->threadFactory(threadFactory);threadManager->start();TNonblockingServer _server(processor, protocolFactory, 9090, threadManager);// 客户端shared_ptr<TTransport> clientTransport(new TFramedTransport(clientSocket)); // 将Buffered修改为Framed就可以了.

服务器方式1,在运行时只使用了1个线程,一般不太可能这样用。
服务器方式2,指定了8个线程,在初始化时是12个,但是当Client连上运行后,又降低为9个并稳定下来。这个应该是更常见的使用情况。

坑1:C++编译时由于依赖Boost和LibEvent, 需要在项目中添加相关依赖,这里libevent使用的是libevent-x86-v120-mt-2_1_4_0.imp.lib
坑2:网上的大部分列子都是使用PosixThreadFactory,这个在Windows上不能使用,需要使用PlatformThreadFactory。

4 其他语言

说到底,最推荐golang方式,利用原生特性,代码简单,效率也很高!

在Golang中,服务器类型代码:

// 服务器  serverTransport, err := thrift.NewTServerSocket(NetWorkAddr)server := thrift.NewTSimpleServer4(processor, serverTransport, transportFactory, protocolFactory)// 客户端  client_socket, _ := thrift.NewTSocket("127.0.0.1:9090")

因为Golang的天生并行特性,即便是SimpleServer也可以并发执行,没有ThreadServer和ThreadPoolServer。经C++ Client测试,正常通信。

在Java语言中,还有AbstractNonblockingServer/THsHaServer/TThreadedSelectorServer好多种服务器模型,不一一列举了。

4.1 纯go的服务器和客户端

服务器,使用FramedTransport。

package mainimport (    "fmt"    "mathservice"    "os"    "time"    "git.apache.org/thrift.git/lib/go/thrift")const (    NetWorkAddr = "127.0.0.1:9090")type MyMathService struct {}func (this *MyMathService) Add(A int32, B int32) (r int32, err error) {    r = A + B    err = nil    fmt.Println("Add", A, B, "[", time.Now(), "]")    return}func main() {    handler := &MyMathService{}    processor := mathservice.NewMathServiceProcessor(handler)    serverTransport, err := thrift.NewTServerSocket(NetWorkAddr)    if err != nil {        fmt.Println("Error!", err)        os.Exit(1)    }    transportFactory := thrift.NewTTransportFactory()    framedtransportFactory := thrift.NewTFramedTransportFactory(transportFactory)    protocolFactory := thrift.NewTBinaryProtocolFactoryDefault()    server := thrift.NewTSimpleServer4(processor, serverTransport, framedtransportFactory, protocolFactory)    fmt.Println("thrift server in", NetWorkAddr)    server.Serve()}

配套的FramedTransport的客户端:

package mainimport (    "mathservice"    "fmt"    "os"    "time"    "git.apache.org/thrift.git/lib/go/thrift")func tt() {    client_socket, _ := thrift.NewTSocket("127.0.0.1:9090")    //client_transport := thrift.NewTBufferedTransport(client_socket, 512)    client_transport := thrift.NewTFramedTransport(client_socket)    protocolFactory := thrift.NewTBinaryProtocolFactoryDefault()    client := mathservice.NewMathServiceClientFactory(client_transport, protocolFactory)    if err := client_transport.Open(); err != nil {        fmt.Fprintln(os.Stderr, "Error opening socket", err)        os.Exit(1)    }    defer client_transport.Close()    for i := int32(0); i < 100; i++ {        nRet, _ := client.Add(i, i)        fmt.Println(i, "Add", nRet)        time.Sleep(100 * 1000 * 1000)    }    fmt.Println("Over!")}func main() {    go tt() // 并行运行两个客户端    tt()}

使用起来感觉棒棒哒^_^!

原创粉丝点击