- 浏览: 333125 次
- 性别:
- 来自: 北京
最新评论
-
perfect_control:
真的很详细,一些东西很容易被我忽略掉了
使用fprof进行性能分析 -
leeyisoft:
http://www.erlangqa.com/ 怎么变成 “ ...
Erlang问答网站,欢迎各位提出问题,解答问题。 -
simsunny22:
4年之后我才看到 慢慢的干货
Erlang服务器内存耗尽bug跟踪过程 -
爱死我:
...
使用etop查看系统中进程信息 -
宋兵甲:
在跑这个服务的时候,每秒建立一个客户端连接,连续建立10000 ...
自己写一个tcp 通用服务器
我们想写这样一个tcp server,其绑定本地某个端口,用户可以接入实现特定的业务,比如一个傻傻的echo server,一个帮助服务器等等。。毫无疑问这个tcp的框架是相同的,想想我们一直以来怎么写tcp server:
创建socket -> 绑定端口 -> listen监听 -> accept tcp 连接 -> 处理业务 -> 关闭连接。中间可能会有多线程或者线程池等等不同的实现方式。
在erlang的世界,我们还是需要绑定端口,接受连接,处理业务,关闭连接,但是我们没有什么线程,锁的烦恼。我们为每个连接建立一个process,处理业务。因为erlang的process轻量,高效,成千上万。
我们用gen_sever behaviour来实现这个通用的tcp server,gen_server其实内部包含一个大循环,如果我们的tcp server再gen_server的循环中调用gen_tcp:accept/1,那么我们会阻塞gen_server,这肯定行不通。那么只有将 gen_tcp:accept放到一个独立的process中了。
我们的generic_server中定义了一个record:
这个record用来记录服务器的相关信息,注释已经相当清楚。
接下来让我们实现gen_server的init/1函数(如果对gen_server不熟悉,请参看以前文章,或者erlang官方文档),这个部分非常关键:
创建listen 端口成功时,我们调用accept/1函数:
accept_loop照此实现:
看明白了么?
我们就是在accept里生成一个新的process,其执行accept_loop函数,在accept_loop中调用 gen_tcp:accpet/1接收新的tcp连接,从而达到不阻塞gen_server主循环的目的!看看accept_loop,我们收到一个连接时,判断是否达到最大连接数,如果达到我们则发送给client一个消息,随后关闭这个sock;正常情况下,我们首先同志gen_server一个新的连接加入,这样gen_server就通过accept创建一个新的process,来处理gen_tcp:accept/1函数,接收用户请求。而当前的process来处理具体的业务逻辑,比如一个echo server。
为什么这么做呢?因为通过调用gen_tcp:accept/1创建的Socket所在的process成为此Socket的controlling process,如果其关闭我们的socket也会关闭!所以我们便把这个process留给这个连接,让他处理具体的逻辑:M:F(Sock),注意这里相当于一个会调的函数,具体的我们要实现什么server,就需要实现什么逻辑。
还有一个情况,我们要限定并发总数,因此在gen_tcp:accept/1中,我们通过判断是否达到最大连接数而决定是否关闭这个新接入的socket,这样就可以限定连接总数。
好的,让我们看看,我实现的一个echo_server:
十几行代码就解决了问题!首先调用generic_server:start启动我们的tcp server,我们将其命名为echo_server,我们还指定了绑定端口,最大连接数,逻辑处理回调函数。
以后只要有用户连接成功server,就会进入我们的loop循环,我们在这里可以做的逻辑,我们这里只是接收用户的数据,然后将数据原封不动的返回给client,这就实现了一个echo server。需要说明的是,我们的tcp server在创建的时候指定了{active, false}选项,需要手动调用gen_tcp:recv/2接收数据,如果收到{error, closed},表明socket已经被client关闭,我们调用gen_server:cast通知并发数减一。
最后写一个测试的tcp client,其连接我们的echo server,发送数据。
下面编译所有的模块,实验一下吧:
启动两个erl控制台:A server;B client
在A erl控制台启动server:
B erl控制台运行:
A 控制台显示:
继续...
B:
A:
关闭某个socket:
B:
A:
创建socket -> 绑定端口 -> listen监听 -> accept tcp 连接 -> 处理业务 -> 关闭连接。中间可能会有多线程或者线程池等等不同的实现方式。
在erlang的世界,我们还是需要绑定端口,接受连接,处理业务,关闭连接,但是我们没有什么线程,锁的烦恼。我们为每个连接建立一个process,处理业务。因为erlang的process轻量,高效,成千上万。
我们用gen_sever behaviour来实现这个通用的tcp server,gen_server其实内部包含一个大循环,如果我们的tcp server再gen_server的循环中调用gen_tcp:accept/1,那么我们会阻塞gen_server,这肯定行不通。那么只有将 gen_tcp:accept放到一个独立的process中了。
我们的generic_server中定义了一个record:
-record(server_state, { port, % 监听的端口 loop, % 具体的逻辑处理循环 ip=any, % 绑定的ip lsocket=null, % 监听socket conn=0, % 当前的连接数 maxconn % 最大的连接数 }).
这个record用来记录服务器的相关信息,注释已经相当清楚。
接下来让我们实现gen_server的init/1函数(如果对gen_server不熟悉,请参看以前文章,或者erlang官方文档),这个部分非常关键:
%% gen_server初始化,创建监听socket init(State = #server_state{port=Port}) -> case gen_tcp:listen(Port, ?TCP_OPTIONS) of {ok, LSocket} -> {ok, accept(State#server_state{lsocket=LSocket})}; {error, Reason} -> {stop, {create_listen_socket, Reason}} end.
创建listen 端口成功时,我们调用accept/1函数:
%% 生成一个新的process,用来accept tcp接入,同时返回初始化State accept(State =#server_state{lsocket=LSocket, loop=Loop, conn=Conn, maxconn=Max}) -> proc_lib:spawn(generic_server, accept_loop, [self(), LSocket, Loop, Conn, Max]), State.
accept_loop照此实现:
%% 接收新的tcp连接 accept_loop(Server, LSocket, {M, F}, Conn, Max) -> {ok, Sock} = gen_tcp:accept(LSocket), if Conn + 1 > Max -> io:format("reach the max connection~n"), gen_tcp:close(Sock); true -> gen_server:cast(Server, {accept_new, self()}), M:F(Sock) end.
看明白了么?
我们就是在accept里生成一个新的process,其执行accept_loop函数,在accept_loop中调用 gen_tcp:accpet/1接收新的tcp连接,从而达到不阻塞gen_server主循环的目的!看看accept_loop,我们收到一个连接时,判断是否达到最大连接数,如果达到我们则发送给client一个消息,随后关闭这个sock;正常情况下,我们首先同志gen_server一个新的连接加入,这样gen_server就通过accept创建一个新的process,来处理gen_tcp:accept/1函数,接收用户请求。而当前的process来处理具体的业务逻辑,比如一个echo server。
为什么这么做呢?因为通过调用gen_tcp:accept/1创建的Socket所在的process成为此Socket的controlling process,如果其关闭我们的socket也会关闭!所以我们便把这个process留给这个连接,让他处理具体的逻辑:M:F(Sock),注意这里相当于一个会调的函数,具体的我们要实现什么server,就需要实现什么逻辑。
还有一个情况,我们要限定并发总数,因此在gen_tcp:accept/1中,我们通过判断是否达到最大连接数而决定是否关闭这个新接入的socket,这样就可以限定连接总数。
好的,让我们看看,我实现的一个echo_server:
-module(echo_server). -export([start/2, loop/1]). %% @spec start(Port::integer(), Max::integer()) -> ServerRet %% @doc 启动echo server start(Port, Max) -> generic_server:start(echo_server, Port, Max, {?MODULE, loop}). %% @spec loop(Sock::port()) %% @doc 处理echo_server中用户的请求 loop(Sock) -> case gen_tcp:recv(Sock, 0) of {ok, Data} -> gen_tcp:send(Sock, Data), loop(Sock); {error, closed} -> io:format("client sock close~n"), gen_server:cast(echo_server, {connect_close, self()}) end.
十几行代码就解决了问题!首先调用generic_server:start启动我们的tcp server,我们将其命名为echo_server,我们还指定了绑定端口,最大连接数,逻辑处理回调函数。
以后只要有用户连接成功server,就会进入我们的loop循环,我们在这里可以做的逻辑,我们这里只是接收用户的数据,然后将数据原封不动的返回给client,这就实现了一个echo server。需要说明的是,我们的tcp server在创建的时候指定了{active, false}选项,需要手动调用gen_tcp:recv/2接收数据,如果收到{error, closed},表明socket已经被client关闭,我们调用gen_server:cast通知并发数减一。
最后写一个测试的tcp client,其连接我们的echo server,发送数据。
-module(tcp_client). -export([start/1, send_data/2, close/1]). start(Port) -> {ok, Socket} = gen_tcp:connect("127.0.0.1", Port, [binary, {packet, raw}, {active, true}, {reuseaddr, true}]), Socket. send_data(Socket, Data) when is_list(Data) orelse is_binary(Data) -> gen_tcp:send(Socket, Data), receive {tcp, Socket, Bin} -> io:format("recv ~p~n", [Bin]); {tcp_closed, Socket} -> io:format("remote server closed!~n") end. close(Socket) when is_port(Socket) -> gen_tcp:close(Socket).
下面编译所有的模块,实验一下吧:
> c(generic_server). {ok, generic_server} > c(echo_server). {ok, echo_server} > c(tcp_client). {ok, tcp_client}
启动两个erl控制台:A server;B client
在A erl控制台启动server:
1> echo_server:start(1234, 4). max connection is 4 {ok,<0.31.0>}
B erl控制台运行:
1>Sock = tcp_client:start(1234). #Port<0.140>
A 控制台显示:
current connect:1
继续...
B:
2>Sock2 = tcp_client:start(1234). #Port<0.141> 3> Sock3 = tcp_client:start(1234). #Port<0.142> 4> Sock4 = tcp_client:start(1234). #Port<0.143> 4> Sock5 = tcp_client:start(1234). #Port<0.144>
A:
current connect:2 current connect:3 current connect:4 reach the max connection
关闭某个socket:
B:
> tcp_client:close(Sock4). ok
A:
> client sock close current connect:3
- generic_server.zip (1.9 KB)
- 下载次数: 255
评论
4 楼
宋兵甲
2014-03-25
在跑这个服务的时候,每秒建立一个客户端连接,连续建立100000个,发现有10000多个Socket连接建立超时。不知道是什么原因。
3 楼
falood
2013-03-25
仔细看了一下,貌似有点 bug,accept_loop 函数在链接数超出限制后,就会停止工作了,不再接收新的请求,即使有链接被释放。
在 gen_tcp:close(Sock); 之后重新启动 accept_loop 可以解决这个问题,但 Conn 和 Max 无法与 gen_server 的 status 同步,还在纠结中,期待楼主的解决方案。
在 gen_tcp:close(Sock); 之后重新启动 accept_loop 可以解决这个问题,但 Conn 和 Max 无法与 gen_server 的 status 同步,还在纠结中,期待楼主的解决方案。
2 楼
wrj913
2012-01-06
M:F(Sock),什么时候执行
1 楼
suchuan19890730
2011-10-26
写的真的很好。对我帮助很大。我现在在想,写的gen_sever的服务器,当接收到connection的时候,spawn一个 FSM process 来处理这个connection连接 会不会更好,因为毕竟在tcp传输的时候,并不是已建立连接就会trasmite data 还要经过handshake等等 才会最终开始传输数据的,您说不是吗? 不知道您对此有何看法,期待您的回复。
发表评论
-
Erlang问答网站,欢迎各位提出问题,解答问题。
2012-03-18 15:07 5228平时收到很多关于Erlang的问题,我都尽量一一解答,可是时间 ... -
Emakefile并行编译
2011-11-17 13:15 7598项目代码越来越多,使用erlang编译也越来越慢。无论是Mak ... -
Erlang服务器内存耗尽bug跟踪过程
2011-10-25 21:44 21709本文描述朋友Erlang服务器内存耗尽bug的解决过程 ... -
inet:getstat/2小用法
2011-04-27 09:32 4546inet:getstat/2的用处 在 ... -
Erlang游戏开发-协议
2011-04-22 16:10 10673Erlang游戏开发-协议 ... -
Gearman Erlang Client
2010-10-17 21:14 3677Gearman Gearman是一个通用的任务调度框架。 ... -
ECUG归来
2010-10-17 21:02 2936今天ECUG V圆满结束了,不知不觉作为讲师已经参加过3次大会 ... -
gen-erl-app快速生成erlang app 框架
2010-04-07 14:22 3949经常需要创建各种erlang app,这个过程一旦掌握,就很繁 ... -
erl-redis发布
2010-03-30 11:44 5759最近几天因为需要,实现了一个redis erlang clie ... -
用Erlang做了很多事
2010-01-19 14:08 5036因为工作及时间关系,最近比较忙碌,没有太多的时间写文章。 ... -
ecug topic - erlang开发实践
2009-11-11 10:04 3713从ecug归来,感觉不错,大家学习探讨的积极性很高哦。 很高 ... -
reltool用户指南
2009-11-02 22:27 6293说明,最近比较忙,没有太多时间更新blog,请各位朋友谅解. ... -
Erlang定时任务server (仿crontab语法)
2009-09-23 18:03 6288好久不写blog了,看到yufeng老大那么活跃,我也“耐不住 ... -
Erlang进程之错?
2009-07-27 15:06 3652前阵子erlang-china关于erla ... -
CNode指南
2009-07-27 14:13 3285好久不发文章,因为工作太忙。这个东西就凑凑数吧。各位见谅。 ... -
Erlang类型及函数声明规格
2009-06-08 22:41 9476Erlang类型及函数声明 ... -
使用etop查看系统中进程信息
2009-05-29 13:57 6095Erlang提供了丰富的开发工具,你认为没有的时候,很可能是你 ... -
创建gen_server组解决单process瓶颈
2009-05-27 17:05 5208并发和顺序是一个令人 ... -
又有人投入Erlang的怀抱了:37Signals Campfire loves Erlang
2009-05-14 23:00 3577就喜欢看这样的东西... This is so juicy ... -
list random shuffle实现
2009-05-07 13:41 4318在项目中需要对list进行随机shuffle,但是在erlan ...
相关推荐
使用Socket实现的异步TCP服务器、同步TCP服务器、异步UDP服务器、同步UDP服务器 and 使用TcpListener和UdpClient实现的异步TCP服务器
用于通用的基于TCP协议的数据转发,支持多代理服务器。
TCP和MODBUS-TCP通讯调试软件V1.2_Wince_Windows_通用版调试测试工具,能方便编写针对Modbus-tcp连接的服务器和客户端程序
修改一次最多读写寄存器个数120个。 4.增加WIN7等高版本系统支持。 5.欢迎交流,指正,本软件免费。 软件运行要求: 1.WINCE 系统上可以直接运行。 2.WINXP 系统上没装 VS2005 或更高版的 VS 软件的用户,可以到...
QT5 串口调试助手 环境:WIN7/WIN8/WIN10, MinGW-64 bits,已测试完成,串口工具实现了自动刷新串口,16...QT5 TCP 服务器,是 1 对 1 的,目前只支持一个客户端连接。但也实现了简单的通讯功能,16进制收发,显示发送~
写这些的动机是为了使用nginx作为一个通用的TCP服务器框架,所以叫ngx tcp。ngx tcp.jpg说明了这个框架。大部分代码是从nginx邮件模块修改而来的。我开发了一个名为的应用协议TCP 上的命令协议。 命令协议格式 0 1 ...
通用 TCP 服务器 通用 TCP 服务器( gen_tcp_server ) 是一种 Erlang 行为,提供快速简便的方法将 TCP 服务器功能添加到您的应用程序。 它被实现为管理 TCP 连接的主管,因为它是孩子。如何使用它? 运行make来构建。...
C#中通用TCP/IP客户/服务器通信库源代码(附聊天程序实例)
一个简单的TCP服务器框架,通用性强,上手快,稍作修改即可用于工程
Modbus/TCP 协议已经成为一通用工业网络应用层的标准,在 2004 年 成为国家标准。有了它,不同厂商生产的控制设备可以连成工业网络,进行集中监控。 ZLG/ModbusTCP 协议栈是广州致远电子有限司开发的基于TCP/IP ...
是的matlab代码iSpindel通用TCP服务器 该版本现已过时,不再开发。 对于当前版本,请遵循以下分支: (iSpindle.py版本1.6.3) 新(06/20/2019)合并avollkopf的fork。 非常感谢! 文档将很快完全更新。 新(04/28/...
3.7 一个子网的例子 33 3.8 ifconfig命令 35 3.9 netstat命令 36 3.10 IP的未来 36 3.11 小结 37 第4章 ARP:地址解析协议 38 4.1 引言 38 4.2 一个例子 38 4.3 ARP高速缓存 40 4.4 ARP的分组格式 40 4.5 ARP举例 41...
您还可以在一个脚本中创建多个TCP服务器。 此UDF需要AutoIt beta 3.3.15.0,因为它广泛使用了该语言的新地图功能。 服务器创建和配置 _TCPSrv_Create :创建一个TCP服务器。 $ sIp,$ iPort:侦听地址(请参阅TCP...
基本实现原理: 1)粘包和拆包采用帧头里面加长度信息来做; 2)超时机制:采用一个单独的线程来判断,每次收到数据...配套有一个C#客户端(vs2008工程,全部源代码),可以向服务器发送数据和接收服务器返回的数据。
基于Nodejs开发的TCP通用物联网设备网关程序源码(已集成支持多种Modbus协议设备)+运行说明.zip 基于nodejs开发的TCP通用物联网设备网关程序,目前已集成支持多种Modbus协议的设备;设备包含:多种类型的传感器(新...
一个基于棋牌、MMORPG游戏的分布式java游戏服务器,理论上可以无限水平扩展网关服,大厅服、游戏服达到人数承载。实现了集群注册中心,网关、登陆、后台服务器监控等通用服务器;封装了redis集群、mongodb等数据库...
3.7 一个子网的例子 33 3.8 ifconfig命令 35 3.9 netstat命令 36 3.10 IP的未来 36 3.11 小结 37 第4章 ARP:地址解析协议 38 4.1 引言 38 4.2 一个例子 38 4.3 ARP高速缓存 40 4.4 ARP的分组格式 40 4.5 ARP举例 41...
3.7 一个子网的例子 33 3.8 ifconfig命令 35 3.9 netstat命令 36 3.10 IP的未来 36 3.11 小结 37 第4章 ARP:地址解析协议 38 4.1 引言 38 4.2 一个例子 38 4.3 ARP高速缓存 40 4.4 ARP的分组格式 40 4.5 ARP举例 41...
3.7 一个子网的例子 33 3.8 ifconfig命令 35 3.9 netstat命令 36 3.10 IP的未来 36 3.11 小结 37 第4章 ARP:地址解析协议 38 4.1 引言 38 4.2 一个例子 38 4.3 ARP高速缓存 40 4.4 ARP的分组格式 40 4.5 ARP举例 41...