由大意引发的Socket编程中的小错误

70 views
Skip to first unread message

Yupei

unread,
May 15, 2012, 4:27:05 AM5/15/12
to erlang...@googlegroups.com
这邮件组好久没邮件了。发个今天排查的小错误凑个热闹。

原有一段代码是使用被动式消息接收(阻塞)方式,后来被改成 混合模式(半阻塞)。
结果莫名失效,排查代码,基本格式如下:

start() -> {ok, Listen} = gen_tcp:listen(8080,[{active,once}].

...........

loop_accept(Listen) ->
    case gen_tcp:accept(Listen) of 
        {ok, Socket} ->
            spawn(fun()->loop_socket(Socket) end),
            loop_accept(Listen)
    end.

loop_socket(Socket) ->
    receive
        {tcp,Socket, Binary} -> .....
        {tcp_closed, Socket} -> ....
   end.


代码本意是想接收到一个accept后,当前进程继续负责接收,用spawn出来的新进程来接手Socket请求,
现在情况是socket断开后,spawn出来的loop_socket进程仍然存在,


后将飘红处代码改为
spawn(fun()->loop_accept(Listen) end),
loop_socket(Socket)

便得以解决。即将当前进程处理socket请求,新开进程处理accept连接。

非阻塞的模式下,socket发来的消息是放到进程的msg-box里,新开进程receive是收不到数据的,大囧。




李建业

unread,
May 15, 2012, 4:30:06 AM5/15/12
to erlang...@googlegroups.com, Yupei
��������Щ�˶����������һֱ���ò�̫��������Ƶ�����ԭ��


�� 2012��05��15�� 16:27, Yupei �:
���ʼ���þ�û�ʼ��ˡ����������Ų��С����ո����֡�

ԭ��һ�δ�����ʹ�ñ���ʽ��Ϣ���գ�����ʽ���������ij� ���ģʽ��������
���Ī��ʧЧ���Ų���룬���ʽ���£�

start() -> {ok, Listen} = gen_tcp:listen(8080,[{active,once}].

...........

loop_accept(Listen) ->
    case gen_tcp:accept(Listen) of 
        {ok, Socket} ->
            spawn(fun()->loop_socket(Socket) end),
            loop_accept(Listen)
    end.

loop_socket(Socket) ->
    receive
        {tcp,Socket, Binary} -> .....
        {tcp_closed, Socket} -> ....
   end.


���뱾��������յ�һ��accept�󣬵�ǰ��̼�������գ���spawn�������½��������Socket����
���������socket�Ͽ���spawn������loop_socket�����Ȼ���ڣ�


��Ʈ�촦�����Ϊ
spawn(fun()->loop_accept(Listen) end),
loop_socket(Socket)

����Խ����������ǰ��̴���socket�����¿���̴���accept���ӡ�

�������ģʽ�£�socket��������Ϣ�Ƿŵ���̵�msg-box��¿����receive���ղ�����ݵģ���塣






-- 
-----------------

Best Regard��

�ҵ

Zheng Zhibin

unread,
May 15, 2012, 4:50:52 AM5/15/12
to erlang...@googlegroups.com, Yupei
这个原因是因为,Socket是具有进程属性的。如果,要做到红字的效果,就要调用gen_tcp:controlling_process/2来,把socket交给新的进程。

Thanks and regards,
==========================
郑智斌(Witeman Zheng)
广州安浩软件科技有限公司
研发总监
公司电邮:wit...@iconventure.com
手机:+86 186 2020 2930
QQ:19653403
==========================

在 2012-5-15,下午4:30, 李建业 写道:

这个错误好些人都遇到过,不过我一直觉得不太理解这个设计的深层次原因



于 2012年05月15日 16:27, Yupei 写道:
这邮件组好久没邮件了。发个今天排查的小错误凑个热闹。

原有一段代码是使用被动式消息接收(阻塞)方式,后来被改成 混合模式(半阻塞)。
结果莫名失效,排查代码,基本格式如下:

start() -> {ok, Listen} = gen_tcp:listen(8080,[{active,once}].

...........

loop_accept(Listen) ->
    case gen_tcp:accept(Listen) of 
        {ok, Socket} ->
            spawn(fun()->loop_socket(Socket) end),
            loop_accept(Listen)
    end.

loop_socket(Socket) ->
    receive
        {tcp,Socket, Binary} -> .....
        {tcp_closed, Socket} -> ....
   end.


代码本意是想接收到一个accept后,当前进程继续负责接收,用spawn出来的新进程来接手Socket请求,
现在情况是socket断开后,spawn出来的loop_socket进程仍然存在,


后将飘红处代码改为
spawn(fun()->loop_accept(Listen) end),
loop_socket(Socket)

便得以解决。即将当前进程处理socket请求,新开进程处理accept连接。

非阻塞的模式下,socket发来的消息是放到进程的msg-box里,新开进程receive是收不到数据的,大囧。






-- 
-----------------

Best Regard,

李建业

Yupei

unread,
May 15, 2012, 5:10:31 AM5/15/12
to erlang...@googlegroups.com
然也,这也是保持listen-work模式的正解。

李建业

unread,
May 15, 2012, 7:42:40 AM5/15/12
to erlang...@googlegroups.com, Zheng Zhibin, Yupei
�о�������ͻ��Dz������룬Ϊʲôsocket����н�������أ��ǿ������ƻ��DZ��ʲôԭ�򣿽���api��ƵĽǶȿ���һ��process�� �𲻶ϵ�accept���յ�socket�Ժ���ɳ�process���д������һ���˼άϰ�߰�

�� 2012��05��15�� 16:50, Zheng Zhibin �:
���ԭ������Ϊ��Socket�Ǿ��н�����Եġ����Ҫ�������ֵ�Ч���Ҫ���� gen_tcp:controlling_process/2������socket�����µĽ�̡�

Thanks and regards,
==========================
֣�DZ�Witeman Zheng��
���ݰ�������Ƽ����޹�˾
�з��ܼ�
��˾���ʣ�wit...@iconventure.com
�ֻ�+86 186 2020 2930
QQ��19653403
==========================

�� 2012-5-15������4:30�� �ҵ д����

��������Щ�˶����������һֱ���ò�̫��������Ƶ�����ԭ��


�� 2012��05��15�� 16:27, Yupei �:
���ʼ���þ�û�ʼ��ˡ����������Ų��С����ո����֡�

ԭ��һ�δ�����ʹ�ñ���ʽ��Ϣ���գ�����ʽ���������ij� ���ģʽ��������
���Ī��ʧЧ���Ų���룬���ʽ���£�

start() -> {ok, Listen} = gen_tcp:listen(8080,[{active,once}].

...........

loop_accept(Listen) ->
    case gen_tcp:accept(Listen) of 
        {ok, Socket} ->
            spawn(fun()->loop_socket(Socket) end),
            loop_accept(Listen)
    end.

loop_socket(Socket) ->
    receive
        {tcp,Socket, Binary} -> .....
        {tcp_closed, Socket} -> ....
   end.


���뱾��������յ�һ��accept�󣬵�ǰ��̼�������գ���spawn�������½��������Socket ����
���������socket�Ͽ���spawn������loop_socket�����Ȼ���ڣ�


��Ʈ�촦�����Ϊ
spawn(fun()->loop_accept(Listen) end),
loop_socket(Socket)

����Խ����������ǰ��̴���socket�����¿���̴���accept���ӡ�

�������ģʽ�£�socket��������Ϣ�Ƿŵ���̵�msg-box��¿����receive���ղ������ �ģ������






-- 
-----------------

Best Regard��

�ҵ

Zheng Zhibin

unread,
May 15, 2012, 8:08:43 AM5/15/12
to erlang...@googlegroups.com, Yupei
请用COP的思维习惯,一切皆进程的世界观。这才是最自然,最有效模拟世界的。

我不知道你的疑惑去到什么程度,^_^当然可以解释到机械码的级别,这个要看你疑惑的功力了。

首先,在Erlang里一切皆进程,进程之前的交互只能通过消息传递(当然Erlang也给你提供了违背的方法,可是这些是另有用处的)。

其次,一切皆进程,所以自然Socket也是一个进程。所以,它只会向创建它的进程转发网络过来的东西,因为它只知道创建它的进程的Pid。你把Socket作为参数,开一个新的进程是没用的,因为Socket进程怎么知道新的进程的Pid呢?你有告诉它吗?所以,我们要用gen_tcp:controlling_process/2来告诉它。这样这个Socket就会把网络过来的数据,转发给新的进程了。

再有,这样设计便于负载均衡和Socket控制。具体,需要有实战经验,只能意会不能言传了。



Thanks and regards,
==========================
郑智斌(Witeman Zheng)
广州安浩软件科技有限公司
研发总监
公司电邮:wit...@iconventure.com
手机:+86 186 2020 2930
QQ:19653403
==========================

在 2012-5-15,下午7:42, 李建业 写道:

感觉这个解释还是不够深入,为什么socket会具有进程属性呢?是刻意的设计还是别的什么原因?仅从api设计的角度看,一个process负 责不断的accept,收到socket以后分派出process进行处理更符合一般的思维习惯阿

于 2012年05月15日 16:50, Zheng Zhibin 写道:
这个原因是因为,Socket是具有进程属性的。如果,要做到红字的效果,就要调用 gen_tcp:controlling_process/2来,把socket交给新的进程。

Thanks and regards,
==========================
郑智斌(Witeman Zheng)
广州安浩软件科技有限公司
研发总监
公司电邮:wit...@iconventure.com
手机:+86 186 2020 2930
QQ:19653403
==========================

在 2012-5-15,下午4:30, 李建业 写道:

这个错误好些人都遇到过,不过我一直觉得不太理解这个设计的深层次原因


于 2012年05月15日 16:27, Yupei 写道:
这邮件组好久没邮件了。发个今天排查的小错误凑个热闹。

原有一段代码是使用被动式消息接收(阻塞)方式,后来被改成 混合模式(半阻塞)。
结果莫名失效,排查代码,基本格式如下:

start() -> {ok, Listen} = gen_tcp:listen(8080,[{active,once}].

...........

loop_accept(Listen) ->
    case gen_tcp:accept(Listen) of 
        {ok, Socket} ->
            spawn(fun()->loop_socket(Socket) end),
            loop_accept(Listen)
    end.

loop_socket(Socket) ->
    receive
        {tcp,Socket, Binary} -> .....
        {tcp_closed, Socket} -> ....
   end.


代码本意是想接收到一个accept后,当前进程继续负责接收,用spawn出来的新进程来接手Socket 请求,
现在情况是socket断开后,spawn出来的loop_socket进程仍然存在,


后将飘红处代码改为
spawn(fun()->loop_accept(Listen) end),
loop_socket(Socket)

便得以解决。即将当前进程处理socket请求,新开进程处理accept连接。

非阻塞的模式下,socket发来的消息是放到进程的msg-box里,新开进程receive是收不到数据 的,大囧凵






-- 
-----------------

Best Regard,

李建业

月忧茗

unread,
May 15, 2012, 8:50:01 AM5/15/12
to erlang...@googlegroups.com, Yupei

自从加入这个列表后, 这是收到的一封邮件啊!!!!

Wei Cao

unread,
May 15, 2012, 9:12:19 AM5/15/12
to erlang...@googlegroups.com, Zheng Zhibin, Yupei
这里的Listen和Socket都是Port,Port是Erlang虚拟机里的概念,你可以把它理解为可以被虚拟机调度执行的C代码模块

Port带有一个controller属性,记录这个Port是由哪个Process创建的,Port的owner是谁


    case gen_tcp:accept(Listen) of 
        {ok, Socket} ->

新连接上来后,Listen被调度运行了,会创建一个新的Port表示新连接,就是Socket,并且把Socket的controller被设置为accept的调用者,同时Socket继承了Listen的属性{active, once}

当Socket上收到数据包时,
在active=:=false的情况下,消息会被发送给gen_tcp:recv的调用者;
在active=:= true 或者 active =:= once情况下,因为没有调用者,消息会被Socket的controller,也就是accept的调用者

所以这个代码还有一种改法:
Listen的时候{active, false},
在spawn出来的进程里调gen_tcp:recv收包



在 2012年5月15日 下午7:42,李建业 <li.j...@gmail.com>写道:
感觉这个解释还是不够深入,为什么socket会具有进程属性呢?是刻意的设计还是别的什么原因?仅从api设计的角度看,一个process负 责不断的accept,收到socket以后分派出process进行处理更符合一般的思维习惯阿

于 2012年05月15日 16:50, Zheng Zhibin 写道:
这个原因是因为,Socket是具有进程属性的。如果,要做到红字的效果,就要调用 gen_tcp:controlling_process/2来,把socket交给新的进程。

Thanks and regards,
==========================
郑智斌(Witeman Zheng)
广州安浩软件科技有限公司
研发总监
公司电邮:wit...@iconventure.com
手机:+86 186 2020 2930
QQ:19653403
==========================

在 2012-5-15,下午4:30, 李建业 写道:

这个错误好些人都遇到过,不过我一直觉得不太理解这个设计的深层次原因


于 2012年05月15日 16:27, Yupei 写道:
这邮件组好久没邮件了。发个今天排查的小错误凑个热闹。

原有一段代码是使用被动式消息接收(阻塞)方式,后来被改成 混合模式(半阻塞)。
结果莫名失效,排查代码,基本格式如下:

start() -> {ok, Listen} = gen_tcp:listen(8080,[{active,once}].

...........

loop_accept(Listen) ->
    case gen_tcp:accept(Listen) of 
        {ok, Socket} ->
            spawn(fun()->loop_socket(Socket) end),
            loop_accept(Listen)
    end.

loop_socket(Socket) ->
    receive
        {tcp,Socket, Binary} -> .....
        {tcp_closed, Socket} -> ....
   end.


代码本意是想接收到一个accept后,当前进程继续负责接收,用spawn出来的新进程来接手Socket 请求,
现在情况是socket断开后,spawn出来的loop_socket进程仍然存在,


后将飘红处代码改为
spawn(fun()->loop_accept(Listen) end),
loop_socket(Socket)

便得以解决。即将当前进程处理socket请求,新开进程处理accept连接。

非阻塞的模式下,socket发来的消息是放到进程的msg-box里,新开进程receive是收不到数据 的,大囧凵






-- 
-----------------

Best Regard,

李建业


-- 
-----------------

Best Regard,

李建业



--

Best,

Wei Cao

Wei Cao

unread,
May 15, 2012, 9:28:21 AM5/15/12
to erlang...@googlegroups.com, Zheng Zhibin, Yupei
更正,Port带有的属性叫connected

为什么要有这个属性?

The Erlang process which creates a port is said to be the port owner, or the connected process of the port. All communication to and from the port should go via the port owner. If the port owner terminates, so will the port (and the external program, if it is written correctly).

摘自http://www.erlang.org/doc/reference_manual/ports.html

这样做的好处之一是,和supervisor统一起来了,当owner process退出时,port也会相应关闭,比较符合Erlang的口味。
--

Best,

Wei Cao

王二伟

unread,
May 15, 2012, 9:18:40 PM5/15/12
to erlang...@googlegroups.com
想请教大家一个网络请求和响应的问题,想知道服务端的响应到客户端后,客户端是如何把响应和之前的请求对应的,就是怎么知道当前的响应的结果是之前哪个请求发出的结果?


请求1 到服务端
请求2 到服务端

服务端响应 内容a
服务器响应 内容b

想知道客户端如何区分 内容a是哪个请求(请求1还是请求2)的结果?


请高手不吝赐教
--
mail:ixq...@gmail.com
phone:13671527966
blog:http://xingqiba.sinaapp.com/

Wei Cao

unread,
May 15, 2012, 10:02:50 PM5/15/12
to erlang...@googlegroups.com
比较常用的方法是,在客户端维护一个递增序号,给请求打包时加上这个序号,把这个序号和这个请求的上下文信息(比如发请求的用户session)记录到一张表里,服务器端返回结果的时候也带上这个序号。
 
这样,客户端收到结果后就可以查表取出请求的上下文信息,再做处理。

--

Best,

Wei Cao

王二伟

unread,
May 15, 2012, 11:07:26 PM5/15/12
to erlang...@googlegroups.com
是不是涉及到网络通信的请求和响应都要如此?

但我看redis的通信协议时没有提到如何识别响应结果和请求对应的,它是如何区分的呢?
有点跑题

Wei Cao

unread,
May 15, 2012, 11:47:51 PM5/15/12
to erlang...@googlegroups.com
 
Redis client和server之间是堵塞式的协议,发一个request过去,server回一个reply
 
Redis is a TCP server using the client-server model and what is called a Request/Response protocol.

This means that usually a request is accomplished with the following steps:

  • The client sends a query to the server, and reads from the socket, usually in a blocking way, for the server response.
  • The server processes the command and sends the response back to the client.
现在也支持Pipelining技术,类似于批处理,就是client发N个请求过去,再顺序读N条结果,节约IO
 
A Request/Response server can be implemented so that it is able to process new requests even if the client didn't already read the old responses. This way it is possible to send multiple commands to the server without waiting for the replies at all, and finally read the replies in a single step.
--

Best,

Wei Cao

Zheng Zhibin

unread,
May 16, 2012, 1:19:08 AM5/16/12
to erlang...@googlegroups.com
补充一下

Thanks and regards,
==========================
郑智斌(Witeman Zheng)
广州安浩软件科技有限公司
研发总监
公司电邮:wit...@iconventure.com
手机:+86 186 2020 2930
QQ:19653403
==========================

在 2012-5-16,上午11:47, Wei Cao 写道:

 
Redis client和server之间是堵塞式的协议,发一个request过去,server回一个reply
这里说的Blocking,是客户端的blocking。也就是,客户端在发出去一个请求(Request)后,没有受到回应(Response)时。不会继续发出下一个请求。
Wei Cao之前说的用一个对于客户端端来说的uid的方法也行。不过,Redis所用的方法对于客户端逻辑来说会更清晰点。这主要看客户端是如何实现的。
 
Redis is a TCP server using the client-server model and what is called a Request/Response protocol.

This means that usually a request is accomplished with the following steps:

  • The client sends a query to the server, and reads from the socket, usually in a blocking way, for the server response.
  • The server processes the command and sends the response back to the client.
现在也支持Pipelining技术,类似于批处理,就是client发N个请求过去,再顺序读N条结果,节约IO
Pipeline技术,应该相当于一个buffer。收到一定数量的请求后,或者到达一定的timeout后。客户端才会向服务器发出Request(里面可能包含多个Request),然后blocking等待Response(如果Request里面有多个Request,那么这就自然包含多个Response)。不过具体可以看看Redis客户端源码^_^

王二伟

unread,
May 16, 2012, 2:08:34 AM5/16/12
to erlang...@googlegroups.com
都是高手,谢谢
Reply all
Reply to author
Forward
0 new messages