使用Ranch搭建自己的TCP连接池

  • 将Ranch集成到自己的项目中
  • 使用ranch
    • 定义ProtoCol
      • 阻塞模式
      • gen_server模型下的非阻塞模式
    • 调用自己定义的ProtoCol

上一篇我们讲了ranch的基础逻辑还看了一个简单的echo例子,这次我们尝试真正使用ranch

将Ranch集成到自己的项目中

集成部分的话参照cowboy使用rebar将ranch配置到自己项目的deps目录中即可,erlang.mk应该也可以,不过我自己没尝试过。。有兴趣的可以自己探索下应该都是共通的。这里就不再赘述集成部分了,ranch本身也说过是包装成一个易集成的结构的。

使用ranch

这里我们假设项目中已经有老的tcp接口了,我们现在换成ranch架构(没有也没关系,差距并不大,比较麻烦的都是消息的解包和登陆逻辑的处理)
这里介绍两种使用方式,一种专门起一个进程让进程阻塞在recv部分,有消息就处理,没就保持阻塞,另外一种就是每次让进程设置socket为{active, once},然后使用{tcp,S, Data} 回调来处理的逻辑,这样可以保持进程不阻塞,这种模式可以适用于gen_server模型,你可以把业务逻辑也放到这个接收进程里,虽然不推荐这样做,下面的两个具体实现都会是将消息转发给业务处理进程而不是由接收进程来处理,所以不会体现业务逻辑部分

定义ProtoCol

阻塞模式

cowboy本身就是一个典型的阻塞模型的例子,Cowboy Git库地址,但是要看懂cowboy的处理逻辑还需要了解一些http底层处理,这里我们用最简单的模型来展示ranch的使用
首先,定义我们的类似echo_protocol的tcp消息处理模块,当然他肯定是一个ranch_protocol描述的模块,也就是这个模块必然提供start_link/4并返回{ok, Pid}

这样就很简单了,我们可以根据上一篇中的echo的例子稍微进行改写,加入消息的解析,解析成自己系统可以识别的就可以了

-module(my_protocol).
-behaviour(ranch_protocol).-export([start_link/4]).
-export([init/4]).-record(state, {accid = 0,buffer = <<>>,pid
}).start_link(Ref, Socket, Transport, Opts) ->Pid = spawn_link(?MODULE, init, [Ref, Socket, Transport, Opts]),{ok, Pid}.init(Ref, Socket, Transport, _Opts = []) ->ok = ranch:accept_ack(Ref),loop(Socket, Transport, #state{}).loop(Socket, Transport, #state{buffer = Buffer} = State) ->case Transport:recv(Socket, 0, 5000) of{ok, Data} ->%%改动部分{MsgList, NewBuffer} = prase_body(<<Buffer/binary, Data/binary>>),NewState = handle_msg(MsgList, State#state{buffer = NewBuffer}),loop(Socket, Transport, NewState);_ ->ok = Transport:close(Socket)end.%%新加逻辑
prase_body(MsgBody) ->prase_body2(MsgBody, []).prase_body2(<<Len:16, MsgBody:Len/binary, Left/binary>>, MsgList) ->prase_body2(Left, [decode_msg(MsgBody)|MsgList]);
prase_body2(MsgBody, MsgList) ->{lists:reverse(MsgList), MsgBody}.handle_msg([{login, accid, AccID}|L], State) ->{ok, Pid} = start_server(AccID),handle_msg(L, State#state{accid = AccID, pid = Pid});
handle_msg([Msg|L], #state{pid = Pid} = State) ->Pid ! Msg,handle_msg(L, State).

基础的这个版本没有解包部分,我们先假设我们的对包协议是用2字节来表示包体的长度Len,然后接下来的长度Len就是具体的打包过的包体,至于包体的打包方式,就多种多样了,可以是json或者protobuf或者其他的,保持自己项目在用的就可以,也就是上面没实现的decode_msg/1,
而start_server的账号登陆部分写的很简陋,根据自己项目进行填充即可。

gen_server模型下的非阻塞模式

-module(my_protocol).
-behaviour(gen_server).
-behaviour(ranch_protocol).%% API.
-export([start_link/4]).%% gen_server.
-export([init/1]).
-export([init/4]).
-export([handle_call/3]).
-export([handle_cast/2]).
-export([handle_info/2]).
-export([terminate/2]).
-export([code_change/3]).-define(TIMEOUT, 5000).-record(state, {socket, transport, buffer, accid, pid}).%% API.start_link(Ref, Socket, Transport, Opts) ->proc_lib:start_link(?MODULE, init, [Ref, Socket, Transport, Opts]).%% gen_server.%% This function is never called. We only define it so that
%% we can use the -behaviour(gen_server) attribute.
init([]) -> {ok, undefined}.init(Ref, Socket, Transport, _Opts = []) ->ok = proc_lib:init_ack({ok, self()}),ok = ranch:accept_ack(Ref),ok = Transport:setopts(Socket, [{active, once}]),gen_server:enter_loop(?MODULE, [],#state{socket=Socket, transport=Transport, buffer = <<>>},?TIMEOUT).handle_info({tcp, Socket, Data}, State=#state{socket=Socket, transport=Transport, buffer = Buffer}) ->Transport:setopts(Socket, [{active, once}]),%% 改动部分{MsgList, NewBuffer} = prase_body(<<Buffer/binary, Data/binary>>),NewState = handle_msg(MsgList, State#state{buffer = NewBuffer}),{noreply, NewState, ?TIMEOUT};
handle_info({tcp_closed, _Socket}, State) ->{stop, normal, State};
handle_info({tcp_error, _, Reason}, State) ->{stop, Reason, State};
handle_info(timeout, State) ->{stop, normal, State};
handle_info(_Info, State) ->{stop, normal, State}.handle_call(_Request, _From, State) ->{reply, ok, State}.handle_cast(_Msg, State) ->{noreply, State}.terminate(_Reason, _State) ->ok.code_change(_OldVsn, State, _Extra) ->{ok, State}.%% Internal.
%% 新加逻辑
prase_body(MsgBody) ->prase_body(MsgBody, []).prase_body(<<Len:16, MsgBody:Len/binary, Left/binary>>, MsgList) ->prase_body(Left, [decode_msg(MsgBody)|MsgList]);
prase_body(MsgBody, MsgList) ->{lists:reverse(MsgList), MsgBody}.handle_msg([{login, accid, AccID}|L], State) ->{ok, Pid} = start_server(AccID),handle_msg(L, State#state{accid = AccID, pid = Pid});
handle_msg([Msg|L], #state{pid = Pid} = State) ->Pid ! Msg,handle_msg(L, State).

emm是的这个例子是抄的例子reverse_protocol,只需要稍微改动下消息体的处理部分和state就可以完成,同样记得实现自己的start_server/1decode_msg/1

调用自己定义的ProtoCol

{ok, _} = ranch:start_listener(my_echo, 10,ranch_tcp, [{port, 5555}], my_protocol, []),

将这句代码加入到项目启动逻辑中即可,同时记得把ranch加入到项目的app.src依赖中,这样就不用主动的调用ranch的启动逻辑了。


这里加入一点杂谈,就是为什么要在已经有“稳定”的tcp框架的情况下,再去费劲改成ranch框架呢,我只谈自己的想法,不一定适用于所有人,大家看看就好

  • 首先呢,我们的另外一个deps库间接调用了ranch库,所以我不得不让ranch存在于我的deps目录,所以既然都没办法裁剪了,干脆就用了。而且ranch的使用项目也非常的多,也就意味着他经过了多个项目多个应用场景的考验,比起自己写的单项目的稳定性和各方面的考虑都是无法比拟的。
  • 另外一个在与ranch的特性,ranch将每一个监听实例用一个ranch_listener_sup来管理,也就意味着一旦你接入了ranch,后续你想再使用tcp接口就相当的廉价了,你只需要像上面一样定义你的消息处理逻辑,之后一句start_litener就可以完成所有的处理,不用再操心断开,重连,数量限制这些各种逻辑,如果以后需要扩展其他的tcp接口,无疑带来了巨大的便利,我们只用重新定义一个模块,而不是重构或者直接copy代码重写一份。
  • 在一个就是将来的新项目架构,你只需要更新ranch到最新版本,就可以支持最新版本的erlang特性和语法,而且是经过检验的何乐而不为呢

使用Ranch搭建自己的TCP连接池相关推荐

  1. 一篇搞懂TCP、HTTP、Socket、Socket连接池

    上一篇:闲鱼面试官:Thread.sleep(0) 到底有什么用?我:有点懵~ 作者:数澜科技 链接:https://www.jianshu.com/p/e47a766e03da 前言:作为一名开发人 ...

  2. 作为后端开发人员应该懂的TCP、HTTP、Socket、Socket连接池,一文详解丨Linux后端开发

    前言:作为一名开发人员我们经常会听到HTTP协议.TCP/IP协议.UDP协议.Socket.Socket长连接.Socket连接池等字眼,然而它们之间的关系.区别及原理并不是所有人都能理解清楚,这篇 ...

  3. springboot——数据层访问搭建 集成Duid连接池

    springboot中默认是使用的tomcat的连接池,如果我们想要第三方的连接池,我们这么配置呢? 首先在application.yml文件中注释掉之前数据库的配置,重新用druid的方式配置: # ...

  4. Jedis连接池:JedisPool及连接池工具类搭建

    文章目录 Jedis连接池 连接池建立步骤 代码案例 JedisPoolUtils工具类 创建配置文件 编写工具类 编写测试代码 Jedis连接池 连接池建立步骤 JedisPool的配置参数大部分是 ...

  5. HttpClient连接池设置引发的一次雪崩

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 来源:http://i7q.cn/50G6cx - 1 - 事件背 ...

  6. HttpClient 连接池设置引发的一次雪崩!

    - 1 - 事件背景 我在凤巢团队独立搭建和运维的一个高流量的推广实况系统,是通过HttpClient 调用大搜的实况服务.最近经常出现Address already in use (Bind fai ...

  7. HttpClient 连接池设置不当引发的一次雪崩

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 作者 | zxcodestudy 来源 | http:/ ...

  8. 糟糕!HttpClient 连接池设置引发的一次雪崩!

    作者:zxcodestudy 来源:blog.csdn.net/qq_16681169/article/details/94592472 凤巢团队独立搭建和运维的一个高流量的推广实况系统,是通过Htt ...

  9. nodejs mysql 创建连接池

    用Nodejs连接MySQL 从零开始nodejs系列文章,将介绍如何利Javascript做为服务端脚本,通过Nodejs框架web开发.Nodejs框架是基于V8的引擎,是目前速度最快的Javas ...

最新文章

  1. Linux根文件系统介绍
  2. django表单提交案例
  3. web前端基础(05htmlimg标签和滚动标签)
  4. 分享一些android的资料 很实用
  5. 蓝桥杯 ALGO-84 算法训练 大小写转换
  6. 【java】抽象类下有两个具体子类,子类下有两个实例
  7. java负零_java数据结构从零基础到负基础
  8. 重庆航天职业技术学院计算机系在哪个校区,2020年重庆航天职业技术学院地址在哪里...
  9. 无线AP 传输、认证
  10. Navigate组件的使用(React Router6)
  11. 计算机专业笔记本用i5还是i7,玩游戏笔记本i5和i7的区别_笔记本电脑游戏用i5还是i7...
  12. wps计算机一级考试,计算机等级考试一级WPS-Office考试大纲
  13. bucket list 函数解析
  14. 人生修煉電影篇之-------------------- 《阿丽塔:战斗天使》
  15. Python+Vue计算机毕业设计青年公寓租房管理系统2b730(源码+程序+LW+部署)
  16. sip客户端源码c语言,SIP客户端选型
  17. 最简单的视音频播放演示样例4:Direct3D播放RGB(通过Texture)
  18. 【软件设计师】历年真题-模糊知识点备忘——15年上 上午真题
  19. 储成才/李家洋/卜庆云团队联合解码东北水稻育种史,助力水稻精准设计育种...
  20. 见事-----人生三境界,您到了哪一个?

热门文章

  1. 野火STM32例程学习笔记
  2. u盘第一扇区 分区表_备份U盘分区表,未雨绸缪
  3. asp.net爱厨房美食菜谱系统
  4. 微信美食菜谱小程序系统毕业设计毕设(2)小程序功能
  5. 【邮件邮箱】点击链接调起微信跳转到公众号、添加好友如何实现?
  6. layui实现下拉多选列表(xm-select.js插件使用,后台添加或修改数据回显提交问题总结)
  7. Audio/Video会触发的方法和事件分别有哪些
  8. php 色彩空间转换,【干货】教你最正确的查色域方式
  9. 达梦数据库课程干货分享LIKE、REGEXP_LIKE、INSTR的正确打开方式
  10. 武汉大学计算机学院硕士英语作文,2016年考研英语作文必背范文:Dreams/ objectives(梦想/目标)...