简单实现了下memcached binary protocol的 get和 set command,体验了下erlang binary语法的强大和方便
代码:
-module(binary_server).
-export([start/0]).
start() ->
{ok,Listen}=gen_tcp:listen(7777,[binary,{packet,0},{reuseaddr,true},{active,true}]),
register(kvs,spawn(fun() -> dict() end)),
accept(Listen).
accept(Listen) ->
{ok,Socket}=gen_tcp:accept(Listen),
spawn(fun() -> accept(Listen) end),
inet:setopts(Socket,[binary,{packet,0},{active,true}]),
process(Socket,<<>>).
process(Socket,Left) ->
receive
{tcp,Socket,Bin} ->
Buffer=list_to_binary(binary_to_list(Left) ++ binary_to_list(Bin)),
case regonized(Buffer) of
{save_ok,RealRemaning} ->
Resp=[16#81,16#1] ++ fill_all(0,22,[]),
gen_tcp:send(Socket,list_to_binary(Resp)),
process(Socket,RealRemaning);
{get_ok,undefined,Remaining} ->
Value= <<"not_found">>,
BodyLen=length(binary_to_list(Value))+4,
Resp=[16#81] ++ fill_all(0,3,[]) ++[16#4]
++ fill_all(0,3,[]) ++
binary_to_list(<<BodyLen:32>>) ++
binary_to_list(<<0:128>>) ++
binary_to_list(Value),
gen_tcp:send(Socket,list_to_binary(Resp)),
process(Socket,Remaining);
{get_ok,{ok,Value},Remaining} ->
BodyLen=length(binary_to_list(Value))+4,
Resp=[16#81] ++ fill_all(0,3,[]) ++[16#4]
++ fill_all(0,3,[]) ++
binary_to_list(<<BodyLen:32>>) ++
binary_to_list(<<0:128>>) ++
binary_to_list(Value),
gen_tcp:send(Socket,list_to_binary(Resp)),
process(Socket,Remaining);
{get_timeout,Remaining} ->
process(Socket,Remaining);
{not_engouh_streams} ->
process(Socket,Buffer)
end;
{tcp_closed,Socket} ->
io:format("peer closed~n")
end.
regonized(Buffer) ->
case Buffer of
%%set command
<<16#80:8,16#1:8,KeyLen:16/big,
ExtraLen:8/big,DataType:8,Reserved:16/big,
BodyLen:32/big,Opaque:32/big,Cas:64/big,
Extras:64/big,Key:KeyLen/big-binary-unit:8,
Remaning/binary>> ->
ValueLen=BodyLen-KeyLen-ExtraLen,
case Remaning of
<<Value:ValueLen/binary-unit:8,
RealRemaning/binary>> ->
%% got completed packet,deal with set command
kvs ! {self(),{store,Key,Value}},
{save_ok,RealRemaning};
<<_/binary>> ->
%% not enough streams
{not_enough_streams}
end;
%%get command
<<16#80:8,16#0:8,KeyLen:16/big,16#0:8,
DataType:8,Reserved:16/big,BodyLen:32/big,
Opaque:32/big,Cas:64/big,Key:KeyLen/big-binary-unit:8,
Remaining/binary>> ->
kvs ! {self(),{lookup,Key}},
receive
{lookup,Value} ->
{get_ok,Value,Remaining}
after 1000 ->
{get_timeout,Remaining}
end;
%% no match
<<_/binary>> ->
{not_engouh_streams}
end.
dict() ->
receive
{From,{store,Key,Value}} ->
put(Key,{ok,Value}),
dict();
{From,{lookup,Key}} ->
From ! {lookup,get(Key)},
dict()
end.
debug_print([H|T]) ->
io:format("~w,",[H]),
debug_print(T);
debug_print([])->
ok.
fill_all(C,0,L)->
L;
fill_all(C,N,L)->
N1=N-1,
L1=[C|L],
fill_all(C,N1,L1).
说明:
1. 16#80:8
16#代表16进制,80是memcached协议头的第一个字节 80代表request
具体协议内容memcached官网上有很详尽的解释
2. KeyLen:16/big
协议中key的长度是占2个字节 ,16是bit数 .
因为多于一个字节的数据在存储和传输时就会涉及字节序问题,
这里big代表的是大端/网络字节序,
因为我的测试client在传输数据前已经将数据转成网络字节序,所以这里接收必须是big
3. Key:KeyLen/big-binary-unit:8 ,
这里Key具体的字节数是由之前得到的KeyLen指定的,所以表示为Key:KeyLen,
因为这里的单位应该是字节,所以需要指定为unit:8,其实这里指定了binary
binary类型默认的unit就是8,即 实际的size = KeyLen * unit 个 bit
4. lookup没有找到指定key对应value时,这里没有按协议处理,以not_found为value返回给client,实际协议是返回一个status为非0的协议包