Chinaunix首页 | 论坛 | 博客
  • 博客访问: 5119014
  • 博文数量: 921
  • 博客积分: 16037
  • 博客等级: 上将
  • 技术积分: 8469
  • 用 户 组: 普通用户
  • 注册时间: 2006-04-05 02:08
文章分类

全部博文(921)

文章存档

2020年(1)

2019年(3)

2018年(3)

2017年(6)

2016年(47)

2015年(72)

2014年(25)

2013年(72)

2012年(125)

2011年(182)

2010年(42)

2009年(14)

2008年(85)

2007年(89)

2006年(155)

分类: Python/Ruby

2012-11-15 11:13:29

今天在家里闲来无事,实践了一下Erlang的Socket的功能。记录一下在过程中遇到的一些问题,以及编码的步骤。

1. 对于测试用例的介绍:
Erlang编写TCP服务器。只做一次Accept,接收到Socket之后开始收数据。用python编写Client,连接到服务器上;发送LEN(int)+CMD(short)+BODY(binary)格式的数据包。用于熟悉Erlang如何做拆解包,数据读取。

2. 编写简单的Erlang TCP服务器:
Erlang里面的TCP socket应该都是这个方式来编写代码。指的修改和优化的是在于可以启动更多的进程来驱动起这个应用。

  1. %% 文件名:server.erl
  2. %% 模块定义
  3. -module(server).

  4. %% 导出函数
  5. -export([start/0]).

  6. %% 宏定义
  7. -define( PORT, 2345 ).
  8. -define( HEAD_SIZE, 4 ).

  9. %% 解数字类型用到的宏
  10. -define( UINT, 32/unsigned-little-integer).
  11. -define( INT, 32/signed-little-integer).
  12. -define( USHORT, 16/unsigned-little-integer).
  13. -define( SHORT, 16/signed-little-integer).
  14. -define( UBYTE, 8/unsigned-little-integer).
  15. -define( BYTE, 8/signed-little-integer).

  16. %% 对外接口
  17. start() ->
  18.  %% 这个地方有些有意思的东西:
  19.  %% 1.{packet,0}这个设定,可以让Erlang不再接管socket的封包了;
  20.  %% 如果被Erlang接管了,在物理网络包前面4Bytes里面写的东西不
  21.  %% 是简单的网络包的Size.
  22.  %% 2.{active,false}这个设定,可以让接受到的Socket Recv指定Size
  23.  %% 网络包,这样也就方便了拆解包的工作了。
  24.     {ok, Listen}=gen_tcp:listen( ?PORT,[ binary,
  25.                     { packet, 0 }, { reuseaddr, true }, { active, false }]),
  26.     io:format("start listen port: ~p~n", [?PORT] ),
  27.     {ok, Socket} = gen_tcp:accept(Listen),
  28.  %% 接收到客户端之后将马上关闭Listen Socket
  29.     gen_tcp:close( Listen ),
  30.  %% 开始读取数据包头
  31.     looph(Socket).

  32. %% 读出包头
  33. looph(Socket) ->
  34.     case gen_tcp:recv( Socket, ?HEAD_SIZE ) of
  35.         { ok, H } ->
  36.             io:format("recv head binary=~p~n", [H] ) ,
  37.             %% 匹配出包头
  38.             << TotalSize:?UINT >> = H ,
  39.    %% 除去包头的SIZE
  40.             BodySize = TotalSize - ?HEAD_SIZE,
  41.             %% 开始收包体
  42.             loopb(Socket,BodySize);
  43.   %% 出异常了
  44.         { error, closed } ->
  45.             io:format("recv head fail." )
  46.     end.

  47. %% 读出包体
  48. loopb(Socket,BodySize) ->
  49.     case gen_tcp:recv( Socket, BodySize ) of
  50.         { ok, B } ->
  51.             %% 模式匹配
  52.    %% 1.得出数据包中的CMD编号
  53.    %% 2.将后面部分的Buffer放到Contain里面
  54.             << CMD:?USHORT, Contain/binary>> = B,
  55.             io:format("recv body binary = ~p~n", [B] ),
  56.             io:format("recv protocol CMD = ~p~n", [CMD] ),
  57.             io:format("recv body = ~p~n", [Contain] ),
  58.    %% 继续读取包头
  59.             looph(Socket);
  60.   %% 异常处理
  61.         {error,close} ->
  62.             io:format("recv body fail.")
  63.     end.

在编写这个代码过程中遇到的麻烦:
2.1. 不知道如何匹配出数据包头来:
<< TotalSize:?UINT >> = H
2.2. 不知道如何将一个binary匹配出来部分,将剩余部分binary放到别的里面:
<< CMD:?USHORT, Contain/binary>> = B
2.3. 在多次调试之后出来这样的错误:
{error,eaddrinuse}
端口被占用了,这个时候去关闭全部后台的.beam也是没有解决这个问题。最后重启了机器才能让这个问题解决。
2.4. Erlang中对于binary操作的熟悉:
term_to_binary和binary_to_term函数的功效:
用于将一个任意的Erlang值转化成为二进制(反向操作),这个特性可能也只有在Erlang之间打交道的时候可以用上。
list_to_binary:
这个函数非常有用,原因是它不挑食。打个比方:
1> A = "A".
"A"
2> B = list_to_binary(A).
<<"A">>
结果这个"A"字符串被好好的放在了binary里面去了。
还有一个用处就是用来连接已经生成好的一些binary的对象
10> A = << 1,2,3,4 >>.
<<1,2,3,4>>
11> B = << "A" >>.
<<"A">>
12> C = list_to_binary( [A, B] ).
<<1,2,3,4,65>>

3. 开始编写python客户端代码:
这个Socket客户端是使用的asyncore的dispatcher来做的。用起来有些像ACE里面的reactor模型。这个代码写起来非常容易了。


 

  1. # -*- coding: utf-8 -*-

  2. import socket
  3. import asyncore

  4. # 宏定义
  5. MAX_RECV_CACHE = 1024
  6. CHAT_MSG = 0x101A

  7. # 聊天客户端
  8. class ChatClient( asyncore.dispatcher ):
  9.     def __init__( self, host = Host, port = Port ):
  10.         asyncore.dispatcher.__init__( self )
  11.         self.create_socket( socket.AF_INET, socket.SOCK_STREAM)
  12.         self.connect( ( host, port) )
  13.         self.buffer_ = ''
  14.         self.recv_buf_ = ''
  15.         pass

  16.     # 链接成功
  17.     def handle_connect( self ):
  18.         print( "[SOCKET] handle_connect event." )
  19.         self.send_message( CHAT_MSG, "hello then world." )
  20.         self.send_message( CHAT_MSG, "this data is come from python." )
  21.         pass

  22.     # 读取内容
  23.     def handle_read( self ):
  24.         ret = self.recv( MAX_RECV_CACHE )
  25.         pass

  26.     def send_message( self, _prop_cmd, _msg ):
  27.         print( "presend size = %d"%len( _msg ) )
  28.         total_size = len( _msg ) + 4 + 2
  29.         self.buffer_ = self.buffer_ + struct.pack( "I", total_size )
  30.         self.buffer_ = self.buffer_ + struct.pack( "H", _prop_cmd )
  31.         self.buffer_ = self.buffer_ + _msg
  32.     pass

  33. if __name__ == "__main__":
  34.     try:
  35.         client = ChatClient()
  36.         asyncore.loop()
  37.     except KeyboardInterrupt:
  38.         print( "退出." )
  39.     pass

文章来自:

阅读(4665) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~