a. 最核心的概念 - socket 控制进程
b. 基本的 C/S 结构的例子(服务器只能处理一个客户端连接)
c. 顺序型服务器的例子(服务器顺序的处理客户端的请求,每次只能处理一个,处理完一个处理下一个)
d. 并发型服务器的例子(服务器并发的处理多个客户端的请求)
e. 控制逻辑 - 主动型消息接收(非阻塞)
f. 控制逻辑 - 被动型消息接收(阻塞)
g. 控制逻辑 - 混合型消息接收(半阻塞)
Server 侧
Client 侧
方式二
【控制逻辑】
{active, true} – 主动 socket - 非阻塞模式
当数据到达系统之后,会向控制进程发送 {tcp, Socket, Data} 的消息,而控制进程无法控制 这些消息(的到来),一个独立的客户端可能向系统发送上万条消息,这些消息都会发送到控制进程,控制 进程无法通过控制停掉这些消息。
{active, false} – 被动 socket - 阻塞模式
如果是被动 socket,则 socket 必须调用 gen_tcp:recv(Socket, N) 来接收数据,它尝试接收 N 字节的数据,如果 N 为 0,那么所有可用的字节都会返回。 默认是 gen_tcp:recv(Socket, N, infinity),即无限等待直到有数据可以接收,所以是 阻塞的模式。
{active, once} – 半主动 socket
会创建一个主动 socket,可以主动接收一条消息,但该 socket 接收一条消息以后,如果打算让它接 收下一条消息,则必须重新激活它。 激活 Socket 的方式如下
【主动型接收 - 非阻塞模式- 异步服务器】
【被动型接收 - 阻塞模式 – 同步服务器】
【混合型模式 - 半同步服务器】
【socket 的出错处理】
b. 基本的 C/S 结构的例子(服务器只能处理一个客户端连接)
c. 顺序型服务器的例子(服务器顺序的处理客户端的请求,每次只能处理一个,处理完一个处理下一个)
d. 并发型服务器的例子(服务器并发的处理多个客户端的请求)
e. 控制逻辑 - 主动型消息接收(非阻塞)
f. 控制逻辑 - 被动型消息接收(阻塞)
g. 控制逻辑 - 混合型消息接收(半阻塞)
【最基本的 Erlang C/S 结构的例子】
- 创建一个 socket 进程(调用 gen_tcp:accept 或 gen_tcp:connect),也就是 socket 的控制进程,这个 socket 接收到的所有数据都转发给控制进程,如果控制进程消亡,socket 也会自行关闭,可以调用 gen_tcp:controlling_process(Socket, NewPid) 来把一个 socket 的控制进程改为新的进程。
- 服务器端和客户端使用的 {packet, N} 的参数必须一致。
- 在接收到一个连接的时候,显式的设置 socket 的属性,是一个好的策略。
1
2
|
{ok, Socket} = gen_tcp:accept(Listen),
inet:setopts(Socket, [{packet, 4}, {active,
true
}, {nodelay,
true
}])
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
-module(server).
-
export
([start
/0
]).
start() ->
{ok, Listen} = gen_tcp:listen(2345, [binary, {packet, 4},
{reuseaddr,
true
},
{active,
true
}]),
{ok, Socket} = gen_tcp:accept(Listen),
gen_tcp:close(Listen),
loop(Socket).
loop(Socket) ->
receive
{tcp, Socket, Bin} ->
io:
format
(
"received: ~p~n"
, [Bin]),
gen_tcp:send(Socket, iolist_to_binary([
"server#"
,Bin])),
loop(Socket);
{tcp_closed, Socket} ->
io:
format
(
"[~p] tcp_closed~n"
, [Socket]);
{tcp_error, Socket, Reason} ->
io:
format
(
"[~p] tcp_error: ~p~n"
, [Socket, Reason])
end.
|
1
2
3
4
5
6
7
8
9
10
11
|
-module(client).
-
export
([
echo
/1
]).
echo
(Data) ->
{ok, Socket} = gen_tcp:connect(
"localhost"
, 2345, [binary, {packet, 4}]),
ok = gen_tcp:send(Socket, Data),
receive
{tcp, Socket, Bin} ->
io:
format
(
"~p~n"
, [Bin]),
gen_tcp:close(Socket)
end.
|
【顺序型服务器的例子】
1
2
3
4
5
6
7
8
9
10
11
12
|
start() ->
{ok, Listen} = gen_tcp:listen(2345, [binary, {packet, 4},
{reuseaddr,
true
},
{active,
true
}]),
seq_accept(Listen).
seq_accept(Listen) ->
{ok, Socket} = gen_tcp:accept(Listen),
loop(Socket),
seq_accept(Listen).
loop(Socket) … 不变
|
【并发型服务器的例子】
方式一
1
2
3
4
5
6
7
8
9
10
11
12
|
start() ->
{ok, Listen} = gen_tcp:listen(2345, [binary, {packet, 4},
{reuseaddr,
true
},
{active,
true
}]),
spawn(fun() -> accept(Listen) end).
accept(Listen) ->
{ok, Socket} = gen_tcp:accept(Listen),
spawn(fun() -> accept(Listen) end),
loop(Socket).
loop(Socket) … 不变
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
start() ->
{ok, Listen} = gen_tcp:listen(2345, [binary, {packet, 4},
{reuseaddr,
true
},
{active,
true
}]),
spawn(fun() -> accept(Listen) end).
accept(Listen) ->
{ok, Socket} = gen_tcp:accept(Listen),
Pid = spawn(fun() -> loop(Socket) end),
gen_tcp:controlling_process(Socket, Pid),
accept(Listen).
loop(Socket) … 不变
|
【控制逻辑】
{active, true} – 主动 socket - 非阻塞模式
当数据到达系统之后,会向控制进程发送 {tcp, Socket, Data} 的消息,而控制进程无法控制 这些消息(的到来),一个独立的客户端可能向系统发送上万条消息,这些消息都会发送到控制进程,控制 进程无法通过控制停掉这些消息。
{active, false} – 被动 socket - 阻塞模式
如果是被动 socket,则 socket 必须调用 gen_tcp:recv(Socket, N) 来接收数据,它尝试接收 N 字节的数据,如果 N 为 0,那么所有可用的字节都会返回。 默认是 gen_tcp:recv(Socket, N, infinity),即无限等待直到有数据可以接收,所以是 阻塞的模式。
{active, once} – 半主动 socket
会创建一个主动 socket,可以主动接收一条消息,但该 socket 接收一条消息以后,如果打算让它接 收下一条消息,则必须重新激活它。 激活 Socket 的方式如下
1
|
inet:setopts(Socket, [{active, once}])
|
【主动型接收 - 非阻塞模式- 异步服务器】
1
2
3
4
5
6
7
8
9
10
11
|
{ok, Listen} = gen_tcp:listen( ..., {active,
true
}, ... ),
{ok, Socket} = gen_tcp:accept(Listen),
loop(Socket).
loop(Socket) ->
receive
{tcp, Socket, Data} ->
...;
{tcp_closed, Socket} ->
...
end.
|
【被动型接收 - 阻塞模式 – 同步服务器】
1
2
3
4
5
6
7
8
9
10
11
12
|
{ok, Listen} = gen_tcp:listen( ..., {active,
false
}, ... ),
{ok, Socket} = gen_tcp:accept(Listen),
loop(Socket).
loop(Socket) ->
case
gen_tcp:recv(Socket, 0) of %% 这里有说法
{ok, Data} ->
...
loop(Socket);
{error, closed} ->
...
end.
|
【混合型模式 - 半同步服务器】
1
2
3
4
5
6
7
8
9
10
11
12
13
|
{ok, Listen} = gen_tcp:listen( ..., {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 的出错处理】
- 每个 socket 都对应一个控制进程,如果控制进程消亡,则 socket 也会自动关闭;
- 如果服务器端因为逻辑上的原因发生崩溃,那么服务器端的 socket 会自动关闭,同时客户端也会收到 {tcp_closed, Socket} 的消息。