[Erlang 0097] TCP半开的几个小测试

简介:
  TCP半开的几个测试,比较简单都在Erlang Shell中完成.立此存照,备忘.
 
 
  gen_tcp提供了shutdown来实现这个功能,下面官方文档中提到了{exit_on_close,false}参数,如果要实现半开,无论是Read Write都要添加这个参数. 官网文档  Doc Ref
 
 
shutdown(Socket, How) -> ok | {error, Reason}    

Types:

Socket = socket()
How = read | write | read_write
Reason = posix()
Immediately close a socket in one or two directions.

How == write means closing the socket for writing, reading from it is still possible.

To be able to handle that the peer has done a shutdown on the write side, the {exit_on_close, false} option is useful.

 

shutdown read

 
复制代码
%% Server
 
Eshell V5.9  (abort with ^G)
1>  {ok,S0}=gen_tcp:listen(5678,[]).   
{ok,#Port<0.506>}
2> {ok,S2}=gen_tcp:accept(S0).
{ok,#Port<0.512>}
3> flush().
Shell got {tcp,#Port<0.512>,
               [104,105,44,121,111,117,32,104,97,118,101,32,110,101,119,32,
                109,115,103,32,0]}
ok
4> gen_tcp:shutdown(S2,read).
ok
5> flush().
Shell got {tcp_closed,#Port<0.512>}
ok
6> gen_tcp:send(S2,"are you alive?").
{error,closed}
7>


%% Client

Eshell V5.9  (abort with ^G)
1> {ok,S1}= gen_tcp:connect("localhost",5678,[]).
{ok,#Port<0.511>}
2> gen_tcp:send(S1,"hi,you have new msg \0").
ok
3> gen_tcp:send(S1,"hi,you have new msg \0").  %% 发送这条消息导致server端 接受到tcp_closed的消息
ok
4> gen_tcp:send(S1,"hi,you have new msg \0").  %% 服务器端宕掉之后再发送消息 Client也会出现{error,closed}错误
{error,closed}
 
复制代码

 

Client添加一下选项 {exit_on_close, false} 试试
 
复制代码
%% Server
 
Eshell V5.9  (abort with ^G)
1>  {ok,S0}=gen_tcp:listen(5678,[]).
{ok,#Port<0.506>}
2>  {ok,S2}=gen_tcp:accept(S0).
{ok,#Port<0.512>}
3>  gen_tcp:send(S2,"are you alive?").
ok
4> flush().
Shell got {tcp,#Port<0.512>,
               [104,105,44,121,111,117,32,104,97,118,101,32,110,101,119,32,
                109,115,103,32,0]}
ok
5> gen_tcp:shutdown(S2,read).  
ok
6>  inet:getstat(S2).  %%这时候检查一下Socket的状态
{ok,[{recv_oct,21},
     {recv_cnt,1},
     {recv_max,21},
     {recv_avg,21},
     {recv_dvi,0},
     {send_oct,14},
     {send_cnt,1},
     {send_max,14},
     {send_avg,14},
     {send_pend,0}]}
7>  gen_tcp:send(S2,"are you alive?====").  %% 由于server只关闭了read 所以发送消息还可以发送出去
ok
8> flush().                   %% Client尝试发送消息
Shell got {tcp_closed,#Port<0.512>}
ok
9>  inet:getstat(S2).
{error,einval}
10>
 
%% Client
 
Eshell V5.9  (abort with ^G)
1>  {ok,S1} = gen_tcp:connect("localhost",5678,[{exit_on_close, false} ]).
{ok,#Port<0.511>}
2> gen_tcp:send(S1,"hi,you have new msg \0").
ok
3> flush().
Shell got {tcp,#Port<0.511>,"are you alive?"}
ok
4> flush().                     %% 服务器端只关闭了read 还可以发送消息 这条消息还可以收到
Shell got {tcp,#Port<0.511>,"are you alive?===="}
ok
5> gen_tcp:send(S1,"hi,you have new msg \0").  %% Client向Server发送消息 会导致Server收到tcp_closed消息
ok
6> gen_tcp:send(S1,"hi,you have new msg \0").
{error,closed}
7>
复制代码

 

 

shutdown write

 
  下面的测试关注点是shutdown write,过程和上面类似:

复制代码
%%Server

Eshell V5.9  (abort with ^G)
1>  {ok,S0}=gen_tcp:listen(5678,[]).
{ok,#Port<0.506>}
2> {ok,S2}=gen_tcp:accept(S0).
{ok,#Port<0.512>}
3> flush().
Shell got {tcp,#Port<0.512>,
               [104,105,44,121,111,117,32,104,97,118,101,32,110,101,119,32,
                109,115,103,32,0]}
ok
4>  gen_tcp:send(S2,"are you alive?").
ok
5> gen_tcp:shutdown(S2,write). %% 执行完这一句 检查Socket已经是einval
ok
6>  inet:getstat(S2).    
{error,einval}
7>


%%Client

Eshell V5.9  (abort with ^G)
1>  {ok,S1}= gen_tcp:connect("localhost",5678,[]).
{ok,#Port<0.511>}
2> gen_tcp:send(S1,"hi,you have new msg \0").
ok
3> flush().
Shell got {tcp,#Port<0.511>,"are you alive?"}
ok
4> gen_tcp:send(S1,"hi,you have new msg \0").
{error,closed}
5>
复制代码

 

调整实验 添加 exit_on_close选项 
 
复制代码
%%Server
 
Eshell V5.9  (abort with ^G)
1> {ok,S0}=gen_tcp:listen(5678,[]).
{ok,#Port<0.506>}
2>  {ok,S2}=gen_tcp:accept(S0).
{ok,#Port<0.512>}
3> flush().
Shell got {tcp,#Port<0.512>,
               [104,105,44,121,111,117,32,104,97,118,101,32,110,101,119,32,
                109,115,103,32,0]}
ok
4>  gen_tcp:send(S2,"are you alive?").
ok
5> gen_tcp:shutdown(S2,write).
ok
6>   inet:getstat(S2).  %% 对比上面的测试结果 这里是正常的状态
{ok,[{recv_oct,21},
     {recv_cnt,1},
     {recv_max,21},
     {recv_avg,21},
     {recv_dvi,0},
     {send_oct,14},
     {send_cnt,1},
     {send_max,14},
     {send_avg,14},
     {send_pend,0}]}
7> flush().                %% 由于只关闭了写,读还是正常的,接受到的消息
Shell got {tcp,#Port<0.512>,
               [104,105,44,121,111,117,32,104,97,118,101,32,110,101,119,32,
                109,115,103,32,50,32,0]}
ok
8>  gen_tcp:send(S2,"are you alive?").  %% 关闭了写  这里的调用就会报错了
{error,closed}
9>   inet:getstat(S2).
{error,einval}
10>
 
 
%% Client
 
Eshell V5.9  (abort with ^G)
1>  {ok,S1} = gen_tcp:connect("localhost",5678,[{exit_on_close, false} ]).
{ok,#Port<0.511>}
2>  gen_tcp:send(S1,"hi,you have new msg \0").
ok
3> flush().
Shell got {tcp,#Port<0.511>,"are you alive?"}
ok
4>  gen_tcp:send(S1,"hi,you have new msg 2 \0").
ok
5>
复制代码

 

 

gen_tcp shutdown

 
看看gen_tcp 的shutdown的逻辑:
 
复制代码
shutdown(S, How) when is_port(S) ->
    case inet_db:lookup_socket(S) of
           {ok, Mod} ->
               Mod:shutdown(S, How);
           Error ->
               Error
    end.
复制代码

 

 inet_db:lookup_socket(S) 的结果是什么?在Shell里面测试一下:
 
Eshell V5.9  (abort with ^G)
1>  {ok,S0}=gen_tcp:listen(5678,[]).
{ok,#Port<0.506>}
2> inet_db:lookup_socket(S0).
{ok,inet_tcp}
3>

 

|-->  inet_tcp

%%
%% Shutdown one end of a socket
%%
shutdown(Socket, How) ->
    prim_inet:shutdown(Socket, How).

 

|--> prim_inet.erl
 
复制代码
shutdown(S, read) when is_port(S) ->
    shutdown_2(S, 0);
shutdown(S, write) when is_port(S) ->
    shutdown_1(S, 1);
shutdown(S, read_write) when is_port(S) ->
    shutdown_1(S, 2).
 
shutdown_1(S, How) ->
    case subscribe(S, [subs_empty_out_q]) of
     {ok,[{subs_empty_out_q,N}]} when N > 0 ->
         shutdown_pend_loop(S, N);   %% wait for pending output to be sent
     _Other -> ok
    end,
    shutdown_2(S, How).

shutdown_2(S, How) ->
    case ctl_cmd(S, ?TCP_REQ_SHUTDOWN, [How]) of
     {ok, []} -> ok;
     {error,_}=Error -> Error
    end.

shutdown_pend_loop(S, N0) ->
    receive
     {empty_out_q,S} -> ok
    after ?INET_CLOSE_TIMEOUT ->
         case getstat(S, [send_pend]) of
                {ok,[{send_pend,N0}]} -> ok;
                {ok,[{send_pend,N}]} -> shutdown_pend_loop(S, N);
          _ -> ok
         end
    end.
复制代码

 

  上面处理考虑的相当周全,如果shutdown的时候有消息还没有发送完成,就会先完成暂存数据的发送,霸爷有一篇很详细的分析:   gen_tcp调用进程收到{empty_out_q, Port}消息奇怪行为分析
 
 

 

 最后小图一张 Natalie Portman :

 

目录
相关文章
|
5月前
|
网络协议 安全 测试技术
手撕测试tcp服务器效率工具——以epoll和io_uring对比为例
手撕测试tcp服务器效率工具——以epoll和io_uring对比为例
106 2
|
2月前
|
网络协议 网络安全 Python
电脑中 TCP/UDP 端口是否开放的测试:令人意想不到的神奇策略等你发现!
【8月更文挑战第19天】在网络管理和维护中,常需确认TCP/UDP端口是否开放以确保服务运行顺畅。端口如同计算机对外通信的“门”,TCP提供可靠连接,UDP则快速但无连接。测试端口是否开放的方法多样:可用`telnet`测试TCP端口,如`telnet localhost 80`;UDP测试较复杂,可用`nc`工具,如`nc -u -z localhost 53`。此外,也有在线工具可供选择,但需确保其安全性。
52 1
|
2月前
|
网络协议 Windows
在电脑上测试TCP/UDP端口是否开放,还是得网络大佬这招厉害!
在电脑上测试TCP/UDP端口是否开放,还是得网络大佬这招厉害!
|
4月前
|
缓存 网络协议 Ubuntu
ubuntu 网卡网速测试bondnetperf测试优化tcp
ubuntu 网卡网速测试bondnetperf测试优化tcp
100 3
|
4月前
|
网络协议 Linux Windows
测试端口是否开放 tcp端口 udp端口 测试服务器端口连通性
测试端口是否开放 tcp端口 udp端口 测试服务器端口连通性
74 0
|
网络协议 测试技术
软件测试|TCP三次握手四次挥手
软件测试|TCP三次握手四次挥手
153 0
软件测试|TCP三次握手四次挥手
|
网络协议 Linux 网络安全
2022-渗透测试-信息收集-Metasploit(基于TCP协议)
2022-渗透测试-信息收集-Metasploit(基于TCP协议)
2022-渗透测试-信息收集-Metasploit(基于TCP协议)
|
网络协议 安全 测试技术
【TCP/IP】【测试】常用发流软件一览
【TCP/IP】【测试】常用发流软件一览
1134 0
|
网络协议 测试技术
软件测试面试题:tcp 和 udp 的区别?
软件测试面试题:tcp 和 udp 的区别?
103 0
|
网络协议 测试技术 网络架构
软件测试面试题:OSI的各个层?tcp/udp位于哪一层?tcp/udp的优缺点?
软件测试面试题:OSI的各个层?tcp/udp位于哪一层?tcp/udp的优缺点?
119 0
下一篇
无影云桌面