当前位置: 代码迷 >> 综合 >> Erlang socket编程 基础(一)
  详细解决方案

Erlang socket编程 基础(一)

热度:17   发布时间:2024-01-28 14:10:31.0

前言

发现erlang 网络编程部分的博客写得都挺多的,但比较缺比较全的总结,所以打算写一篇从基础案例到做个游戏网关的文章。erlang 提供了tcp/udp 套接字编程,本系列只讲Tcp相关

 

服务器和客户端工作方式

服务器

首先,我们打开shell调用 gen_tcp:listen (Port,option)进行监听套接字

1> {ok, ListenSocket} = gen_tcp:listen(8088, [binary,{active,true},{packet,0}]).
{ok,#Port<0.508>}
2> {ok,Sokcet} = gen_tcp:accept(ListenSocket).

这里8088是监听端口,binary里表示接收的是二进制串,{active,true}是主动模式,socket收到消息以{tcp, Socket, Data} | {tcp_closed, Socket} 主动投递给进程。,{packet,N},N是包头的长度,以字节为单位,包头会被自动添加和解析,这个参数关系到是否会粘包(事实上socket是没有粘包这种概念的,很多新手因为这个词都会被搞得云里雾里的,TCP本来就是基于字节流而不是消息包的协议,正确的解读应该是应用层协议的解析,这个后面我再讲),N为0时包头长度为0

返回监听套接字后我们需要调用gen_tcp:accept(ListenSocket)等待接收连接,在没有客户端连接之前,是堵塞的,Socket是不会返回的,shell会卡住。用accept/2的话有一个timeout参数 ,超时没有连接会关闭  

客户端

我们打开另外一个shell作为客户端,连接服务器 ,gen_tcp:connect(IP,Port,Option) 

1> {ok,Socket} = gen_tcp:connect({127,0,0,1},8088,[binary,{active,true},{packet,0}]).
{ok,#Port<0.508>}  
2> gen_tcp:send(Socket, "hello world!").
ok  

 

我们打开服务端的shell,查看下是否收到消息

3> flush().
Shell got {tcp,#Port<0.519>,<<"hello world!">>}
ok  

可以看到服务端已经收到消息(主动模式下socket会将消息投递到进程邮箱)  
同样的我们服务端也可以用gen_tcp:send与客户端通信 

 

一个简单的案例

就这样子,socket编程的基本步骤已经讲完了,但是你发现这个案例,服务端只能监听一个连接后进行通信,多个请求时玩锤子啊?能不能改造成可以监听和处理多个请求呢? 比如这样子

我们改造下

%%%-------------------------------------------------------------------
%%% @author admin
%%% @copyright (C) 2019, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 25. 十一月 2019 16:47
%%%-------------------------------------------------------------------
-module(echo2).
-author("admin").%% API
-export([start_server/0, start_client/0
]).-define(TCP_OPTIONS, [binary, {packet, 0}, {active, true}]).
-define(PORT, 8087).start_server()->{ok, LSocket} = gen_tcp:listen(?PORT, ?TCP_OPTIONS),%%创建一个接收器进程 去监听套接字spawn(fun() -> acceptor(LSocket) end).acceptor(LSocket) ->{ok, Socket} = gen_tcp:accept(LSocket),%% 当监听到连接请求时,创建一个新的进程去监听新的请求,本进程处理这个socket通信spawn(fun() -> accept(LSocket) end),handle_socket(Socket).%% 进入循环,读取socket信息 处理
%% 在客户端断开连接时退出。
handle_socket(Socket) ->receive{tcp,Socket,Bin}->io:format(" received binary  = ~p~n",[Bin]),%% do some thinghandle_socket(Socket);{tcp_closed, Socket} ->io:format("close ~n");Msg ->io:format("receive ~p ~n",[Msg]),handle_socket(Socket)end.start_client()->{ok,Socket} = gen_tcp:connect({127,0,0,1},?PORT,?TCP_OPTIONS),gen_tcp:send(Socket, "Hello"),gen_tcp:send(Socket, "223333"),gen_tcp:close(Socket).

打开shell测试一下

1> c(echo2).
{ok,echo2}
2> echo2:start_server().
<0.39.0>
3> echo2:start_client().
okreceived binary  = <<"Hello">>
4>  received binary  = <<"223333">>
4> close
4> echo2:start_client().
okreceived binary  = <<"Hello">>
5>  received binary  = <<"223333">>
5> close

嗯,没毛病我来讲下这个过程,调用start_server()时会堵塞在gen_tcp:accept(LSocket),直到有客户端连接时,服务费返回socket,然后启动一个新进程,该进程职责也是一样的,spawn返回后,接收来自客户端的消息,然后输出,循环这个过程一直到客户端关闭Socket  
细心的同学可能会发现,你这个接收过程是串行的啊,每次接收到一个请求时才创建一个空闲actor(进程)去监听监听套接字,如果是并发太大时效率也太差了吧!是的,我们可以用类似线程池的思想,一开始就创建多个接收器进程,并把它们交给一个监督者进程去管理,或者是采用异步的方式,这个我后面再讲

消息模式

Erlang的socket有3种消息接收模式:active、passive和active once 

1. active(主动消息接收):

非阻塞。当数据到达时系统会socket会向控制进程发送{tcp, Socket, Data}消息。但控制进程无法控制消息流,想象一下如果盲目相信外部所有发送来的数据转化为消息,那么进程的消息队列堆满了一堆数据,严重影响receive的处理;

2. passive(被动消息接收):

阻塞。控制进程必须主动调用recv()来接收消息。可以控制消息流,以下面为例;

打开shell  
1> {ok, LSocket} = gen_tcp:listen(8086, [{active,false}]).
{ok,#Port<0.508>}
2> {ok, Socket} = gen_tcp:accept(LSocket).
再开一个  
1> {ok,Socket} = gen_tcp:connect({127,0,0,1},8086,[{active,false}]).
{ok,#Port<0.508>}
2> gen_tcp:send(Socket,"23333").  
再看下服务端  
{ok,#Port<0.519>}
3> flush().
ok
4> {ok, Data} = gen_tcp:recv(Socket, 0).
{ok,"23333"}  

可以看到进程邮箱没有收到消息,需要调用gen_tcp:recv获取数据, gen_tcp:recv(Socket,Length)是堵塞的,如果没有收到数据之前是不会返回的,Length代表要获取Socket接收缓冲区数据的大小,基本单位为字节,0代表获取所有 

3. active once(单次主动)

在这个模式中,socket是主动的,但是只能接收一条消息。在控制进程发出一条消息之后,他必须明确的调用 inet:setopts(Socket,[{active,once}]) 以便让socket恢复并接收下一条消息。系统在这发生之前会一直阻塞。 使用 {active,once} 选项,用户可以实现高层次的数据流控制(有时叫交通管制),同时又防止了服务器被过多的消息所淹没。 虽然active once是官方推荐的方式,但是我发现游戏服务器这块大部分都是采用被动模式  

{ok,Listen} = gen_tcp:listen(Port,[...,{active,once}...]),
{ok,Socket} = gen_tcp:accept(Listen), loop(Socket). loop(Socket) ->receive {tcp,Socket,Data} -> ... 数据处理 ... %%准备好启用下一条消息时 inet:setopts(Socket,[{active,once}]),loop(Socket);{tcp_closed,Socket} -> ... end.

这章主要讲了socket的基本用法,后面再讲异步接收、粘包以及与java之类的socket通信案例

  相关解决方案