Thrift 是一种被广泛使用的 rpc 框架,可以比较灵活的定义数据结构和函数输入输出参数,并且可以跨语言调用。为了保证服务接口的统一性和可维护性,我们需要在最开始就制定一系列规范并严格遵守,降低后续维护成本。

Thrift开发流程是:先定义IDL,使用thrift工具生成目标语言接口(interface)代码,然后进行开发。

官网: http://thrift.apache.org/

github:https://github.com/apache/thrift/

安装Thrift

将Thrift IDL文件编译成目标代码需要安装Thrift二进制工具。

Mac

建议直接使用brew安装,节省时间:

  1. brew install thrift

安装后查看版本:

  1. $ thrift -version
  2. Thrift version 0.12.0

也可以下载源码安装,参考:http://thrift.apache.org/docs/install/os_x。

源码地址:http://www.apache.org/dyn/closer.cgi?path=/thrift/0.12.0/thrift-0.12.0.tar.gz

CentOS

需下载源码安装,参考:http://thrift.apache.org/docs/install/centos。

Debian/Ubuntu

需下载源码安装,先安装依赖:http://thrift.apache.org/docs/install/debian,然后安装thrift:http://thrift.apache.org/docs/BuildingFromSource。

Windows

可以直接下载二进制包。地址:http://www.apache.org/dyn/closer.cgi?path=/thrift/0.12.0/thrift-0.12.0.exe。

实战

该小节我们通过一个例子,讲述如何使用Thrift快速开发出一个RPC微服务,涉及到Golang服务端、Golang客户端、PHP客户端、PHP服务端。项目名就叫做thrift-sample,代码托管在 https://github.com/52fhy/thrift-sample。

推荐使用Golang服务端实现微服务,PHP客户端实现调用。

编写thrift IDL

  1. thrift
  2. ├── Service.thrift
  3. └── User.thrift

User.thrift

  1. namespace go Sample
  2. namespace php Sample
  3. struct User {
  4. 1:required i32 id;
  5. 2:required string name;
  6. 3:required string avatar;
  7. 4:required string address;
  8. 5:required string mobile;
  9. }
  10. struct UserList {
  11. 1:required list<User> userList;
  12. 2:required i32 page;
  13. 3:required i32 limit;
  14. }

Service.thrift

  1. include "User.thrift"
  2. namespace go Sample
  3. namespace php Sample
  4. typedef map<string, string> Data
  5. struct Response {
  6. 1:required i32 errCode; //错误码
  7. 2:required string errMsg; //错误信息
  8. 3:required Data data;
  9. }
  10. //定义服务
  11. service Greeter {
  12. Response SayHello(
  13. 1:required User.User user
  14. )
  15. Response GetUser(
  16. 1:required i32 uid
  17. )
  18. }

说明:

1、namespace用于标记各语言的命名空间或包名。每个语言都需要单独声明。

2、struct在PHP里相当于class,golang里还是struct

3、service在PHP里相当于interface,golang里是interfaceservice里定义的方法必须由服务端实现。

4、typedef和c语言里的用法一致,用于重新定义类型的名称。

5、struct里每个都是由1:required i32 errCode;结构组成,分表代表标识符、是否可选、类型、名称。单个struct里标识符不能重复,required表示该属性不能为空,i32表示int32。

接下来我们生产目标语言的代码:


  1. mkdir -p php go
  2. #编译
  3. thrift -r --gen go thrift/Service.thrift
  4. thrift -r --gen php:server thrift/Service.thrift

其它语言请参考上述示例编写。

编译成功后,生成的代码文件有:

  1. gen-go
  2. └── Sample
  3. ├── GoUnusedProtection__.go
  4. ├── Service-consts.go
  5. ├── Service.go
  6. ├── User-consts.go
  7. ├── User.go
  8. └── greeter-remote
  9. └── greeter-remote.go
  10. gen-php
  11. └── Sample
  12. ├── GreeterClient.php
  13. ├── GreeterIf.php
  14. ├── GreeterProcessor.php
  15. ├── Greeter_GetUser_args.php
  16. ├── Greeter_GetUser_result.php
  17. ├── Greeter_SayHello_args.php
  18. ├── Greeter_SayHello_result.php
  19. ├── Response.php
  20. ├── User.php
  21. └── UserList.php

注:如果php编译不加:server则不会生成GreeterProcessor.php文件。如果无需使用PHP服务端,则该文件是不需要的。

golang服务端

本节我们实行golang的服务端,需要实现的接口我们简单实现。本节参考了官方的例子,做了删减,官方的例子代码量有点多,而且是好几个文件,对新手不太友好。建议看完本节再去看官方示例。官方例子:https://github.com/apache/thrift/tree/master/tutorial/go/src。

首先我们初始化go mod:

  1. $ go mod init sample

然后编写服务端代码:

main.go

  1. package main
  2. import (
  3. "context"
  4. "encoding/json"
  5. "flag"
  6. "fmt"
  7. "github.com/apache/thrift/lib/go/thrift"
  8. "os"
  9. "sample/gen-go/Sample"
  10. )
  11. func Usage() {
  12. fmt.Fprint(os.Stderr, "Usage of ", os.Args[0], ":\n")
  13. flag.PrintDefaults()
  14. fmt.Fprint(os.Stderr, "\n")
  15. }
  16. //定义服务
  17. type Greeter struct {
  18. }
  19. //实现IDL里定义的接口
  20. //SayHello
  21. func (this *Greeter) SayHello(ctx context.Context, u *Sample.User) (r *Sample.Response, err error) {
  22. strJson, _ := json.Marshal(u)
  23. return &Sample.Response{ErrCode: 0, ErrMsg: "success", Data: map[string]string{"User": string(strJson)}}, nil
  24. }
  25. //GetUser
  26. func (this *Greeter) GetUser(ctx context.Context, uid int32) (r *Sample.Response, err error) {
  27. return &Sample.Response{ErrCode: 1, ErrMsg: "user not exist."}, nil
  28. }
  29. func main() {
  30. //命令行参数
  31. flag.Usage = Usage
  32. protocol := flag.String("P", "binary", "Specify the protocol (binary, compact, json, simplejson)")
  33. framed := flag.Bool("framed", false, "Use framed transport")
  34. buffered := flag.Bool("buffered", false, "Use buffered transport")
  35. addr := flag.String("addr", "localhost:9090", "Address to listen to")
  36. flag.Parse()
  37. //protocol
  38. var protocolFactory thrift.TProtocolFactory
  39. switch *protocol {
  40. case "compact":
  41. protocolFactory = thrift.NewTCompactProtocolFactory()
  42. case "simplejson":
  43. protocolFactory = thrift.NewTSimpleJSONProtocolFactory()
  44. case "json":
  45. protocolFactory = thrift.NewTJSONProtocolFactory()
  46. case "binary", "":
  47. protocolFactory = thrift.NewTBinaryProtocolFactoryDefault()
  48. default:
  49. fmt.Fprint(os.Stderr, "Invalid protocol specified", protocol, "\n")
  50. Usage()
  51. os.Exit(1)
  52. }
  53. //buffered
  54. var transportFactory thrift.TTransportFactory
  55. if *buffered {
  56. transportFactory = thrift.NewTBufferedTransportFactory(8192)
  57. } else {
  58. transportFactory = thrift.NewTTransportFactory()
  59. }
  60. //framed
  61. if *framed {
  62. transportFactory = thrift.NewTFramedTransportFactory(transportFactory)
  63. }
  64. //handler
  65. handler := &Greeter{}
  66. //transport,no secure
  67. var err error
  68. var transport thrift.TServerTransport
  69. transport, err = thrift.NewTServerSocket(*addr)
  70. if err != nil {
  71. fmt.Println("error running server:", err)
  72. }
  73. //processor
  74. processor := Sample.NewGreeterProcessor(handler)
  75. fmt.Println("Starting the simple server... on ", *addr)
  76. //start tcp server
  77. server := thrift.NewTSimpleServer4(processor, transport, transportFactory, protocolFactory)
  78. err = server.Serve()
  79. if err != nil {
  80. fmt.Println("error running server:", err)
  81. }
  82. }

编译并运行:

  1. $ go run main.go
  2. Starting the simple server... on localhost:9090

客户端

我们先使用go test写客户端代码:

client_test.go

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "github.com/apache/thrift/lib/go/thrift"
  6. "sample/gen-go/Sample"
  7. "testing"
  8. )
  9. var ctx = context.Background()
  10. func GetClient() *Sample.GreeterClient {
  11. addr := ":9090"
  12. var transport thrift.TTransport
  13. var err error
  14. transport, err = thrift.NewTSocket(addr)
  15. if err != nil {
  16. fmt.Println("Error opening socket:", err)
  17. }
  18. //protocol
  19. var protocolFactory thrift.TProtocolFactory
  20. protocolFactory = thrift.NewTBinaryProtocolFactoryDefault()
  21. //no buffered
  22. var transportFactory thrift.TTransportFactory
  23. transportFactory = thrift.NewTTransportFactory()
  24. transport, err = transportFactory.GetTransport(transport)
  25. if err != nil {
  26. fmt.Println("error running client:", err)
  27. }
  28. if err := transport.Open(); err != nil {
  29. fmt.Println("error running client:", err)
  30. }
  31. iprot := protocolFactory.GetProtocol(transport)
  32. oprot := protocolFactory.GetProtocol(transport)
  33. client := Sample.NewGreeterClient(thrift.NewTStandardClient(iprot, oprot))
  34. return client
  35. }
  36. //GetUser
  37. func TestGetUser(t *testing.T) {
  38. client := GetClient()
  39. rep, err := client.GetUser(ctx, 100)
  40. if err != nil {
  41. t.Errorf("thrift err: %v\n", err)
  42. } else {
  43. t.Logf("Recevied: %v\n", rep)
  44. }
  45. }
  46. //SayHello
  47. func TestSayHello(t *testing.T) {
  48. client := GetClient()
  49. user := &Sample.User{}
  50. user.Name = "thrift"
  51. user.Address = "address"
  52. rep, err := client.SayHello(ctx, user)
  53. if err != nil {
  54. t.Errorf("thrift err: %v\n", err)
  55. } else {
  56. t.Logf("Recevied: %v\n", rep)
  57. }
  58. }

首先确保服务端已运行,然后运行测试用例:

  1. $ go test -v
  2. === RUN TestGetUser
  3. --- PASS: TestGetUser (0.00s)
  4. client_test.go:53: Recevied: Response({ErrCode:1 ErrMsg:user not exist. Data:map[]})
  5. === RUN TestSayHello
  6. --- PASS: TestSayHello (0.00s)
  7. client_test.go:69: Recevied: Response({ErrCode:0 ErrMsg:success Data:map[User:{"id":0,"name":"thrift","avatar":"","address":"address","mobile":""}]})
  8. PASS
  9. ok sample 0.017s

接下来我们使用php实现客户端:

client.php

  1. <?php
  2. error_reporting(E_ALL);
  3. $ROOT_DIR = realpath(dirname(__FILE__) . '/lib-php/');
  4. $GEN_DIR = realpath(dirname(__FILE__)) . '/gen-php/';
  5. require_once $ROOT_DIR . '/Thrift/ClassLoader/ThriftClassLoader.php';
  6. use Thrift\ClassLoader\ThriftClassLoader;
  7. use Thrift\Protocol\TBinaryProtocol;
  8. use Thrift\Transport\TSocket;
  9. use Thrift\Transport\TBufferedTransport;
  10. use \Thrift\Transport\THttpClient;
  11. $loader = new ThriftClassLoader();
  12. $loader->registerNamespace('Thrift', $ROOT_DIR);
  13. $loader->registerDefinition('Sample', $GEN_DIR);
  14. $loader->register();
  15. try {
  16. if (array_search('--http', $argv)) {
  17. $socket = new THttpClient('localhost', 8080, '/server.php');
  18. } else {
  19. $socket = new TSocket('localhost', 9090);
  20. }
  21. $transport = new TBufferedTransport($socket, 1024, 1024);
  22. $protocol = new TBinaryProtocol($transport);
  23. $client = new \Sample\GreeterClient($protocol);
  24. $transport->open();
  25. try {
  26. $user = new \Sample\User();
  27. $user->id = 100;
  28. $user->name = "test";
  29. $user->avatar = "avatar";
  30. $user->address = "address";
  31. $user->mobile = "mobile";
  32. $rep = $client->SayHello($user);
  33. var_dump($rep);
  34. $rep = $client->GetUser(100);
  35. var_dump($rep);
  36. } catch (\tutorial\InvalidOperation $io) {
  37. print "InvalidOperation: $io->why\n";
  38. }
  39. $transport->close();
  40. } catch (TException $tx) {
  41. print 'TException: ' . $tx->getMessage() . "\n";
  42. }
  43. ?>

在运行PHP客户端之前,需要引入thrift的php库文件。我们下载下来的thrift源码包里面就有:

  1. ~/Downloads/thrift-0.12.0/lib/php/
  2. ├── Makefile.am
  3. ├── Makefile.in
  4. ├── README.apache.md
  5. ├── README.md
  6. ├── coding_standards.md
  7. ├── lib
  8. ├── src
  9. ├── test
  10. └── thrift_protocol.ini

我们在当前项目里新建lib-php目录,并需要把整个php下的代码复制到lib-php目录:

  1. $ cp -rp ~/Downloads/thrift-0.12.0/lib/php/* ./lib-php/

然后需要修改/lib-php/里的lib目录名为Thrift,否则后续会一直提示Class 'Thrift\Transport\TSocket' not found

然后还需要修改/lib-php/Thrift/ClassLoader/ThriftClassLoader.php,将findFile()方法的$className . '.php';改为$class . '.php';,大概在197行。修改好的参考:https://github.com/52fhy/thrift-sample/blob/master/lib-php/Thrift/ClassLoader/ThriftClassLoader.php

然后现在可以运行了:

  1. $ php client.php
  2. object(Sample\Response)#9 (3) {
  3. ["errCode"]=>
  4. int(0)
  5. ["errMsg"]=>
  6. string(7) "success"
  7. ["data"]=>
  8. array(1) {
  9. ["User"]=>
  10. string(80) "{"id":100,"name":"test","avatar":"avatar","address":"address","mobile":"mobile"}"
  11. }
  12. }
  13. object(Sample\Response)#10 (3) {
  14. ["errCode"]=>
  15. int(1)
  16. ["errMsg"]=>
  17. string(15) "user not exist."
  18. ["data"]=>
  19. array(0) {
  20. }
  21. }

php服务端

thrift实现的服务端不能自己起server服务独立运行,还需要借助php-fpm运行。代码思路和golang差不多,先实现interface里实现的接口,然后使用thrift对外暴露服务:

server.php

  1. <?php
  2. /**
  3. * Created by PhpStorm.
  4. * User: yujc@youshu.cc
  5. * Date: 2019-07-07
  6. * Time: 08:18
  7. */
  8. error_reporting(E_ALL);
  9. $ROOT_DIR = realpath(dirname(__FILE__) . '/lib-php/');
  10. $GEN_DIR = realpath(dirname(__FILE__)) . '/gen-php/';
  11. require_once $ROOT_DIR . '/Thrift/ClassLoader/ThriftClassLoader.php';
  12. use Thrift\ClassLoader\ThriftClassLoader;
  13. use Thrift\Protocol\TBinaryProtocol;
  14. use Thrift\Transport\TSocket;
  15. use Thrift\Transport\TBufferedTransport;
  16. use \Thrift\Transport\TPhpStream;
  17. $loader = new ThriftClassLoader();
  18. $loader->registerNamespace('Thrift', $ROOT_DIR);
  19. $loader->registerDefinition('Sample', $GEN_DIR);
  20. $loader->register();
  21. class Handler implements \Sample\GreeterIf {
  22. /**
  23. * @param \Sample\User $user
  24. * @return \Sample\Response
  25. */
  26. public function SayHello(\Sample\User $user)
  27. {
  28. $response = new \Sample\Response();
  29. $response->errCode = 0;
  30. $response->errMsg = "success";
  31. $response->data = [
  32. "user" => json_encode($user)
  33. ];
  34. return $response;
  35. }
  36. /**
  37. * @param int $uid
  38. * @return \Sample\Response
  39. */
  40. public function GetUser($uid)
  41. {
  42. $response = new \Sample\Response();
  43. $response->errCode = 1;
  44. $response->errMsg = "fail";
  45. return $response;
  46. }
  47. }
  48. header('Content-Type', 'application/x-thrift');
  49. if (php_sapi_name() == 'cli') {
  50. echo "\r\n";
  51. }
  52. $handler = new Handler();
  53. $processor = new \Sample\GreeterProcessor($handler);
  54. $transport = new TBufferedTransport(new TPhpStream(TPhpStream::MODE_R | TPhpStream::MODE_W));
  55. $protocol = new TBinaryProtocol($transport, true, true);
  56. $transport->open();
  57. $processor->process($protocol, $protocol);
  58. $transport->close();

这里我们直接使用php -S 0.0.0.0:8080启动httpserver,就不使用php-fpm演示了:

  1. $ php -S 0.0.0.0:8080
  2. PHP 7.1.23 Development Server started at Sun Jul 7 10:52:06 2019
  3. Listening on http://0.0.0.0:8080
  4. Document root is /work/git/thrift-sample
  5. Press Ctrl-C to quit.

我们使用php客户端,注意需要加参数,调用http协议连接:

  1. $ php client.php --http
  2. object(Sample\Response)#9 (3) {
  3. ["errCode"]=>
  4. int(0)
  5. ["errMsg"]=>
  6. string(7) "success"
  7. ["data"]=>
  8. array(1) {
  9. ["user"]=>
  10. string(80) "{"id":100,"name":"test","avatar":"avatar","address":"address","mobile":"mobile"}"
  11. }
  12. }
  13. object(Sample\Response)#10 (3) {
  14. ["errCode"]=>
  15. int(1)
  16. ["errMsg"]=>
  17. string(4) "fail"
  18. ["data"]=>
  19. NULL
  20. }

thrift IDL语法参考

1、类型定义

(1) 基本类型

  1. bool:布尔值(truefalse
  2. byte8位有符号整数
  3. i1616位有符号整数
  4. i3232位有符号整数
  5. i6464位有符号整数
  6. double64位浮点数
  7. string:使用UTF-8编码编码的文本字符串

注意没有无符号整数类型。这是因为许多编程语言中没有无符号整数类型(比如java)。

(2) 容器类型

  1. list<t1>:一系列t1类型的元素组成的有序列表,元素可以重复
  2. set<t1>:一些t1类型的元素组成的无序集合,元素唯一不重复
  3. map<t1,t2>:key/value对,key唯一

容器中的元素类型可以是除service以外的任何合法的thrift类型,包括结构体和异常类型。

(3) Typedef

Thrift支持C/C++风格的类型定义:

  1. typedef i32 MyInteger

(4) Enum

定义枚举类型:

  1. enum TweetType {
  2. TWEET,
  3. RETWEET = 2,
  4. DM = 0xa,
  5. REPLY
  6. }

注意:编译器默认从0开始赋值,枚举值可以赋予某个常量,允许常量是十六进制整数。末尾没有逗号。

不同于protocol buffer,thrift不支持枚举类嵌套,枚举常量必须是32位正整数。

示例里,对于PHP来说,会生成TweetType类;对于golang来说,会生成TweetType_开头的常量。

(5) Const

Thrift允许用户定义常量,复杂的类型和结构体可以使用JSON形式表示:

  1. const i32 INT_CONST = 1234
  2. const map<string,string> MAP_CONST = {"hello": "world", "goodnight": "moon"}

示例里,对于PHP来说,会生成Constant类;对于golang来说,会生成名称一样的常量。

(6) Exception

用于定义异常。示例:

  1. exception BizException {
  2. 1:required i32 code
  3. 2:required string msg
  4. }

示例里,对于PHP来说,会生成BizException类,继承自TException;对于golang来说,会生成BizException结构体及相关方法。

(7) Struct

结构体struct在PHP里相当于class,golang里还是struct。示例:

  1. struct User {
  2. 1:required i32 id = 0;
  3. 2:optional string name;
  4. }

结构体可以包含其他结构体,但不支持继承结构体。

(8) Service

Thrift编译器会根据选择的目标语言为server产生服务接口代码,为client产生桩(stub)代码。

service在PHP里相当于interface,golang里是interfaceservice里定义的方法必须由服务端实现。

示例:

  1. service Greeter {
  2. Response SayHello(
  3. 1:required User.User user
  4. )
  5. Response GetUser(
  6. 1:required i32 uid
  7. )
  8. }
  9. //继承
  10. service ChildGreeter extends Greeter{
  11. }

注意:

  • 参数可以是基本类型或者结构体,参数只能是只读的(const),不可以作为返回值
  • 返回值可以是基本类型或者结构体,返回值可以是void
  • 支持继承,一个service可使用extends关键字继承另一个service

(9) Union

定义联合体。查看联合体介绍 https://baijiahao.baidu.com/s?id=1623457037181175751&wfr=spider&for=pc。

  1. struct Pixel{
  2. 1:required i32 Red;
  3. 2:required i32 Green;
  4. 3:required i32 Blue;
  5. }
  6. union Pixel_TypeDef {
  7. 1:optional Pixel pixel
  8. 2:optional i32 value
  9. }

联合体要求字段选项都是optional的,因为同一时刻只有一个变量有值。

2、注释

支持shell注释风格、C/C++语言中的单行或多行注释风格。

  1. # 这是注释
  2. // 这是注释
  3. /*
  4. * 这是注释
  5. */

3、namespace

定义命名空间或者包名。格式示例:

  1. namespace go Sample
  2. namespace php Sample

需要支持多个语言,则需要定义多行。命名空间或者包名是多层级,使用.号隔开。例如Sample.Model最终生成的代码里面PHP的命名空间是\Sample\Model,golang则会生成目录Sample/Model,包名是Model

4、文件包含

thrift支持引入另一个thrift文件:

  1. include "User.thrift"
  2. include "TestDefine.thrift"

注意:

(1) include 引入的文件使用的使用,字段必须带文件名前缀:

  1. 1:required User.User user

不能直接写User user,这样会提示找不到User定义。

(2)假设编译的时候A里引入了B,那么编译A的时候,B里面定义的也会被编译。

5、Field

字段定义格式:

  1. FieldID? FieldReq? FieldType Identifier ('= ConstValue)? XsdFieldOptions ListSeparator?

其中:

  • FieldID必须是IntConstant类型,即整型常量。
  • FieldReq (Field Requiredness,字段选项)支持requiredoptional两种。一旦一个参数设置为 required,未来就一定不能删除或者改为 optional,否则就会出现版本不兼容问题,老客户端访问新服务会出现参数错误。不确定的情况可以都使用 optional
  • FieldType 就是字段类型。
  • Identifier 就是变量标识符,不能为数字开头。
  • 字段定义可以设置默认值,支持Const等。

示例:

  1. struct User {
  2. 1:required i32 id = 0;
  3. 2:optional string name;
  4. }

IDE插件

1、JetBrains PhpStorm 可以在插件里找到Thrift Support安装,重启IDE后就支持Thrift格式语法了。

2、VScode 在扩展里搜索 Thrift,安装即可。

参考

1、Apache Thrift - Index of tutorial/

http://thrift.apache.org/tutorial/

2、Apache Thrift - Interface Description Language (IDL)

http://thrift.apache.org/docs/idl

3、Thrift语法参考 - 流水殇 - 博客园

https://www.cnblogs.com/yuananyun/p/5186430.html

4、和 Thrift 的一场美丽邂逅 - cyfonly - 博客园

https://www.cnblogs.com/cyfonly/p/6059374.html

从零开始基于go-thrift创建一个RPC服务的更多相关文章

  1. 使用PHP来简单的创建一个RPC服务

    RPC全称为Remote Procedure Call,翻译过来为"远程过程调用".主要应用于不同的系统之间的远程通信和相互调用. 比如有两个系统,一个是PHP写的,一个是JAVA ...

  2. C# 创建一个WCF服务

    做代码统计,方便以后使用: app.config配置文件设置: <configuration> <system.serviceModel> <bindings> & ...

  3. 搭建QQ聊天通信的程序:(1)基于 networkcomms.net 创建一个WPF聊天客户端服务器应用程序 (1)

    搭建QQ聊天通信的程序:(1)基于 networkcomms.net 创建一个WPF聊天客户端服务器应用程序 原文地址(英文):http://www.networkcomms.net/creating ...

  4. 为MongoDB创建一个Windows服务

    一:选型,根据机器的操作系统类型来选择合适的版本,使用下面的命令行查询机器的操作系统版本 wmic os get osarchitecture 二:下载并安装 附上下载链接 点击安装包,我这里是把文件 ...

  5. (4opencv)如何基于GOCW,创建一个实时视频程序

    直接使用提供的代码框架进行修改,是最快得到效果的方法:但是这样的灵活性较差,而且真正的程序员从来都不会停滞在这一步:我们需要的是"将框架解析到最小化.理清楚每个构建之间的关系",只 ...

  6. 【LINUX】——linux如何使用Python创建一个web服务

    问:linux如何使用Python创建一个web服务? 答:一句话,Python! 一句代码: /usr/local/bin/python -m SimpleHTTPServer 8686 > ...

  7. ng 通过factory方法来创建一个心跳服务

    <!DOCTYPE html> <html ng-app="myApp"> <head lang="en"> <met ...

  8. 使用PHP创建一个socket服务端

    与常规web开发不同,使用socket开发可以摆脱http的限制.可自定义协议,使用长连接.PHP代码常驻内存等.学习资料来源于workerman官方视频与文档. 通常创建一个socket服务包括这几 ...

  9. Thrift 个人实战--RPC服务的发布订阅实现(基于Zookeeper服务)

    前言: Thrift作为Facebook开源的RPC框架, 通过IDL中间语言, 并借助代码生成引擎生成各种主流语言的rpc框架服务端/客户端代码. 不过Thrift的实现, 简单使用离实际生产环境还 ...

随机推荐

  1. Entity种类(动态代理)

    动态代理:延迟加载+自动化修改跟踪满足条件     修改方式见修改EF设置eg.     测试     结果    

  2. laravel 报错SQLSTATE[HY000] [2002] No such file or directory

    在mac中执行php artisan migrate时报错 SQLSTATE[HY000] [2002] No such file or directory (SQL: select * from i ...

  3. wcf服务端代码方式及客户端代码方式

    ServiceHost  host;  // 全局 host = new ServiceHost(typeof(实现服务接口的类)); host.open(); 用代码配置端点的方法 host.add ...

  4. 简洁的导出 datatable到excel,不用组件

    简洁的导出 datatable到excel var lines = new List<string>(); string[] columnNames = dataTable.Columns ...

  5. 使用ServiceStack.Redis实现Redis数据读写

    原文:使用ServiceStack.Redis实现Redis数据读写 User.cs实体类 public class User { public string Name { get; set; } p ...

  6. Advanced Installer读取注册表时将Program Files读取为Program Files (x86)的解决办法

    原文:Advanced Installer读取注册表时将Program Files读取为Program Files (x86)的解决办法 今天同事在做安装包的时候,有一个读取注册表路径的需求,需要根据 ...

  7. Android零基础入门第86节:探究Fragment生命周期

    一个Activity可以同时组合多个Fragment,一个Fragment也可被多个Activity 复用.Fragment可以响应自己的输入事件,并拥有自己的生命周期,但它们的生命周期直接被其所属的 ...

  8. 获取UWP配置文件中的版本信息

    原文:获取UWP配置文件中的版本信息 在一般的软件中,我们都会显示当前软件的版本信息.以前作者都是在发版的时候修改一下UWP的配置文件中的版本信息和软件中的版本信息.但是每次这样很麻烦,有时间忘记修改 ...

  9. Android零基础入门第65节:RecyclerView分割线开发技巧

    在上一期通过简单学习,已经领略到了RecyclerView的灵活性,当然都是一些最基础的用法,那么本期一起来学习RecyclerView的分割线使用. 相信有的比较细心的同学已经发现了,使用Recyc ...

  10. 高性能JSON解析器及生成器RapidJSON

    RapidJSON是腾讯公司开源的一个C++的高性能的JSON解析器及生成器,同时支持SAX/DOM风格的API. 直击现场 RapidJSON是腾讯公司开源的一个C++的高性能的JSON解析器及生成 ...