转载: Erlang Socket解析二进制数据包
转自:http://www.itkee.com/developer/detail-318.html
今天在家里闲来无事,实践了一下Erlang的Socket的功能。记录一下在过程中遇到的一些问题,以及编码的步骤。
1. 对于测试用例的介绍:
Erlang编写TCP服务器。只做一次Accept,接收到Socket
之后开始收数据。用python编写Client,连接到服务器上;发送LEN(int)+CMD(short)+BODY(binary)格式的数据
包。用于熟悉Erlang如何做拆解包,数据读取。
2. 编写简单的Erlang TCP服务器:
Erlang里面的TCP socket应该都是这个方式来编写代码。指的修改和优化的是在于可以启动更多的进程来驱动起这个应用。
%% 文件名:server.erl
%% 模块定义
-module(server).
%% 导出函数
-export([start/0]).
%% 宏定义
-define( PORT, 2345 ).
-define( HEAD_SIZE, 4 ).
%% 解数字类型用到的宏
-define( UINT, 32/unsigned-little-integer).
-define( INT, 32/signed-little-integer).
-define( USHORT, 16/unsigned-little-integer).
-define( SHORT, 16/signed-little-integer).
-define( UBYTE, 8/unsigned-little-integer).
-define( BYTE, 8/signed-little-integer).
%% 对外接口
start() ->
%% 这个地方有些有意思的东西:
%% 1.{packet,0}这个设定,可以让Erlang不再接管socket的封包了;
%% 如果被Erlang接管了,在物理网络包前面4Bytes里面写的东西不
%% 是简单的网络包的Size.
%% 2.{active,false}这个设定,可以让接受到的Socket Recv指定Size
%% 网络包,这样也就方便了拆解包的工作了。
{ok, Listen}=gen_tcp:listen( ?PORT,[ binary,
{ packet, 0 }, { reuseaddr, true }, { active, false }]),
io:format("start listen port: ~p~n", [?PORT] ),
{ok, Socket} = gen_tcp:accept(Listen),
%% 接收到客户端之后将马上关闭Listen Socket
gen_tcp:close( Listen ),
%% 开始读取数据包头
looph(Socket).
%% 读出包头
looph(Socket) ->
case gen_tcp:recv( Socket, ?HEAD_SIZE ) of
{ ok, H } ->
io:format("recv head binary=~p~n", [H] ) ,
%% 匹配出包头
<< TotalSize:?UINT >> = H ,
%% 除去包头的SIZE
BodySize = TotalSize - ?HEAD_SIZE,
%% 开始收包体
loopb(Socket,BodySize);
%% 出异常了
{ error, closed } ->
io:format("recv head fail." )
end.
%% 读出包体
loopb(Socket,BodySize) ->
case gen_tcp:recv( Socket, BodySize ) of
{ ok, B } ->
%% 模式匹配
%% 1.得出数据包中的CMD编号
%% 2.将后面部分的Buffer放到Contain里面
<< CMD:?USHORT, Contain/binary>> = B,
io:format("recv body binary = ~p~n", [B] ),
io:format("recv protocol CMD = ~p~n", [CMD] ),
io:format("recv body = ~p~n", [Contain] ),
%% 继续读取包头
looph(Socket);
%% 异常处理
{error,close} ->
io:format("recv body fail.")
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模型。这个代码写起来非常容易了。
# -*- coding: utf-8 -*-
import socket
import asyncore
# 宏定义
MAX_RECV_CACHE = 1024
CHAT_MSG = 0x101A
# 聊天客户端
class ChatClient( asyncore.dispatcher ):
def __init__( self, host = Host, port = Port ):
asyncore.dispatcher.__init__( self )
self.create_socket( socket.AF_INET, socket.SOCK_STREAM)
self.connect( ( host, port) )
self.buffer_ = ''
self.recv_buf_ = ''
pass
# 链接成功
def handle_connect( self ):
print( "[SOCKET] handle_connect event." )
self.send_message( CHAT_MSG, "hello then world." )
self.send_message( CHAT_MSG, "this data is come from python." )
pass
# 读取内容
def handle_read( self ):
ret = self.recv( MAX_RECV_CACHE )
pass
def send_message( self, _prop_cmd, _msg ):
print( "presend size = %d"%len( _msg ) )
total_size = len( _msg ) + 4 + 2
self.buffer_ = self.buffer_ + struct.pack( "I", total_size )
self.buffer_ = self.buffer_ + struct.pack( "H", _prop_cmd )
self.buffer_ = self.buffer_ + _msg
pass
if __name__ == "__main__":
try:
client = ChatClient()
asyncore.loop()
except KeyboardInterrupt:
print( "退出." )
pass
完结。下次开始学习Erlang的OTP ETS了。
转载: Erlang Socket解析二进制数据包的更多相关文章
- C# TCP socket发送大数据包时,接收端和发送端数据不一致 服务端接收Receive不完全
简单的c# TCP通讯(TcpListener) C# 的TCP Socket (同步方式) C# 的TCP Socket (异步方式) C# 的tcp Socket设置自定义超时时间 C# TCP ...
- c#网络通信框架networkcomms内核解析之八 数据包的核心处理器
NetworkComms网络通信框架序言 本文基于networkcomms2.3.1开源版本 gplv3协议 我们先回顾一个 c#网络通信框架networkcomms内核解析之六 处理接收到的二进制 ...
- ubuntu下解析udt数据包
udt是通过udp进行端到端可靠传输的一个协议,有其默认拥塞控制算法. 之前ubuntu下wireshark的版本是1.10,不能直接解析udt数据包[1],升级到最新的2.0.0即可过滤udt数据包 ...
- 用原生socket发送HTTP数据包
分享一个写扫描器和POC时的小技巧. 有时候有的漏洞需要一些特殊的数据包,比如说畸形的HTTP头.畸形的Multipart.畸形的chunk包等,此时用编程语言自己的HTTP库可能构造不出这种数据包, ...
- python网络编程-socket发送大数据包问题
一:什么是socket大数据包发送问题 socket服务器端或者客户端在向对方发送的数据大于对方接受的缓存时,会出现第二次接受还接到上次命令发送的结果.这就出现象第一次接受结果不全,第二次接果出现第一 ...
- 学习:erlang的不定长数据包头部。
- IM通信协议逆向分析、Wireshark自定义数据包格式解析插件编程学习
相关学习资料 http://hi.baidu.com/hucyuansheng/item/bf2bfddefd1ee70ad68ed04d http://en.wikipedia.org/wiki/I ...
- Erlang 位串和二进制数据
http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=25876834&id=3300393 因为在本人工作中,服务端Erla ...
- 在SQL中使用CLR提供基本函数对二进制数据进行解析与构造
二进制数据包的解析一般是借助C#等语言,在通讯程序中解析后形成字段,再统一单笔或者批量(表类型参数)提交至数据库,在通讯程序中,存在BINARY到struct再到table的转换. 现借助CLR提 ...
随机推荐
- ansible 一些参数的整理
用ansible 来管理远程的主机,最大的好处是方便,ansible不用在远程的主机上安装ansible的客户端,ansible只要能通过ssh连接上远程主机就 能对它进行管理.也就是说ansible ...
- object-c 获得目录(包括子目录)下所有文件和文件夹路径
void getAllPathNameInDirectory(vector<string>&filePathList,vector<string>&direct ...
- Android开发6——布局中的wrap_content和fill_parent以及match_parent
一.言简意赅 fill_parent 是让控件宽或者高占全屏 wrap_content是让控件的高或宽仅仅把控件里的内容包裹住而不是全屏 二.分别来看 1 fill_parent 设置一个构件的布局 ...
- js中的前绑定和后绑定详解
这篇文章详细介绍了js中的前绑定和后绑定,有需要的朋友可以参考一下 其主要意思就是看我有没有用过前绑定,即Dom树中的某些元素在还没有创建出来时,就指定该类型的元素一出生就应该拥有的某些事件.在实际开 ...
- [na][win]AD域组策略wifi自动配置
http://wenku.baidu.com/link?url=MC950wliAZNeVUJ2M6Y1VTi5faqo7kG374fyBjW57r0qyLJkBZLg5ypiql4RFywQ8q7y ...
- 【Android】6.3 ProgressDialog
分类:C#.Android.VS2015: 创建日期:2016-02-08 一.简介 进度条对话框(ProgressDialog)常用于不能在短时间内快速完成的操作,显示进度条的目的是为了让用户明白程 ...
- linux 日志定时轮询流程详解(logrotate)
logrotate介绍 对于Linux系统安全来说,日志文件是极其重要的工具.日志文件包含了关于系统中发生的事件的有用信息,在排障过程中或者系统性能分析时经常被用到.当日志文件不断增长的时候,就需要定 ...
- 配置 logrotate 指导
一般来说,日志是任何故障排除过程中非常重要的一部分,但这些日志会随着时间增长.在这种情况下,我们需要手动执行日志清理以回收空间,这是一件繁琐的管理任务.为了解决这个问题,我们可以在 Linux 中配置 ...
- C++ 11 STL算法
STL算法部分主要由头文件<algorithm>,<numeric>,<functional>组成.要使用 STL中的算法函数必须包含头文件<algorith ...
- C++ 11 nullptr关键字
熟悉C++的童鞋都知道,为了避免“野指针”(即指针在首次使用之前没有进行初始化)的出现,我们声明一个指针后最好马上对其进行初始化操作.如果暂时不明确该指针指向哪个变量,则需要赋予NULL值.除了NUL ...