[Erlang 0097] TCP半开的几个小测试-阿里云开发者社区

开发者社区> 唐玄奘> 正文

[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 :

 

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
【七天入门Go语言】 网络编程 | 第六天
目录 1. Socket 编程 1.1 Dial()函数 2. HTTP 编程 2.1 HTTP 客户端 2.2 HTTP 服务端 2.2.1 处理 HTTP 请求 3. RPC 编程 3.1 Go 语言中的 RPC 支持与处理 3.2 Gob 简介 3.3 设计优雅的 RPC 接口 最后
8 0
Java开发面试避开这些雷,通过率达90%
  Java的要求越来越高了,还是Java面试的要求越来越高了呢?   先说结论,企业对员工Java水平的要求其实并不变态,随着新技术不断涌现,对新晋员工的要求逐年增高是正常现象,但目前Java面试难度整体来说其实是大于实际开发所需的Java水平的,也就造成了“面试造航母,开发拧螺母”的现象。
3 0
Linux学习笔记之防火墙设置
  在上节的笔记中,大家还记得那条防火墙的设置命令么?为什么要使用防火墙呢?Linux 系统中,安全的第一道防线就是它。跟 Windows中防火墙一样,都是要设置端口的开放与关闭。那除了增加的命令之外,还有没有其它的操作呢?是如何实现的?那我们本节进行简单地讲解一下与防火墙有关的笔记内容。   防火墙的配置,在 Centos 6 以前是使用命令iptables,而到了 Centos 7以之后的版本,改成了 firewall-cmd命令,为了大家更方便的理解,我把整个过程分解成(查看状态、增加端口操作、其它安全设置)三个方面给大家讲解,让大家尽可能明白防火墙是如何去配置的。
5 0
进入正在运行状态中的Docker容器
进入正在运行状态中的Docker容器
5 0
在Google Cloud platform上的Kubernetes集群部署HANA Express
在Google Cloud platform上的Kubernetes集群部署HANA Express
4 0
Swarm的基本认知
  Swarm 是分布式存储平台和内容分发服务,是以太坊 web3 栈的本地基础层服务。Swarm 的主要目标是提供充分分散和冗余存储的以太坊公共记录,尤其是存储和分发 DApp 的代码和数据以及区块链数据。从经济角度来看,它允许参与者有效汇集他们的存储容量和带宽资源,以给网络的所有参与者提供这些服务,同时接受以太坊的激励。   目标   Swarm 更广泛的目标,是为去中心化的 web 应用程序(DApp)开发人员提供基础设施服务,特别是:消息传递、数据流、点对点记账、可变资源更新、存储保险、监管扫描和修复、支付渠道和数据库服务。
4 0
linux安装oracle client客户端远程连接数据库
  linux安装oracle client客户端远程连接数据库。   1.到oracle官网下载basic,sqlplus,devel三个软件包   oracle-instantclient11.2-basic-11.2.0.4.0-1.x86_64.tar   oracle-instantclient11.2-sqlplus-11.2.0.4.0-1.x86_64.tar   oracle-instantclient11.2-devel-11.2.0.4.0-1.x86_64.tar   2.到root用户下创建一个oracle文件夹
5 0
一步步把一个SpringBoot应用打包成Docker镜像并运行
一步步把一个SpringBoot应用打包成Docker镜像并运行
5 0
【大数据基础实践】(六)数据仓库Hive的基本操作
目录 1. 数据仓库概念 2. Hive简介 2.1 简介 2.2 特性 2.3 生态系统 3. Hive系统架构 4. HQL转成MapReduce作业的原理 4.1 join的实现原理 4.2 group by的实现原理 5. 实验练习 5.1 环境配置 5.1.1 HIVE 5.1.2 MYSQL 5.1.3 配置MySql为hive元数据存储数据库 5.2 Shell进行实验内容 5.2.1 新建一个数据库; 5.2.2 新建表 5.2.3 添加分区 5.2.4 导入grade_zqc 5.2.5 统计男、女生人数 5.2.6 统计每个学生所有科目的总分以及平均分
6 0
+关注
641
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
文娱运维技术
立即下载
《SaaS模式云原生数据仓库应用场景实践》
立即下载
《看见新力量:二》电子书
立即下载