erlang轻松实现memcached binary协议

简单实现了下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的协议包





你可能感兴趣的:(erlang,socket,memcached,网络协议,大众软件)