【原创】导读”淘宝褚霸关于 gen_tcp 的分享“

本文纯粹作为学习淘宝褚霸关于 gen_tcp 所分享内容的摘录,方便记忆和后续翻阅。原文详细内容请移步 这里 。 

========= 我是美丽的分割线 =========== 

0.《 Erlang gen_tcp相关问题汇编索引 》 

1.《 Erlang版TCP服务器对抗攻击解决方案 》 
本文列举了 TCP 服务器各种可能被攻击的情况,有网友给出了可能的解决办法。 

2.《 gen_tcp接受链接时enfile的问题分析及解决 》 

知识点: 
a.确定 EMFILE 的含义; 
b.查看系统本身资源设置情况; 

?


1

2

3

$ uname -r

$ cat /proc/sys/fs/file-nr

$ ulimit -n

c.通过 stap 脚本验证是否为操作系统本身的问题; 
d.通过 gdb attach 到相应进程查看运行时设置 

?


1

(gdb) p erts_max_ports

e.给出 Erlang 服务器性能调优的几个关键的参数,参考 http://www.ejabberd.im/tuning 。 

3.《 gen_tcp连接半关闭问题 》 

知识点: 当 tcp 对端调用 shutdown(RD/WR) 时候, 宿主进程默认将收到 {tcp_closed, Socket} 消息,导致宿主端 socket 被强制关闭,可以通过设置 inets:setopts(Socket, [{exit_on_close, false}]). 来避免。 

4.《 gen_tcp容易误用的一点解释 》 

知识点:产生 {error,einval} 错误的原因是由于 {active, true} 和 gen_tcp:recv 混用。 

5.《 未公开的gen_tcp:unrecv以及接收缓冲区行为分析 》 

知识点: 
a.分析了 gen_tcp 接收缓冲区的工作原理以及影响大小的因素,还顺便介绍了 unrecv 的用途。 
b.推荐阅读 misultin(小型的 erlang web 服务器)中对于 socket 的处理,里面基于 packet 类型和 active 模式,并利用 erts 已有的协议进行包分析。 

6.《 gen_tcp如何限制封包大小 》 

介绍在主动模式和被动模式两种情况下对封包的处理原则: 
a.当 {active, false} + {packet, raw} 情况下,通过 gen_tcp:recv(Socket, Length) 中的 Length 来对封包大小进行限制,若 Length 为 0,则等同于由对端来对封包大小进行限制。同时 gen_tcp:recv 所使用的接收缓冲区存在最大限制,即 TCP_MAX_PACKET_SIZE(64M)。 
b.在 {active, true} 情况下,默认不限制包长度。但可以通过 inet:setopts(Socket, Options) 中的 {packet_size, Integer} 选项进行控制。 

文档说明如下: 

{packet_size, Integer}(TCP/IP sockets) Sets the max allowed length of the packet body. If the packet header indicates that the length of the packet is longer than the max allowed length, the packet is considered invalid. The same happens if the packet header is too big for the socket receive buffer. 

For line oriented protocols (line,http*), option packet_size also guarantees that lines up to the indicated length are accepted and not considered invalid due to internal buffer limitations.

7.《 gen_tcp调用进程收到{empty_out_q, Port}消息奇怪行为分析 》 

知识点: 
a.描述了 erts 内部 inet_drv 工作原理,其中涉及数据暂存到 port 驱动发送队列的情况,以及对 gen_tcp:close 或者 gen_tcp:shutdown 行为的影响。 
b.促使我梳理了一遍 socket 相关代码处理的层次。 
c.指出容易触发收到 {empty_out_q, Port} 消息的条件:a) 对端先关闭的时候;b) 我端是被动接收;c) socket 打开 {exit_on_close, true} 和 {delay_send,true} 这二个选项的时候最容易发生。 
d.最后给出了能够产生该现象的测试程序(值得学习);ss 命令的使用。 

8.《 gen_tcp发送缓冲区以及水位线问题分析 》 

知识点: 
a.erlang 中拥有消息队列的实体:a) 进程; b) port 。并且 erlang VM 对 port 和 erlang 进程一视同仁的进行公平调度的。 

b.我们知道 ! 符号是 erlang:send 的语法糖,当执行 Port ! msg 或者 Pid ! msg 时,最终都是调用 erlang:send 来发送消息。但在后续版本中,erlang 的设计者专门为 port 设计了 erlang:port_command 系列函数用于 port 上发送消息。 

erlang:send 的调用层次(本人整理): 

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

erlang:send/3

   |

send_3  ->  (erts\emulator\beam\bif.c)

   |

do_send/5  -> (erts\emulator\beam\bif.c)

   |

erts_port_command  -> (当 is_internal_port(to) == true 的情况)

   |

erts_port_output ->  (erts\emulator\beam\io.c)

   |

call_driver_outputv -> (调用(*drv->outputv)((ErlDrvData) prt->drv_data, bufp, size))

   |

tcp_inet_commandv -> (erts\emulator\drivers\common\inet_drv.c  -- 驱动层)

   |

tcp_sendv

   |

sock_sendv

   |

writev  -> (linux 下)

c.水位线的作用:和每个消息队列一样,为了防止发送者和接收者能力的失衡,通常都会设置高低水位线来保护队列不至于太大把系统撑爆。{high_watermark, Size},{low_watermark, Size} 就是干这个用的。水位线的特点:a) 水位线设置是可以继承的;b) 高低水位线默认是 8K/4K ;c) 进入高水位后,port 进入 busy 状态;当消息消耗到小于低水位线,busy 解除。 

d.port 的行为:当消息量达到高水位线的时候,port 进入 busy 状态,这时候会把发送进程 suspend 起来,等消息达到低水位线的时候,解除 busy 状态,同时让发送进程继续执行。 

e.一个 port 进入 busy 状态会产生什么样的问题?这个状态通常很严重,发送进程被挂起,会引起很大的 latency 。可以通过 erlang:system_monitor(MonitorPid, Options) -> MonSettings 来获取发生 busy_port 的进程,从而知道哪个进程进程由于碰到高水位线被挂起了,方便后续调整水位线避免这种情况的发生。 

f.tcp_sendv 的行为: 
a)检查 port 驱动队列中是否存在数据,若已存在数据,则直接将当前数据放入 port 驱动队列尾部,在判定总数据长度是否超出 port 驱动队列的高水位线,若超出高水位线,则设置当前 tcp_descriptor 中的相应值,以表明当前 port 已处于 busy 状态,同时记录下 busy_caller 对象,用于后续回复。若未超出,则什么也不做,仅通过 inet_reply_ok 告之 {inet_reply, S, ok} 。而 port 驱动队列中的数据,则会等到 epoll 发现有可写事件时,通过调用 tcp_inet_drv_output 来发送(内部还是调用 sock_sendv)。 
b)若检测 port 驱动队列时发现原本无数据,则先检测是否设置了 delay_send 标志,若设置了,则不去调用 sock_sendv 发送,而是之间调用 driver_enqv 将数据放入 port 驱动队列后,再执行 sock_select(...,(FD_WRITE|FD_CLOSE),...) 来检测可写事件。若未设置 delay_send 标志,则调用 sock_sendv 进行数据发送,若数据被全部发送出去,则通过 inet_reply_ok 告之 {inet_reply, S, ok} ;否则通过调用 driver_enqv(ix, ev, n) 来跳过成功写出去的字节数(即下次从剩下的字节位置开始发送)。最后执行 sock_select(...,(FD_WRITE|FD_CLOSE),...) 来检测可写事件。 

g.delay_send 的行为:在第一阶段不尝试发送数据,而是直接把数据推入 port 的消息队列去,等后面 epoll 说 socket 可写的时候再一起发送出去。好处是 gen_tcp:send 马上就可以返回,因为 sock_send 通常要耗费几十 us 的时间,可用在对发送的 latency 很敏感的场合。 

h.port 中的高低水位线:当执行 gen_tcp:send 而数据无法通过网络发送出去的时候,会暂时保留在 port 的消息队列里面,当消息队列满(到高水位线)的时候,port 就会 busy,抑制发送者推送更多的数据。当 epoll 探测到 socket 可写的时候,vm 会调用 tcp_inet_output 把消息队列里面的数据,发送到网络上去。在这个过程中,队列里面的数据会越来越少,少到低水位线的时候,则解除 port 的 busy 状态,好让发送者发送更多的数据。 

i.新的 VM 引入了 port 的 parallelism(R16B的发布note里面和port相关的重大变化)。而 port 的并行发送行为默认是关闭的(为了和过去的版本兼容),但是可以用 +spp 全局打开;在 open_port 的时候可以通过参数 {parallelism, true} 来个别打开这个选项。 

j.msgq_watermark 的产生原因:前面分析过,每个 port 的消息队列都有高低水位线来控制,总能保证消息在一定的量。但在开启了 parallelism 了后,当 port 在忙着做 call_driver_outputv 的时候,其他进程就不等了,直接把消息加引用计数保存到一个地方去,然后请求 port 调度器稍后调度执行这个消息,它就立即返回了。如果不做控制的话,每个进程都会积累很多消息,都等着 port 调度器后续执行。所以 port 调度器就有义务来为这部分消息做水位线的控制,这就很自然的引入了 msgq_watermark 选项。 

9.《 gen_tcp接收缓冲区易混淆概念纠正 》 

知识点: 
a.Erlang 的每个 TCP 网络链接是由相应的 gen_tcp 对象来表示的,说白了就是个 port 。 
b.给出 gen_tcp 接收包的流程:并给出 INET_LOPT_BUFFER 值选取的依据。 
c.inets:getstat(Socket, [recv_avg]). 可以帮统计到平均包大小。 

10.《 gen_tcp发送进程被挂起起因分析及对策 》 

知识点: 
a.如果系统中有大量的 tcp 链接要发送数据,基于 gen_tcp:send 默认行为这种方式有点低效。所以很多系统把这个动作改成集中提交数据,集中等待回应。例如在 rabbit_writer.erl 中的实现。在正常情况下,这种处理会大大提高进程切换的开销,减少等待时间。但是也会带来问题,我们看到 port_command 这个操作如果出现意外,被阻塞了,那么这个系统的消息发送会被卡死。 

b.gen_tcp 的默认高低水位线分别为 8K/4K 。可以通过 inet:getopts(Sock,[high_watermark, low_watermark]). 获取当前的设置。 

c.erlang 的 tcp port 驱动是支持 ERL_DRV_FLAG_SOFT_BUSY 的。其含义如下 

Marks that driver instances can handle being called in the output and/or outputv callbacks even though a driver instance has marked itself as busy (see set_busy_port()). Since erts version 5.7.4 this flag is required for drivers used by the Erlang distribution (the behaviour has always been required by drivers used by the distribution).

d.在 do_port_command 代码中可以看到:a) 如果设置了 force 标志,但是 port 驱动不支持 ERL_DRV_FLAG_SOFT_BUSY,要返回 EXC_NOTSUP 错误;而 elrang 的 port 驱动支持 ERL_DRV_FLAG_SOFT_BUSY 的,所以如果 force 的话,数据会被写入缓冲区;b) 如果设置了 NOSUSPEND,但是 port 已经 busy 了,则返回 false,表明发送失败。若未设置 NOSUSPEND,就把发送进程 suspend,同时告诉 system_monitor 系统现在有 port 进入 busy_port 了。 

e.port 是和进程一样公平调度的。进程是按照 reductions 为单位进行调度的,port 是把发送的字节数折合成 reductions 来调度的。所以如果一个进程发送大量的 tcp 数据,那么这个进程不会一致得到执行,运行期会强制其停止一段时间, 让其他 port 有机会执行。 

f.port 的调度时间片是从宿主的进程的时间片里面扣的。每个读写占用 200 个时间片,而每个进程初始分配 2000 个时间片,也就是说做 10 次输出就要被调度了。因为 gen_tcp 在发送数据时需要占用宿主进程的 reductions,这也可能造成宿主进程被挂起,在设计的时候尽量避免一个进程拥有太多的 port 。 

g.如果 gen_tcp 发送进程不能阻塞,那么就添加 force 标志,强行往缓冲区加入数据,同时设置 {send_timeout, Integer} ;如果该 socket 在指定的时间内无法把数据发送完成,那么就直接宣告 socket 发送超时,避免了潜在的 force 加数据造成的缓冲区占用大量内存而出现问题。 

上面分析过,gen_tcp 数据的发送需要占用宿主进程的 reds,这也可能造成宿主进程被挂起,在设计的时候尽量避免一个进程拥有太多的 port. 

11.《 gen_tcp:send的深度解刨和使用指南(初稿) 》 

a. gen_tcp:send  调用层次(本人整理): 

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

gen_tcp:send

     |

inet_tcp:send

|

prim_inet:send

     |

erlang:port_command  -> (将数据推到 ERTS 中)

     |

erts_internal:port_command

     |

erts_internal_port_command_3 -> (erts\emulator\beam\erl_bif_port.c -- port 层)

     |

erts_port_output ->  (erts\emulatro\beam\io.c)

     |

call_driver_output(v) -> (调用(*drv->output(v))((ErlDrvData) prt->drv_data, bufp, size))

     |

tcp_inet_command(v) -> (erts\emulator\drivers\common\inet_drv.c  -- 驱动层)

     |

tcp_send(v)

在 inet_drv 内部会为每个 socket 都设置一个消息队列,用于保持上层推来的消息。这个消息队列有上下水位线。当消息的字节数目超过了高水位线的时候,inet_drv 就把 socket 标志为 busy。这个 busy 要到队列的字节数少于低水位线的时候才解除。 

关键数据结构: 

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

typedef struct {

    inet_descriptor inet;       /* common data structure (DON'T MOVE) */

    int   high;                 /* high watermark */

    int   low;                  /* low watermark */

    int   send_timeout;         /* timeout to use in send */

    int   send_timeout_close;   /* auto-close socket on send_timeout */

    int   busy_on_send;         /* busy on send with timeout! */

    int   i_bufsz;              /* current input buffer size (<= bufsz) */

    ErlDrvBinary* i_buf;        /* current binary buffer */

    char*         i_ptr;        /* current pos in buf */

    char*         i_ptr_start;  /* packet start pos in buf */

    int           i_remain;     /* remaining chars to read */

    int           tcp_add_flags;/* Additional TCP descriptor flags */

    int           http_state;   /* 0 = response|request  1=headers fields */

    inet_async_multi_op *multi_first;/* NULL == no multi-accept-queue, op is in ordinary queue */

    inet_async_multi_op *multi_last;

    MultiTimerData *mtd;        /* Timer structures for multiple accept */

} tcp_descriptor;

 

struct _erl_drv_port {

    ErtsPTabElementCommon common; /* *Need* to be first in struct */

 

    ErtsPortTaskSched sched;

    ErtsPortTaskHandle timeout_task;

#ifdef ERTS_SMP

    erts_mtx_t *lock;

    ErtsXPortsList *xports;

    erts_smp_atomic_t run_queue;

#else

    erts_atomic32_t refc;

    int cleanup;

#endif

    erts_atomic_t connected;    /* A connected process */

    Eterm caller;    /* Current caller. */

    erts_smp_atomic_t data; /* Data associated with port. */

    Uint bytes_in;   /* Number of bytes read */

    Uint bytes_out;  /* Number of bytes written */

 

    ErlIOQueue ioq;              /* driver accessible i/o queue */

    DistEntry *dist_entry;       /* Dist entry used in DISTRIBUTION */

    char *name;          /* String used in the open */

    erts_driver_t* drv_ptr;

    UWord drv_data;

    SWord os_pid;                /* Child process ID */

    ErtsProcList *suspended;     /* List of suspended processes. */

    LineBuf *linebuf;            /* Buffer to hold data not ready for

   process to get (line oriented I/O)*/

    erts_atomic32_t state;   /* Status and type flags */

    int control_flags;   /* Flags for port_control()  */

    ErlDrvPDL port_data_lock;

 

    ErtsPrtSD *psd;  /* Port specific data */

    int reds; /* Only used while executing driver callbacks */

};

b.可以在 prim_inet:send 的实现中看到,当执行 erlang:port_command 时返回 true 后,其后续使用了 receive 来获取来自 ERTS 的反馈,此处为同步动作,对发送大量的消息的场合很不利。更好的做法是手工把 gen_tcp 的 2 个步骤分开做,a) 不停的 erlang:port_command/3 且最好加上 force 标志;b) 被动等待 {inet_reply,S,Status} 消息。具体实现可参考 hotwheels 或者 rabbitmq 项目的代码。 

c.分析 erlang:port_command 返回 false 的原因:a) port 驱动不支持 soft_busy, 但是我们用了 force 标志;b) port 驱动已经 busy 了, 但是我们不允许进程挂起。 

d.分析 tcp:send 在虚拟机执行这个层面上,调用者进程被挂起的几种可能原因:a) 数据成功推到 ERTS, 等待 ERTS 的发送结果通知,这是大多数情况;b) 该 socket 忙, 并且我们没有设定 port_command 的 force 标志;c) 调用者进程发送了大量的数据,时间片用完被运行期挂起。失败的可能原因:我们设定了 nosuspend 标志,但是 socket 忙。 

12.《 Erlang open_port极度影响性能的因素 》 

知识点: 
a.Erlang 的 port 相当于系统的 I/O,打开了 Erlang 世界通往外界的通道,可以很方便的执行外部程序。 
b.open_port 一个外部程序的时候流程大概是这样的:beam.smp 先 vfork,子进程调用 child_setup 程序做进一步的清理操作。清理完成后才真正 exec 我们的外部程序。 
c.在支持 vfork 的系统下,比如说 linux,除非禁止,默认会采用 vfork 来执行 child_setup 来调用外部程序。而执行 vfork 的时候 beam.smp 整个进程会被阻塞,所以这里是个很重要的性能影响点。 
参考 vfork 文档: 

?


1

vfork() differs from fork() in that the parent is suspended until the child makes a call to execve(2) or _exit(2). The child shares all memory with its parent, including the stack, until execve() is issued by the child. The child must not return from the current function or call exit(), but may call _exit().

d.在 erl_child_setup.c 代码中可以看到存在遍历所有打开句柄并将其关闭的的动作,而对于一个繁忙的 I/O 服务器来讲,会打开大量的句柄,可能都有几十万,关闭这么多的句柄会是个灾难。 

e.设计个 open_port 的场景,服务器打开 768 个 socke 句柄,再运行 cat 外部程序。并使用 stap 进行性能分析。 

?


1

2

3

4

5

6

7

8

9

$ cat demo.erl

-module(demo).

-compile(export_all).

  

start()->

    _ = [gen_udp:open(0) || _ <- lists:seq(1,768)],

    Port = open_port({spawn, "/bin/cat"}, [in, out, {line, 128}]),

    port_close(Port),

    ok.

f.解决方案: 
a) 改用 fork 避免阻塞 beam.smp,erl -env ERL_NO_VFORK 1 ; 
b) 减少文件句柄,如果确实需要大量的 open_port ,让另外一个专注的节点来做。 

13.《 Erlang集群RPC通道拥塞问题及解决方案 》 

知识点: 
a.erlang 的消息发送是透明的,只要调用 Pid!Msg ,虚拟机和集群的基础设施会保证消息到达指定的进程的消息队列,这个是语义方面的保证。那么如果该 Pid 是在别的节点,这个消息就会通过节点间的 rpc 通道来传递。 
b.目前社区推比较推荐 erlang 服务分层,所以层和层之间的交互基本上透过 rpc 来进行的。分层结构越来越多,当大量的消息在节点间流动的话,势必会造成通道拥塞。阻塞会导致发送进程被挂起,而 rpc 是单进程(gen_server)的,被挂起,rpc 调用就废了。当然除了 RPC,Pid!Msg 这种方式还是可以并行的走的。 
c.当我们收到 {monitor, SusPid, busy_dist_port, Port} 消息的时候,就可以确认系统经常有阻塞问题(例如在 riak_sysmon 中的使用)。 
d.可以通过修改 dist_buf_busy_limit 的值来解决。默认情况下,其值是 1M,一般情况下是够用了,但如果你的 rpc 没设计好,常常会返回大量的数据,这个值就可能不够了。 

文档一 

?


1

2

3

erlang:system_info(Item :: dist_buf_busy_limit) -> integer() >= 0

dist_buf_busy_limit

Returns the value of the distribution buffer busy limit in bytes. This limit can be set on startup by passing the +zdbbl command line flag to erl.

文档二 

?


1

2

+zdbbl size

Set the distribution buffer busy limit (dist_buf_busy_limit) in kilobytes. Valid range is 1-2097151. Default is 1024.

14.《 Erlang新添加选项 +zerts_de_busy_limit 控制节点间通讯的数据量 》 

知识点: 
a.erlang 节点间通信默认是通过 tcp 通道进行的, 而且每对节点间只有一个 tcp 链接,所有的 rpc 和内置的类似 monitor 这样的消息也都是通过这个通道进行的。当数据量过大的时候,系统就会发出 busy distribution port 警告,同时限制数据的吞吐。这个值默认是 128k。 
b.可以通过 erl +zerts_de_busy_limit size 来修改这个值。 

Set the value of erts_de_busy_limit. Larger values can help prevent busy distribution port system messages. The default limit is 128 kilobytes.

c.如果在 system monitor 的时候发现 busy dist port,不妨改大这个值,这个值的下限是 4k。 

15.《 TCP链接主动关闭不发fin包奇怪行为分析 》 

知识点: 
a.发现一条 tcp 链接在 close 的时候,对端会收到 econnrest,而不是正常的 fin 包。通过抓包发现 close 系统调用的时候,我端发出 rst 报文,而不是正常的 fin。 
b.在 net/ipv4/tcp.c:1900 附近,代码里面写的很清楚,如果你的接收缓冲区还有数据,协议栈就会发 rst 代替 fin 。 

16.《 gen_tcp的close与delay_send交叉问题 》 

知识点: 
a.若干 tcp port 上有大量数据发送时,关闭这些 port,造成 erlang:ports/0 与 erlang:port_info/1 得到的 port 状态不一致。 

b.erlang:ports/0 这个函数将当前系统中处于非 ERTS_PORT_SFLGS_DEAD 状态的 port 快照组成一个列表返回,得到当前所有的活动 port,这里有如下几点值得注意:a) ERTS_PORT_SFLGS_DEAD 状态由下列状态组成:ERTS_PORT_SFLG_FREE|ERTS_PORT_SFLG_FREE_SCHEDULED| ERTS_PORT_SFLG_INITIALIZING;b) 若在调用 erlang:ports/0 进行统计的期间内,某个 port 退出,则这个 port 也将位于 erlang:ports/0 的返回列表内,但这些 port 在下次调用 erlang:ports/0 时将不会再次出现。 

c.erlang:port_info/1 这个函数仅将当前系统中处于非 ERTS_PORT_SFLGS_INVALID_LOOKUP 状态的 port 的 port_info 返回,这里有如下几点值得注意: a) ERTS_PORT_SFLGS_DEAD 状态由下列状态组成:ERTS_PORT_SFLG_FREE | ERTS_PORT_SFLG_FREE_SCHEDULED | ERTS_PORT_SFLG_INITIALIZING | ERTS_PORT_SFLG_INVALID | ERTS_PORT_SFLG_CLOSING;b) 与 erlang:ports/0 的区别在于,除了前三个状态,erlang:port_info/1 也不会将处于 ERTS_PORT_SFLG_INVALID 或 ERTS_PORT_SFLG_CLOSING 状态的 port_info 返回,其中 ERTS_PORT_SFLG_INVALID 不会真正赋予 port,而 ERTS_PORT_SFLG_CLOSING 可以被赋予 port。 

d.ERTS_PORT_SFLG_CLOSING 状态是当前这个场景的问题核心,若一个 port 处于 ERTS_PORT_SFLG_CLOSING 状态,而不处于 ERTS_PORT_SFLGS_DEAD | ERTS_PORT_SFLG_FREE_SCHEDULED | ERTS_PORT_SFLG_INITIALIZING 状态,则它将出现在 erlang:ports/0 的列表中,同时在 erlang:port_info/1 的结果中返回 undefined。 

e.作者设计场景(看原文)重现该问题,结果发现,server 的 port 在 delay_send 选项控制下,若发送缓冲有数据却强行 close,此时若 client 不 close 而仅仅是 shutdown,则 server 的 port 将不能释放,并出现 erlang:ports/0 与 erlang:port_info/1 不一致的情形,解决的办法是 server 也进行 shutdown,并令控制进程退出或 client 进行 close。 

f.梳理了 gen_tcp:close/1 的执行流程: 
a) unlink 掉 port 与当前进程的关系,使得 port 成为无主 port; 
b) 将调用进程作为 port 的一个订阅进程,通过 subscribe 函数订阅 port 的 empty_out_q 消息,这个消息仅在 port 的发送缓冲被清空时,由虚拟机投递给订阅进程(也即当前的调用进程); 
c) 循环等待 empty_out_q 消息的到达,或者触发 5 秒超时。若 empty_out_q 消息到达,则正常关闭 port 即可,若超时,则进入超时处理流程; 
d) 超时后,通过 getstat(S, [send_pend]) 检查 port 的发送缓冲是否出现了变化,若未变化,则表明 port 的发送缓冲在过去的 5 秒内没能将任何数据发送出去,因此推测将来也不可能再将数据发送出去,因此强行关闭 port,若出现了变化,则继续循环等待,直到 port 的发送缓冲清空; 
e) 强行关闭 port 将导致调用前述的 erts_do_exit_port 函数,在 port 的发送缓冲未清空的场景下,这个函数将设置 port 的状态为 ERTS_PORT_SFLG_CLOSING,导致调用 erlang:ports/0 与 erlang:port_info/1 观察到了 port 的中间状态,得到不一致的结果; 
f) 这个问题不是资源泄露,而是发送缓冲这种设计机制导致的,port 在 close 前必须将发送缓冲的数据全部推送到客户端,而客户端如果既不 recv,也不 close,而是 shutdown 半关闭或不作为,则导致 server 的 port 仍然被占用,此时连接仍然存在,只是难于被观察到,通过 netstat 可以看到 client 套接字处于 FIN_WAIT2,而 server 套接字处于 CLOSING,符合半关闭的状态。 

17.《 R15B01版本controlling_process一个port到self的问题 》 
(略) 

18.《 异步gen_server进行port访问时性能严重下降的原因和应对方法(五)套接字发送的应对 》 

知识点: 
a.从大道理上来讲,需要开发者预估一个进程的处理能力,不要向进程投递过多的消息以致于处理不完,如果处理不完,则需要重新设计,将消息分布到多个进程中处理;这确实是一个使用广泛的大道理,能解决一切,却好像又什么都没有解决,开发者需要不断摸索才能做到,我还在摸索中,所以就再次略过; 
b.将异步接收消息的进程与调用 port(receive_match)的模式的进程分开;前文已经介绍过思路和优缺点,此处也就不再赘述; 
c.拆分向 port 投递命令的过程,由进程来接收 port 回传的结果,而不是由模块接收;rabbitmq 已经给出了实现范例; 
d.不使用 port 编写的模块,利用 nif 重新实现一套;目前还没有找到实现,但是由于网络连接随时可以断开,进程也必须收到连接断开的异步通知,因此必须要使用消息机制异步通知进程,因此完全通过 nif 实现套接字访问是不可接受的; 
e.其它。 

19.《 节点间通讯的通道微调 》 

port 信息获取方法: 

?


1

2

3

node_port(Node)->

    {_, Owner}=lists:keyfind(owner, 1, element(2, net_kernel:node_info(Node))),

    hd([P|| P<-erlang:ports(), erlang:port_info(P, connected) == {connected,Owner}])

有了 Port,就可以设置 tcp port 的水位线,buffer 等等。  

?


1

inet:setopts(node_port('xx@nd-desktop'), [{high_watermark, 131072}]).

另外要注意 nodeup nodedown 可能会换了个 tcp 链接 要注意重新获取。  

还有另外一种方法,设置所有 gen_tcp 的行为, 比如以下方法:  

?


1

erl -kernel inet_default_connect_options '[{sndbuf, 1048576}, {high_watermark, 131072}]'

但是这个影响面非常大, 影响到正常 tcp 的参数了。 

20.《 inet驱动新增加{active,N} socket选项 》 

知识点: 
a.效率最高的当然是 {active, true} 方式,因为这种实现相当于对于每一个连接,只执行一次 epoll_ctl 把 socket 的读事件挂到 epoll 上去的动作。 
b.对于 {active,once} 方式,每次进行设定都意味着调用一次 epoll_ctl 。而 erlang 在实现中只使用一个线程来收割 epoll_wait 事件,如果大量的 epoll_ctl 动作阻塞了事件的收割,网络处理的能力会大大下降。 
c.{active, true} 有安全问题,{active, once} 太慢, {active, N} 让我们一次设定接收 N 个消息包,摊薄 epoll_ctl 的代价,这样就可以大大缓解性能的压力。 
d.霸爷说可以查看 lib/kernel/test/gen_tcp_misc_SUITE.erl 中的用法,但是没有找到使用 {active, N} 的地方。 

时间: 2024-11-02 03:56:02

【原创】导读”淘宝褚霸关于 gen_tcp 的分享“的相关文章

淘宝开店月入万元经验分享:做纯利润虚拟商品

中介交易 http://www.aliyun.com/zixun/aggregation/6858.html">SEO诊断 淘宝客 云主机 技术大厅 大家好,我是大家的老朋友老客,今天给大家分享的是:淘宝开店月入万元经验分享:做纯利润虚拟商品.上次分享了"淘宝开店月入万元经验分享:市场选择"这篇文章后,我看了一下评论,有些朋友就指出了,月入10000元,按利润20%算,一个月只能赚2000元了,很少.这位朋友可能不知道我的店铺主推宝贝都是100%利润的商品!那么今天给大

百度i贴吧推广日IP过千淘宝客收入过千经验分享

中介交易 http://www.aliyun.com/zixun/aggregation/6858.html">SEO诊断 淘宝客 云主机 技术大厅 大家好,我是淘侠客,今天给大家带来的是我本人原创的通过百度i贴吧轻松带来流量的教程,首先大家知道现在想从百度贴吧带来流量是很困难的一件事了,甚至你在百度贴吧留下自己的超链接都会被删除掉,百度贴吧自从改版后现在捉的很严格啊,基本上很明显的广告帖子是很难存活的,从前我主要流量来自于百度贴吧,用朋友写的软件在上面宣传一天基本3000左右IP是很稳定

一个月淘宝客推广赚钱方法经验分享

中介交易 http://www.aliyun.com/zixun/aggregation/6858.html">SEO诊断 淘宝客 云主机 技术大厅 大家都知道现在通过淘宝客赚钱是一种网赚方式了,但是有多少人在赚钱,有多少人在摸索,还有多人看着别人的推广成交单傻笑-所有的这一切都是淘宝客惹的祸. 不容否认,通过淘宝客赚钱的人大有人在,通过淘宝客赚钱的互联网大军一波一波的前仆后继,很多的新手一个月乃至二个月几乎没有成交量,这是为什么? 很多的人都问我,怎么做才能做一个真正赚钱的淘宝客?其实天

Python模拟登陆淘宝并统计淘宝消费情况的代码实例分享_python

支付宝十年账单上的数字有点吓人,但它统计的项目太多,只是想看看到底单纯在淘宝上支出了多少,于是写了段脚本,统计任意时间段淘宝订单的消费情况,看那结果其实在淘宝上我还是相当节约的说. 脚本的主要工作是模拟了浏览器登录,解析"已买到的宝贝"页面以获得指定的订单及宝贝信息. 使用方法见代码或执行命令加参数-h,另外需要BeautifulSoup4支持,BeautifulSoup的官方项目列表页:https://www.crummy.com/software/BeautifulSoup/bs4

淘宝店铺流量流量拓展思路分享

举例,新疆干货巴达木,这块我来给大家一个截图看下. 就上面这个,就算你再会做标题优化,直通车推广,那你的流量也不会很大.那该怎么办呢? 这里李雷霆有一个观点就是,产品可以差异化,流量同样也可以差异化.具体怎么讲呢?先说下产品差异化,这点很多人都懂,找出自己产品的唯一卖点,然后进行包装,这样是没错,不过能够对产品进行很好的卖点提炼以及能做出诱人的包装,这点我想大部分的卖家是做不到的,就算找人来做,那这个价钱也不是一般小卖家能承受的了的.同时店铺装修等等这些都要根据产品的最新定位来设计.这些花费都是

让百度快速收录你淘宝客站点的一些方法分享

中介交易 http://www.aliyun.com/zixun/aggregation/6858.html">SEO诊断 淘宝客 云主机 技术大厅 现在做淘宝客的越来越多,都自己独立去建站,然而现在百度针对淘宝大家都知道,一般都很难直接收录,就是收录了也是很少,那我们该怎么办呢?根据自己的一点经验,今天给大家他分享一下. 如果你无法直接发布到百度者淘宝上找一下,会发现有很多的软文发布服务,也就是将您的软文发布到比较大型的各类站点上,作为站点的新闻稿件从而达到外链的建立和知名度的提升效果,

手机淘宝APP关注收藏店铺教程分享

给各位手机淘宝软件的使用者们来详细的解析分享一下关注收藏店铺的教程. 教程分享: 1.打开手机淘宝,随意找一个商品,点击下方的[店铺]按钮   2.点击右上角的[关注]按钮   淘宝当中店铺的收藏其实都是称之为"关注",关注之后有新商品上架或者店铺有什么活动,用户就可第一时间知道啦. 好了,以上的信息就是小编给各位手机淘宝的这一款软件的使用者们带来的详细的关注收藏店铺的教程解析分享的全部内容了,各位看到这里的软件使用者们,小编相信你们现在那是非常的清楚关注收藏的方法了吧,那么就快去按照

jQuery实现的类似淘宝网站搜索框样式代码分享_jquery

运行效果图:                                                ----------------------查看效果----------------------- 小提示:浏览器中如果不能正常运行,可以尝试切换浏览模式. 为大家分享的类似淘宝网站搜索框样式代码如下 <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8&quo

新手淘宝开店货源选择的心得分享

中介交易 http://www.aliyun.com/zixun/aggregation/6858.html">SEO诊断 淘宝客 云主机 技术大厅 大家好,我是大家的老朋友老客,今天给大家分享一下新手开淘宝网店应该如何选择好货源的问题.货源的选择,直接影响我们淘宝网店后期的发展及生存,特别是对于新手开淘宝网店更应该慎重考虑.分析.踩点等. 货源,大范围来讲主要分为两种,1.实物 2.虚拟.那么两大类别里面包含了很多小分类.作为新手卖家,我们的店铺信誉很低,以及其它的宝贝排名条件相对都还不