1、实验环境:
联想小型机:
操作系统:RedHat Enterprise LinuxServer release6.4(Santiago)
内核版本:Linux server1 2.6.32-358.el6.x86_64#1 SMP
CPU型号:Intel(R)Xeon(R)CPU E7-4820 @2.00Ghz;
4颗cpu,每颗cpu物理核数为8,总物理核数为32,逻辑核数为64
内存:132G
磁盘:289G
2、基于erlang开发设计了TCP服务器,
Erlang的TCP通信机制,首先在指定的端口创建socket,通过侦听会在指定的端口返回一个侦听的#Port<0.501>,然后服务器生成一个进程在accept等待客户端连接到来,客户端通过connect与服务器进行连接,一旦连接成功客户端在本地会返回一个#Port<0.502>,以后客户端通过该项端口进行数据的发送和接收;服务器端一旦accept到连接了,便将在服务器端返回一个#Port<0.503>,这样一个tcp连接便有服务器的#Port<0.503>和客户端的#Port<0.502>进行标识,由此得出:(1)tcp连接过程序的建立,服务器端的port数=客户端port数+1;(2)port的打开的数量上限决定着tcp连接的个数。
在erlang中port即IO句柄,因而最终决定tcp连接数的是服务器能够同时打开文件的个数。在该系统下默认打开是1024,通过ulimit –n 1000000可以修改上限值为1000000,该值不能超过硬限制的个数。如果要进一步提高,需要从软限制和硬限制两方面考虑,具体可参考:另一方面/proc/sys/net/ipv4/ip_local_port_range的值决定了一个侦听端口所能接收的最大tcp连接数,修改该值可以通过vi /etc/sysctl.conf,修改后执行sysctcl -p使其生效。提高连接数的一个方法是通过开多个侦听端口来接收客户端的连接。如何修改操作系统所能打开的最大文件数和ip_local_port_range成为影响tcp连接总数的两个关键点。
Erlang虚拟机默认的端口上限为65536, erlang17通过erl +Q 1000000可以修改端口上限为1000000,利用erlang:system_info(port_limit)进行查询,系统可以打开的最大文件描述符可以通过erlang:system_info(check_io)中的max_fds进行查看,查看系统当前port数量可以用erlang:length(erlang:ports())得到
3、代码:
tcp_client:
-module(tcp_client).
-export([start/1,send_data/2,close/1,loop_start/2]).
loop_start(Port,Num)->
case start(Port)of
system_limit -> io:format("");
_-> loop_start(Port,Num+1)
end.
start(Port)->
try
case gen_tcp:connect("127.0.0.1",Port, [binary,{packet,raw},{active,true},{reuseaddr,true}])of
{ok,Socket}->Socket;
{error,Reason}-> {error,Reason}
end
catch
throw:T->T;
exit:R->R;
error:E->E
end.
send_data(Socket,Data)when is_list(Data)orelse is_binary(Data)->
gen_tcp:send(Socket,Data),
receive
{tcp,Socket,Bin}->
io:format("recv~p~n",[Bin]);
{tcp_closed,Socket}->
io:format("receive server don't accept connection!~n")
end.
close(Socket)when is_port(Socket)->
gen_tcp:close(Socket).
generic_server:
-module(generic_server).
-behaviour(gen_server).
-define(TCP_OPTIONS,[binary,{packet,raw},{reuseaddr,true},{active,false}]).
-export([start/4,init/1, handle_cast/2,accept/1,accept_loop/5]).
-export([]).
-record(server_state, {port,loop,ip=any,lsocket=null,conn=0,maxconn}).
start(ServerName,Port,Max,{M,F})->
gen_server:start_link({local,ServerName},?MODULE,[#server_state{port=Port,maxconn=Max,loop={M,F}}],[]).
init([State=#server_state{port=Port}])->
case gen_tcp:listen(Port,?TCP_OPTIONS)of
{ok,LSocket}->
{ok,accept(State#server_state{lsocket=LSocket})};
{error,Reason}->
{stop,{create_listen_socket,Reason}}
end.
accept(State=#server_state{lsocket=LSocket,loop=Loop,conn=Conn,maxconn=Max})->
Pid = spawn(generic_server,accept_loop,[self(),LSocket,Loop,Conn,Max]),
State.
accept_loop(Server,LSocket,{M,F},Conn,Max)->
erlang:group_leader(erlang:whereis(user),self()),
{ok,Sock} = gen_tcp:accept(LSocket),
if
true ->
gen_server:cast(Server,{accept_new,self()}),
M:F(Sock)
end.
handle_cast({accept_new,FromPid},State)->
Conn =State#server_state.conn,
io:format("current connect is~p~n",[Conn+1]),
LSocket =State#server_state.lsocket,
Loop=State#server_state.loop,
Max=State#server_state.maxconn,
Pid=spawn(generic_server,accept_loop,[self(),LSocket,Loop,Conn+1,Max]),
{noreply, State#server_state{conn=Conn+1}};
handle_cast({connect_close,FromPid},State)->
Conn=State#server_state.conn,
io:format("current connect is~p~n",[Conn-1]),
{noreply, State#server_state{conn=Conn-1}}.
echo_server:
-module(echo_server).
-export([start/1,loop/1]).
start(Port)->
Max =4,
generic_server:start(echo_server, Port, Max, {?MODULE,loop}).
loop(Sock)->
case gen_tcp:recv(Sock,0)of
{ok,Data}->
gen_tcp:send(Sock,Data),
loop(Sock);
{error,closed} ->
io:format("client sock close~n"),
gen_server:cast(echo_server,{connect_close,self()})
end.
4、通信流程
5、结论
设定ulimit –n 100000,假定ip_local_port_range 1 65535,侦听一个端口的连接数为65534,再打开一个侦听端口为100000-65534=34466, 单端口受制于ip_local_port_range的范围,总共的连接数为打开的文件描述符个数。