command + N  查找类

command + shift + N 查找文件

alt + enter 快速import class

alt + command 格式化代码

shift + control + f 搜索全部文件

shift + command + u 大小写转换

command + alt + t
用*来围绕选中的代码行( * 包括if、while、try catch等)这个功能也很方便,把我以前要做的:①先写if-else,②然后调整代码的缩进格式,还要注意括号是否匹配了,现在用这个功能来做,省事多了(不过让我变得越来越懒了)

F2/Shift + F2
跳转到下/上一个错误语句处IDEA提供了一个在错误语句之间方便的跳转的功能,你使用这个快捷键可以快捷在出错的语句之间进行跳转。

command + Alt + O
优化import自动去除无用的import语句,蛮不错的一个功能。

command + ]/[
跳转到代码块结束/开始处,这个功能vi也有,也是很常用的一个代码编辑功能了。

command + E
可以显示最近编辑的文件列表

command + Shift +Backspace
可以跳转到上次编辑的地方

command + F12
可以显示当前文件的结构

command + F7
可以查询当前元素在当前文件中的引用,然后按F3可以选择

command+Alt+V
可以引入变量。例如把括号内的SQL赋成一个变量

command+Shift+F7
可以显示当前元素在文件中的使用

alt + F7
查找方法被调用的地方

 

link: http://blog.btnotes.com/articles/226.html

套接字模式

主动模式(选项{active, true})一般让人很喜欢,非阻塞消息接收,但在系统无法应对超大流量请求时,客户端发送的数据快过服务器可以处理的速度,那么系统就可能会造成消息缓冲区被塞满,可能出现持续繁忙的流量的极端情况下,系统因请求而溢出,虚拟机造成内存不足的风险而崩溃。

使用被动模式(选项{active, false})的套接字,底层的TCP缓冲区可用于抑制请求,并拒绝客户端的消息,在接收数据的地方都会调用gen_tcp:recv,造成阻塞(单进程模式下就只能消极等待某一个具体的客户端套接字,很危险)。需要注意的是,操作系统可能还会做一些缓存允许客户端机器继续发送少量数据,然后才会将其阻塞,此时Erlang尚未调用recv函数。

混合型模式(半阻塞),使用选项{active, once}打开,主动仅针对一个消息,在控制进程发送完一个数据消息后,必须显示调用inet:setopts(Socket, [{active, once}])重新激活以便接受下一个消息(在此之前,系统处于阻塞状态)。可见,混合型模式综合了主动模式和被动模式的两者优势,可实现流量控制,防止服务器被过多消息淹没。

以下TCP Server代码,都是建立在混合型模式(半阻塞)基础上。

prim_inet相关说明

prim_inet没有官方文档,可以认为是对底层socket的直接包装。淘宝yufeng说,这是otp内部实现的细节 是针对Erlang库开发者的private module,底层模块,不推荐使用。但在Building a Non-blocking TCP server using OTP principles示范中演示了prim_inet操作Socket异步特性。

设计模式

一般来说,需要一个单独进程进行客户端套接字监听,每一个子进程进行处理来自具体客户端的socket请求。

Building a Non-blocking TCP server using OTP principles示范中,子进程使用gen_fsm处理,很巧妙的结合状态机和消息事件,值得学习。

Erlang: A Generalized TCP Server文章中,作者也是使用此模式,但子进程不符合OTP规范,因此个人认为不是一个很好的实践模式。

simple_one_for_one

简易的一对一监督进程,用来创建一组动态子进程。对于需要并发处理多个请求的服务器较为合适。比如socket 服务端接受新的客户端连接请求以后,需要动态创建一个新的socket连接处理子进程。若遵守OTP原则,那就是子监督进程。

TCP Server实现

基于标准API简单实现

也是基于{active, once}模式,但阻塞的等待下一个客户端连接的任务被抛给了子监督进程。

看一下入口tcp_server_app吧

module(tcp_server_app).
author(‘yongboy@gmail.com’).
behaviour(application).
export([start/2, stop/1]).
define(DEF_PORT, 2222).
start(_Type, _Args) ->
Opts = [binary, {packet, 2}, {reuseaddr, true},
{keepalive, true}, {backlog, 30}, {active, false}],
ListenPort = get_app_env(listen_port, ?DEF_PORT),
{ok, LSock} = gen_tcp:listen(ListenPort, Opts),
case tcp_server_sup:start_link(LSock) of
{ok, Pid} ->
tcp_server_sup:start_child(),
{ok, Pid};
Other ->
{error, Other}
end.
stop(_S) ->
ok.
get_app_env(Opt, Default) ->
case application:get_env(application:get_application(), Opt) of
{ok, Val} -> Val;
_ ->
case init:get_argument(Opt) of
[[Val | _]] -> Val;
error -> Default
end
end.

读取端口,然后启动主监督进程(此时还不会监听处理客户端socket请求),紧接着启动子监督进程,开始处理来自客户端的socket的连接。

监督进程tcp_server_sup也很简单:

module(tcp_server_sup).
author(‘yongboy@gmail.com’).
behaviour(supervisor).
export([start_link/1, start_child/0]).
export([init/1]).
define(SERVER, ?MODULE).
start_link(LSock) ->
supervisor:start_link({local, ?SERVER}, ?MODULE, [LSock]).
start_child() ->
supervisor:start_child(?SERVER, []).
init([LSock]) ->
Server = {tcp_server_handler, {tcp_server_handler, start_link, [LSock]},
temporary, brutal_kill, worker, [tcp_server_handler]},
Children = [Server],
RestartStrategy = {simple_one_for_one, 0, 1},
{ok, {RestartStrategy, Children}}.

需要注意的是,只有调用start_child函数时,才真正调用tcp_server_handler:start_link([LSock])函数。

tcp_server_handler的代码也不复杂:

module(tcp_server_handler).
behaviour(gen_server).
export([start_link/1]).
export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
record(state, {lsock, socket, addr}).
start_link(LSock) ->
gen_server:start_link(?MODULE, [LSock], []).
init([Socket]) ->
inet:setopts(Socket, [{active, once}, {packet, 2}, binary]),
{ok, #state{lsock = Socket}, 0}.
handle_call(Msg, _From, State) ->
{reply, {ok, Msg}, State}.
handle_cast(stop, State) ->
{stop, normal, State}.
handle_info({tcp, Socket, Data}, State) ->
inet:setopts(Socket, [{active, once}]),
io:format(~p got message ~p\n, [self(), Data]),
ok = gen_tcp:send(Socket, <<Echo back : , Data/binary>>),
{noreply, State};
handle_info({tcp_closed, Socket}, #state{addr=Addr} = StateData) ->
error_logger:info_msg(~p Client ~p disconnected.\n, [self(), Addr]),
{stop, normal, StateData};
handle_info(timeout, #state{lsock = LSock} = State) ->
{ok, ClientSocket} = gen_tcp:accept(LSock),
{ok, {IP, _Port}} = inet:peername(ClientSocket),
tcp_server_sup:start_child(),
{noreply, State#state{socket=ClientSocket, addr=IP}};
handle_info(_Info, StateData) ->
{noreply, StateData}.
terminate(_Reason, #state{socket=Socket}) ->
(catch gen_tcp:close(Socket)),
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.

代码很精巧,有些小技巧在里面。子监督进程调用start_link函数,init会返回{ok, #state{lsock = Socket}, 0}. 数字0代表了timeout数值,意味着gen_server马上调用handle_info(timeout, #state{lsock = LSock} = State)函数,执行客户端socket监听,阻塞于此,但不会影响在此模式下其它函数的调用。直到有客户端进来,然后启动一个新的子监督进程tcp_server_handler,当前子监督进程解除阻塞。

 

基于prim_inet实现

这个实现师从于Non-blocking TCP server using OTP principles一文,但子进程改为了gen_server实现。

看一看入口,很简单的:

module(tcp_server_app).
author(‘yongboy@gmail.com’).
behaviour(application).
export([start_client/1]).
export([start/2, stop/1]).
define(DEF_PORT, 2222).
%% A startup function for spawning new client connection handling FSM.
%% To be called by the TCP listener process.
start_client(Socket) ->
tcp_server_sup:start_child(Socket).
start(_Type, _Args) ->
ListenPort = get_app_env(listen_port, ?DEF_PORT),
tcp_server_sup:start_link(ListenPort, tcp_client_handler).
stop(_S) ->
ok.
get_app_env(Opt, Default) ->
case application:get_env(application:get_application(), Opt) of
{ok, Val} -> Val;
_ ->
case init:get_argument(Opt) of
[[Val | _]] -> Val;
error -> Default
end
end.

监督进程代码:

module(tcp_server_sup).
author(‘yongboy@gmail.com’).
behaviour(supervisor).
export([start_child/1, start_link/2, init/1]).
define(SERVER, ?MODULE).
define(CLIENT_SUP, tcp_client_sup).
define(MAX_RESTART, 5).
define(MAX_TIME, 60).
start_child(Socket) ->
supervisor:start_child(?CLIENT_SUP, [Socket]).
start_link(ListenPort, HandleMoudle) ->
supervisor:start_link({local, ?SERVER}, ?MODULE, [ListenPort, HandleMoudle]).
init([Port, Module]) ->
TcpListener = {tcp_server_sup, % Id = internal id
{tcp_listener, start_link, [Port, Module]}, % StartFun = {M, F, A}
permanent, % Restart = permanent | transient | temporary
2000, % Shutdown = brutal_kill | int() >= 0 | infinity
worker, % Type = worker | supervisor
[tcp_listener] % Modules = [Module] | dynamic
},
TcpClientSupervisor = {?CLIENT_SUP,
{supervisor, start_link, [{local, ?CLIENT_SUP}, ?MODULE, [Module]]},
permanent,
infinity,
supervisor,
[]
},
{ok,
{{one_for_one, ?MAX_RESTART, ?MAX_TIME},
[TcpListener, TcpClientSupervisor]
}
};
init([Module]) ->
{ok,
{_SupFlags = {simple_one_for_one, ?MAX_RESTART, ?MAX_TIME},
[
% TCP Client
{ undefined, % Id = internal id
{Module, start_link, []}, % StartFun = {M, F, A}
temporary, % Restart = permanent | transient | temporary
2000, % Shutdown = brutal_kill | int() >= 0 | infinity
worker, % Type = worker | supervisor
[] % Modules = [Module] | dynamic
}
]
}
}.

策略不一样,one_for_one包括了一个监听进程tcp_listener,还包含了一个tcp_client_sup进程树(simple_one_for_one策略)

tcp_listener单独一个进程用于监听来自客户端socket的连接:

module(tcp_listener).
author(‘saleyn@gmail.com’).
behaviour(gen_server).
export([start_link/2]).
export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
code_change/3]).
record(state, {
listener, % Listening socket
acceptor, % Asynchronous acceptor’s internal reference
module % FSM handling module
}).
start_link(Port, Module) when is_integer(Port), is_atom(Module) ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [Port, Module], []).
init([Port, Module]) ->
process_flag(trap_exit, true),
Opts = [binary, {packet, 2}, {reuseaddr, true},
{keepalive, true}, {backlog, 30}, {active, false}],
case gen_tcp:listen(Port, Opts) of
{ok, Listen_socket} ->
%%Create first accepting process
{ok, Ref} = prim_inet:async_accept(Listen_socket, 1),
{ok, #state{listener = Listen_socket,
acceptor = Ref,
module = Module}};
{error, Reason} ->
{stop, Reason}
end.
handle_call(Request, _From, State) ->
{stop, {unknown_call, Request}, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info({inet_async, ListSock, Ref, {ok, CliSocket}},
#state{listener=ListSock, acceptor=Ref, module=Module} = State) ->
try
case set_sockopt(ListSock, CliSocket) of
ok -> ok;
{error, Reason} -> exit({set_sockopt, Reason})
end,
%% New client connected – spawn a new process using the simple_one_for_one
%% supervisor.
{ok, Pid} = tcp_server_app:start_client(CliSocket),
gen_tcp:controlling_process(CliSocket, Pid),
%% Signal the network driver that we are ready to accept another connection
case prim_inet:async_accept(ListSock, 1) of
{ok, NewRef} -> ok;
{error, NewRef} -> exit({async_accept, inet:format_error(NewRef)})
end,
{noreply, State#state{acceptor=NewRef}}
catch exit:Why ->
error_logger:error_msg(Error in async accept: ~p.\n, [Why]),
{stop, Why, State}
end;
handle_info({inet_async, ListSock, Ref, Error}, #state{listener=ListSock, acceptor=Ref} = State) ->
error_logger:error_msg(Error in socket acceptor: ~p.\n, [Error]),
{stop, Error, State};
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, State) ->
gen_tcp:close(State#state.listener),
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%% Taken from prim_inet. We are merely copying some socket options from the
%% listening socket to the new client socket.
set_sockopt(ListSock, CliSocket) ->
true = inet_db:register_socket(CliSocket, inet_tcp),
case prim_inet:getopts(ListSock, [active, nodelay, keepalive, delay_send, priority, tos]) of
{ok, Opts} ->
case prim_inet:setopts(CliSocket, Opts) of
ok -> ok;
Error -> gen_tcp:close(CliSocket), Error
end;
Error ->
gen_tcp:close(CliSocket), Error
end.
view rawtcp_listener.erl hosted with ❤ by GitHub

很显然,接收客户端的连接之后,转交给tcp_client_handler模块进行处理:

module(tcp_client_handler).
behaviour(gen_server).
export([start_link/1]).
export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
record(state, {socket, addr}).
define(TIMEOUT, 120000).
start_link(Socket) ->
gen_server:start_link(?MODULE, [Socket], []).
init([Socket]) ->
inet:setopts(Socket, [{active, once}, {packet, 2}, binary]),
{ok, {IP, _Port}} = inet:peername(Socket),
{ok, #state{socket=Socket, addr=IP}}.
handle_call(Request, From, State) ->
{noreply, ok, State}.
handle_cast(Msg, State) ->
{noreply, State}.
handle_info({tcp, Socket, Data}, State) ->
inet:setopts(Socket, [{active, once}]),
io:format(~p got message ~p\n, [self(), Data]),
ok = gen_tcp:send(Socket, <<Echo back : , Data/binary>>),
{noreply, State};
handle_info({tcp_closed, Socket}, #state{addr=Addr} = StateData) ->
error_logger:info_msg(~p Client ~p disconnected.\n, [self(), Addr]),
{stop, normal, StateData};
handle_info(_Info, StateData) ->
{noreply, StateData}.
terminate(_Reason, #state{socket=Socket}) ->
(catch gen_tcp:close(Socket)),
ok.
code_change(OldVsn, State, Extra) ->
{ok, State}.

和标准API对比一下,可以感受到异步IO的好处。

小结

通过不同的模式,简单实现一个基于Erlang OTP的TCP服务器,也是学习总结,不至于忘记。

您若有更好的建议,欢迎告知,谢谢。

link: http://www.blogjava.net/yongboy/archive/2012/10/24/390185.html

为了研究怎么用Erlang写一个游戏服务器,我很幸运的下到了一份英雄远征的服
务器Erlang源码,这两天花了点时间看代码,其中看到做TCP的accept动作时,它
是用的一个函数prim_inet:async_accept/2,这个可跟书上说的不一样(一般来
说书上教的是用gen_tcp:accept/1),于是我google了一下,发现找不到文档,
再翻一下发现已经有不少人问为什么这是一个undocumented的函数,也就是说
Erlang就没想让你去用这个函数,所以文档自然没提供。一般来说undocumented
的函数你是最好别用,因为下一次Erlang更新的时候没准就没这个函数了,或者
参数变了,或者行为变了。总之各种不靠谱的事都可以发生。这个事情可以由
这个帖子 看到。不过,这个帖子还特地说了:However, you might find
prim_inet:async_accept/2 useful.这样又把我们带到了 这个帖子 。在这里,
楼主说看起来这个函数很有趣,很有用,对此提了2个问题,1是为什么这个函数
还是一个undocumented,2是用这个函数安全吗?楼主还给了一篇讲如何用OTP原
理打造一个非阻塞的TCP服务器的文章。

这篇文章中说,虽然prim_inet:async_accept/2是一个undocumented的函数,但
因为他要写一个非阻塞的accept,所以还是会冒险去挖掘它的潜能。因为普通的
函数gen_tcp:accept/1是阻塞的,而现在需要做一个非阻塞的accept,只好用这
个undocumented的函数。

考虑一下这种异步的accept实现可以比同步的accept快多少?当同时有100个并发
的连接请求时,如果同样是有10个进程在做accept,异步的情况能让这100个请求
同时开始被处理。而同步的实现则需要让90个请求等待,先和10个请求accept完
再处理。最撮的那10个请求将要等待9次。(注:这只是简化的思考,实际上有可
能某个请求要等上几十次)再考虑每次的accept,这个我不太清楚,但我想应该
就是建立TCP连接的过程,也就是说client和server之间来回要跑3个包。假设平
均的延时是40ms,那么来回3次是120ms,等待10次差不多是要1秒多。考虑更差的
情况下,可能要等上几十秒,这种就已经是不能忍受的了。从这个角度来说,异
步的accept还是有价值的。

但是,同时有大量并发的连接请求的情况并不会经常出现。以游戏为例,只在刚
开服的时候会遇到这样的问题。正常运行的服务器很少再遇到大量的并发连接请
求。我想说的是,如果我们用100个,甚至1000个阻塞性的accept进程来代替这种
非正式的异步实现,也未尝不可。毕竟1000个进程对于erlang来说还是小case,
而对一个游戏服务器已经够用了。

最后总结一下,一,prim_inet:async_accept/2实现的异步OTP的TCP server在处
理大量并发的情况有优势;二,这种情况可以通过多开一些同步的阻塞性accept
进程在一定程度上克服;三,调用这个函数理论上来说毕竟还是不能完全放心的,
用不用看你的选择了。

最后,如果有时间,可以考虑做一些测试,对这2种情况实际对比。

Temporary fix

Put the following in a file “patch.m”:

#import <AppKit/AppKit.h>
__attribute((constructor)) void Patch_10_10_2_entry()
{
NSLog(@”10.10.2 patch loaded”);
}
@interface NSTouch ()
– (id)_initWithPreviousTouch:(NSTouch *)touch newPhase:(NSTouchPhase)phase position:(CGPoint)position isResting:(BOOL)isResting force:(double)force;
@end
@implementation NSTouch (Patch_10_10_2)
– (id)_initWithPreviousTouch:(NSTouch *)touch newPhase:(NSTouchPhase)phase position:(CGPoint)position isResting:(BOOL)isResting
{
return [self _initWithPreviousTouch:touch newPhase:phase position:position isResting:isResting force:0];
}
@end

Compile it:

clang -dynamiclib -framework AppKit patch.m -o patch.dylib

Use it:

env DYLD_INSERT_LIBRARIES=/path/to/patch.dylib “/path/to/Google Chrome.app/Contents/MacOS/Google Chrome”

Safer way

A safer way would be to copy the 10.1.1 AppKit.framework and create as a local framework for just Google Chrome:

cd /Applications/Google Chrome.app/Contents/
mkdir Frameworks
cp -R path-to-10.1.1/AppKit.framework Frameworks/
install_name_tool -change /System/Library/Frameworks/AppKit.framework/Versions/C/AppKit @executable_path/../Frameworks/AppKit.framework/Versions/C/AppKit MacOS/Google Chrome

link:  http://www.reddit.com/r/apple/comments/2n721c/temporary_fix_for_chrome_crash_in_yosemite_10102/

当连接数多时,经常出现大量FIN_WAIT1,可以修改 /etc/sysctl.conf
修改

net.ipv4.tcp_fin_timeout = 10
net.ipv4.tcp_keepalive_time = 30
net.ipv4.tcp_window_scaling = 0
net.ipv4.tcp_sack = 0

然后:

/sbin/sysctl -p

使之生效
#######################################################################################
apache服务器的time_wait过多 fin_wait1过多等问题
1。time_wait状态过多。
    通常表现为apache服务器负载高,w命令显示load average可能上百,但是web服务基本没有问题。同时ssh能够登陆,但是反应非常迟钝。
原因:最可能的原因是httpd.conf里面keepalive没有开,导致每次请求都要建立新的tcp连接,请求完成以后关闭,增加了很多 time_wait的状态。另,keepalive可能会增加一部分内存的开销,但是问题不大。也有一些文章讨论到了sysctl里面一些参数的设置可以改善这个问题,但是这就舍本逐末了。
2。fin_wait1状态过多。fin_wait1状态是在server端主动要求关闭tcp连接,并且主动发送fin以后,等待client端回复ack时候的状态。fin_wait1的产生原因有很多,需要结合netstat的状态来分析。
netstat -nat|awk ‘{print awk $NF}’|sort|uniq -c|sort -n
上面的命令可以帮助分析哪种tcp状态数量异常
netstat -nat|grep “:80″|awk ‘{print $5}’ |awk -F: ‘{print $1}’ | sort| uniq -c|sort -n
则可以帮助你将请求80服务的client ip按照连接数排序。
回到fin_wait1这个话题,如果发现fin_wait1状态很多,并且client ip分布正常,那可能是有人用肉鸡进行ddos攻击、又或者最近的程序改动引起了问题。一般说来后者可能性更大,应该主动联系程序员解决。
但是如果有某个ip连接数非常多,就值得注意了,可以考虑用iptables直接封了他。

Mac下3065破解方法

使用vi打开SublimeText

使用命令:%!xxd 转换 搜索 3342 3442 替换成 3242 3442

:%!xxd -r转回

:wq保存退出

—– BEGIN LICENSE —–
Andrew Weber
Single User License
EA7E-855605
813A03DD 5E4AD9E6 6C0EEB94 BC99798F
942194A6 02396E98 E62C9979 4BB979FE
91424C9D A45400BF F6747D88 2FB88078
90F5CC94 1CDC92DC 8457107A F151657B
1D22E383 A997F016 42397640 33F41CFC
E1D0AE85 A0BBD039 0E9C8D55 E1B89D5D
5CDB7036 E56DE1C0 EFCC0840 650CD3A6
B98FC99C 8FAC73EE D2B95564 DF450523
—— END LICENSE ——

link: http://zhixinhu.blog.51cto.com/3132199/1546931

TCP是一种面向连接的协议,连接的建立和断开需要通过收发相应的分节来实现。某些时候,由于网络的故障或是一方主机的突然崩溃而另一方无法检测到,以致始终保持着不存在的连接。下面介绍一种方法来检测这种异常断开的情况

1) 在TCP协议中提供了KEEPALIVE检测。该选项使能后,在一个TCP连接上,若指定的一段时间内没有数据交换,则自动发送分节等待对方确认。

     SO_KEEPALIVE : 该选项设置是否打开探测
TCP_KEEPIDLE : 开始发送探测分节前等待的空闲时间
TCP_KEEPINTVL: 两次发送探测分节的时间间隔
TCP_KEEPCNT: 判定断开前发送探测分节的次数

2) 设定探测相关选项值

     int keepalive = 1;             // 打开探测
int keepidle = 60;        // 开始探测前的空闲等待时间
int keepintvl = 10;        // 发送探测分节的时间间隔
int keepcnt = 3;        // 发送探测分节的次数

3) 设置套接字的属性

     if (setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepalive, sizeof (keepalive) < 0)
{
perror(“fail to set SO_KEEPALIVE”);
exit(-1);
}
if (setsockopt(sockfd, SOL_TCP, TCP_KEEPIDLE, (void *) &keepidle, sizeof (keepidle) < 0)
{
perror(“fail to set SO_KEEPIDLE”);
exit(-1);
}
if (setsockopt(sockfd, SOL_TCP, TCP_KEEPINTVL, (void *)&keepintvl, sizeof (keepintvl) < 0)
{
perror(“fail to set SO_KEEPINTVL”);
exit(-1);
}
if (setsockopt(sockfd, SOL_TCP, TCP_KEEPCNT, (void *)&keepcnt, sizeof (keepcnt) < 0)
{
perror(“fail to set SO_KEEPALIVE”);
exit(-1);
}

一旦打开KEEPALIVE探测,当TCP连接异常断开后,对sockfd进行recv操作会返回-1,并且errno的值为ETIMEDOUT。

这样一来就可以很方便的在应用程序中检测TCP连接的情况,如果检测到异常断开最简单的处理就是关闭连接。

link: http://www.embedu.org/Column/Column318.htm

有利的部分

erlang是好的,吸引了很多人的兴趣。不少人甚至将erlang体系的影响带到了自己所在的其他语言项目中,构建一些类似erlang的基础设施。包括我。

  1. 随意挥霍的process。 端游从早期的单进程支持一个游戏服,发展到现在的多进程支持一个游戏服。其中涉及多个进程协作的逻辑越来越多,异步任务相互之间由于时间差带来的问题也越来越频繁。
    通常我总会利用状态机或者类似的办法管理异步任务,但并不能顾及到每一处,而且状态机增加了理解代码的难度。
    erlang轻量的process使我能为每个异步任务创建一个独立的执行过程,同时变量不可变的机制几乎消除了多线程中的资源共用的问题。
  2. 发送消息太简单。
    用C++,发送消息从socket干起,还只能发给一个真正的系统进程,然后switch分发处理。
    在异步任务逐渐增多的环境下,有时候很希望语法糖是:能将消息发送给某个异步任务。需要消息平台是一种逻辑上的抽象,而不是底层的socket。
    这个可以C++包装一个,而erlang提供了一个极其完善的面向process的消息系统,process又可以映射到异步任务中。
  3. ETS,还有Mnesia,it’s great
    在多进程支持一个游戏功能的情况下,有大把的数据需要在不同的进程之间同步共享。也用C++包装一个类似的系统能降低很多重复的数据同步工作。
    erlang提供了ETS甚至Mnesia,几乎无条件的为我们提供了数据的分布、持久和恢复。

困难的部分

用erlang开发游戏最困难的地方就是实现场景逻辑。这里没有异步任务,这里是效率、指针和角色相互操作的天堂,但是对erlang来说可能是地狱。
常见的是player/NPC角色之间的立即相互修改,从地图区域查询一批player/NPC角色等。在指针环境下这些操作非常有效率。
一个实时战斗的MMORPG要求核心逻辑要达到25帧/S,区域地图在线1000人。

  1. 如果我们为每个player/NPC映射到process用于交互,那么他们必然使用消息去操作访问对方。 这很美,但是效率不行。因为player/NPC的操作访问实在太频繁了,这一来erlang就变成一个消息繁忙的系统,真正的游戏计算反而耽误了。
  2. 每个场景一个process,场景中所有的player/NPC集中保存到tuple,list中,通过参数传递。
    这样具体操作player/NPC的数据确实快了,但是erlang的原生list类型不是利于搜索的类型,这一趟趟查下来也就将计算时间花光光了。
    另外通过参数传递也会导致每个相关的子函数都要留一个口子。
  3. 每个场景一个process,场景中的所有player/NPC都保存到进程字典中。
    进程字典的存取速度在erlang中算快了,插入100ns,查询40ns。ETS的速度是us。参见进程字典到底有多快

游戏的三种逻辑

如果要用erlang来做MMORPG,那规划一下游戏的基本逻辑模块,根据模块的特性选择不同的erlang模式,是有意义的。像C++那样一种模式就行得通的做法,并不适用于erlang。

  1. 自我修改逻辑
    比如升级,强化之类,就是改改自己的属性,改改自己的技能。
    这种逻辑erlang可以为每个player/NPC创建process负责,也可以一个process处理多个player/NPC的类似逻辑。客户端对于这种操作的延时总能容忍1,2秒,慢慢来,性能没关系。
  2. 低频角色交互逻辑
    比如组队,领取任务,NPC思考等等,角色需要相互操作对方数据。同样也是延时容忍较高,可以尽情发挥erlang在这种交互逻辑中的异步任务支持能力。
  3. 高频角色交互逻辑
    同一个场景中,角色交互频率最高的就是移动和战斗。
    移动影响相互间的视野,战斗则直接读取修改彼此的数据。不要忘了,1000人25帧/S的计算。

移动和战斗

这一块比较纯粹,由一个process为一个场景的player/NPC提供服务,基本上有下面几样内容就搞定计算:

  1. pos,角色坐标
  2. atb,角色属性
  3. skill,技能列表
  4. state,状态列表
  5. 同屏表
  6. 地图数据
  7. 事件列表

前4条属于角色数据,简单操作即可。
同屏表通过四叉树管理角色的空间,为各种类型的群攻判定提供快目标的快速查询。
地图数据为角色检测技能释放的空间合法性,以及战斗寻路。
事件列表则每帧更新技能、状态等数值的计算。

效率优化

仅仅是纯粹的技能效果计算,erlang在虚拟机上的效率肯定不及C++。不过属性计算这块要求的性能并不高,猜测还是能扛下这个计算。
费时、难写的计算可能有两块,同屏表和地图数据。
erlang的数据类型很难高效的处理关系型结构,空间四叉树和地图三角关系用erlang实现起来相当凑合。那么,同屏表和地图的功能到底作为场景战斗process的一个功能,还是以独立process的形式服务呢?
process发送消息的性能大概是1us。
通常一个角色的一次战斗计算需要访问同屏表和地图功能各一次,4us。1000人的话是4ms。1帧40ms,那么每帧大概有30ms左右的时间用于完整的战斗计算,包括同屏计算和地图计算。
为了进一步提高同屏计算和地图计算的性能,可以用C++实现erlang的内置函数。此外,原先逐个角色串行计算同屏功能和地图功能,可以改并行:

  1. 先收集全部角色的同屏计算需求
  2. 将全部计算需求均匀发送到多个同屏表process进行计算
  3. 一边收集结果一边继续计算剩下的战斗部分。

地图计算也采用类似的并行处理,即可提高效率。

Mac下的启动服务主要有三个地方可配置:

1,系统偏好设置->帐户->登陆项

2,/System/Library/StartupItems 和 /Library/StartupItems/

3,launchd 系统初始化进程配置。

前两种优化比较简单,本文主要介绍的是第三种更为复杂的launchd配置优化。

launchd是Mac OS下,用于初始化系统环境的关键进程。类似Linux下的init, rc。

我们先来看一下Mac OS X的启动原理:

1,mac固件激活,初始化硬件,加载BootX引导器。

2,BootX加载内核与内核扩展(kext)。

3,内核启动launchd进程。

4,launchd根据 /System/Library/LaunchAgents , /System/Library/LaunchDaemons , /Library/LaunchDaemons, Library/LaunchAgents , ~/Library/LaunchAgents 里的plist配置,启动服务守护进程。

看完了Mac OS X的启动原理,我们不难发觉 /System/Library/LaunchAgents , /System/Library/LaunchDaemons , /Library/LaunchDaemons, Library/LaunchAgents 五个目录下的plist属性文件是优化系统的关键。

下面再来理解几个基础概念:

/System/Library和/Library和~/Library目录的区别?

/System/Library目录是存放Apple自己开发的软件。

/Library目录是系统管理员存放的第三方软件。

~/Library/是用户自己存放的第三方软件。

LaunchDaemons和LaunchAgents的区别?

LaunchDaemons是用户未登陆前就启动的服务(守护进程)。

LaunchAgents是用户登陆后启动的服务(守护进程)。

上面提到的五个目录下的plist文件格式及每个字段的含义:

Key Description Required
Label The name of the job yes
ProgramArguments Strings to pass to the program when it is executed yes
UserName The job will be run as the given user, who may not necessarily be the one who submitted it to launchd. no
inetdCompatibility Indicates that the daemon expects to be run as if it were launched by inetd no
Program The path to your executable. This key can save the ProgramArguments key for flags and arguments. no
onDemand A boolean flag that defines if a job runs continuously or not no
RootDirectory The job will be chrooted into another directory no
ServiceIPC Whether the daemon can speak IPC to launchd no
WatchPaths Allows launchd to start a job based on modifications at a file-system path no
QueueDirectories Similar to WatchPath, a queue will only watch an empty directory for new files no
StartInterval Used to schedule a job that runs on a repeating schedule. Specified as the number of seconds to wait between runs. no
StartCalendarInterval Job scheduling. The syntax is similar to cron. no
HardResourceLimits Controls restriction of the resources consumed by any job no
LowPriorityIO Tells the kernel that this task is of a low priority when doing file system I/O no
Sockets An array can be used to specify what socket the daemon will listen on for launch on demand no

看不懂上面地plist配置吗?没关系,我们的优化策略是完全卸载服务,所以我们不用关心plist里的配置含义。

开始优化禁用服务,我们需要用到Mac OS提供的一个工具指令-launchctl

launchctl 指令会针对服务设置一个禁用标志,launchd启动时会先检查这个服务是否被禁用,从而确定是否需要启用这个服务。

禁用服务的方法1

先找到禁用标志文件 /var/db/launchd.db/com.apple.launchd/overrides.plist,查看你要禁用的服务是否已被禁用了。

有些服务已被禁用,但未列在overrides.plist里。此时,你还需要检查这个服务的plist文件Label字段是否已经标记为 Disable。

确认这个服务未禁用后,我们就可以通过调用如下命令,来禁用服务:

sudo launchctl unload plist文件路径

sudo launchctl unload -w plist文件路径

比如,我想禁用spotlight,则输入

sudo launchctl unload /System/Library/LaunchAgents/com.apple.Spotlight.plist

sudo launchctl unload -w /System/Library/LaunchAgents/com.apple.Spotlight.plist

禁用完服务以后,重启Mac OS即可生效。

禁用服务的方法2,一种更有效且暴力的方法(推荐)

先卸载服务

sudo launchctl unload /System/Library/LaunchAgents/com.apple.Spotlight.plist

然后将plist文件mv到其他目录备份。重启。搞定。是不是很简单!

我个人比较喜欢这种禁用服务的方式,所以推荐一下。

如果发现服务禁用后,系统或软件出现异常,可以通过如下命令,还原服务:

方法1:

sudo launchctl load -wF plist文件路径

方法2:

将备份的plist文件mv回原来的文件夹。

sudo launchctl load plist文件路径

注意:系统级服务的禁用要异常小心,请在禁用前google,确保你熟知这个服务的作用。否则可能导致系统无法启动。

最安全的做法就是不要去禁用它了。

当然,用户服务我们还是可以放心禁用的,有问题最多再启用呗。

下面是我禁用的服务列表:

/System/Library/LaunchDaemons/com.apple.metadata.mds.plist (禁用spotlight的前提)

/System/Library/LaunchAgents/com.apple.Spotlight.plist (Spotlight)

/Library/LaunchDaemons/com.google.keystone.daemon.plist (Google Software Update)

/Library/LaunchAgents/com.google.keystone.root.agent (Google Software Update)

~/Library/LaunchAgents/com.google.keystone.agent.plist (Google Software Update,用户下的进程不需要加 sudo)

~/Library/LaunchAgents/com.apple.CSConfigDotMacCert-ken.wug@me.com-SharedServices.Agent.plist (me.com的共享服务,我不用)

/System/Library/LaunchDaemons/org.cups.cupsd.plist (打印机)

/System/Library/LaunchDaemons/org.cups.cups-lpd.plist (打印机)

/System/Library/LaunchDaemons/com.apple.blued.plist (蓝牙)

/System/Library/LaunchAgents/com.apple.AirPortBaseStationAgent.plist (apple无线基站,我没有这个设备)

知道守护进程(服务)名,如何找到对应的plist文件?

将进程(服务)名拷贝,然后到 /System/Library/LaunchAgents , /System/Library/LaunchDaemons , /Library/LaunchDaemons, Library/LaunchAgents , ~/Library/LaunchAgents 五个目录里,通过以下命令查找:

ll|grep 进程(服务)名

比如

ll|grep blued

在 /System/Library/LaunchDaemons 中找到了它。接下来,请按上面指导的步骤,禁用该服务。

 

link: http://www.3lian.com/edu/2013/04-26/67191.html

在Mac OS X中,有三种方式来实现启动项的配置:1)Login Items;2)StartupItems;3)launchd daemon。

1.Login Items

打开System Preferences,选择System -> Accounts,选择Login Items选项卡,将/Applications目录下的.app直接拖进右边的列表中。重启电脑之后就会发现列表中的程序在开机之后就自动启动了。

2.StartupItems

StartupItems,顾名思义,就是在系统启动过程中运行的程序,它们可以是运行完就立即终止的程序(比如,开机清空废纸篓),也可以是一直持续在系统运行周期的后台进程。

StartupItems一般存放在以下两个路径下:

1)/System/Library/StartupItems

2)/Library/StartupItems

大部分与系统相关的StartupItems都放在/System/Library/StartupItems这个路径下,它们会先于/Library/StartupItems路径下的执行,因为前者路径下的StartupItems提供了系统级的基础服务,比如crash reporting,core graphics services,system accounting等,而后者路径在默认情况下是不存在的,需要自己手动创建。

这里我们以/Library/StartupItems目录下的IcebergControlTower为例。

简单来说,在Mac OS X上,一个StartupItems包含以下两个方面的内容:

1)可执行程序;

2)包含依赖进程关系的plist文件(StartupParameters.plist)。

2.1 The Property List

2.1.1 Plist的key值与含义

StartupParameters.plist 是一个属性列表,包含了运行可执行程序的必要条件。

该plist需要获得root权限,包含了几个方面的内容:

1)Description;

对该服务的一个简单的描述,仅仅是描述,并不是说明实际的进程名称。

2)Provides;

指定StartupItems提供的服务。如图plist文件Provides中说明,StartupItems开启的后台进程名为:Iceberg Control Tower。

Provides可以指定多个服务,反映在图中就是Item0,Item1…等。这里只有Item0。

3)Uses;

指定了在StartupItems加载之前需要开启的服务。Mac OS X系统先尝试着加载Uses中指定的服务,然后再加载StartupItems。也就是说,即使Uses中指定的服务没有加载成功,系统仍然会加载StartupItems。

4)OrderPreference;

指定执行StartupItems的时间顺序。这个顺序的重要程度排在Uses之后,是指定执行完Uses之后的顺序。可能的取值包括:First, Early, None(default), Late, Last。

5)Messages。

2.1.2 创建一个StartupParameters.plist文件

  1. <?xml version=“1.0” encoding=“UTF-8”?>
  2. <!DOCTYPE plist PUBLIC “-//Apple Computer//DTD PLIST 1.0//EN”
  3.     “http://www.apple.com/DTDs/PropertyList-1.0.dtd”>
  4. <plist version=“1.0”>
  5. <dict>
  6.     <key>Description</key>
  7.     <string>Iceberg Control Tower Initilaization</string>
  8.     <key>Provides</key>
  9.     <array>
  10.         <string>Iceberg Control Tower</string>
  11.     </array>
  12.      <key>Uses</key>
  13.     <array>
  14.         <string>Disks</string>
  15.     </array>
  16.     <key>OrderPreference</key>
  17.         <string>None</string>
  18. </dict>
  19. </plist>

 

2.2 The Executable File

注意:1)可执行文件的名称和它所在的文件夹的文件名是一样的,这是系统默认的规则。

2)操作可执行文件需要获得root权限。

3)可执行文件是一个shell脚本。

打开IcebergControlTower文件目录下同名的可执行文件,可以看到脚本的具体内容:

一般的可执行文件包含这样几个方面的内容:

1)./etc/rc.common

Apple提供的一个脚本库,该脚本库里包含了为可执行文件引进参数的接口。在这里load这个库主要是调用RunService。

2)StartService(), StopService(), RestartService()

当可执行文件接收到的参数为start,stop或者restart时,执行相对应的函数。

参数含义:

start:开机过程中开启服务;

stop:关机过程中停止服务;

restart:在特定条件下重启服务。

3)RunService  “$1”

执行传递给该脚本的第一个参数指定的服务。

“$1” 表示传给该脚本的第一个参数。例如,传入的参数为start,则执行StartService()。

3. Launchd Daemon

launchd是Mac OS下用于初始化系统环境的关键进程,它是内核装载成功之后在OS环境下启动的第一个进程。

采用这种方式来配置自启动项很简单,只需要一个plist文件,通常(同时也是系统推荐)是将plist放在~/Library/LaunchAgents路径下。

3.1 plist文件格式及每个字段的含义:

1)Label【required】

该项服务的名称。

2)OnDemand【optional】

10.4提供的一个key值,功能与KeepAlive的基本功能相似,在10.5及之后的系统中被KeepAlive替代。KeepAlive扩展了更多的功能,除了使用单一的boolean作为key值之外,还能使用字典结合多个key值。

3)Program【ProgramArgument是required的,在没有ProgramArgument的情况下,必须要包含Program这个key】

指定可执行文件的路径。

4)RunAtLoad【optional】

标识launchd在加载完该项服务之后立即启动路径指定的可执行文件。默认值为false。

5)WorkingDirectory【optional】

该key在开启可执行文件之前,指定当前工作目录的路径。

6)KeepAlive【optional】

这个key值是用来控制可执行文件是持续运行呢,还是满足具体条件之后再启动。默认值为false,也就是说满足具体条件之后才启动。当设置值为ture时,表明无条件的开启可执行文件,并使之保持在整个系统运行周期内。

3.2 创建一个plist文件:

  1. <?xml version=“1.0” encoding=“UTF-8”?>
  2. <!DOCTYPE plist PUBLIC “-//Apple Computer//DTD PLIST 1.0//EN”
  3.     “http://www.apple.com/DTDs/PropertyList-1.0.dtd”>
  4. <plist version=“1.0”>
  5. <dict>
  6.     <key>Label</key>
  7.     <string>com.yourcompany.HSPA_USB_MODEM</string>
  8.     <key>OnDemand</key>
  9.     <false/>
  10.     <key>Program</key>
  11.     <string>/Application/HSPA USB MODEM.app/Running</string>
  12.     <key>RunAtLoad</key>
  13.     <true/>
  14.     <key>WorkingDirectory</key>
  15.     <string>/Application/HSPA USB MODEM.app</string>
  16. </dict>
  17. </plist>

4.三种方式的区别

初步了解了系统的启动过程之后(http://blog.csdn.net/abby_sheen/article/details/7817132),再来看这三种配置启动项的方式,就很容易理解这三种方式之间的差异了。

总的来说,LoginItems 和StartupItems的区别较明显。

StartupItems

LoginItems

Depedency Ordering

Need

Do not Need

Load Timing

during startup

after a user logs in

Launched by WHO

by root, but not necessarily

the user

Processes types

background processes and processes that terminate after running

any Mac OS X executable

对于我们自定义的Launchd daemon,通常(同时也是系统推荐)是放在~/Library/LaunchAgents路径下。launchd进程需要在用户login之后才能加载。这种方式与LoginItems最大的区别在于,启动的进程不同。LoginItems是通过loginwindow去启动的,而Launchd daemon是通过com.apple.launchd.peruser启动。

 

link: http://blog.csdn.net/abby_sheen/article/details/7817198