├── rebar ├── src ├── grpc_client_erlang_fast.app.src ├── lconnect.erl ├── lconnect_process_lb.erl ├── lconnect_worker.erl ├── poolManager.erl ├── grpc_client.erl └── lconnect_ets.erl ├── rebar.config ├── include ├── gpb_version.hrl ├── lconnect_conf.hrl ├── ms_transform.hrl └── gpb.hrl └── README.md /rebar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/optd-dl/grpc-client-erlang-fast/HEAD/rebar -------------------------------------------------------------------------------- /src/grpc_client_erlang_fast.app.src: -------------------------------------------------------------------------------- 1 | {application, grpc_client_erlang_fast, [{vsn, "1"}]}. 2 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {deps, [ 2 | {chatterbox, {git, "https://github.com/optd-dl/http2-client-erlang-fast", {branch,"master"}}}, 3 | {gpb, {git, "https://github.com/tomas-abrahamsson/gpb.git", {branch,"master"}}}, 4 | {poolboy, {git, "https://github.com/devinus/poolboy", {branch,"master"}}} 5 | ]}. 6 | -------------------------------------------------------------------------------- /include/gpb_version.hrl: -------------------------------------------------------------------------------- 1 | -ifndef(gpb_version_hrl). 2 | -define(gpb_version_hrl, true). 3 | 4 | %% DO NOT EDIT -- generated from gpb_version.hrl.in 5 | %% The version below was substituted 6 | %% at build-time, see the rebar.config or the Makefile 7 | 8 | -define(gpb_version, "3.26.3"). 9 | 10 | -endif. %% gpb_version_hrl 11 | -------------------------------------------------------------------------------- /include/lconnect_conf.hrl: -------------------------------------------------------------------------------- 1 | -define(MODULE_NAME, roprpc_thrift). 2 | -define(FUNCTION_NAME, call). 3 | -define(POOLTABLE_NAME, t_pooltable). 4 | -define(POOL_CLEAN_TIME, 15 * 60 * 1000). 5 | -define(POOL_UPDATE_TIME, 5 * 60 * 1000). 6 | -define(POOL_CONNECT_NUM,50). 7 | -define(POOL_MAX_NUM,200). 8 | 9 | 10 | 11 | %----------------------------------------------------- 12 | -define(POOL_DYN_NAME,rpc_dynamic_pool). 13 | -define(POOL_DYN_CONNECT_NUM,1000). 14 | -------------------------------------------------------------------------------- /include/ms_transform.hrl: -------------------------------------------------------------------------------- 1 | %% 2 | %% %CopyrightBegin% 3 | %% 4 | %% Copyright Ericsson AB 2002-2009. All Rights Reserved. 5 | %% 6 | %% Licensed under the Apache License, Version 2.0 (the "License"); 7 | %% you may not use this file except in compliance with the License. 8 | %% You may obtain a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, software 13 | %% distributed under the License is distributed on an "AS IS" BASIS, 14 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | %% See the License for the specific language governing permissions and 16 | %% limitations under the License. 17 | %% 18 | %% %CopyrightEnd% 19 | %% 20 | -compile({parse_transform,ms_transform}). 21 | -------------------------------------------------------------------------------- /src/lconnect.erl: -------------------------------------------------------------------------------- 1 | -module(lconnect). 2 | -include("../include/lconnect_conf.hrl"). 3 | -export([start_link/3, stop/1,post/4,post_time/5]). 4 | 5 | start_link(PoolName, NumCon, []) -> 6 | SizeArgs = [{size, NumCon}, 7 | {max_overflow, 0}], 8 | PoolArgs = [{name, {local, PoolName}}, 9 | {worker_module, lconnect_worker}] ++ SizeArgs, 10 | WorkerArgs = [], 11 | poolboy:start_link(PoolArgs, WorkerArgs); 12 | start_link(PoolName, NumCon, Option) -> 13 | SizeArgs = [{size, NumCon}, 14 | {max_overflow, 0}], 15 | PoolArgs = [{name, {local, PoolName}}, 16 | {worker_module, lconnect_worker}] ++ SizeArgs, 17 | WorkerArgs = Option, 18 | poolboy:start_link(PoolArgs, WorkerArgs). 19 | 20 | stop(PoolPid) -> 21 | poolboy:stop(PoolPid). 22 | 23 | post(Interface,IP,Port,InterfaceParam) -> 24 | case lconnect_process_lb:checkout({Interface,IP,Port}) of 25 | full -> 26 | try 27 | Interface:send_request(IP,Port,InterfaceParam) 28 | catch 29 | _:Reason -> 30 | {error,Reason,erlang:get_stacktrace()} 31 | end; 32 | Worker when is_pid(Worker) -> 33 | try 34 | gen_server:call(Worker, {post,InterfaceParam}, 30 * 1000) 35 | catch 36 | _:Reason1 -> 37 | {error,Reason1,erlang:get_stacktrace()} 38 | after 39 | lconnect_process_lb:checkin({IP,Port},Worker) 40 | end 41 | end. 42 | 43 | post_time(Interface,IP,Port,InterfaceParam,ReconnectTime) -> 44 | case lconnect_process_lb:checkout({Interface,IP,Port}) of 45 | full -> 46 | try 47 | Interface:send_request(IP,Port,InterfaceParam) 48 | catch 49 | _:Reason -> 50 | {error,Reason,erlang:get_stacktrace()} 51 | end; 52 | Worker when is_pid(Worker) -> 53 | try 54 | gen_server:call(Worker, {post_time,InterfaceParam,ReconnectTime}, 30 * 1000) 55 | catch 56 | _:Reason1 -> 57 | {error,Reason1,erlang:get_stacktrace()} 58 | after 59 | lconnect_process_lb:checkin({IP,Port},Worker) 60 | end 61 | end. 62 | -------------------------------------------------------------------------------- /src/lconnect_process_lb.erl: -------------------------------------------------------------------------------- 1 | %% @author ydwiner 2 | %% @doc @todo Add description to rpcProcessLb. 3 | 4 | 5 | -module(lconnect_process_lb). 6 | 7 | %% ==================================================================== 8 | %% API functions 9 | %% ==================================================================== 10 | -export([start/1,checkout/1,checkin/2,startCleanTimer/0]). 11 | 12 | -export([init/1, 13 | handle_call /3, 14 | terminate/2]). 15 | 16 | -record(process_info,{process_list :: list(), 17 | interface ::module(), 18 | ip :: string(), 19 | port :: integer()}). 20 | 21 | -include("../include/lconnect_conf.hrl"). 22 | 23 | %% ==================================================================== 24 | %% Internal functions 25 | %% ==================================================================== 26 | 27 | start(Args) -> 28 | gen_server:start_link(?MODULE, Args, []). 29 | 30 | init([Interface,IP,Port]) -> 31 | State = #process_info{process_list = [],interface = Interface, ip = IP, port = Port}, 32 | lconnect_ets:insertData(?POOLTABLE_NAME, {IP,Port}, self()), 33 | spawn(?MODULE, startCleanTimer, []), 34 | {ok,State} 35 | . 36 | 37 | terminate(_Reason, _State = #process_info{process_list = List}) -> 38 | clean(List), 39 | ok. 40 | 41 | handle_call(checkout,_From,State = #process_info{process_list = [],interface = Interface,ip = IP,port = Port}) -> 42 | Pid = poolboy:checkout(?POOL_DYN_NAME,false), 43 | case Pid of 44 | full -> 45 | {reply,full,State}; 46 | _ -> 47 | gen_server:call(Pid, {worker_init,{Interface,IP,Port}}, infinity), 48 | {reply,Pid,State} 49 | end; 50 | 51 | handle_call(checkout,_From,State = #process_info{process_list = List}) -> 52 | [Pid | Rest] = List, 53 | {reply, Pid, State#process_info{process_list = Rest}}; 54 | 55 | handle_call({checkin,Pid},_From,State = #process_info{process_list = List}) -> 56 | {reply,ok,State#process_info{process_list = [Pid | List]}}. 57 | 58 | clean([]) -> 59 | ok; 60 | clean(List) -> 61 | [Pid | Rest] = List, 62 | gen_server:cast(Pid, clean), 63 | poolboy:checkin(?POOL_DYN_NAME,Pid), 64 | clean(Rest). 65 | 66 | checkout(Name) -> 67 | {Interface,IP,Port} = Name, 68 | F = fun([]) -> couchbase_error end, 69 | case lconnect_ets:getDataFun(?POOLTABLE_NAME,{IP,Port},F,[]) of 70 | null -> 71 | poolManager:createSubPool([Interface,IP,Port]), 72 | checkout(Name); 73 | Pid -> 74 | gen_server:call(Pid, checkout, infinity) 75 | end. 76 | 77 | checkin(Name,Worker) -> 78 | case lconnect_ets:getData(?POOLTABLE_NAME, Name, 3) of 79 | [] -> 80 | clean([Worker]); 81 | Pid -> 82 | gen_server:call(Pid, {checkin,Worker}, infinity) 83 | end. 84 | 85 | startCleanTimer() -> 86 | erlang:send_after(?POOL_CLEAN_TIME, self(), clean), 87 | receive 88 | clean -> 89 | poolManager:etsTimeFun(?POOLTABLE_NAME, ?POOL_CLEAN_TIME, fun stopServer/1), 90 | startCleanTimer() 91 | end. 92 | 93 | stopServer(Pid) -> 94 | gen_server:stop(Pid). 95 | -------------------------------------------------------------------------------- /src/lconnect_worker.erl: -------------------------------------------------------------------------------- 1 | -module(lconnect_worker). 2 | 3 | %% API 4 | -export([start_link/1]). 5 | 6 | %% gen_server callbacks 7 | -export([init/1, 8 | handle_call/3, 9 | handle_cast/2, 10 | handle_info/2, 11 | terminate/2]). 12 | 13 | -include("../include/lconnect_conf.hrl"). 14 | 15 | -record(information,{client :: any(), 16 | module :: any(), 17 | host :: list(), 18 | port :: integer(), 19 | time :: integer()}). 20 | 21 | start_link(Arg) -> 22 | gen_server:start_link(?MODULE, Arg, []). 23 | 24 | init([]) -> 25 | process_flag(trap_exit, true), 26 | State = #information{client = [], module = [], host = "", port = 0}, 27 | {ok,State}. 28 | 29 | handle_call({post,InterfaceParam}, _From, State = #information{client = [],module = Interface,host = Host, port = Port}) -> 30 | Client = 31 | case Interface:new(Host, Port) of 32 | {ok,Client_1} -> Client_1; 33 | _ -> [] 34 | end, 35 | case Client of 36 | [] -> {reply, {error,connect_faild}, State}; 37 | _ -> 38 | {_, Response} = Interface:call(Client,InterfaceParam), 39 | {reply,Response,State#information{client = Client}} 40 | end; 41 | 42 | handle_call({post,InterfaceParam}, _From, State = #information{client = Client,host = Host,port = Port,module = Interface}) -> 43 | New_Client = 44 | case erlang:is_process_alive(Client) of 45 | false -> {ok,Client_1} = Interface:new(Host,Port), 46 | Client_1; 47 | true -> Client 48 | end, 49 | {_, Response} = Interface:call(New_Client,InterfaceParam), 50 | {reply, Response, State#information{client = New_Client}}; 51 | 52 | handle_call({post_time,InterfaceParam,ReconnectTime}, _From, State = #information{client = Client,host = Host,port = Port,module = Interface,time = Time}) -> 53 | {New_Client,New_Time} = 54 | if Time >= ReconnectTime -> 55 | Interface:stop(Client), 56 | {ok,Client_1} = Interface:new(Host,Port), 57 | {Client_1,0}; 58 | true -> 59 | {Client,Time + 1} 60 | end, 61 | {_,Response} = Interface:call(New_Client,InterfaceParam), 62 | {reply,Response,State#information{client = New_Client, time = New_Time}}; 63 | 64 | handle_call({worker_init,{Interface,Host,Port}},_From,State) -> 65 | {Result,Client} = 66 | case Interface:new(Host, Port) of 67 | {ok,Client1} -> 68 | {ok,Client1}; 69 | {error,Reason} -> 70 | {Reason,[]} 71 | end, 72 | {reply, Result,State#information{client = Client,module = Interface,host = Host, port = Port, time = 0}}; 73 | 74 | handle_call(_Request, _From, State) -> 75 | Reply = ok, 76 | {reply, Reply, State}. 77 | 78 | handle_cast(clean,State = #information{client = Client,module = Interface}) -> 79 | Interface:stop(Client), 80 | {noreply,State#information{client = [], host = "", port = 0}}; 81 | 82 | handle_cast(_Msg, State) -> 83 | {noreply, State}. 84 | 85 | handle_info(_Info, State) -> 86 | {noreply, State}. 87 | 88 | terminate(_Reason,_State=#information{client = Client,module = Interface}) -> 89 | Interface:stop(Client), 90 | ok. 91 | -------------------------------------------------------------------------------- /src/poolManager.erl: -------------------------------------------------------------------------------- 1 | %% @author ydwiner 2 | %% @doc @todo Add description to poolManager. 3 | 4 | -module(poolManager). 5 | 6 | %% ==================================================================== 7 | %% API functions 8 | %% ==================================================================== 9 | -export([start/0,create/0,createPool/3,createSubPool/1,etsTimeFun/3]). 10 | -include("../include/lconnect_conf.hrl"). 11 | 12 | %% ==================================================================== 13 | %% Internal functions 14 | %% ==================================================================== 15 | start() -> 16 | register(poolServer, spawn(poolManager, create, [])), 17 | lconnect_ets:createNewTable(self(), ?POOLTABLE_NAME), 18 | poolServer ! {create_pool,self(),?POOL_DYN_NAME,?POOL_DYN_CONNECT_NUM,[]}, 19 | lconnect_ets:setTableReleaseTime(?POOLTABLE_NAME, ?POOL_UPDATE_TIME). 20 | 21 | create() -> 22 | receive 23 | {create_pool,_From,PoolName,NumCon,Option} -> 24 | lconnect:start_link(PoolName, NumCon, Option), 25 | _From ! done, 26 | create(); 27 | {create_sub_pool,_From,Args} -> 28 | lconnect_process_lb:start(Args), 29 | _From ! done, 30 | create() 31 | end. 32 | 33 | createPool(PoolName,NumCon,Option) -> 34 | poolServer ! {create_pool,self(),PoolName,NumCon,Option}, 35 | receive 36 | done -> ok; 37 | _ -> error 38 | end. 39 | 40 | createSubPool(Args) -> 41 | poolServer ! {create_sub_pool,self(),Args}, 42 | receive 43 | done -> ok; 44 | _ -> error 45 | end. 46 | 47 | etsTimeFun(TableName,Time,Fun) -> 48 | releaseTableData(TableName,Time,{first,first_key}, Fun). 49 | 50 | releaseTableData(TableName,Time,{first,_},Fun) -> 51 | Key = ets:first(TableName), 52 | case Key of 53 | '$end_of_table' -> 54 | done; 55 | Key -> 56 | case lconnect_ets:etsTableElementExist(TableName,Key,2) of 57 | true -> 58 | Data_Time = ets:lookup_element(TableName,Key,2), 59 | Now_Time = erlang:system_time(milli_seconds), 60 | if Now_Time - Data_Time > Time -> 61 | Next_Key = ets:next(TableName,Key), 62 | Data = ets:lookup_element(TableName, Key, 3), 63 | Fun(Data), 64 | ets:delete(TableName,Key), 65 | releaseTableData(TableName,Time,{next,Next_Key},Fun); 66 | true -> 67 | Next_Key = ets:next(TableName,Key), 68 | releaseTableData(TableName,Time,{next,Next_Key},Fun) 69 | end; 70 | false -> 71 | faild 72 | end 73 | end; 74 | releaseTableData(TableName,Time,{next,Key},Fun) -> 75 | case Key of 76 | '$end_of_table' -> 77 | done; 78 | Key -> 79 | case lconnect_ets:etsTableElementExist(TableName,Key,2) of 80 | true -> 81 | Data_Time = ets:lookup_element(TableName,Key,2), 82 | Now_Time = erlang:system_time(milli_seconds), 83 | if Now_Time - Data_Time > Time -> 84 | Next_Key = ets:next(TableName,Key), 85 | Data = ets:lookup_element(TableName, Key, 3), 86 | Fun(Data), 87 | ets:delete(TableName,Key), 88 | releaseTableData(TableName,Time,{next,Next_Key},Fun); 89 | true -> 90 | Next_Key = ets:next(TableName,Key), 91 | releaseTableData(TableName,Time,{next,Next_Key},Fun) 92 | end; 93 | false -> 94 | faild 95 | end 96 | end. 97 | -------------------------------------------------------------------------------- /include/gpb.hrl: -------------------------------------------------------------------------------- 1 | -ifndef(gpb_hrl). 2 | -define(gpb_hrl, true). 3 | 4 | -type gpb_scalar() :: 5 | int32 | int64 | uint32 | uint64 | sint32 | sint64 6 | | fixed32 | fixed64 | sfixed32 | sfixed64 7 | | bool 8 | | float | double 9 | | string 10 | | bytes. 11 | 12 | -type gpb_map_key() :: % "any scalar type except floating point types and bytes" 13 | int32 | int64 | uint32 | uint64 | sint32 | sint64 14 | | fixed32 | fixed64 | sfixed32 | sfixed64 15 | | bool 16 | | string. 17 | 18 | %% It is not possible to have maps in maps directly, 19 | %% so this type is any gpb_field_type() except {map,K,V}. 20 | -type gpb_map_value() :: 21 | gpb_scalar() 22 | | {enum,atom()} 23 | | {msg,atom()}. 24 | 25 | -type gpb_field_type() :: %% Erlang type Comment 26 | int32 | int64 % integer() variable-length encoded 27 | | uint32 | uint64 % integer() variable-length encoded 28 | | sint32 | sint64 % integer() variable-length zig-zag encoded 29 | | fixed32 | fixed64 % integer() always 4 | 8 bytes on wire 30 | | sfixed32 | sfixed64 % integer() always 4 | 8 bytes on wire 31 | | bool % true | false 32 | | float | double % float() 33 | | string % string() UTF-8 encoded 34 | % | binary() iff option `strings_as_binaries' 35 | | bytes % binary() 36 | | {enum,atom()} % atom() the enum literal is the atom 37 | | {msg,atom()} % record() the message name is record name 38 | % | map() iff option `maps' 39 | | {map,gpb_map_key(),gpb_map_value()}. % [{K,V}] | map() 40 | 41 | %% An intermediary type temporarily used internally within gpb during parsing, 42 | %% neither returned from gpb, nor accepted as input go gpb. 43 | -type gpb_internal_intermediary_ref() :: 44 | {ref, term()} | 45 | {msg, list()} | 46 | {enum, list()}. 47 | 48 | -type gpb_internal_intermediary_map_ref() :: 49 | {map, gpb_map_key(), gpb_map_value() | gpb_internal_intermediary_ref()}. 50 | 51 | %% The following two definitions (`gpb_field' and `gpb_rpc') are to 52 | %% avoid clashes with other code, since the `field' and `rpc' are 53 | %% really too general names, they should have been prefixed. 54 | %% 55 | %% Unfortunately, they are already part of the API, so they can't 56 | %% be changed without breaking backwards compatibility. 57 | %% (They appear as parameters or return values for functions in `gpb' 58 | %% in generated code.) 59 | %% 60 | %% In case a clash, it is possible to redefine the name locally. 61 | %% The recommendation is to redefine them with prefix, ie to `gpb_field' 62 | %% and `gpb_rpc', since this is what they will change to in some future. 63 | %% 64 | -ifdef(gpb_field_record_name). 65 | -define(gpb_field, ?gpb_field_record_name). 66 | -else. 67 | -define(gpb_field, field). %% odd definition is due to backwards compatibility 68 | -endif. 69 | 70 | -ifdef(gpb_rpc_record_name). 71 | -define(gpb_rpc, ?gpb_rpc_record_name). 72 | -else. 73 | -define(gpb_rpc, rpc). %% odd definition is due to backwards compatibility 74 | -endif. 75 | 76 | -record(?gpb_field, % NB: record name is (currently) `field' (not `gpb_field')! 77 | {name :: atom(), 78 | fnum :: integer(), 79 | rnum :: pos_integer() % field number in the record 80 | | undefined, % temporarily, during parsing 81 | type :: gpb_field_type() | 82 | gpb_internal_intermediary_ref() | 83 | gpb_internal_intermediary_map_ref(), 84 | occurrence :: 'required' | 'optional' | 'repeated', 85 | opts = [] :: [term()] 86 | }). 87 | 88 | -record(gpb_oneof, 89 | {name :: atom(), 90 | rnum :: pos_integer() % field number in the record 91 | | undefined, % temporarily, during parsing 92 | fields :: [#?gpb_field{}] % all fields have the same rnum 93 | }). 94 | 95 | -record(?gpb_rpc, % NB: record name is (currently) `rpc' (not `gpb_rpc')! 96 | {name :: atom(), 97 | input, 98 | output, 99 | input_stream :: boolean(), 100 | output_stream :: boolean(), 101 | opts :: [term()] 102 | }). 103 | 104 | -endif. 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #gPRC Client for Erlang -- Fast version 2 | ******************************* 3 | 4 | ##DESCRIPTION 5 | ------------- 6 | **This is a fast version of gRPC client implementation for the Erlang programming language.** 7 | **The so-called fast version is that this gRPC client has used fast http/2 and long connection.** 8 | 9 | ###Base protocol: 10 | * [gRPC](http://www.grpc.io/ "gRPC") 11 | * [Protocol Buffers](https://developers.google.com/protocol-buffers/docs/overview "Protocol Buffers") 12 | 13 | ###Dependencies: 14 | * [Fast erlang http/2 client](https://github.com/optd-dl/http2-client-erlang-fast "Fast erlang http/2 client") 15 | * [Protocol Buffers for erlang](https://github.com/tomas-abrahamsson/gpb "Protocol Buffers for erlang") 16 | * [Poolboy](https://github.com/devinus/poolboy "Poolboy - A hunky Erlang worker pool factory") 17 | 18 | ###Author: 19 | * **RongCapital(DaLian) information service Ltd.** 20 | 21 | ###Contact us: 22 | * [yangdawei@rongcapital.cn](mailto:yangdawei@rongcapital.cn) 23 | * [jinxin@rongcapital.cn](mailto:jinxin@rongcapital.cn) 24 | * [gaoliang@rongcapital.cn](mailto:gaoliang@rongcapital.cn) 25 | 26 | ##HowTo 27 | ------------------- 28 | ***Refer to:*** 29 | [src/grpc_client.erl](https://github.com/optd-dl/grpc-client-erlang-fast/blob/master/src/grpc_client.erl "grpc_client.erl") 30 | ***e.g.*** 31 | #####helloworld.proto 32 | ```protobuf 33 | package helloworld; 34 | 35 | // The greeting service definition. 36 | service Greeter { 37 | // Sends a greeting 38 | rpc SayHello (HelloRequest) returns (HelloReply) {} 39 | } 40 | 41 | // The request message containing the user's name. 42 | message HelloRequest { 43 | string name = 1; 44 | } 45 | 46 | // The response message containing the greetings 47 | message HelloReply { 48 | string message = 1; 49 | } 50 | ``` 51 | #####grpc_client.erl 52 | ```erlang 53 | %% gRPC server parameters 54 | -define(GRPC_SERVER_IP, "localhost"). 55 | -define(GRPC_SERVER_PORT, 50051). 56 | 57 | %% Protocol Buffers parameters 58 | -define(PROTO_FILE_NAME, helloworld). 59 | -define(PROTO_FILE_SERVICE_NAME, 'Greeter'). 60 | -define(PROTO_FILE_SERVICE_FUNCTION, 'SayHello'). 61 | -define(PROTO_FILE_SERVICE_FUNCTION_PARAMETERS, [{name,"world"}]). 62 | 63 | howto_single_conn() -> 64 | send_request(?GRPC_SERVER_IP, ?GRPC_SERVER_PORT, [ 65 | {module, ?PROTO_FILE_NAME}, 66 | {service,?PROTO_FILE_SERVICE_NAME}, 67 | {function, ?PROTO_FILE_SERVICE_FUNCTION}, 68 | {param, ?PROTO_FILE_SERVICE_FUNCTION_PARAMETERS}]). 69 | 70 | %% After how many times reconnect the gRPC connection 71 | -define(RECONNECT_TIMES, 1000). 72 | 73 | howto_long_conn() -> 74 | %% long connection precondition 75 | lconnect_ets:start(), 76 | poolManager:start(), 77 | %% repeat call 78 | lconnect:post_time(?MODULE, ?GRPC_SERVER_IP, ?GRPC_SERVER_PORT, [ 79 | {module, ?PROTO_FILE_NAME}, 80 | {service,?PROTO_FILE_SERVICE_NAME}, 81 | {function, ?PROTO_FILE_SERVICE_FUNCTION}, 82 | {param, ?PROTO_FILE_SERVICE_FUNCTION_PARAMETERS}], 83 | ?RECONNECT_TIMES), % 1st 84 | lconnect:post_time(?MODULE, ?GRPC_SERVER_IP, ?GRPC_SERVER_PORT, [ 85 | {module, ?PROTO_FILE_NAME}, 86 | {service,?PROTO_FILE_SERVICE_NAME}, 87 | {function, ?PROTO_FILE_SERVICE_FUNCTION}, 88 | {param, ?PROTO_FILE_SERVICE_FUNCTION_PARAMETERS}], 89 | ?RECONNECT_TIMES). % 2nd 90 | ``` 91 | 92 | ##Build 93 | ------------------- 94 | $ ./rebar get-deps 95 | $ ./rebar compile 96 | $ ./deps/gpb/bin/protoc-erl -I. -o src deps/chatterbox/java-server/src/main/proto/helloworld.proto 97 | $ ./rebar compile 98 | 99 | ##Run 100 | ------------ 101 | ***e.g.*** 102 | ###Run Server -- Precondition -- Javal 103 | $ cd deps/chatterbox && ./start-server.sh 104 | #####Notice 105 | ***The first running will take a long time, because need to download gradle and jars.*** 106 | 107 | ###Run Client -- Erlang 108 | $ ./rebar shell 109 | ```erlang 110 | grpc_client:howto_single_conn(). 111 | ``` 112 | ***or*** 113 | ```erlang 114 | grpc_client:howto_long_conn(). 115 | ``` 116 | 117 | ###Use a new proto file 118 | $ ./deps/gpb/bin/protoc-erl -I. -o src ${NEW_PROTO_FILE_PATH} 119 | $ ./rebar compile 120 | 121 | ##Dependencies 122 | ------------------- 123 | ***The following is the environment of author, may not be mandatory dependencies.*** 124 | 125 | * Linux -- CentOS-7-x86_64-Everything-1503-01 126 | * Erlang -- otp 18.1 127 | * Java -- jdk-8u111-linux-x64 128 | * Gradle -- gradle-2.13 (audo download in [start-server.sh](https://github.com/optd-dl/http2-client-erlang-fast/blob/master/start-server.sh "howto.erl")) 129 | * Git -- git x86_64 1.8.3.1 130 | 131 | -------------------------------------------------------------------------------- /src/grpc_client.erl: -------------------------------------------------------------------------------- 1 | %% @author ydwiner and gaoliang 2 | %% @doc @todo Add description to gprc_client. 3 | 4 | -module(grpc_client). 5 | 6 | %% ==================================================================== 7 | %% API functions 8 | %% ==================================================================== 9 | -export([howto_single_conn/0, howto_long_conn/0]). 10 | -compile(export_all). 11 | -include("../include/gpb.hrl"). 12 | 13 | %% gRPC server parameters 14 | -define(GRPC_SERVER_IP, "localhost"). 15 | -define(GRPC_SERVER_PORT, 50051). 16 | 17 | %% Protocol Buffers parameters 18 | -define(PROTO_FILE_NAME, helloworld). 19 | -define(PROTO_FILE_SERVICE_NAME, 'Greeter'). 20 | -define(PROTO_FILE_SERVICE_FUNCTION, 'SayHello'). 21 | -define(PROTO_FILE_SERVICE_FUNCTION_PARAMETERS, [{name,"world"}]). 22 | 23 | %% After how many times reconnect the gRPC connection 24 | -define(RECONNECT_TIMES, 1000). 25 | 26 | %% ==================================================================== 27 | %% Internal functions 28 | %% ==================================================================== 29 | 30 | %% ---------- 31 | %% How to use 32 | %% ---------- 33 | howto_single_conn() -> 34 | send_request(?GRPC_SERVER_IP, ?GRPC_SERVER_PORT, [ 35 | {module, ?PROTO_FILE_NAME}, 36 | {service,?PROTO_FILE_SERVICE_NAME}, 37 | {function, ?PROTO_FILE_SERVICE_FUNCTION}, 38 | {param, ?PROTO_FILE_SERVICE_FUNCTION_PARAMETERS}]). 39 | 40 | howto_long_conn() -> 41 | %% long connection precondition 42 | lconnect_ets:start(), 43 | poolManager:start(), 44 | %% repeat call 45 | R = lconnect:post_time(?MODULE, ?GRPC_SERVER_IP, ?GRPC_SERVER_PORT, [ 46 | {module, ?PROTO_FILE_NAME}, 47 | {service,?PROTO_FILE_SERVICE_NAME}, 48 | {function, ?PROTO_FILE_SERVICE_FUNCTION}, 49 | {param, ?PROTO_FILE_SERVICE_FUNCTION_PARAMETERS}], 50 | ?RECONNECT_TIMES), % 1st 51 | R = lconnect:post_time(?MODULE, ?GRPC_SERVER_IP, ?GRPC_SERVER_PORT, [ 52 | {module, ?PROTO_FILE_NAME}, 53 | {service,?PROTO_FILE_SERVICE_NAME}, 54 | {function, ?PROTO_FILE_SERVICE_FUNCTION}, 55 | {param, ?PROTO_FILE_SERVICE_FUNCTION_PARAMETERS}], 56 | ?RECONNECT_TIMES). % 2nd 57 | 58 | %% ---------- 59 | %% I/Fs 60 | %% ---------- 61 | new(IP,Port) -> 62 | try 63 | h2_client:start_link(http, IP, Port) 64 | catch 65 | _:Reason -> 66 | {error, Reason, erlang:get_stacktrace()} 67 | end. 68 | 69 | call(Pid,InterfaceParam) -> 70 | Module = proplists:get_value(module, InterfaceParam, null), 71 | Service = proplists:get_value(service,InterfaceParam, null), 72 | Fun = proplists:get_value(function,InterfaceParam, null), 73 | Param = proplists:get_value(param, InterfaceParam, ""), 74 | Option = proplists:get_value(option, InterfaceParam, []), 75 | call(Pid,Module,Service,Fun,Param,Option). 76 | 77 | call(Pid,Module,Service,Fun,Param,Option) -> 78 | try 79 | TimeOut = proplists:get_value(timeout, Option, 30000), 80 | {RequestName,ResponseName} = get_msg_def(Module, Service, Fun), 81 | Request = makeRecordTuple(RequestName, Module:find_msg_def(RequestName), Param, []), 82 | Body = Module:encode_msg(Request), 83 | H2Path = get_interface_info(Module, Service, Fun), 84 | RequestHeader = makeH2Header([{path,H2Path} | Option]), 85 | Size = erlang:size(Body), 86 | RequestBody = <<0:8,Size:32,Body/binary>>, 87 | Response = hs_send_request(Pid, RequestHeader, RequestBody,TimeOut), 88 | {ok,parseResponse(Module,ResponseName,Response)} 89 | catch 90 | _:Reason -> 91 | {error, Reason, erlang:get_stacktrace()} 92 | end. 93 | 94 | send_request(IP,Port,InterfaceParam) -> 95 | Module = proplists:get_value(module, InterfaceParam, null), 96 | Service = proplists:get_value(service,InterfaceParam, null), 97 | Fun = proplists:get_value(function,InterfaceParam, null), 98 | Param = proplists:get_value(param, InterfaceParam, ""), 99 | Option = proplists:get_value(option, InterfaceParam, []), 100 | send_request(IP,Port,Module,Service,Fun,Param,Option). 101 | 102 | send_request(IP,Port,Module,Service,Fun,Param) -> 103 | send_request(IP,Port,Module,Service,Fun,Param,[]). 104 | 105 | send_request(IP,Port,Module,Service,Fun,Param,Option) -> 106 | {ok,Pid} = new(IP,Port), 107 | Request = call(Pid,Module,Service,Fun,Param,Option), 108 | stop(Pid), 109 | Request. 110 | 111 | stop(Pid) -> 112 | h2_client:stop(Pid). 113 | 114 | makeH2Header(Option) -> 115 | Method = proplists:get_value(method, Option, "POST"), 116 | Scheme = proplists:get_value(scheme, Option, "http"), 117 | Path = proplists:get_value(path, Option, ""), 118 | Content_type = proplists:get_value(content_type, Option, "application/grpc"), 119 | Te = proplists:get_value(te, Option, "trailers"), 120 | [{<<":method">>,list_to_binary(Method)},{<<":scheme">>,list_to_binary(Scheme)},{<<":path">>,list_to_binary(Path)} 121 | ,{<<"content-type">>,list_to_binary(Content_type)},{<<"te">>,list_to_binary(Te)}]. 122 | 123 | get_interface_info(Module,Service,Fun) -> 124 | lists:append(["/",atom_to_list(Module),".",atom_to_list(Service),"/",atom_to_list(Fun)]). 125 | 126 | get_msg_def(Module,Service,Fun) -> 127 | {rpc,_,Request,Reply,_,_,_} = Module:find_rpc_def(Service,Fun), 128 | {Request,Reply}. 129 | 130 | makeRecordTuple(RecordName,[],_,Result) -> 131 | list_to_tuple([RecordName | lists:reverse(Result)]); 132 | makeRecordTuple(RecordName,RecordStru,DataList,Result) -> 133 | [Stru | RestStru] = RecordStru, 134 | {field,Name,_,_,_,_,_} = Stru, 135 | Data = 136 | case lists:keyfind(Name, 1, DataList) of 137 | false -> 138 | undefine; 139 | {Name,Value} -> 140 | unicode:characters_to_binary(Value,utf8) 141 | end, 142 | makeRecordTuple(RecordName, RestStru, DataList, [Data | Result]). 143 | 144 | parseResult([],_,Result) -> 145 | Result; 146 | parseResult(RecordStru,DataList,Result) -> 147 | [Stru | ResStru] = RecordStru, 148 | {field,Name,_,_,Type,_,_} = Stru, 149 | [Data | RestList] = DataList, 150 | Value = transData(Type,Data), 151 | parseResult(ResStru,RestList,[{Name,Value}|Result]). 152 | 153 | parseResponse(Module,ResponseName,Response) -> 154 | {Reuslt,{_,[ResultRes]}} = Response, 155 | case Reuslt of 156 | ok -> 157 | Size = erlang:size(ResultRes) - 5, 158 | <<_:5/binary,Body:Size/binary>> = ResultRes, 159 | [_|ResponseResult] = tuple_to_list(Module:decode_msg(Body,ResponseName)), 160 | parseResult(Module:find_msg_def(ResponseName), ResponseResult, []); 161 | error -> 162 | {error, ResultRes} 163 | end. 164 | 165 | hs_send_request(CliPid,Headers,Body,TimeOut) -> 166 | StreamId = h2_connection:new_stream(CliPid), 167 | h2_connection:send_headers(CliPid, StreamId, Headers), 168 | h2_connection:send_body(CliPid,StreamId,Body), 169 | receive 170 | {'END_STREAM', StreamId} -> 171 | h2_connection:get_response(CliPid, StreamId) 172 | after TimeOut -> 173 | {error, {reply,[timeout]}} 174 | end. 175 | 176 | transData(Type,Data) -> 177 | case Type of 178 | string -> 179 | Data; 180 | int32 -> 181 | integer_to_list(Data); 182 | int64 -> 183 | integer_to_list(Data); 184 | uint32 -> 185 | integer_to_list(Data); 186 | uint64 -> 187 | integer_to_list(Data); 188 | sint32 -> 189 | integer_to_list(Data); 190 | sint64 -> 191 | integer_to_list(Data); 192 | fixed32 -> 193 | integer_to_list(Data); 194 | fixed64 -> 195 | integer_to_list(Data); 196 | sfixed32 -> 197 | integer_to_list(Data); 198 | sfixed64 -> 199 | integer_to_list(Data); 200 | bool -> 201 | atom_to_list(Data); 202 | enum -> 203 | atom_to_list(Data); 204 | message -> 205 | atom_to_list(Data); 206 | bytes -> 207 | binary_to_list(Data) 208 | end. 209 | -------------------------------------------------------------------------------- /src/lconnect_ets.erl: -------------------------------------------------------------------------------- 1 | -module(lconnect_ets). 2 | -export([start/0,createtable/3,createNewTable/2,createNewTable/3,insertDatas/3,insertData/3, 3 | etsTableIsExist/1,etsTableKeyIsExist/2,deleteTable/1,deleteData/2,deleteAllData/1,updateData/4, 4 | getDataFun/4,getData/2,getData/3,listen/3,listen/2,releaseData/0, 5 | setAutoReleaseParam/1,setAutoOutpuParam/5,stopAutoOutput/1,setConfig/0, 6 | addTableToReleaseList/2,deleteTableFromReleaseList/1,insertDataFun/3, 7 | insertEtsData/2,setTableReleaseTime/2,tableSizeListen/2, 8 | merge_to_tuple/2,find/2,compare/2,makeData/2,getTableSize/1,etsTableElementExist/3,releaseTableData/3]). 9 | 10 | -include("../include/ms_transform.hrl"). 11 | 12 | start() -> 13 | Time = erlang:system_time(milli_seconds), 14 | ConfigListPid = spawn(lconnect_ets,createtable,[self(),configList,[set,public,named_table,{keypos,1}]]), 15 | TableListPid = spawn(lconnect_ets,createtable,[self(),tableList,[set,public,named_table,{keypos,1}]]), 16 | AutoOutputConfigListPid = spawn(lconnect_ets,createtable,[self(),autoOutputConfigList,[set,public,named_table,{keypos,1}]]), 17 | ListenProcessId = spawn(lconnect_ets,listen,[10 * 60 * 1000,releaseData,[]]), 18 | SetConfigProcessId = spawn(lconnect_ets,setConfig,[]), 19 | receive 20 | {tableList,table_create} -> 21 | lconnect_ets:insertEtsData(tableList,[{tableList,Time,TableListPid,0},{configList,Time,ConfigListPid,0},{autoOutputConfigList,Time,AutoOutputConfigListPid,0}]) 22 | end, 23 | receive 24 | {configList,table_create} -> 25 | lconnect_ets:insertDatas(configList,[list_process_id],[ListenProcessId]), 26 | lconnect_ets:insertDatas(configList,[auto_releaseData_param],[[10 * 60 * 1000]]), 27 | lconnect_ets:insertDatas(configList,[setConfig_process_id],[SetConfigProcessId]) 28 | end, 29 | success. 30 | 31 | setConfig() -> 32 | receive 33 | {insert_release_tablelist,TableName,Falg_Time} -> 34 | case lconnect_ets:etsTableKeyIsExist(tableList,TableName) of 35 | false -> 36 | go_on; 37 | true -> 38 | ets:update_element(tableList,TableName,{4,Falg_Time}), 39 | case lconnect_ets:etsTableKeyIsExist(configList,release_tablelist) of 40 | false -> 41 | lconnect_ets:insertDatas(configList,[release_tablelist],[[TableName]]); 42 | true -> 43 | lconnect_ets:insertDatas(configList,[release_tablelist],[[TableName | ets:lookup_element(configList,release_tablelist,3)]]) 44 | end 45 | end; 46 | {delete_release_tablelist,TableList} -> 47 | case lconnect_ets:etsTableKeyIsExist(configList,release_tablelist) of 48 | true -> 49 | List = lconnect_ets:getData(configList,release_tablelist,3), 50 | NewList = List -- TableList, 51 | lconnect_ets:insertDatas(configList,[release_tablelist],[NewList]); 52 | false -> 53 | not_exit 54 | end; 55 | {set_auto_releaseData_param,Do_Time} -> 56 | case lconnect_ets:etsTableElementExist(configList,list_process_id,3) of 57 | true -> 58 | PID = lconnect_ets:getData(configList,list_process_id,3), 59 | PID ! release, 60 | lconnect_ets:insertDatas(configList,[auto_releaseData_param],[Do_Time]), 61 | ListenProcessId = spawn(lconnect_ets,listen,[Do_Time,releaseData,[]]), 62 | lconnect_ets:insertDatas(configList,[list_process_id],[ListenProcessId]) 63 | end; 64 | {set_table_release_time,TableName,Flag_Time} -> 65 | case lconnect_ets:etsTableKeyIsExist(tableList,TableName) of 66 | true -> 67 | ets:update_element(tableList,TableName,{4,Flag_Time}); 68 | false -> 69 | go_on 70 | end 71 | end, 72 | setConfig(). 73 | 74 | -spec listen(integer(),atom(),list()) -> any(). 75 | listen(Time,Fun,ParamList) -> 76 | erlang:send_after(Time,self(),message), 77 | receive 78 | message -> 79 | spawn(lconnect_ets,Fun,ParamList), 80 | lconnect_ets:listen(Time,Fun,ParamList); 81 | release -> 82 | release 83 | end. 84 | 85 | -spec listen(integer(),fun()) -> any(). 86 | listen(Time,Fun) -> 87 | erlang:send_after(Time,self(),message), 88 | receive 89 | message -> 90 | spawn(Fun), 91 | lconnect_ets:listen(Time,Fun); 92 | release -> 93 | release 94 | end. 95 | 96 | merge_to_tuple(List1,List2) -> 97 | case lconnect_ets:compare(List1, List2) of 98 | false -> 99 | false; 100 | true -> 101 | lists:zip(List1, List2) 102 | end. 103 | 104 | -spec find(any(),list()) -> atom(). 105 | find(Name,TableList) -> 106 | lists:member(Name,TableList). 107 | 108 | -spec compare(list(),list()) -> atom(). 109 | compare(List1,List2) -> 110 | if erlang:length(List1) == erlang:length(List2) -> 111 | true; 112 | true -> 113 | false 114 | end. 115 | 116 | -spec makeData(list(),list()) -> any(). 117 | makeData([],[]) -> 118 | []; 119 | makeData(Keys,DataList) -> 120 | [FirstKey | RestKeys] = Keys, 121 | [FirstData | RestData] = DataList, 122 | Time = erlang:system_time(milli_seconds), 123 | Contect = {FirstKey,Time,FirstData}, 124 | [Contect | lconnect_ets:makeData(RestKeys,RestData)]. 125 | 126 | 127 | -spec getTableSize(atom()) -> integer(). 128 | getTableSize(TableName) -> 129 | ets:info(TableName,size). 130 | 131 | -spec etsTableIsExist(atom()) -> atom(). 132 | etsTableIsExist(TableName) -> 133 | lconnect_ets:find(TableName,ets:all()). 134 | 135 | -spec etsTableKeyIsExist(atom(),atom()) -> atom(). 136 | etsTableKeyIsExist(TableName,Key) -> 137 | ets:member(TableName,Key). 138 | 139 | -spec etsTableElementExist(atom(),atom(),integer()) -> atom(). 140 | etsTableElementExist(TableName,Key,Pos) -> 141 | case lconnect_ets:etsTableIsExist(TableName) of 142 | true -> 143 | case etsTableKeyIsExist(TableName,Key) of 144 | true -> 145 | Datas = ets:lookup(TableName,Key), 146 | [Data | _] = Datas, 147 | Length = erlang:length(tuple_to_list(Data)), 148 | if Length < Pos -> 149 | false; 150 | true -> 151 | true 152 | end; 153 | false -> 154 | false 155 | end; 156 | false -> 157 | false 158 | end. 159 | 160 | 161 | createtable(PID,TableName,ParamList)-> 162 | case lconnect_ets:etsTableIsExist(TableName) of 163 | false -> 164 | ets:new(TableName,ParamList), 165 | PID ! {TableName,table_create}; 166 | true -> 167 | PID ! {TableName,table_has_created} 168 | end, 169 | receive 170 | release -> 171 | PID ! {TableName,table_release} 172 | end. 173 | 174 | 175 | -spec createNewTable(pid(), atom()) -> atom(). 176 | createNewTable(PID,TableName) -> 177 | TablePID = spawn(lconnect_ets,createtable,[self(),TableName,[set,public,named_table,{keypos,1}]]), 178 | receive 179 | {TableName,table_create} -> 180 | Time = erlang:system_time(milli_seconds), 181 | lconnect_ets:insertEtsData(tableList,[{TableName,Time,TablePID,0}]), 182 | PID ! {TableName,success}, 183 | success; 184 | {TableName,table_has_created} -> 185 | PID ! {TableName,table_exist}, 186 | TablePID ! release, 187 | table_exist 188 | end. 189 | 190 | createNewTable(PID,TableName,ParamList) -> 191 | TablePID = spawn(lconnect_ets,createtable,[self(),TableName,ParamList]), 192 | receive 193 | {TableName,table_create} -> 194 | Time = erlang:system_time(milli_seconds), 195 | lconnect_ets:insertEtsData(tableList,[{TableName,Time,TablePID,0}]), 196 | PID ! {TableName,success}, 197 | success; 198 | {TableName,table_has_created} -> 199 | PID ! {TableName,table_exist}, 200 | table_exist 201 | end. 202 | 203 | -spec insertEtsData(atom(), tuple() | list()) -> any(). 204 | insertEtsData(TableName,DataList) -> 205 | ets:insert(TableName,DataList). 206 | 207 | -spec insertDatas(atom(), list(),list()) -> any(). 208 | insertDatas(TableName,Keys,Datas) -> 209 | case lconnect_ets:compare(Keys,Datas) of 210 | true -> 211 | case lconnect_ets:etsTableIsExist(TableName) of true -> 212 | ets:insert(TableName,lconnect_ets:makeData(Keys,Datas)); 213 | false -> 214 | notable 215 | end; 216 | false -> 217 | faild 218 | end. 219 | 220 | insertData(TableName,Key,Data) -> 221 | Time = erlang:system_time(milli_seconds), 222 | ets:insert(TableName, {Key,Time,Data}). 223 | 224 | 225 | 226 | -spec deleteTable(atom()) -> atom(). 227 | deleteTable(TableName) -> 228 | case etsTableKeyIsExist(tableList,TableName) of 229 | true -> 230 | TablePID = lconnect_ets:getData(tableList,TableName,3), 231 | TablePID ! release, 232 | ets:delete(tableList,TableName), 233 | success; 234 | false -> 235 | table_not_exit 236 | end. 237 | 238 | -spec deleteData(atom(),atom()) -> atom(). 239 | deleteData(TableName,Key) -> 240 | case etsTableKeyIsExist(tableList,TableName) of 241 | true -> 242 | ets:delete(TableName,Key), 243 | success; 244 | false -> 245 | table_not_exit_or_no_access 246 | end. 247 | 248 | 249 | -spec deleteAllData(atom()) ->atom(). 250 | deleteAllData(TableName) -> 251 | ets:delete_all_objects(TableName). 252 | 253 | -spec updateData(atom(),atom(),list(),list()) -> any(). 254 | updateData(TableName,Key,Poses,Values) -> 255 | case lconnect_ets:compare(Poses, Values) of 256 | false -> 257 | false; 258 | true -> 259 | MaxPos = lists:max(Poses), 260 | case lconnect_ets:etsTableElementExist(TableName, Key, MaxPos) of 261 | false -> 262 | false; 263 | true -> 264 | ets:update_element(TableName, Key, lconnect_ets:merge_to_tuple(Poses, Values)), 265 | true 266 | end 267 | end. 268 | 269 | -spec getDataFun(atom(),atom(),fun(),any()) -> any(). 270 | getDataFun(TableName,Key,Fun,ParamList) -> 271 | case lconnect_ets:etsTableKeyIsExist(TableName,Key) of false -> 272 | Data = Fun(ParamList), 273 | case Data of 274 | couchbase_error -> 275 | insertData(TableName,Key,null), 276 | null; 277 | Data -> 278 | insertData(TableName,Key,Data), 279 | Data 280 | end; 281 | true -> 282 | [{_,Data_Time,Data}] = ets:lookup(TableName,Key), 283 | Flag_Time = case Data of 284 | null -> 60000; 285 | Data -> ets:lookup_element(tableList,TableName,4) end, 286 | Now_Time = erlang:system_time(milli_seconds), 287 | if Flag_Time == 0 -> 288 | Data; 289 | true -> 290 | if Now_Time - Data_Time < Flag_Time -> 291 | Data; 292 | true -> 293 | New_Data = Fun(ParamList), 294 | case New_Data of 295 | couchbase_error -> 296 | insertData(TableName,Key,Data), 297 | Data; 298 | New_Data -> 299 | insertData(TableName,Key,New_Data), 300 | New_Data 301 | end 302 | end 303 | end 304 | end. 305 | 306 | insertDataFun(TableName,Key,DataList) -> 307 | SizeFunPid = lconnect_ets:getData(autoOutputConfigList,TableName,5), 308 | case SizeFunPid of 309 | 0 -> 310 | go_on; 311 | SizeFunPid -> 312 | SizeFunPid ! {self(),size_do}, 313 | receive 314 | go_on -> 315 | go_on 316 | end 317 | end, 318 | %insertEtsData(TableName,DataList). 319 | lconnect_ets:insertData(TableName, Key, DataList). 320 | 321 | -spec getData(atom(),atom()) -> any(). 322 | getData(TableName,Key) -> 323 | ets:lookup(TableName, Key). 324 | 325 | -spec getData(atom(),atom(),integer()) -> any(). 326 | getData(TableName,Key,Pos) -> 327 | case lconnect_ets:etsTableElementExist(TableName,Key,Pos) of 328 | true -> 329 | Data = ets:lookup_element(TableName,Key,Pos), 330 | Data; 331 | false -> 332 | [] 333 | end. 334 | 335 | -spec releaseData() -> any(). 336 | releaseData() -> 337 | case lconnect_ets:etsTableElementExist(configList,release_tablelist,3) of 338 | true -> 339 | Release_Table_List = ets:lookup_element(configList,release_tablelist,3), 340 | lconnect_ets:createNewTable(self(),tempTable), 341 | F = fun(TableName) -> 342 | case lconnect_ets:etsTableIsExist(TableName) of 343 | false -> 344 | case lconnect_ets:etsTableElementExist(tempTable,deleteReleaseConfig,3) of 345 | true -> 346 | Datas = lconnect_ets:getData(tempTable,deleteReleaseConfig,3), 347 | lconnect_ets:insertData(tempTable,[deleteReleaseConfig],[[TableName | Datas]]); 348 | false -> 349 | lconnect_ets:insertData(tempTable,[deleteReleaseConfig],[[TableName]]) 350 | end; 351 | true -> 352 | Time = lconnect_ets:getData(tableList,TableName,4), 353 | if Time == 0 -> 354 | go_on; 355 | true -> 356 | lconnect_ets:releaseTableData(TableName,Time,{first,firstKey}) 357 | end 358 | end 359 | end, 360 | lists:foreach(F,Release_Table_List), 361 | case lconnect_ets:etsTableElementExist(tempTable,deleteReleaseConfig,3) of 362 | true -> 363 | Deleted = lconnect_ets:getData(tempTable,deleteReleaseConfig,3), 364 | SetConfigPID = lconnect_ets:getData(configList,setConfig_process_id,3), 365 | SetConfigPID ! {delete_release_tablelist,Deleted}; 366 | false -> 367 | go_on 368 | end, 369 | lconnect_ets:deleteTable(tempTable); 370 | false -> 371 | no_config 372 | end, 373 | done 374 | . 375 | 376 | -spec releaseTableData(atom(),integer(),tuple()) -> any(). 377 | releaseTableData(TableName,Time,{first,_}) -> 378 | Key = ets:first(TableName), 379 | case Key of 380 | '$end_of_table' -> 381 | done; 382 | Key -> 383 | case lconnect_ets:etsTableElementExist(TableName,Key,2) of 384 | true -> 385 | Data_Time = ets:lookup_element(TableName,Key,2), 386 | Now_Time = erlang:system_time(milli_seconds), 387 | if Now_Time - Data_Time > Time -> 388 | Next_Key = ets:next(TableName,Key), 389 | ets:delete(TableName,Key), 390 | lconnect_ets:releaseTableData(TableName,Time,{next,Next_Key}); 391 | true -> 392 | Next_Key = ets:next(TableName,Key), 393 | lconnect_ets:releaseTableData(TableName,Time,{next,Next_Key}) 394 | end; 395 | false -> 396 | faild 397 | end 398 | end; 399 | releaseTableData(TableName,Time,{next,Key}) -> 400 | case Key of 401 | '$end_of_table' -> 402 | done; 403 | Key -> 404 | case lconnect_ets:etsTableElementExist(TableName,Key,2) of 405 | true -> 406 | Data_Time = ets:lookup_element(TableName,Key,2), 407 | Now_Time = erlang:system_time(milli_seconds), 408 | if Now_Time - Data_Time > Time -> 409 | Next_Key = ets:next(TableName,Key), 410 | ets:delete(TableName,Key), 411 | lconnect_ets:releaseTableData(TableName,Time,{next,Next_Key}); 412 | true -> 413 | Next_Key = ets:next(TableName,Key), 414 | lconnect_ets:releaseTableData(TableName,Time,{next,Next_Key}) 415 | end; 416 | false -> 417 | faild 418 | end 419 | end. 420 | 421 | -spec setAutoReleaseParam(integer()) -> any(). 422 | setAutoReleaseParam(Do_Time) -> 423 | SetConfigPID = lconnect_ets:getData(configList,setConfig_process_id,3), 424 | SetConfigPID ! {set_auto_releaseData_param,Do_Time}, 425 | success 426 | . 427 | 428 | -spec setTableReleaseTime(atom(),integer()) -> atom(). 429 | setTableReleaseTime(TableName,Flag_Time) -> 430 | SetConfigPID = lconnect_ets:getData(configList,setConfig_process_id,3), 431 | SetConfigPID ! {set_table_release_time,TableName,Flag_Time}, 432 | success. 433 | 434 | -spec addTableToReleaseList(atom(),integer()) -> any(). 435 | addTableToReleaseList(TableName,Flag_Time) -> 436 | case lconnect_ets:etsTableIsExist(TableName) of 437 | true -> 438 | SetConfigPID = lconnect_ets:getData(configList,setConfig_process_id,3), 439 | SetConfigPID ! {insert_release_tablelist,TableName,Flag_Time}, 440 | sucess; 441 | false -> 442 | table_not_exit 443 | end. 444 | 445 | -spec deleteTableFromReleaseList(atom()) -> any(). 446 | deleteTableFromReleaseList(TableName) -> 447 | SetConfigPID = lconnect_ets:getData(configList,setConfig_process_id,3), 448 | SetConfigPID ! {delete_release_tablelist,[TableName]}, 449 | success. 450 | 451 | -spec setAutoOutpuParam(atom(),integer(),integer(),fun(),fun()) -> any(). 452 | setAutoOutpuParam(TableName,Time,Size,Fun,SizeFun) -> 453 | SizeFunPid = 454 | case lconnect_ets:etsTableKeyIsExist(autoOutputConfigList,TableName) of 455 | true -> 456 | SizePID = lconnect_ets:getData(autoOutputConfigList,TableName,5), 457 | SizePID ! release, 458 | case Size of 459 | 0 -> 460 | 0; 461 | Size -> 462 | spawn(lconnect_ets, tableSizeListen, [TableName,SizeFun]) 463 | end; 464 | false -> 465 | case Size of 466 | 0 -> 467 | 0; 468 | Size -> 469 | spawn(lconnect_ets, tableSizeListen, [TableName,SizeFun]) 470 | end 471 | end, 472 | if Time == 0 -> 473 | lconnect_ets:insertEtsData(autoOutputConfigList,{TableName,Time,Size,0,SizeFunPid}), 474 | go_on; 475 | true -> 476 | F = fun() -> 477 | case lconnect_ets:etsTableIsExist(TableName) of 478 | true -> 479 | Now_Time = erlang:system_time(nano_seconds) - 5000000, 480 | Data = ets:select(TableName, ets:fun2ms(fun({_,X,Z}) when X * 1000000 < Now_Time -> Z end)), 481 | ets:select_delete(TableName,ets:fun2ms(fun({_,X,_}) when X * 1000000 < Now_Time -> true end)), 482 | Fun(Data); 483 | false -> 484 | table_not_exist 485 | end 486 | end, 487 | case lconnect_ets:etsTableKeyIsExist(autoOutputConfigList,TableName) of 488 | true -> 489 | TimePID = lconnect_ets:getData(autoOutputConfigList,TableName,4), 490 | TimePID ! release, 491 | NewTimePID = spawn(lconnect_ets,listen,[Time,F]), 492 | lconnect_ets:insertEtsData(autoOutputConfigList,{TableName,Time,Size,NewTimePID,SizeFunPid}); 493 | false -> 494 | NewTimePID = spawn(lconnect_ets,listen,[Time,F]), 495 | lconnect_ets:insertEtsData(autoOutputConfigList,{TableName,Time,Size,NewTimePID,SizeFunPid}) 496 | end 497 | end, 498 | SetConfgPid = lconnect_ets:getData(configList,setConfig_process_id,3), 499 | SetConfgPid ! {{delete_release_tablelist,[TableName]}}. 500 | 501 | -spec stopAutoOutput(atom()) -> any(). 502 | stopAutoOutput(TableName) -> 503 | case lconnect_ets:etsTableKeyIsExist(autoOutputConfigList,TableName) of 504 | true -> 505 | TimePID = lconnect_ets:getData(autoOutputConfigList,TableName,4), 506 | SizePID = lconnect_ets:getData(autoOutputConfigList,TableName,5), 507 | case TimePID of 508 | 0 -> 509 | go_on; 510 | TimePID -> 511 | TimePID ! release 512 | end, 513 | case SizePID of 514 | 0 -> 515 | go_on; 516 | SizePID -> 517 | SizePID ! release 518 | end, 519 | ets:delete(autoOutputConfigList,TableName), 520 | SetConfigPid = lconnect_ets:getData(configList,setConfig_process_id,3), 521 | SetConfigPid ! {insert_release_tablelist,TableName,10 * 60 * 1000}; 522 | false -> 523 | no_table_config 524 | end 525 | . 526 | 527 | -spec tableSizeListen(atom(),fun()) -> atom(). 528 | tableSizeListen(TableName,Fun) -> 529 | receive 530 | {PID,size_do} -> 531 | Size = lconnect_ets:getData(autoOutputConfigList,TableName,3), 532 | if Size == 0 -> 533 | PID ! go_on; 534 | true -> 535 | Now_Size = lconnect_ets:getTableSize(TableName), 536 | if Now_Size < Size -> 537 | PID ! go_on; 538 | true -> 539 | Now_Time = erlang:system_time(nano_seconds) - 5000000, 540 | Data = ets:select(TableName, ets:fun2ms(fun({_,X,Z}) when X * 1000000 < Now_Time -> Z end)), 541 | ets:select_delete(TableName,ets:fun2ms(fun({_,X,_}) when X * 1000000 < Now_Time -> true end)), 542 | F = fun() -> 543 | Fun(Data) 544 | end, 545 | spawn(F), 546 | PID ! go_on 547 | end 548 | end, 549 | lconnect_ets:tableSizeListen(TableName,Fun); 550 | {Pid,size} -> 551 | Size = lconnect_ets:getData(autoOutputConfigList,TableName,3), 552 | Pid ! Size, 553 | lconnect_ets:tableSizeListen(TableName,Fun); 554 | release -> 555 | release 556 | end. 557 | --------------------------------------------------------------------------------