因ؓ(f)能力有限Q还是有很多东西QSO_REUSEADDR和SO_REUSEPORT的区别等Q没有能够在一文字中表达清楚Q作遗,也方便以后自己回q头来复?fn)?/p>
两者不是一码事Q没有可比性。有时也?x)被其搞晕,自己ȝ的不好,推荐StackOverflow?a >Socket options SO_REUSEADDR and SO_REUSEPORT, how do they differ?资料Qȝ的很全面?/p>
单来_(d)(x)
若有困惑Q推荐两者都讄Q不?x)有冲突?/p>
上一讲到SO_REUSEPORTQ多个程l定同一个端口,可以Ҏ(gu)需要控制进E的数量。这里讲讲基?code>Netty 4.0.25+Epoll navtie transport在单个进E内多个U程l定同一个端口的情况Q也是比较实用的?/p>
q是一个PING-PONGC应用Q?/p>
public void run() throws Exception {
final EventLoopGroup bossGroup = new EpollEventLoopGroup();
final EventLoopGroup workerGroup = new EpollEventLoopGroup();
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(EpollServerSocketChannel. class)
.childHandler( new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(
new StringDecoder(CharsetUtil.UTF_8 ),
new StringEncoder(CharsetUtil.UTF_8 ),
new PingPongServerHandler());
}
}).option(ChannelOption. SO_REUSEADDR, true)
.option(EpollChannelOption. SO_REUSEPORT, true)
.childOption(ChannelOption. SO_KEEPALIVE, true);
int workerThreads = Runtime.getRuntime().availableProcessors();
ChannelFuture future;
for ( int i = 0; i < workerThreads; ++i) {
future = b.bind( port).await();
if (!future.isSuccess())
throw new Exception(String. format("fail to bind on port = %d.",
port), future.cause());
}
Runtime. getRuntime().addShutdownHook (new Thread(){
@Override
public void run(){
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
});
}
打成jar包,在CentOS 7下面q行Q检查同一个端口所打开的文件句柄?/p>
# lsof -i:8000
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
java 3515 root 42u IPv6 29040 0t0 TCP *:irdmi (LISTEN)
java 3515 root 43u IPv6 29087 0t0 TCP *:irdmi (LISTEN)
java 3515 root 44u IPv6 29088 0t0 TCP *:irdmi (LISTEN)
java 3515 root 45u IPv6 29089 0t0 TCP *:irdmi (LISTEN)
同一q程Q但打开的文件句柄是不一L(fng)?/p>
/**
* UDP谚语服务器,单进E多U程l定同一端口C
*/
public final class QuoteOfTheMomentServer {
private static final int PORT = Integer.parseInt(System. getProperty("port" ,
"9000" ));
public static void main(String[] args) throws Exception {
final EventLoopGroup group = new EpollEventLoopGroup();
Bootstrap b = new Bootstrap();
b.group(group).channel(EpollDatagramChannel. class)
.option(EpollChannelOption. SO_REUSEPORT, true )
.handler( new QuoteOfTheMomentServerHandler());
int workerThreads = Runtime.getRuntime().availableProcessors();
for (int i = 0; i < workerThreads; ++i) {
ChannelFuture future = b.bind( PORT).await();
if (!future.isSuccess())
throw new Exception(String.format ("Fail to bind on port = %d.",
PORT), future.cause());
}
Runtime. getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
group.shutdownGracefully();
}
});
}
}
}
@Sharable
class QuoteOfTheMomentServerHandler extends
SimpleChannelInboundHandler<DatagramPacket> {
private static final String[] quotes = {
"Where there is love there is life." ,
"First they ignore you, then they laugh at you, then they fight you, then you win.",
"Be the change you want to see in the world." ,
"The weak can never forgive. Forgiveness is the attribute of the strong.", };
private static String nextQuote() {
int quoteId = ThreadLocalRandom.current().nextInt( quotes .length );
return quotes [quoteId];
}
@Override
public void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet)
throws Exception {
if ("QOTM?" .equals(packet.content().toString(CharsetUtil. UTF_8))) {
ctx.write( new DatagramPacket(Unpooled.copiedBuffer( "QOTM: "
+ nextQuote(), CharsetUtil. UTF_8), packet.sender()));
}
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
}
}
同样也要(g)一下端口文件句柄打开情况Q?/p>
# lsof -i:9000
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
java 3181 root 26u IPv6 27188 0t0 UDP *:cslistener
java 3181 root 27u IPv6 27217 0t0 UDP *:cslistener
java 3181 root 28u IPv6 27218 0t0 UDP *:cslistener
java 3181 root 29u IPv6 27219 0t0 UDP *:cslistener
以上为Netty+SO_REUSEPORT多线E绑定同一端口的一些情况,是ؓ(f)记蝲?/p>
本篇用于记录学习(fn)SO_REUSEPORT的笔记和?j)得Q末还?sh)(x)提供一个bindp工具也能ؓ(f)已有的程序n受这个新的特性?/p>
q行在Linuxpȝ上网l应用程序,Z(jin)利用多核的优势,一般用以下比较典型的多进E?多线E服务器模型Q?/p>
上面模型虽然可以做到U程和CPU核绑定,但都?x)存在?x)
比如HTTP CPS(Connection Per Second)吞吐量ƈ没有随着CPU核数增加呈现U性增长:(x)
Linux kernel 3.9带来?jin)SO_REUSEPORTҎ(gu),可以解决以上大部分问题?/p>
linux man文档中一D|字描q其作用Q?/p>
The new socket option allows multiple sockets on the same host to bind to the same port, and is intended to improve the performance of multithreaded network server applications running on top of multicore systems.
SO_REUSEPORT支持多个q程或者线E绑定到同一端口Q提高服务器E序的性能Q解决的问题Q?/p>
其核?j)的实现主要有三点?x)
代码分析Q可以参考引用资?[多个q程l定相同端口的实现分析[Google Patch]]?/p>
以前通过fork
形式创徏多个子进E,现在有了(jin)SO_REUSEPORTQ可以不用通过fork
的Ş式,让多q程监听同一个端口,各个q程?code>accept socket fd不一P有新q接建立Ӟ内核只会(x)唤醒一个进E来accept
Qƈ且保证唤醒的均衡性?/p>
模型单,l护方便?jin),q程的管理和应用逻辑解耦,q程的管理水qx(chng)展权限下攄E序?理员,可以Ҏ(gu)实际q行控制q程启动/关闭Q增加了(jin)灉|性?/p>
q带来了(jin)一个较为微观的水^扩展思\Q线E多是否合适,状态是否存在共享,降低单个q程的资源依赖,针对无状态的服务器架构最为适合?jin)?/p>
可以很方便的试新特性,同一个程序,不同版本同时q行中,Ҏ(gu)q行l果军_新老版本更q与否?/p>
针对对客L(fng)而言Q表面上感受不到其变动,因ؓ(f)q些工作完全在服务器端进行?/p>
x(chng)是,我们q代?jin)一版本Q需要部|到U上Qؓ(f)之启动一个新的进E后Q稍后关闭旧版本q程E序Q服务一直在q行中不间断Q需要^衡过度。这像Erlang语言层面所提供的热更新一栗?/p>
x(chng)不错Q但是实际操作v来,׃是那么^滑了(jin)Q还好有一?a >hubtime开源工P原理?code>SIGHUP信号处理?SO_REUSEPORT+LD_RELOADQ可以帮助我们轻村ց刎ͼ有需要的同学可以(g)?gu)用一下?/p>
SO_REUSEPORTҎ(gu)数据包的四元l{src ip, src port, dst ip, dst port}和当前绑定同一个端口的服务器套接字数量q行数据包分发。若服务器套接字数量产生变化Q内怼(x)把本该上一个服务器套接字所处理的客L(fng)q接所发送的数据包(比如三次握手期间的半q接Q以?qing)已l完成握手但在队列中排队的连接)(j)分发到其它的服务器套接字上面Q可能会(x)D客户端请求失败,一般可以用:(x)
与RFS/RPS/XPS-mq协作Q可以获得进一步的性能Q?/p>
目的嘛,数据包的软硬中断、接收、处理等在一个CPU怸Qƈ行化处理Q尽可能做到资源利用最大化?/p>
虽然SO_REUSEPORT解决?jin)多个进E共同绑?监听同一端口的问题,但根据新林晓峰同学试l果来看Q在多核扩展层面也未能够做到理想的线性扩展:(x)
可以参考Fastsocket在其基础之上的改q,链接地址?/p>
淘宝的Tengine已经支持?jin)SO_REUSEPORTҎ(gu),在其试报告中,有一个简单测试,可以看出来相Ҏ(gu)SO_REUSEPORT所带来的性能提升Q?/p>
使用SO_REUSEPORT以后Q最明显的效果是在压力下不容易出Ch的情况,CPU均衡性^E?/p>
JDK 1.6语言层面不支持,至于以后的版本,׃暂时没有使用刎ͼ不多说?/p>
Netty 3/4版本默认都不支持SO_REUSEPORTҎ(gu),但Netty 4.0.19以及(qing)之后版本才真正提供了(jin)JNI方式单独包装的epoll native transport版本Q在Linuxpȝ下运行)(j)Q可以配|类gSO_REUSEPORT{(JAVA NIIO没有提供Q选项Q这部分是在io.netty.channel.epoll.EpollChannelOption
中定义(在线代码部分Q?/p>
在linux环境下用epoll native transportQ可以获得内核层面网l堆栈增强的U利Q如何用可参?a >Native transports文档?/p>
使用epoll native transport倒也单,cdE作替换Q?/p>
NioEventLoopGroup → EpollEventLoopGroup
NioEventLoop → EpollEventLoop
NioServerSocketChannel → EpollServerSocketChannel
NioSocketChannel → EpollSocketChannel
比如写一个PING-PONG应用服务器程序,cM代码Q?/p>
public void run() throws Exception {
EventLoopGroup bossGroup = new EpollEventLoopGroup();
EventLoopGroup workerGroup = new EpollEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
ChannelFuture f = b
.group(bossGroup, workerGroup)
.channel(EpollServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch)
throws Exception {
ch.pipeline().addLast(
new StringDecoder(CharsetUtil.UTF_8),
new StringEncoder(CharsetUtil.UTF_8),
new PingPongServerHandler());
}
}).option(ChannelOption.SO_REUSEADDR, true)
.option(EpollChannelOption.SO_REUSEPORT, true)
.childOption(ChannelOption.SO_KEEPALIVE, true).bind(port)
.sync();
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
若不要这么折腾,q想让以往Java/Netty应用E序在不做Q何改动的前提下顺利在Linux kernel >= 3.9下同样n受到SO_REUSEPORT带来的好处,不妨试一?a >bindpQ更为经,q一部分下面?x)讲到?/p>
以前所?a >bindp程序,可以为已有程序绑定指定的IP地址和端口,一斚w可以省去编码,另一斚w也ؓ(f)试提供?jin)一些方ѝ?/p>
另外Qؓ(f)?jin)让以前没有编?code>SO_REUSEPORT的应用程序可以在Linux内核3.9以及(qing)之后Linuxpȝ上也能够得到内核增强支持Q稍做修改,d支持?/p>
但要求如下:(x)
不满以上条Ӟ此特性将无法生效?/p>
使用CQ?/p>
REUSE_PORT=1 BIND_PORT=9999 LD_PRELOAD=./libbindp.so java -server -jar pingpongserver.jar &
当然Q你可以Ҏ(gu)需要运行命令多ơ,多个q程监听同一个端口,单机q程水^扩展?/p>
使用python脚本快速构Z个小的示范原型,两个q程Q都监听同一个端?0000Q客L(fng)hq回不同内容Q仅供娱乐?/p>
server_v1.pyQ简单PING-PONGQ?/p>
# -*- coding:UTF-8 -*-
import socket
import os
PORT = 10000
BUFSIZE = 1024
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('', PORT))
s.listen(1)
while True:
conn, addr = s.accept()
data = conn.recv(PORT)
conn.send('Connected to server[%s] from client[%s]\n' % (os.getpid(), addr))
conn.close()
s.close()
server_v2.pyQ输出当前时_(d)(x)
# -*- coding:UTF-8 -*-
import socket
import time
import os
PORT = 10000
BUFSIZE = 1024
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('', PORT))
s.listen(1)
while True:
conn, addr = s.accept()
data = conn.recv(PORT)
conn.send('server[%s] time %s\n' % (os.getpid(), time.ctime()))
conn.close()
s.close()
借助于bindpq行两个版本的程序:(x)
REUSE_PORT=1 LD_PRELOAD=/opt/bindp/libindp.so python server_v1.py &
REUSE_PORT=1 LD_PRELOAD=/opt/bindp/libindp.so python server_v2.py &
模拟客户端请?0ơ:(x)
for i in {1..10};do echo "hello" | nc 127.0.0.1 10000;done
看看l果吧:(x)
Connected to server[3139] from client[('127.0.0.1', 48858)]
server[3140] time Thu Feb 12 16:39:12 2015
server[3140] time Thu Feb 12 16:39:12 2015
server[3140] time Thu Feb 12 16:39:12 2015
Connected to server[3139] from client[('127.0.0.1', 48862)]
server[3140] time Thu Feb 12 16:39:12 2015
Connected to server[3139] from client[('127.0.0.1', 48864)]
server[3140] time Thu Feb 12 16:39:12 2015
Connected to server[3139] from client[('127.0.0.1', 48866)]
Connected to server[3139] from client[('127.0.0.1', 48867)]
可以看出来,CPU分配很均衡,各自分配50%的请求量?/p>
嗯,虽是玩P有些意?:))
更多使用说明Q请参?a >README?/p>
前面啰啰嗦嗦的几文字,各个斚w介绍?jin)FastsocketQ盲人摸象一般,能力有限Q还得l深入学?fn)不是。这不,C(jin)该小l收时候了(jin)?/p>
使用Linux作ؓ(f)服务器,在请求量很小的时候,是不用担?j)其性能。但在v量的数据h下,Linux内核在TCP/IP|络处理斚wQ已l成为瓶颈。比如新在某台HAProxy服务器上取样Q?0%的CPU旉被内核占用,应用E序只能够分配到较少的CPU旉周期的资源?/p>
l过Haproxypȝ详尽分析后,发现大部分CPU资源消耗在kernel里,q且在多核^CQkernel在网l协议栈处理q程中存在着大量同步开销?/p>
同时在多怸q行试QHTTP CPS(Connection Per Second)吞吐量ƈ没有随着CPU核数增加呈现U性增长:(x)
TCP处理&多核
Linux VFS的同步损耗严?/p>
CPU之间不共享数据,q行化各自独立处理TCPq接Q也是其高效的主要原因。其架构囑֏以看出其改进Q?/p>
Fastsocket架构囑֏以很清晰说明其大致结构,内核态和用户态通过ioctl
函数传输。记得netmap在重写网卡驱动里面通过ioctl
函数直接透传到用h中Q其更ؓ(f)高效Q但没有完整的TCP/IP|络堆栈支持嘛?/p>
在新测试中Q在24核的安装有Centos 6.5的服务器上,借助于FastsocketQNginx和HAProxy每秒处理q接数指标(connection/secondQ性能很惊人,分别增加290%?20%。这也证明了(jin)QFastsocket带来?jin)TCPq接快速处理的能力?除此之外Q借助于硬件特性:(x)
Fastsocket V1.0正式版从2014q?月䆾开始已l在新浪生环境中用,用作代理服务器,因此大家可以考虑是否可以采用。针?.0版本Q以下环境较为收益:(x)
多线E嘛Q就得需要参考示范应用所提供实践?jin)?/p>
从下表测试图片中Q可以看刎ͼ(x)
试l果中:(x)
8核服务器U上环境q行?4时的成l,图a展示?jin)部|fastsocket之前CPU利用率,图b为部|了(jin)fastsocekt之后的CPU利用率?Fastsocket带来的收益:(x)
其实吧,q一块期待新公布更多的数据?/p>
长连接支持,q是需要等一{的。但是要支持什么类型长q接Q百万别应用服务器cdQ还是redisQ可能是后者。虽然目前正做,但目前没有时间表Q但目前所做特性ȝ如下Q?/p>
试环境:
Redis配置选项:
试l果Q?/p>
但需要注意:(x)
V1.1版本要增加长q接的支持,那么cM于Redis的服务器应用E序很受益?jin),因?f)没有具体的时间表Q只能够慢慢{待?jin)?/p>
说是Ҏ(gu)Q其实是我从mTCP论文中摘取出来,增加?jin)Fastsocket一栏,可以看出Z一直努力的脚步?/p>
Types | Accept queue | Conn. Locality | Socket API | Event Handling | Packet I/O | Application Mod- ification | Kernel Modification |
PSIO , DPDK , PF RING , netmap | No TCP stack | Batched | No interface for transport layer | No (NIC driver) | |||
Linux-2.6 | Shared | None | BSD socket | Syscalls | Per packet | Transparent | No |
Linux-3.9 | Per-core | None | BSD socket | Syscalls | Per packet | Add option SO REUSEPORT | No |
Affinity-Accept | Per-core | Yes | BSD socket | Syscalls | Per packet | Transparent | Yes |
MegaPipe | Per-core | Yes | lwsocket | Batched syscalls | Per packet | Event model to completion I/O | Yes |
FlexSC,VOS | Shared | None | BSD socket | Batched syscalls | Per packet | Change to use new API | Yes |
mTCP | Per-core | Yes | User-level socket | Batched function calls | Batched | Socket API to mTCP API | No (NIC driver) |
Fastsocket | Per-core | Yes | BSD socket | Ioctl + kernel calls | Per packet | Transparent | No |
有一个大致的印象Q也方便Ҏ(gu)Q但q只能是一个暂时的摘要而已Qhcd性能的(f)求L朝着更好的方向发展着?/p>
怎么说呢QFastsocket是ؓ(f)大家耳熟能详服务器程序NginxQHAProxy{而开发的。但若应用环境ؓ(f)大量的短q接Qƈ且是文件类型请求,不需要强制支持Keep-aliveҎ(gu)(短连接要的是快速请?相应Q然后关闭)(j)Q那么管理员可以试一下FastsocketQ至于部|策略,选择性部|几C为实验看看结果?/p>
本系列到此算是告一D落啦。以后呢Q自然是希望Fastsocket快发布寚wq接的支持,q有更高性能的提升咯 :))
前面分析Fastsocket慢慢凑成?jin)几烂文字Q要把一件事情坚持做下来Q有时味同爵蜡,但既焉择?jin),也得着头皮做下厅R闲话少_(d)文归正文。本文接自上内核模块篇Ql记录学?fn)Fastsocket内核的笔记内宏V?/p>
Linux kernel 3.9包含TCP/UDP支持多进E、多U程l定同一个IP和端口的Ҏ(gu),?code>SO_REUSEPORTQ在内核层面同时也让U程/q程之间各自独nSOCKETQ避免CPU怹间以锁资源争?code>accept queue的调用。在fastsocket/kernel/net/sock.h定义sock_common
l构Ӟ可以看到其n影:(x)
unsigned char skc_reuse:4;
unsigned char skc_reuseport:4;
在多个socket.h文g中(比如fastsocket/kernel/include/asm/socket.hQ,定义?jin)SO_REUSESORT的变量|(x)
#define SO_REUSEPORT 15
在fastsocket/kernel/net/core/sock.c的sock_setsockopt和sock_getsockopt函数中,都有SO_REUSEPORT
的n影:(x)
sock_setsockopt函数中:(x)
case SO_REUSEADDR:
sk->sk_reuse = valbool;
break;
case SO_REUSEPORT:
sk->sk_reuseport = valbool;
break;
sock_getsockopt函数体中Q?/p>
case SO_REUSEADDR:
v.val = sk->sk_reuse;
break;
case SO_REUSEPORT:
v.val = sk->sk_reuseport;
break;
?code>SO_REUSEPORTҎ(gu)支持之前的事g驱动驱动服务器资源竞争:(x)
之后呢,可以看做是ƈ行的?jin)?x)
Fastsocket没有重复发明轮子Q在SO_REUSEPORT
基础上进行进一步的优化{?/p>
嗯,后面准备写一个动态链接库程序,打算让以前的没有编?code>SO_REUSEPORT的程序也能够在Linux kernel >= 3.9pȝ上n受真正的端口重用的新Ҏ(gu)的支持?/p>
下面按照其架构图所C内核层面从上到下一一列出?/p>
因ؓ(f)Linux Kernel VFS的同步损耗严?/p>
提交记录Q?/p>
a209dfc vfs: dont chain pipe/anon/socket on superblock s_inodes list
4b93688 fs: improve scalability of pseudo filesystems
对VFS的改q,在所提升的性能中占有超q?0%的比例,效果非常明显Q?/p>
对于多核多接攉列来_(d)linux原生的协议栈只能listen在一个socket上面Qƈ且所有完成三ơ握手还没来得及(qing)被应用accept的套接字都会(x)攑օ光带的accept队列中,acceptpȝ调用必须串行的从队列取出Q当q发量较大时多核竞争Q这成为性能瓉Q媄(jing)响徏立连接处理速度?/p>
Local Listen TableQfastsocket为每一个CPU核克隆监听套接字Qƈ保存到其本地表中QCPU怹间不?x)存在accept的竞争关pR下面ؓ(f)引用描述内容Q?/p>
使用程图概括上面所qͼ(x)
Linux内核使用一个全局的hash表以?qing)锁操作来维护establised socketsQ被用来跟踪q接的socketsQ。Fastsocket x(chng)是把全局table分散到per-Core tableQ当一个core需要访问socket的时候,只在隶属于自qtable中搜索,因此不需要锁操纵Q也不存在资源竞争。由fastsocket建立的socket本地l(f)ocal established table中,其他的regular sockets保存在global的table中。core首先去自qlocal table中查找(不需要锁Q,然后去global中查找?/p>
默认情况下,应用E序d发包的时候,发出ȝ包是通过正在执行本进E的那个CPU 核(pȝ分配的)(j)来完成的Q而接收数据包的时CPU 核是由前面提到的RSS或RPS来传递。这样一来,q接可能׃同的两个CPU核来完成。连接应该在本地化处理。RFS和Intel|卡的FlowDirector可以从Y件和g上缓解这U情况,但是不完备?/p>
RFDQReceive Flow DeliverQ主要的思想是CPU核数d发vq接的时候可以把CPU core的标识和q接的source port~码C赗CPU cores和ports的关pȝ一个关p集合来军_【coresQports】, 对于一个portQ有唯一的一个core与之对应。当一个core来徏立connection的时候,RFD随机选择一个跟当前core匚w的port。接收包的时候,RFD负责军_q个包应该让哪一个core来处理,如果当前core不是被选中的cpu coreQ那么就deliver到选中的cpu core?/p>
一般来_(d)RFD对代理程序收益比较大Q单U的WEB服务器可以选择用?/p>
以上参考了(jin)大量的外部资料进行整理而成Q进而可以获得一个较为整体的Fastsocket内核架构印象?/p>
Fastsocket的努力,在单个TCPq接的管理从|卡触发的硬中断、Y中断、三ơ握手、数据传输、四ơ挥手等完整的过E在完整在一个CPU怸q行处理Q从而实C(jin)每一个CPU核心(j)TCP资源本地化,q样为多核水qx(chng)展打好了(jin)基础Q减全局资源竞争Q^行化处理q接Q同旉低文仉的副作用Q做C(jin)极ؓ(f)高效的短q接处理Ҏ(gu)Q不得不赞啊?/p>
本篇学习(fn)Fastsocket内核模块fastsocket.so
Q作为用h?code>libfsocket.so的内核态的支持Q处?code>ioctl传递到/dev/fastsocket
的数据,非常核心(j)和基。嗯Q还是先译Q随后挟带些点评q来?/p>
Fastsocket内核模块 (fastsocket.ko
) 提供若干Ҏ(gu),q各自具有开启和关闭{丰富选项可配|?/p>
CentOS 6.5带来的内栔R竞争处处可见Q导致无论如何优化TCP/IP|络堆栈都不能够带来很好的性能扩展。比较严重锁竞争例子Q?code>inode_lock?code>dcache_lockQ针对套接字文gpȝsockfs而言Qƈ不是必须。fastsocket通过在VFS初始化结构时提供fastpath快速\径用以解x(chng)w题,已经向代号ؓ(f)香草QvanillaQ的内核提交?jin)两处修改?x)
a209dfc vfs: dont chain pipe/anon/socket on superblock s_inodes list
4b93688 fs: improve scalability of pseudo filesystems
此项修改没有提供选项可供配置Q因此所有fastsocket创徏的套接字sockets都会(x)强制l由fastpath传输?/p>
fastsocket为每个CPU创徏?jin)一个本地socket监听表(local listen tableQ,应用E序可以军_在一个特定CPU内核上处理某个新的连接,具体是通过拯原始监听套接字socketQ然后插入到本地套接字socket监听表中。当新徏q接在某CPU处理Ӟpȝ内核试匚w本地socket监听表,匚w成功?x)插入到本地accept队列中。稍后,CPU?x)从本地accept队列中获取进行处理?/p>
q种方式每一个网lY中断都会(x)有隶属于自己本地套接字队列当新的q接q来时可以压入,每一个进E从本地队列中弹?gu)接进行处理。当q程和CPUq行l定Q一旦有|卡接口军_投递到某个CPU内核上,那么包括中断、Y中断、系l调用以?qing)用戯E,都会(x)有这个CPU全程负责。好处就是客L(fng)hq接在没有锁的竞争环境下分散到各个CPU上被动处理本地连接?/p>
本特性更适合以下情况Q?/p>
W一U情况下QRPS可以在网卡接攉列小于CPU核数时被使用。第二种Ҏ(gu)可以满两个斚wQ?/p>
因此Q?code>enable_listen_spawnh三个值可供配|:(x)
一旦开启,需要ؓ(f)文gl构额外d一字段用以保存文g与epitem的映关p,q样可省depoll_ctl
Ҏ(gu)被调用时从epollU黑?wi)查找epitem的开销?/p>
虽然此项优化有所修改epoll语义Q但带来?jin)套接字性能提升。开启的前提是一个套接字只允许添加到一个epoll实例中,但不包括监听套接字。默认gؓ(f)true可以适用于绝大多数应用程序,若你的程序不满条g得需要禁用了(jin)?/p>
enable_fast_epoll 为布?yu)(dng)型boolean选项:
RFDQReceive Flow DeliverQ会(x)把ؓ(f)新徏q接分配的CPU ID装到其q接的端口号中,而不是随机选择新创建的dq接的源端口q行分配到CPU上?/p>
当应用从zdq接收到数据包RFD解码Ӟ?x)从目的地端口上解析出对应的CPU内核IDQ而{发给对应的CPU内核。再加上listen_spawnQ保证了(jin)一个连接CPU处理的完全本地化?/p>
enable_receive_flow是一个布?yu)(dng)型选项:
注意事项Q?/p>
以上Q翻译完毕?/em>
fastsocket的内核模块相对\径ؓ(f)fastsocket/module/Q除?jin)README.md外,是两个软连接文件了(jin)Q?/p>
换种说法Qfastsocket内核模块真正路径?code>fastsocket/kernel/net/fastsocketQ具体文件列表ؓ(f)Q?/p>
fastsocket_api.c实现内核模块接口Q在源码里面注册?jin)好多文档暂时没有公开的可配置目Q?/p>
int enable_fastsocket_debug = 3;
/* Fastsocket feature switches */
int enable_listen_spawn = 2;
int enable_receive_flow_deliver;
int enable_fast_epoll = 1;
int enable_skb_pool;
int enable_rps_framework;
int enable_receive_cpu_selection = 0;
int enable_direct_tcp = 0;
int enable_socket_pool_size = 0;
module_param(enable_fastsocket_debug,int, 0);
module_param(enable_listen_spawn, int, 0);
module_param(enable_receive_flow_deliver, int, 0);
module_param(enable_fast_epoll, int, 0);
module_param(enable_direct_tcp, int, 0);
module_param(enable_skb_pool, int, 0);
module_param(enable_receive_cpu_selection, int, 0);
module_param(enable_socket_pool_size, int, 0);
MODULE_PARM_DESC(enable_fastsocket_debug, " Debug level [Default: 3]" );
MODULE_PARM_DESC(enable_listen_spawn, " Control Listen-Spawn: 0 = Disabled, 1 = Process affinity required, 2 = Autoset process affinity[Default]");
MODULE_PARM_DESC(enable_receive_flow_deliver, " Control Receive-Flow-Deliver: 0 = Disabled[Default], 1 = Enabled");
MODULE_PARM_DESC(enable_fast_epoll, " Control Fast-Epoll: 0 = Disabled, 1 = Enabled[Default]");
MODULE_PARM_DESC(enable_direct_tcp, " Control Direct-TCP: 0 = Disbale[Default], 1 = Enabled");
MODULE_PARM_DESC(enable_skb_pool, " Control Skb-Pool: 0 = Disbale[Default], 1 = Receive skb pool, 2 = Send skb pool, 3 = Both skb pool");
MODULE_PARM_DESC(enable_receive_cpu_selection, " Control RCS: 0 = Disabled[Default], 1 = Enabled");
MODULE_PARM_DESC(enable_socket_pool_size, "Control socket pool size: 0 = Disabled[Default], other are the pool size");
接收用户态的libfsocket.so通过ioctl传递过来的数据Q根据命令进行数据分发:(x)
static long fastsocket_ioctl(struct file *filp, unsigned int cmd, unsigned long __user u_arg)
{
struct fsocket_ioctl_arg k_arg;
if (copy_from_user(&k_arg, (struct fsocket_ioctl_arg *)u_arg, sizeof(k_arg))) {
EPRINTK_LIMIT(ERR, "copy ioctl parameter from user space to kernel failed\n");
return -EFAULT;
}
switch (cmd) {
case FSOCKET_IOC_SOCKET:
return fastsocket_socket(&k_arg);
case FSOCKET_IOC_LISTEN:
return fastsocket_listen(&k_arg);
case FSOCKET_IOC_SPAWN_LISTEN:
return fastsocket_spawn_listen(&k_arg);
case FSOCKET_IOC_ACCEPT:
return fastsocket_accept(&k_arg);
case FSOCKET_IOC_CLOSE:
return fastsocket_close(&k_arg);
case FSOCKET_IOC_SHUTDOWN_LISTEN:
return fastsocket_shutdown_listen(&k_arg);
//case FSOCKET_IOC_EPOLL_CTL:
// return fastsocket_epoll_ctl((struct fsocket_ioctl_arg *)arg);
default:
EPRINTK_LIMIT(ERR, "ioctl [%d] operation not support\n", cmd);
break;
}
return -EINVAL;
}
fastsocket/library/libsocket.h
头文件定义的FSOCKET_IOC_*
操作状态码p够一一对应的上?ioctl
传输数据从用h?>内核态,需要经q一ơ拷贝过E(copy_from_user
Q,然后Ҏ(gu)cmd命o(h)q行功能路由?/p>
通过指定的设备通道/dev/fastsocketq行交互Q?/p>
/dev/fastsocket
讑֤获得文g句柄Q开?code>ioctl数据传?单梳理了(jin)fastsocket内核模块Q但一h很多的点没有涉及(qing)Q后面可能会(x)在Fastsocket内核中再次梳理一下?/p>
本篇为fastsocket的动态链接库学习(fn)W记Q对应源码目录ؓ(f) fastsocket/libraryQ先译README.md文g内容Q后面添加上个h学习(fn)?j)得?/p>
动态链接库libfsocket.so
Qؓ(f)已有应用E序提供加速服务,h可维护性和兼容性?/p>
很简单,q入目录之后Q执?code>make命o(h)~译卛_Q?/p>
cd fastsocket/library
make
最后在当前目录下生?code>libfsocket.so文g?/p>
很简单的_(d)借助?code>LD_PRELOAD加蝲libfsocket.so
Q启动应用程序,以nginxZQ?/p>
LD_PRELOAD=/your_path/fastsocket/library/libfsocket.so nginx
若回滚,q单了(jin)Q直接启动nginxpQ?/p>
nginx
注意事项Q?/p>
fastsocket.ko
内核模块已经加蝲成功
Fastsocket拦截|络套接字的常规pȝ调用Qƈ使用ioctl接口取代之?/p>
若不依赖?code>libfsocket.soQ上层应用程序要想用Fastsocket Percore-Listen-Table的特点,应用E序需要在父流Eforking之后Q以?qing)提前做事g循环Qevent loopQ处理,应用工作q程需要手动调?code>listen_spawn函数Q复制全局的监听套接字q插入到本地监听表中?/p>
libfsocket.so
Z层应用程序做?code>listien_spawn的工作,用以保持应用E序的代码不变,Ҏ(gu)如下:
libfsocket.so
跟踪所有需要监听的套接字文件句?
libfsocket.so
拦截?code>epoll_ctlpȝ调用
epoll_ctl
d监听套接字文件句柄到epollӞlibfsocket.so
?x)调?code>listen_spawnҎ(gu) 不是所有应用程序都适合本方案,但nginx、haproxy、lighttpd与之配合工作得相当不错。因此当你在其他应用E序中想使用Percore-Listen-TableҎ(gu)时Q请务必心(j)试?jin),保是否合适?/p>
OKQ翻译完毕?/em>
fastsocket/library用于构徏libfsocket.so
动态链接库Q主要组成:(x)
定义?code>ioctlQؓ(f)Input/Output ConTroL~写Q函数和伪设?/dev/fastsocket
)交换数据所使用到的几个命o(h)Q?/p>
#define IOC_ID 0xf5
#define FSOCKET_IOC_SOCKET _IO(IOC_ID, 0x01)
#define FSOCKET_IOC_LISTEN _IO(IOC_ID, 0x02)
#define FSOCKET_IOC_ACCEPT _IO(IOC_ID, 0x03)
#define FSOCKET_IOC_CLOSE _IO(IOC_ID, 0x04)
//#define FSOCKET_IOC_EPOLL_CTL _IO(IOC_ID, 0x05)
#define FSOCKET_IOC_SPAWN_LISTEN _IO(IOC_ID, 0x06)
#define FSOCKET_IOC_SHUTDOWN_LISTEN _IO(IOC_ID, 0x07)
紧接着定义?jin)需要在用户态和内核态通过ioctl
q行交互的结构:(x)
struct fsocket_ioctl_arg {
u32 fd;
u32 backlog;
union ops_arg {
struct socket_accept_op_t {
void *sockaddr;
int *sockaddr_len;
int flags;
}accept_op;
struct spawn_op_t {
int cpu;
}spawn_op;
struct io_op_t {
char *buf;
u32 buf_len;
}io_op;
struct socket_op_t {
u32 family;
u32 type;
u32 protocol;
}socket_op;
struct shutdown_op_t {
int how;
}shutdown_op;
struct epoll_op_t {
u32 epoll_fd;
u32 size;
u32 ep_ctl_cmd;
u32 time_out;
struct epoll_event *ev;
}epoll_op;
}op;
};
q样看来Q?code>ioctl函数原型调用为:(x)
ioctl(/dev/fastsocket讑֤文g句柄Q?FSOCKET_IOC_具体宏命令, fsocket_ioctl_argl构指针)
现在大致能够弄清楚了(jin)内核态和用户态之间通过ioctl
传递结构化的数据的方式?jin)?/p>
q接内核模块已经注册好的讑֤道/dev/fastsocket
Q获取到文g描述W,同时做些CPUq程l定的工?/p>
#define INIT_FDSET_NUM 65536
......
__attribute__((constructor))
void fastsocket_init(void)
{
int ret = 0;
int i;
cpu_set_t cmask;
ret = open("/dev/fastsocket", O_RDONLY); // 建立fastsocket通道
if (ret < 0) {
FSOCKET_ERR("Open fastsocket channel failed, please CHECK\n");
/* Just exit for safty*/
exit(-1);
}
fsocket_channel_fd = ret;
fsocket_fd_set = calloc(INIT_FDSET_NUM, sizeof(int));
if (!fsocket_fd_set) {
FSOCKET_ERR("Allocate memory for listen fd set failed\n");
exit(-1);
}
fsocket_fd_num = INIT_FDSET_NUM; // gؓ(f)65535
CPU_ZERO(&cmask);
for (i = 0; i < get_cpus(); i++)
CPU_SET(i, &cmask);
ret = sched_setaffinity(0, get_cpus(), &cmask);
if (ret < 0) {
FSOCKET_ERR("Clear process CPU affinity failed\n");
exit(-1);
}
return;
}
主观上,仅仅是ؓ(f)?jin)短q接而设|的Q定义的fastsocket文g句柄数组大小?5535Q针对类gWEB Server、HTTP API{环境够了(jin)Q针对百万别的长连接服务器环境?yu)׃适合?jin)?/p>
socket/listen/accept/close/shutdown/epoll_ctl{函敎ͼ通过dlsym
方式替换已有套接字系l函数等Q具体的交互q程使用ioctl
替代一些系l调用?/p>
除了(jin)重写socket/listen/accept/close/shutdown{套接字接口Q同时也?code>epoll_ctlҎ(gu)动了(jin)手术Q江湖传aCPU多核多进E的epoll服务器存在惊现象)(j)Q更好利用多核:(x)
int epoll_ctl(int efd, int cmd, int fd, struct epoll_event *ev)
{
static int (*real_epoll_ctl)(int, int, int, struct epoll_event *) = NULL;
int ret;
struct fsocket_ioctl_arg arg;
if (fsocket_channel_fd >= 0) {
arg.fd = fd;
arg.op.spawn_op.cpu = -1;
/* "Automatically" do the spawn */
if (fsocket_fd_set[fd] && cmd == EPOLL_CTL_ADD) {
ret = ioctl(fsocket_channel_fd, FSOCKET_IOC_SPAWN_LISTEN, &arg);
if (ret < 0) {
FSOCKET_ERR("FSOCKET: spawn failed!\n");
}
}
}
if (!real_epoll_ctl)
real_epoll_ctl = dlsym(RTLD_NEXT, "epoll_ctl");
ret = real_epoll_ctl(efd, cmd, fd, ev);
return ret;
}
因ؓ(f)定义?jin)作用于内部的?rn)态变?code>real_epoll_ctlQ只有在W一ơ加载的时候才?x)被赋|real_epoll_ctl = dlsym(RTLD_NEXT, "epoll_ctl")
Q后面调用时通过ioctl
把fsocket_ioctl_arg传递到内核模块中去?/p>
其它socket/listen/accept/close/shutdown{套接字接口Q流E类伹{?/p>
以上单翻译、粗略分析用hfastsocket动态链接库大致情况Q若要v作用Q需要和内核态Fastsocketq行交互、传递数据才能够作用的很好?/p>
前面~译安装好了(jin)包含有fastsocket的内核模块,以及(qing)fastsocket的动态链接库libfsocket.soQ下面其实就可以讄|卡?jin)?/p>
下面Z些名词解释,上下文中需要用到Q?/p>
本文|卡讄W记内容Q大部分来自于fastsocket源码相对路径fastsocket/scripts/
Q老规矩,先翻译?/p>
nic.sh
脚本负责|卡配置以尽可能的最大化受益于fastsocket带来的问题。给定一个网卡接口, 它调整接口的各种Ҏ(gu)以?qing)一些系l配|?/p>
每个|卡g队列?qing)其兌中断l定C同的CPU核心(j)。若g队列数大于CPU核数Q队列需要配|成循环round-robin方式Q?Irqbalance服务需要被用以防其更攚w|?/p>
nic.sh
脚本通过ethtool
命o(h)讄每秒中断C限,防止中断风暴。两个Rx中断间隔讄成至?33usQ约3000个中断每U?/p>
为每个CPU核心(j)与不同的|卡g队列之间建立一一映射对应关系Q这样CPU核心(j)?yu)可以很均匀地处理网l数据包。当|卡g队列于CPU内核敎ͼnic.sh
脚本利用RPS (Receive Packet Steering)软g方式qq入量负蝲Q这样CPU和硬仉列不存在对应关系。RPS机制可以让进入的数据包自由分发到MCPU怸?/p>
|卡接收产生的中断可以均衡分配到对应CPU上?/p>
XPS (Transmit Packet Steering) 建立CPU内核和Tx发送队列映对应关p,掌控出站数据包。系l有N个CPU核心(j)Q脚本会(x)讄XPS臛_存在N个Tx队列在网卡接口上Q这样就可以建立CPU内核和Tx队列1?的映关pR?/p>
|卡传送数据生的中断一样可以均很分配到CPU上,避免单个CPU核心(j)q于J忙?/p>
压测Ӟ防火墙iptables的规则会(x)占用更多的CPU周期Q有所降低|络堆栈性能。因?code>nic.sh脚本若检到iptables后台q行中会(x)直接输出报警信息Q提C关闭之?/p>
nic.sh
脚本脚本分析l过验证好用的Intel和博通系列千兆和万兆|卡列表Q?/p>
# igb
"Intel Corporation 82576 Gigabit Network Connection (rev 01)"
"Intel Corporation I350 Gigabit Network Connection (rev 01)"
# ixgbe
"Intel Corporation 82599EB 10-Gigabit SFI/SFP+ Network Connection (rev 01)"
"Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection (rev 01)"
# tg3
"Broadcom Corporation NetXtreme BCM5720 Gigabit Ethernet PCIe"
"Broadcom Corporation NetXtreme BCM5761 Gigabit Ethernet PCIe (rev 10)"
# bnx2
"Broadcom Corporation NetXtreme II BCM5708 Gigabit Ethernet (rev 12)"
"Broadcom Corporation NetXtreme II BCM5709 Gigabit Ethernet (rev 20)"
若当前服务器没有以上|卡Q会(x)警告一下,无碍?/p>
q里把一些常规性的CPU、网卡驱动、网l队列情冉|查单独抽取出来,重温好多已经遗忘的命令,有改变,q样写较单嘛Q便于以后用:(x)
egrep -c eth0 /proc/interrupts
脚本先是获取CPU、网卡等信息Q接着讄中断单位U内吞吐量:(x) ethtool -C eth0 rx-usecs 333 > /dev/null 2>&1
启用XPSQ充分借助|卡发送队列,提升|卡发送吞吐量Q是有条仉制的Q发送队列数要大于CPU核数Q?/p>
if [[ $TX_QUEUES -ge $CORES ]]; then
for i in $(seq 0 $((CORES-1))); do
cpuid_to_mask $((i%CORES)) | xargs -i echo {} > /sys/class/net/$IFACE/queues/tx-$i/xps_cpus
done
info_msg " XPS enabled"
fi
接着判断是否可以启用PRSQ省L动设|的ȝ(ch)Q但启用RPS前提是CPU核数与网卡硬仉列不相等Q?/p>
if [[ ! $HW_QUEUES == $CORES ]]; then
for i in /sys/class/net/$IFACE/queues/rx-*; do
printf "%x\n" $((2**CORES-1)) | xargs -i echo {} > $i/rps_cpus;
done
info_msg " RPS enabled"
else
for i in /sys/class/net/$IFACE/queues/rx-*; do
echo 0 > $i/rps_cpus;
done
info_msg " RPS disabled"
fi
若没有用fastsocketQ单U借助于RPSQ会(x)带来处理中断的CPU和处理当前数据包的CPU不是同一个,自然?x)造成CPU Cache MissQCPU~存?sh)失Q,造成许的性能影响Qؓ(f)?jin)避免这U情况,Z?x)依赖于RFSQReceive Flow SteeringQ?/p>
使用?jin)fastsocket后,׃用这么麻?ch)?jin)?/p>
irqbalance和fastsocket有冲H,?x)强制禁用?x)
if ps aux | grep irqbalance | grep -v grep; then
info_msg "Disable irqbalance..."
# XXX Do we have a more moderate way to do this?
killall irqbalance > /dev/null 2>&1
fi
脚本也包含了(jin)讄中断和CPU的亲和性:(x)
i=0
intr_list $IFACE $DRIVER | while read irq; do
cpuid_to_mask $((i%CORES)) | xargs -i echo {} > /proc/irq/$irq/smp_affinity
i=$((i+1))
done
若iptables服务存在Q会(x)友善用?x)好一些,毕竟?x)带来性能损耗。文件打开句柄不大?024Q脚本同样会(x)提醒Q怎么讄文g打开句柄Q可以参考以前博文?/p>
针对不用fastsocket的服务器Q当前比较流行的针对|卡的网l堆栈性能扩展、优化措施,一般会(x)使用到RSS、RPS、RFS、XFS{方式,以便充分利用CPU多核和硬件网卡等自n性能Q达到ƈ?q发处理的目的。下面ȝ一个表|可以凑合看一下?/p>
RSS (Receive Side Scaling) |
RPS (Receive Packet Steering) |
RFS (Receive Flow Steering) |
Accelerated RFS (Accelerated Receive Flow Steering) |
XPS (Transmit Packet Steering) | |
---|---|---|---|---|---|
解决问题 | |卡和驱动支?/td> | 软g方式实现RSS | 数据包生的中断和应用处理在同一个CPU?/td> | ZRFSg加速的负蝲q机制 | 选择|卡多队列的队列快速发?/td> |
内核支持 | 2.6.36开始引入,需要硬件支?/td> | 2.6.35 | 2.6.35 | 2.6.35 | 2.6.38 |
|卡队列数和物理核数一?/td> | x(chng)多队列的|卡若RSS已经配置?jin),则不需要RPS?/td> | 需要rps_sock_flow_entries和rps_flow_cnt属?/td> | 需要网卡设备和驱动都支持加速。ƈ且要求ntupleqo(h)已经通过ethtool启用 | 单传输队列的|卡无效Q若队列比CPU,׃n指定队列的CPU最好是与处理传输硬中断的CPU׃n~存的CPU | |
fastsocket | |卡Ҏ(gu)?/td> | 改进版RPSQ性能提升 | 源码包含Q文档没有涉?/td> | 文档没有涉及(qing) | 要求发送队列数要大于CPU核数 |
传送方?/td> | |卡接收 | 内核接收 | CPU接收处理 | 加速ƈ接收 | |卡发送数?/td> |
更具体优化措施,可以参考文档:(x)Scaling in the Linux Networking Stack?/p>
另,若网卡支?code>Flow Director FiltersҎ(gu)(q里有一个非常有的动画介绍Q?a >Intel® Ethernet Flow DirectorQ值得一看)(j)Q那么可以结合F(tun)astsocket一起加速。比如,在其所作Redis长连接测试中Q启用Flow-DirectorҎ(gu)要比禁用可以带?5%的性能提升?/p>
自然软硬l合Q可以做的更好一些嘛?/p>
延阅读Q?a >多队列网卡简?/a>
以上记录?jin)学习(fn)fastsocket的网卡设|脚本方面笔记?/p>
不过呢,nic.sh
脚本Q值得收藏Q无Z不用fastsocketQ对U上服务器网卡调优都是不错选择哦?/p>
q行环境为Centos 6.5pȝQ默认内ؓ(f)2.6.32-431.el6.x86_64Q下面所有编译安装操作是?code>root用户权限q行操作?/p>
W一步需要下载代码,当然q是废话?jin),下蝲?opt目录下:(x)
git clone https://github.com/fastos/fastsocket.git
下蝲之后Q需要进入其目录中:(x)
cd fastsocket/kernel
因ؓ(f)是涉?qing)到内核嘛,~译之前需要做一些参数选项配置Q?code>make config?x)篏Mh的,好几千个选项参数需要你一一配置Q大部分旉Q默认配|就挺好的:(x)
make defconfig
然后嘛,~译内核的节奏:(x)
make
内核~译相当耗费旉Q至?0分钟旉。之后紧接着是编译所需的内核模块,fastsocket模块Q?/p>
make modules_install
~译完成之后Q最后一条输出,?x)看刎ͼ?x)
DEPMOD 2.6.32-431.17.1.el6.FASTSOCKET
fastsocket内核模块~译好之后,需要安装内核:(x)
make install
上面命o(h)其实执行shell脚本q行安装Q?/p>
sh /opt/fastsocket/kernel/arch/x86/boot/install.sh 2.6.32-431.17.1.el6.FASTSOCKET arch/x86/boot/bzImage \ System.map "/boot"
基本上,fastsocket内核模块已经构徏安装完毕?jin),但需要告知Linuxpȝ在下ơ启动的时候切换到新编译的、包含有fastsocket模块的内核?/p>
q部分需要在/etc/grup.conf中配|,现在看一下其文g内容Q?/p>
default=1
timeout=5
splashimage=(hd0,0)/grub/splash.xpm.gz
hiddenmenu
title CentOS (2.6.32-431.17.1.el6.FASTSOCKET)
root (hd0,0)
kernel /vmlinuz-2.6.32-431.17.1.el6.FASTSOCKET ro root=/dev/mapper/vg_centos6-lv_root rd_NO_LUKS rd_NO_MD rd_LVM_LV=vg_centos6/lv_swap crashkernel=auto LANG=zh_CN.UTF-8 rd_LVM_LV=vg_centos6/lv_root KEYBOARDTYPE=pc KEYTABLE=us rd_NO_DM rhgb quiet
initrd /initramfs-2.6.32-431.17.1.el6.FASTSOCKET.img
title CentOS (2.6.32-431.el6.x86_64)
root (hd0,0)
kernel /vmlinuz-2.6.32-431.el6.x86_64 ro root=/dev/mapper/vg_centos6-lv_root rd_NO_LUKS rd_NO_MD rd_LVM_LV=vg_centos6/lv_swap crashkernel=auto LANG=zh_CN.UTF-8 rd_LVM_LV=vg_centos6/lv_root KEYBOARDTYPE=pc KEYTABLE=us rd_NO_DM rhgb quiet
initrd /initramfs-2.6.32-431.el6.x86_64.img
defautl=1
Q表C目前系l选择的以原先内核作作为启动项Q原先位于第二个root (hd0,0)
后面Q需要切换到新的内核下面Q需要修?code>default=0Q保存后Qreboot重启pȝQ之生效?/p>
pȝ重启后,需要加载fastsocket模块到系l运行中去,下面以默认选项参数方式加蝲Q?/p>
modprobe fastsocket
加蝲之后Q列出当前系l所加蝲模块列表Q检查是否成?/p>
lsmod | grep fastsocket
若能看到cM输出信息Q表COKQ?/p>
fastsocket 39766 0
上面内核模块安装好之后,可以构徏fastsocket的动态链接库文g?jin)?x)
cd /opt/fastsocket/library/
make
可能?x)收C些警告信息,无碍Q?/p>
gcc -g -shared -ldl -fPIC libsocket.c -o libfsocket.so -Wall
libsocket.c: 在函?#8216;fastsocket_init’?
libsocket.c:59: 警告Q隐式声明函?#8216;open’
libsocket.c: 在函?#8216;fastsocket_expand_fdset’?
libsocket.c:109: 警告Q隐式声明函?#8216;ioctl’
libsocket.c: 在函?#8216;accept’?
libsocket.c:186: 警告Q对指针赋值时目标与指针符号不一?
libsocket.c: 在函?#8216;accept4’?
libsocket.c:214: 警告Q对指针赋值时目标与指针符号不一?
最后,可以看到gcc~译之后生成?code>libfsocket.so库文Ӟ说明~译成功?/p>
OKQ编译安装到此结束,后面是如何使用fastsocket的示范程序进行测试了(jin)?/p>
上篇介绍?jin)如何构建安装f(xi)astsocket内核模块Q下面将Zfastsocket/demo/README.md
文g译整理而成?/p>
嗯,下面q入译?/p>
CZ个简单TCP Server服务器程序,用于基准试和剖析Liunx内核|络堆栈性能表现Q当然也是ؓ(f)?jin)演CFastsocket可扩展和其性能改进?/p>
C应用Zepoll模型和非d性IOQ处理网l连接,但只有在多核的模式下才能够工作得很好Q程序的每一个进E被l定到CPU的不同核Qv始于CPU core 0Q各自独立处理客L(fng)q接h?/p>
CE序h两种工作模式Q?/p>
q是一个简单傻瓜Ş式的Tcp ServerQ仅仅用于测试用,使用时要求客L(fng)和服务器端只能够携带一个packet包大的数据Q否则程序会(x)处理不了(jin)?/p>
以下面方式进行构建:(x)
cd demo && make
最单方式以默认配置无参数Ş式运行:(x)
./server
参数如下:
在运行之前,需要注意两点:(x)
script
目录 服务器模式至需要两C机:(x)
讑֮每台LCPU 12核,|络大概讄如下Q?/p>
+--------------------+ +--------------------+
| Host A | | Host B |
| | | |
| 10.0.0.1/24 |-----| 10.0.0.2/24 |
| | | |
+--------------------+ +--------------------+
下面是运行两C机的步骤Q?/p>
LBQ?/p>
Web服务器模式单独运行,开?2个工作进E,和CPU核心(j)C_(d)(x)
./server -w 12 -a 10.0.0.2:80
或者测试借助于Fastsocket所带来的性能
LD_PRELOAD=../library/libfsocket.so ./server -w 12 -a 10.0.0.2:80
LAQ?/p>
ab -n 1000000 -c 100 http://10.0.0.2:80/
N=12; for i in $(seq 1 $N); do ab -n 1000000 -c 100 http://10.0.0.2:80/ > /dev/null 2>&1; done
代理模式下,需要三台机器:(x)
讑֮每台机器CPU内核?2Q网l结构如下:(x)
+--------------------+ +--------------------+ +--------------------+
| Host A | | Host B | | Host C |
| | | | | |
| 10.0.0.1/24 | | 10.0.0.2/24 | | 10.0.0.3/24 |
+---------+----------+ +---------+----------+ +----------+---------+
| | |
+---------+--------------------------+---------------------------+---------+
| switch |
+--------------------------------------------------------------------------+
下面为具体的q行步骤Q?/p>
LBQ?/p>
./server -w 12 -a 10.0.0.2:80 -x 10.0.0.3:80
LD_PRELOAD=../library/libsocket.so ./server -w 12 -a 10.0.0.2:80 -x 10.0.0.3:80
LCQ?/p>
./server -w 12 -a 10.0.0.3:80
LAQ?/p>
N=12; for i in $(seq 1 $N); do ab -n 1000000 -c 100 http://10.0.0.2:80/ > /dev/null 2>&1; done
以上译完毕Q下面将是根据上面内容进行动手测试描q吧?/p>
(g)查一下包含Apache ab命o(h)的Y件包Q?/p>
yum provides /usr/bin/ab
可以看到cM于如下字P(x)
httpd-tools-2.2.15-39.el6.centos.x86_64 : Tools for use with the Apache HTTP Server
安装它就可以?/p>
yum install httpd-tools
Windows 7专业版跑VMware Workstation 10.04虚拟机,两个Centos 6.5pȝQ配|一_(d)2G内存Q?个CPU逻辑处理器核?j)?/p>
客户端安装Apache ab命o(h)试Q跑8个实例:(x) for i in $(seq 1 8); do ab -n 10000 -c 100 http://192.168.192.16:80/ > /dev/null 2>&1; done
服务器端Q分别记录:(x)/opt/fast/server -w 8
LD_PRELOAD=../library/libfsocket.so ./server -w 8
两组数据Ҏ(gu)Q?/p>
q行方式 | 处理消耗时?U? | 处理L | q_每秒处理?/th> | 最大?/th> |
---|---|---|---|---|
单独q行 | 34s | 80270 | 2361 | 2674 |
加蝲fasocket | 28s | 80399 | 2871 | 2964 |
试方式如上Q三台服务器Q测试端+代理?服务器端Q配|一栗第一ơ代理单独启动,W二ơ代理预加蝲fastsocket方式?/p>
q行方式 | 处理消耗时?U? | 处理L | q_每秒处理?/th> | 最大?/th> |
---|---|---|---|---|
W一ơ测试后?/td> | 44s | 80189 | 1822 | 2150 |
W一ơ测试代?/td> | 44s | 80189 | 1822 | 2152 |
W二ơ测试后?/td> | 42s | 80051 | 1906 | 2188 |
W二ơ测试代?/td> | 42s | 80051 | 1906 | 2167 |
备注Q虚拟机上数据,不代表真实服务器上数据,仅供参考?/p>
虽然Z虚拟机,试环境受限Q但一样可以看到基于fastsocket服务器模型,处理性能有所提升QM处理旉Q每U^均处理数Q以?qing)处理上限等?/p>
动态链接预先加载LD_PRELOAD虽是利器Q但不是万能药,LD_PRELOAD遇到下面情况?x)失效?x)
情况很复杂,心(j)Z?/p>
学习(fn)q测试了(jin)fastsocket的源码示范部分,前后Ҏ(gu)可以看到fastsocket带来?jin)处理性能的提升?/p>
以前在infoq上看到fastsocket的宣?a >《两周内在Github上收?800+个星Q内核层|络栈优化项目Fastsocket背后的故事?/a>Q明白了(jin)fastsocket是什么:(x)
开源协议ؓ(f)GPLv2
M很吸引hQ从内核层面q行优化TCP/IP|络堆栈Q上层网l应用程序不用做修改Q就可以得到处理性能的提升,很赞Q?/p>
q期有点空Ԍ开始对Fastsocketq行x(chng)Q虽然资料不多,但也记录?jin)几连l的学习(fn)W记。大部分W记Q思\主要是优先翻译官Ҏ(gu)档,紧接着?x)夹带些个h一些学?fn)笔记?/p>
fastsocket目地址是:(x)https://github.com/fastos/fastsocketQ其wiki和代码是本系列笔C要来源。一开始想q一步全面认知fastsocketQ发现无从下手,只能从侧面开始一一旁敲侧击Q逐渐加深。本pdW记Ҏ(gu)其源码目录结构划分特性,分开记录学习(fn):
怎么说呢Q能力有限,若发现问?U漏Q请帮忙?qing)时指正Q不胜感Ȁ?/p>
代码贡献者,除了(jin)林晓?/a>之外Q目前提交最为频J的?a >greewind同学Q其博客地址?a >http://blog.chinaunix.net/uid/23629988.htmlQ也是一位牛人?/p>
优秀的开源项目,L可以吸引到最优秀的开发者?/p>
|络延迟是客观存在的Q但|络游戏行业已经U篏?jin)大量优质经验,使用一些策略、技术手D在客户端消?隐藏掉gq带来的不便Q以可能的掩盖实际存在的gӞ同时实现实时渲染Q将用户带入快速的交互式实时游戏中Q体验完的互动׃中?/p>
q样处理l果Q稍高gq的玩家也不?x)因为网l不是那么好Q也能够很和谐的与其它网l参差不?qing)玩家一h戏中?/p>
虽然延时军_?jin)实时游戏的最低反应时_(d)但最重要的是客户端看h要流畅。第一人称设计游戏QF(tun)PSQ可巧妙的化解与规避Q最l在适合普遍用户|络环境?200ms)Q实现实时快速互动游戏?/p>
嗯,下面是q期脑补l果?/p>
早先|游使用P2P|络拓扑在玩家之间进行交换数据通信。但P2P模型引v的高延迟在FPS游戏中无法被很好掩盖Q所有玩家的延迟取决于当前玩家中延迟最烂的那个。好比木桶理论,低gq网l好的玩家会(x)被高延迟坏网l的玩家拖篏。最l结果导_(d)所有玩安不太开?j)?jin)。但在局域网环境下,不会(x)感觉到gq带来的问题。另Q游戏逻辑大部分都集中在客L(fng)?jin),很难避免作弊行?f)?/p>
C/Sl构|游Q?/p>
服务器可以允许某些情况下客户端本地即时执行移动操作,q种Ҏ(gu)可以UCؓ(f)客户端预?/p>
比如游戏中键盘控制角色行赎ͼq个时候可以在很小的时间段Q时间很短,比如1-3U)(j)内预用戯动轨q(方向+加速度Q角色行走结果)(j)Q这部分的命令客L(fng)?x)全部发送到服务器端校验正确与否Q避免瞬间{Uȝ外挂Q。但客户端预有时也不是癑ֈ癑և,需要服务器q行U正Q所谓服务器是上帝QThe sever is the manQ)(j)。纠正结果可能就是游戏角色行走轨q和客户端预轨qҎ(gu)所偏差Q客L(fng)可以使用插值方式(_略来讲Q就是角色在两点之间Ud渲染的方式)(j)渲染游戏角色在游戏世界中的位|{Ud^滑一些,避免游戏角色从一个位|瞬间拉回到另一个位|,让h有些莫名其妙?/p>
插|有h也称之ؓ(f)路径补偿Q都是一回事。插值的Ҏ(gu)?x)涉及(qing)到很多数学公式Q线性插倹{三ơ线性插值等Q比如这文章所讲到?a >插值那些事?/p>
结Q客L(fng)预测Q服务器端纠正,客户端采用插值方式微调?/p>
针对交互的一玩Ӟ|络好坏层次不齐Q游戏的一些操作效果可能需?#8221;延迟补偿“{略q行
延迟补偿是游戏服务器端执行的一U策略,处理用户命o(h)回退到客L(fng)发送命令的准确旉Qgq导_(d)(j)Q根据客L(fng)的具体情况进行修正,以牺牲游戏在伤害判定斚w的真实感来I补攻击行为等斚w真实感,本质上是一U折?sh)选择?/p>
主要注意Qgq补偿不是发生在客户端?/p>
关于延迟补偿的一个例子:(x)
若游戏gq补偿被用Q那么就?x)有许多玩家抱怨自己明明打中了(jin)Ҏ(gu)却没有造成M伤害。?/p>
有所得,有所失:(x)但这对低延时玩家貌似有些不公qIUd速度快,可能已经跑到角落里ƈ且已y在一个箱子后面隐藏v来时被对手击中的错觉Q子Ҏ(gu)视掩体,玩家隔着墙被击Q,实有些不乐意?/p>
延迟补偿Q网l高延迟的玩家有利,低gq的玩家优势可能?x)被降低Q低延迟玩家利益受损Q,但对l护游戏世界的^衡还是有利的?/p>
客户端和服务器需要对Ӟ互相知道彼此延迟情况Q比如云风定义的某个步骤Q?/p>
客户端发送一个本地时间量l服务器Q服务收到包后,夹带一个服务器旉q回l客L(fng)。当客户端收到这个包后,可以估算出包在\E上l过的时间。同时把本地新时间夹带进去,再次发送给服务器。服务器也可以进一步的?jin)解响应旉?/p>
C/S两端通过cM步骤q行计算彼此延时/时差Q同时会(x)对实时同步设|一个阀|比如对gq低?0msQ?.01U)(j)的交互认为是x(chng)同步发生Q不?x)认为是延迟?/p>
不同cd的游戏会(x)钟爱不同的协议呢Q不一而Q?/p>
TCP?x)认定丢包是因?f)本地带宽不DQ本地带宽不x(chng)丢包的一部分原因Q,但国内ISP可能?x)在自n机房|络拥挤时丢弃数据包Q这时候可能需要快速发包争抢通道Q而非TCPH口收羃QUDP没有TCPH口收羃的负担,可以很容易做到这一炏V?/p>
要求实时性放在第一位的FPS游戏QegQQuakeQCSQ,q域|一般采用UDPQ因可容许有丢失数据包存在(另客L(fng)若等待一D|间中间丢包,可以通过插值等手段忽略掉)(j)Q一旦检到可以快速发送,另不涉及(qing)到重发的时候UDP比TCP要快一点嘛。但?x)在UDP应用层面有所增加协议控制Q比如ACK{?/p>
很多时候协议用,比如MMO客户端也?dng)R先用HTTP去获取上一ơ的更新内容Q?重要信息如角色获得的物品和经验需要通过TCP传输Q而周围h物的动向、NPCUd、技能动L令等则可以用UDP传输Q虽然可能丢包,但媄(jing)响不大?/p>
|游通过客户端预、插值和服务器端延迟补脓(chung){,化解/消除用户端网lgq造成的停ѝ我们虽然可能没有机?x)接触游戏开发,学习(fn)跨界的优良经验和实践Q说不准?x)对当前工作某些业务点的处理有所启发呢?/p>
本集由韩国宇航局赞助播出Q我们要去远方看看,q有什么是我们的思密达?------ 《万万没惛_》王大锤
说是Android|络调试Q其实也不过是在被ROOT后Androidpȝ操作Q用adb shell执行一些常规的l端命o(h)Q检?G/3G/4G/WIFI|络{,q而确定一些因|络{导致的问题而已。但adb shell默认没有几个支持的命令,比如 cat
, tcpdump
Q这些都是最基本的必备命令,也不支持。对于想要查看网l请求有几次跌{Q不借助些外力,实是g很不可能的事情?/p>
基本会(x)包含如下内容Q?/p>
- 如何安装需要的Linuxl端命o(h)tcpdump,mtr
- 调试2G/3G{网l连通,域名h跌{
- h丢包情况
说它是神器,一炚w不夸张。HomepageQ?http://dan.drown.org/android/)Q上开明义:(x)
Unix command-line programs ported to run on android. This project uses opkg, which handles downloading and installing packages and their dependencies (like yum or apt). Source for all packages are available.
作?strong>Dan (http://blog.dan.drown.org/)为我们移植到Androidq_Qƈ且还?sh)我们编译好相当多的常用E序Q具体支持列表,可从Changelog
(http://dan.drown.org/android/)中找刎ͼq里不再累述?/p>
十分隑־Q由h谢?/p>
预先把依赖下载到本地:
http://dan.drown.org/android/system/xbin/busybox
http://dan.drown.org/android/opkg.tar.gz
讑֮装到Android手机?/data/local 目录Q那么首先需要确保这个目录具有可d权限?/p>
记得要用su命o(h)切换到root理员̎P操作、权限才不会(x)受阻?/p>
adb shell chmod 777 /data/local
拯opkg?data/local目录
adb push busybox /data/local
adb push opkg.tar.gz /data/local
adb shellq去之后Q开始编译安装:(x)
cd /data/local
chmod 777 busybox
./busybox tar zxvf opkg.tar.gz
讄环境变量Q?/p>
export PATH=$PATH:/data/local/bin
执行更新、安装准?/p>
opkg update
opkg install opkg
opkg list # 可以查看可以支持安装的终端应用程?命o(h))
话说Qopkg可以应用于各U嵌入式环境中,强的说?/p>
可以一口气安装几个试试Q?/p>
opkg install mtr curl tcpdump cat
当然Q你也可以一个一个安装?/p>
安装好之后呢Q就是直接运行应?命o(h)?jin),试baidu.com域名解析、丢包情c(din)?/p>
mtr -r baidu.com HOST: localhost Loss% Snt Last
Avg Best Wrst StDev
1.|-- ??? 100.0 10 0.0 0.0 0.0 0.0 0.0
2.|-- 192.168.61.1 0.0% 10 504.3 635.0 339.3 1024. 238.7
3.|-- 192.168.63.138 0.0% 10 392.9 588.7 298.5 847.7 220.3
4.|-- 221.130.39.106 0.0% 10 340.9 557.3 257.4 823.5 211.7
5.|-- 221.179.159.45 10.0% 10 649.6 631.4 332.6 821.4 165.0
6.|-- 111.13.14.6 10.0% 10 561.9 551.3 268.2 777.0 170.0
7.|-- 111.13.0.162 10.0% 10 510.6 570.6 385.5 767.6 116.6
8.|-- 111.13.1.14 10.0% 10 775.4 565.2 377.7 775.4 130.9
9.|-- 111.13.2.130 10.0% 10 707.2 564.6 381.1 887.3 173.4
嗯,通过mtr实很容易就看出Q网l蟩敎ͼ每一个节点丢包率。这样就能很Ҏ(gu)扑ֈ在移?G/3G|络q接时比较严重的问题所在。下面就是希望运l的同学快处理好,避免再次出现p通机房再ơ蟩转到Ud机房问题?/p>
非常感谢陈杰同学推荐的比ping+tracerouteq要好用命o(h)mtr。一旦拥有,不会(x)放手Q?/p>
要想抓取2G/3G|络下数据包Q必d装一个tcpdump命o(h)Q?/p>
opkg install tcpdump
opkg很脓(chung)?j)的会(x)把所依赖的libpcap也都一q安装上Q完全不用担?j)版本问题?/p>
tcpdump -i any -p -vv -s 0 -w /sdcard/capture.pcap
下面是一气呵成的导出Q用wiresharkq行分析?jin)?/p>
adb pull /sdcard/tmp1.pcap c:/tmp
不习(fn)惯用终端诊断网l,可以直接使用现成的APP?/p>
有更好的APP推荐Q欢q推荐一二?/p>
U上情况Q?/p>
改进工作Q?/p>
实际效果Q?/p>
一般命令前~若添加上m字符Ԍ表示支持多个、批量命令提交了(jin)?/p>
昑ּ?..
MSET key value [key value ...]
MSETNX key value [key value ...]
HMGET key field [field ...]
HMSET key field value [field value ...]
一般方式的...
HDEL key field [field ...]
SREM key member [member ...]
RPUSH key value [value ...]
......
更多Q请参考:(x)http://redis.cn/commands.html
官方文档Q?a >http://redis.io/topics/pipelining
一般业务、接入前端请求量q大Q生产者速度q快Q这时候用队列暂时缓存(sh)(x)比较好一些,消费者直接直接从队列获取dQ通过队列让生产者和消费者进行分这也是业界普通采用的方式?/p>
有的时候,若可以监控一下队列消Ҏ(gu)况,可以监控一下,很直观。同事ؓ(f)队列d?jin)一个监控线E,清晰明了(jin)?jin)解队列消费情况?/p>
C使用?jin)Redis PipelineQ线E池Q准备数据,生?消费者队列,队列监控{,消费完毕Q程序关闭?/p>
/**
* 以下试在Jedis 2.6下测试通过
*
* @author nieyong
*
*/
public class TestJedisPipeline {
private static final int NUM = 512;
private static final int MAX = 1000000; // 100W
private static JedisPool redisPool;
private static final ExecutorService pool = Executors.newCachedThreadPool();
protected static final BlockingQueue<String> queue = new ArrayBlockingQueue<String>(
MAX); // 100W
private static boolean finished = false;
static {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxActive(64);
config.setMaxIdle(64);
try {
redisPool = new JedisPool(config, "192.168.192.8", 6379, 10000,
null, 0);
} catch (Exception e) {
System.err.println("Init msg redis factory error! " + e.toString());
}
}
public static void main(String[] args) throws InterruptedException {
System.out.println("prepare test data 100W");
prepareTestData();
System.out.println("prepare test data done!");
// 生者,模拟h100W?
pool.execute(new Runnable() {
@Override
public void run() {
for (int i = 0; i < MAX; i++) {
if (i % 3 == 0) {
queue.offer("del_key key_" + i);
} else {
queue.offer("get_key key_" + i);
}
}
}
});
// CPU核数*2 个工作者线E?
int threadNum = 2 * Runtime.getRuntime().availableProcessors();
for (int i = 0; i < threadNum; i++)
pool.execute(new ConsumerTask());
pool.execute(new MonitorTask());
Thread.sleep(10 * 1000);// 10sec
System.out.println("going to shutdown server ...");
setFinished(true);
pool.shutdown();
pool.awaitTermination(1, TimeUnit.MILLISECONDS);
System.out.println("colse!");
}
private static void prepareTestData() {
Jedis redis = redisPool.getResource();
Pipeline pipeline = redis.pipelined();
for (int i = 0; i < MAX; i++) {
pipeline.set("key_" + i, (i * 2 + 1) + "");
if (i % (NUM * 2) == 0) {
pipeline.sync();
}
}
pipeline.sync();
redisPool.returnResource(redis);
}
// queue monitorQ生产?消费队列监控
private static class MonitorTask implements Runnable {
@Override
public void run() {
while (!Thread.interrupted() && !isFinished()) {
System.out.println("queue.size = " + queue.size());
try {
Thread.sleep(500); // 0.5 second
} catch (InterruptedException e) {
break;
}
}
}
}
// consumerQ消费?
private static class ConsumerTask implements Runnable {
@Override
public void run() {
while (!Thread.interrupted() && !isFinished()) {
if (queue.isEmpty()) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
continue;
}
List<String> tasks = new ArrayList<String>(NUM);
queue.drainTo(tasks, NUM);
if (tasks.isEmpty()) {
continue;
}
Jedis jedis = redisPool.getResource();
Pipeline pipeline = jedis.pipelined();
try {
List<Response<String>> resultList = new ArrayList<Response<String>>(
tasks.size());
List<String> waitDeleteList = new ArrayList<String>(
tasks.size());
for (String task : tasks) {
String key = task.split(" ")[1];
if (task.startsWith("get_key")) {
resultList.add(pipeline.get(key));
waitDeleteList.add(key);
} else if (task.startsWith("del_key")) {
pipeline.del(key);
}
}
pipeline.sync();
// 处理q回列表
for (int i = 0; i < resultList.size(); i++) {
resultList.get(i).get();
// handle value here ...
// System.out.println("get value " + value);
}
// d完毕Q直接删除之
for (String key : waitDeleteList) {
pipeline.del(key);
}
pipeline.sync();
} catch (Exception e) {
redisPool.returnBrokenResource(jedis);
} finally {
redisPool.returnResource(jedis);
}
}
}
}
private static boolean isFinished(){
return finished;
}
private static void setFinished(boolean bool){
finished = bool;
}
}
代码作ؓ(f)C。若U上则需要处理一些异常等?/p>
若能够批量请求进行合q操作,自然可以节省很多的网l带宽、CPU{资源。有cM问题的同学,不妨考虑一下?/p>
新申L(fng)服务器内ؓ(f)2.6.32Q原先的TCP Server直接在新内核的Linxu服务器上q行Q运行dmesg命o(h)Q可以看到大量的SYN flooding警告Q?/p>
possible SYN flooding on port 8080. Sending cookies.
原先?.6.18内核的参数在2.6.32内核版本情况下,单调?net.ipv4.tcp_max_syn_backlog"已经没有作用?/p>
怎么办,只能再次阅读2.6.32源码Q以下即是?/p>
最后小l处有直接结论,?j)急的你可以直接阅Lȝ好了(jin)?/p>
net/Socket.c:
SYSCALL_DEFINE2(listen, int, fd, int, backlog)
{
struct socket *sock;
int err, fput_needed;
int somaxconn;
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (sock) {
somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;
if ((unsigned)backlog > somaxconn)
backlog = somaxconn;
err = security_socket_listen(sock, backlog);
if (!err)
err = sock->ops->listen(sock, backlog);
fput_light(sock->file, fput_needed);
}
return err;
}
net/ipv4/Af_inet.c:
/*
* Move a socket into listening state.
*/
int inet_listen(struct socket *sock, int backlog)
{
struct sock *sk = sock->sk;
unsigned char old_state;
int err;
lock_sock(sk);
err = -EINVAL;
if (sock->state != SS_UNCONNECTED || sock->type != SOCK_STREAM)
goto out;
old_state = sk->sk_state;
if (!((1 << old_state) & (TCPF_CLOSE | TCPF_LISTEN)))
goto out;
/* Really, if the socket is already in listen state
* we can only allow the backlog to be adjusted.
*/
if (old_state != TCP_LISTEN) {
err = inet_csk_listen_start(sk, backlog);
if (err)
goto out;
}
sk->sk_max_ack_backlog = backlog;
err = 0;
out:
release_sock(sk);
return err;
}
inet_listen调用inet_csk_listen_start函数Q所传入的backlog参数改头换面Q变成了(jin)不可修改的常量nr_table_entries?jin)?/p>
net/ipv4/Inet_connection_sock.c:
int inet_csk_listen_start(struct sock *sk, const int nr_table_entries)
{
struct inet_sock *inet = inet_sk(sk);
struct inet_connection_sock *icsk = inet_csk(sk);
int rc = reqsk_queue_alloc(&icsk->icsk_accept_queue, nr_table_entries);
if (rc != 0)
return rc;
sk->sk_max_ack_backlog = 0;
sk->sk_ack_backlog = 0;
inet_csk_delack_init(sk);
/* There is race window here: we announce ourselves listening,
* but this transition is still not validated by get_port().
* It is OK, because this socket enters to hash table only
* after validation is complete.
*/
sk->sk_state = TCP_LISTEN;
if (!sk->sk_prot->get_port(sk, inet->num)) {
inet->sport = htons(inet->num);
sk_dst_reset(sk);
sk->sk_prot->hash(sk);
return 0;
}
sk->sk_state = TCP_CLOSE;
__reqsk_queue_destroy(&icsk->icsk_accept_queue);
return -EADDRINUSE;
}
下面处理的是TCP SYN_RECV状态的q接Q处于握手阶D,也可以说是半q接Ӟ{待着q接方第三次握手?/p>
/*
* Maximum number of SYN_RECV sockets in queue per LISTEN socket.
* One SYN_RECV socket costs about 80bytes on a 32bit machine.
* It would be better to replace it with a global counter for all sockets
* but then some measure against one socket starving all other sockets
* would be needed.
*
* It was 128 by default. Experiments with real servers show, that
* it is absolutely not enough even at 100conn/sec. 256 cures most
* of problems. This value is adjusted to 128 for very small machines
* (<=32Mb of memory) and to 1024 on normal or better ones (>=256Mb).
* Note : Dont forget somaxconn that may limit backlog too.
*/
int reqsk_queue_alloc(struct request_sock_queue *queue,
unsigned int nr_table_entries)
{
size_t lopt_size = sizeof(struct listen_sock);
struct listen_sock *lopt;
nr_table_entries = min_t(u32, nr_table_entries, sysctl_max_syn_backlog);
nr_table_entries = max_t(u32, nr_table_entries, 8);
nr_table_entries = roundup_pow_of_two(nr_table_entries + 1);
lopt_size += nr_table_entries * sizeof(struct request_sock *);
if (lopt_size > PAGE_SIZE)
lopt = __vmalloc(lopt_size,
GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO,
PAGE_KERNEL);
else
lopt = kzalloc(lopt_size, GFP_KERNEL);
if (lopt == NULL)
return -ENOMEM;
for (lopt->max_qlen_log = 3;
(1 << lopt->max_qlen_log) < nr_table_entries;
lopt->max_qlen_log++);
get_random_bytes(&lopt->hash_rnd, sizeof(lopt->hash_rnd));
rwlock_init(&queue->syn_wait_lock);
queue->rskq_accept_head = NULL;
lopt->nr_table_entries = nr_table_entries;
write_lock_bh(&queue->syn_wait_lock);
queue->listen_opt = lopt;
write_unlock_bh(&queue->syn_wait_lock);
return 0;
}
关键要看nr_table_entries变量Q在reqsk_queue_alloc函数中nr_table_entries变成?jin)无W号变量Q可修改的,变化受限?/p>
比如实际内核参数gؓ(f)Q?/p>
net.ipv4.tcp_max_syn_backlog = 65535
所传入的backlogQ不大于net.core.somaxconn = 65535Qؓ(f)8102Q那?/p>
// 取listen函数的backlog和sysctl_max_syn_backlog最|l果?102
nr_table_entries = min_t(u32, nr_table_entries, sysctl_max_syn_backlog);
// 取nr_table_entries?q行比较的最大|l果?102
nr_table_entries = max_t(u32, nr_table_entries, 8);
// 可看?nr_table_entries*2Q结果ؓ(f)8102*2=16204
nr_table_entries = roundup_pow_of_two(nr_table_entries + 1);
计算l果Qmax_qlen_log = 14
for (lopt->max_qlen_log = 6;
(1 << lopt->max_qlen_log) < sysctl_max_syn_backlog;
lopt->max_qlen_log++);
作ؓ(f)listen_sockl构定义?jin)需要处理的处理半连接的队列元素个数为nr_table_entriesQ此例中?6204长度?/p>
/** struct listen_sock - listen state
*
* @max_qlen_log - log_2 of maximal queued SYNs/REQUESTs
*/
struct listen_sock {
u8 max_qlen_log;
/* 3 bytes hole, try to use */
int qlen;
int qlen_young;
int clock_hand;
u32 hash_rnd;
u32 nr_table_entries;
struct request_sock *syn_table[0];
};
l描q而知Q?^max_qlen_log = 半连接队列长度qlen倹{?/p>
再回头看看报告SYN flooding的函敎ͼ(x)
net/ipv4/Tcp_ipv4.c
#ifdef CONFIG_SYN_COOKIES
static void syn_flood_warning(struct sk_buff *skb)
{
static unsigned long warntime;
if (time_after(jiffies, (warntime + HZ * 60))) {
warntime = jiffies;
printk(KERN_INFO
"possible SYN flooding on port %d. Sending cookies.\n",
ntohs(tcp_hdr(skb)->dest));
}
}
#endif
被调用的处,已精若干代码Q?/p>
int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
{
......
#ifdef CONFIG_SYN_COOKIES
int want_cookie = 0;
#else
#define want_cookie 0 /* Argh, why doesn't gcc optimize this :( */
#endif
......
/* TW buckets are converted to open requests without
* limitations, they conserve resources and peer is
* evidently real one.
*/
// 判断半连接队列是否已?&& !0
if (inet_csk_reqsk_queue_is_full(sk) && !isn) {
#ifdef CONFIG_SYN_COOKIES
if (sysctl_tcp_syncookies) {
want_cookie = 1;
} else
#endif
goto drop;
}
/* Accept backlog is full. If we have already queued enough
* of warm entries in syn queue, drop request. It is better than
* clogging syn queue with openreqs with exponentially increasing
* timeout.
*/
if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1)
goto drop;
req = inet_reqsk_alloc(&tcp_request_sock_ops);
if (!req)
goto drop;
......
if (!want_cookie)
TCP_ECN_create_request(req, tcp_hdr(skb));
if (want_cookie) {
#ifdef CONFIG_SYN_COOKIES
syn_flood_warning(skb);
req->cookie_ts = tmp_opt.tstamp_ok;
#endif
isn = cookie_v4_init_sequence(sk, skb, &req->mss);
} else if (!isn) {
......
}
......
}
判断半连接队列已满的函数很关键,可以看看q算法则Q?/p>
include/net/Inet_connection_sock.h:
static inline int inet_csk_reqsk_queue_is_full(const struct sock *sk)
{
return reqsk_queue_is_full(&inet_csk(sk)->icsk_accept_queue);
}
include/net/Rquest_sock.h:
static inline int reqsk_queue_is_full(const struct request_sock_queue *queue)
{
// 向右UMmax_qlen_log个单?
return queue->listen_opt->qlen >> queue->listen_opt->max_qlen_log;
}
q回1Q自然表C半q接队列已满?/p>
以上仅仅是分析了(jin)半连接队列已满的判断条gQM应用E序所传入的backlog很关键,如值太,很容易得?.
?somaxconn = 128Qsysctl_max_syn_backlog = 4096Qbacklog = 511 则最l?nr_table_entries = 256Qmax_qlen_log = 8。那么超q?56个半q接的队列,257 >> 8 = 1Q队列已满?/p>
如何讄backlogQ还得需要结合具体应用程序,需要ؓ(f)其调用listenҎ(gu)赋倹{?/p>
Tcp Server使用Netty 3.7 版本Q版本较低,在处理backlogQ若我们不手动指定backlog|JDK 1.6默认?0?/p>
有证如下Q?java.net.ServerSocket:
public void bind(SocketAddress endpoint, int backlog) throws IOException {
if (isClosed())
throw new SocketException("Socket is closed");
if (!oldImpl && isBound())
throw new SocketException("Already bound");
if (endpoint == null)
endpoint = new InetSocketAddress(0);
if (!(endpoint instanceof InetSocketAddress))
throw new IllegalArgumentException("Unsupported address type");
InetSocketAddress epoint = (InetSocketAddress) endpoint;
if (epoint.isUnresolved())
throw new SocketException("Unresolved address");
if (backlog < 1)
backlog = 50;
try {
SecurityManager security = System.getSecurityManager();
if (security != null)
security.checkListen(epoint.getPort());
getImpl().bind(epoint.getAddress(), epoint.getPort());
getImpl().listen(backlog);
bound = true;
} catch(SecurityException e) {
bound = false;
throw e;
} catch(IOException e) {
bound = false;
throw e;
}
}
netty中,处理backlog的地方:(x)
org/jboss/netty/channel/socket/DefaultServerSocketChannelConfig.java:
@Override
public boolean setOption(String key, Object value) {
if (super.setOption(key, value)) {
return true;
}
if ("receiveBufferSize".equals(key)) {
setReceiveBufferSize(ConversionUtil.toInt(value));
} else if ("reuseAddress".equals(key)) {
setReuseAddress(ConversionUtil.toBoolean(value));
} else if ("backlog".equals(key)) {
setBacklog(ConversionUtil.toInt(value));
} else {
return false;
}
return true;
}
既然需要我们手动指定backlog|那么可以q样做:(x)
bootstrap.setOption("backlog", 8102); // 讄大一些没有关p,pȝ内核?x)自动与net.core.somaxconn相比较,取最低?
相对比Netty 4.0Q有些不Q可参考:(x)http://www.tkk7.com/yongboy/archive/2014/07/30/416373.html
在linux内核2.6.32Q若在没有遭受到SYN floodingd的情况下Q可以适当调整Q?/p>
sysctl -w net.core.somaxconn=32768
sysctl -w net.ipv4.tcp_max_syn_backlog=65535
sysctl -p
另千万别忘记修改TCP Server的listen接口所传入的backlog|若不讄或者过,都会(x)有可能造成SYN flooding的警告信息。开始不妨设|成1024Q然后观察一D|间根据实际情况需要再慢慢往上调?/p>
无论你如何设|,最lbacklogD围ؓ(f)Q?/p>
backlog <= net.core.somaxconn
半连接队列长度约为:(x)
半连接队列长?≈ 2 * min(backlog, net.ipv4.tcpmax_syn_backlog)
另,若出现SYN floodingӞ此时TCP SYN_RECV数量表示半连接队列已l满Q可以查看一下:(x)
ss -ant | awk 'NR>1 {++s[$1]} END {for(k in s) print k,s[k]}'
感谢q维书坤伙提供的比较好用查看命令?/p>
最q线上服务器Qdmesg?x)给Z些警告信息:(x)
possible SYN flooding on port 8080. Sending cookies.
初看以ؓ(f)是受到DOS拒绝性攻击,但仔l一分析Q一天量也就是在1000多条左右Q感觉上属于正常可接受范围?/p>
下面需要找出来源,以及(qing)原因Q以下内容基于Linux 2.6.18内核?/p>
net/ipv4/Tcp_ipv4.c:
#ifdef CONFIG_SYN_COOKIES
static void syn_flood_warning(struct sk_buff *skb)
{
static unsigned long warntime; // W一ơ加载初始化为零Q后lwarntime = jiffies
if (time_after(jiffies, (warntime + HZ * 60))) {
warntime = jiffies;
printk(KERN_INFO
"possible SYN flooding on port %d. Sending cookies.\n",
ntohs(skb->h.th->dest));
}
}
#endif
很显?dng)CONFIG_SYN_COOKIES在Linuxpȝ~译Ӟ已被讄true?/p>
time_after宏定义:(x)
#define time_after(a,b) \
(typecheck(unsigned long, a) && \
typecheck(unsigned long, b) && \
((long)(b) - (long)(a) < 0))
两个无符L(fng)旉比较Q确定先后顺序?/p>
jiffies真nQ?/p>
# define jiffies raid6_jiffies()
#define HZ 1000
......
static inline uint32_t raid6_jiffies(void)
{
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec*1000 + tv.tv_usec/1000; // U?1000 + 微秒/1000
}
回过头来Q再看看syn_flood_warning函数Q?/p>
static void syn_flood_warning(struct sk_buff *skb)
{
static unsigned long warntime; // W一ơ加载初始化为零Q后lwarntime = jiffies
if (time_after(jiffies, (warntime + HZ * 60))) {
warntime = jiffies;
printk(KERN_INFO
"possible SYN flooding on port %d. Sending cookies.\n",
ntohs(skb->h.th->dest));
}
}
warntime为staticcdQ第一ơ调用时被初始化为零Q下ơ调用就是上ơ的jiffiesg(jin)Q前后间隔DqHZ*60׃?x)输(gu)告信息?jin)?/p>
有关time_after和jiffiesQ分享几文章:(x)
http://wenku.baidu.com/view/c75658d480eb6294dd886c4e.html
注意观察want_cookie=1时的条g?/p>
net/ipv4/Tcp_ipv4.c:
int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
{
struct inet_request_sock *ireq;
struct tcp_options_received tmp_opt;
struct request_sock *req;
__u32 saddr = skb->nh.iph->saddr;
__u32 daddr = skb->nh.iph->daddr;
__u32 isn = TCP_SKB_CB(skb)->when; // when在tcp_v4_rcv()中会(x)被置?
struct dst_entry *dst = NULL;
#ifdef CONFIG_SYN_COOKIES
int want_cookie = 0;
#else
#define want_cookie 0 /* Argh, why doesn't gcc optimize this :( */
#endif
/* Never answer to SYNs send to broadcast or multicast */
if (((struct rtable *)skb->dst)->rt_flags &
(RTCF_BROADCAST | RTCF_MULTICAST))
goto drop;
/* TW buckets are converted to open requests without
* limitations, they conserve resources and peer is
* evidently real one.
*/
// if(判断半连接队列已?&& !0)
if (inet_csk_reqsk_queue_is_full(sk) && !isn) {
#ifdef CONFIG_SYN_COOKIES
if (sysctl_tcp_syncookies) { // net.ipv4.tcp_syncookies = 1
want_cookie = 1;
} else
#endif
goto drop;
}
/* Accept backlog is full. If we have already queued enough
* of warm entries in syn queue, drop request. It is better than
* clogging syn queue with openreqs with exponentially increasing
* timeout.
*/
// if(q接队列是否已满 && 半连接队列中q有未重传ACK半连接数?> 1)
if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1)
goto drop;
......
tcp_openreq_init(req, &tmp_opt, skb);
ireq = inet_rsk(req);
ireq->loc_addr = daddr;
ireq->rmt_addr = saddr;
ireq->opt = tcp_v4_save_options(sk, skb);
if (!want_cookie)
TCP_ECN_create_request(req, skb->h.th);
if (want_cookie) { // 半连接队列已满会(x)触发
#ifdef CONFIG_SYN_COOKIES
syn_flood_warning(skb);
#endif
isn = cookie_v4_init_sequence(sk, skb, &req->mss);
} else if (!isn) {
......
}
/* Kill the following clause, if you dislike this way. */
// net.ipv4.tcp_syncookies未设|情况下Qsysctl_max_syn_backlog发生的作?
else if (!sysctl_tcp_syncookies &&
(sysctl_max_syn_backlog - inet_csk_reqsk_queue_len(sk) <
(sysctl_max_syn_backlog >> 2)) &&
(!peer || !peer->tcp_ts_stamp) &&
(!dst || !dst_metric(dst, RTAX_RTT))) {
/* Without syncookies last quarter of
* backlog is filled with destinations,
* proven to be alive.
* It means that we continue to communicate
* to destinations, already remembered
* to the moment of synflood.
*/
LIMIT_NETDEBUG(KERN_DEBUG "TCP: drop open "
"request from %u.%u.%u.%u/%u\n",
NIPQUAD(saddr),
ntohs(skb->h.th->source));
dst_release(dst);
goto drop_and_free;
}
isn = tcp_v4_init_sequence(sk, skb);
}
tcp_rsk(req)->snt_isn = isn;
if (tcp_v4_send_synack(sk, req, dst))
goto drop_and_free;
if (want_cookie) {
reqsk_free(req);
} else {
inet_csk_reqsk_queue_hash_add(sk, req, TCP_TIMEOUT_INIT);
}
return 0;
drop_and_free:
reqsk_free(req);
drop:
return 0;
}
MQ如pȝ出现Q?/p>
possible SYN flooding on port 8080. Sending cookies.
若量不大Q是在提醒你需要关?j)一下sysctl_max_syn_backlog其值是否过?
sysctl -a | grep 'max_syn_backlog'
不妨成倍增加一?/p>
sysctl -w net.ipv4.tcp_max_syn_backlog=8192
sysctl -p
若进E无法做到重新加载,那就需要重启应用,以适应新的内核参数。进而持l观察一D|间?/p>
貌似tcp_max_syn_backlog参数其完整作用域q没有理解完_(d)下次有时间再写吧?/p>
有些东西L很容易遗忘,一时记得了(jin)Q过两天q正还l周公了(jin)。零零碎的不如一q记下来Q以后可以直接拿q来查询卛_?/p>
以下内容ZLinux 2.6.18内核?/p>
q个参数具体意义Q先看看Linux Socket的listen解释
man listen
#include <sys/socket.h>
int listen(int sockfd, int backlog);
intcd的backlog参数QlistenҎ(gu)的backlog意义为,已经完成三次握手、已l成功徏立连接的套接字将要进入队列的长度?/p>
一般我们自己定义设定backlog|若我们设|的backlog值大于net.core.somaxconn|被|ؓ(f)net.core.somaxconn值大。若不想直接性指定,跟随pȝ讑֮Q则需要读?proc/sys/net/core/somaxconn?/p>
net\Socket.c :
/*
* Perform a listen. Basically, we allow the protocol to do anything
* necessary for a listen, and if that works, we mark the socket as
* ready for listening.
*/
int sysctl_somaxconn = SOMAXCONN;
asmlinkage long sys_listen(int fd, int backlog)
{
struct socket *sock;
int err, fput_needed;
if ((sock = sockfd_lookup_light(fd, &err, &fput_needed)) != NULL) {
if ((unsigned) backlog > sysctl_somaxconn)
backlog = sysctl_somaxconn;
err = security_socket_listen(sock, backlog);
if (!err)
err = sock->ops->listen(sock, backlog);
fput_light(sock->file, fput_needed);
}
return err;
}
比如l常使用的netty(4.0)框架Q在Linux下启动时Q会(x)直接d/proc/sys/net/core/somaxconn值然后作为listen的backlog参数q行调用Linuxpȝ的listenq行初始化等?/p>
int somaxconn = 3072;
BufferedReader in = null;
try {
in = new BufferedReader(new FileReader("/proc/sys/net/core/somaxconn"));
somaxconn = Integer.parseInt(in.readLine());
logger.debug("/proc/sys/net/core/somaxconn: {}", somaxconn);
} catch (Exception e) {
// Failed to get SOMAXCONN
} finally {
if (in != null) {
try {
in.close();
} catch (Exception e) {
// Ignored.
}
}
}
SOMAXCONN = somaxconn;
......
private volatile int backlog = NetUtil.SOMAXCONN;
一般稍微增大net.core.somaxconn值就昑־很有必要?/p>
讄其值方法:(x)
sysctl -w net.core.somaxconn=65535
较大内存的LinuxQ?5535数g般就可以?jin)?/p>
若让其生效,sysctl -p 卛_Q然后重启你的Server应用卛_?/p>
内核代码中sysctl.c文g解释Q?/p>
number of unprocessed input packets before kernel starts dropping them, default 300
我所理解的含义,每个|络接口接收数据包的速率比内核处理这些包的速率快时Q允?dng)R到队列的最大数目,一旦超q将被丢弃?/p>
所起作用处Qnet/core/Dev.cQ?/p>
int netif_rx(struct sk_buff *skb)
{
struct softnet_data *queue;
unsigned long flags;
/* if netpoll wants it, pretend we never saw it */
if (netpoll_rx(skb))
return NET_RX_DROP;
if (!skb->tstamp.off_sec)
net_timestamp(skb);
/*
* The code is rearranged so that the path is the most
* short when CPU is congested, but is still operating.
*/
local_irq_save(flags);
queue = &__get_cpu_var(softnet_data);
__get_cpu_var(netdev_rx_stat).total++;
if (queue->input_pkt_queue.qlen <= netdev_max_backlog) {
if (queue->input_pkt_queue.qlen) {
enqueue:
dev_hold(skb->dev);
__skb_queue_tail(&queue->input_pkt_queue, skb);
local_irq_restore(flags);
return NET_RX_SUCCESS;
}
netif_rx_schedule(&queue->backlog_dev);
goto enqueue;
}
__get_cpu_var(netdev_rx_stat).dropped++;
local_irq_restore(flags);
kfree_skb(skb);
return NET_RX_DROP;
}
以上代码看一下,大概?x)明白netdev_max_backlog?x)在什么时候v作用?/p>
公司内技术分享文档,不涉?qing)公司内部技术等Q可以拿出来分n一下?/p>
讉K地址Q?a >https://speakerdeck.com/yongboy/linuxxi-tong-fu-wu-duan-kou-de-na-xie-shi
有些_糙Q有些点可能未表达清楚,(zhn)若发现谬误之处Q欢q及(qing)时指出?/p>
d模式Q选项{active, true}Q一般让人很喜欢Q非d消息接收Q但在系l无法应对超大流量请求时Q客L(fng)发送的数据快过服务器可以处理的速度Q那么系l就可能?x)造成消息~冲塞满Q可能出现持l繁忙的量的极端情况下Q系l因h而溢出,虚拟机造成内存?sh)的风险而崩溃?/p>
使用被动模式Q选项{active, false}Q的套接字,底层的TCP~冲区可用于抑制hQƈ拒绝客户端的消息Q在接收数据的地斚w?x)调用gen_tcp:recvQ造成dQ单q程模式下就只能消极{待某一个具体的客户端套接字Q很危险Q。需要注意的是,操作pȝ可能q(sh)(x)做一些缓存允许客L(fng)机器l箋(hu)发送少量数据,然后才会(x)其dQ此时Erlang未调用recv函数?/p>
混合型模式(半阻塞)(j)Q用选项{active, once}打开Q主动仅针对一个消息,在控制进E发送完一个数据消息后Q必LC用inet:setopts(Socket, [{active, once}])重新ȀzM便接受下一个消息(在此之前Q系l处于阻塞状态)(j)。可见,混合型模式综合了(jin)d模式和被动模式的两者优势,可实现流量控Ӟ防止服务器被q多消息Ҏ(gu)?/p>
以下TCP Server代码Q都是徏立在混合型模式(半阻塞)(j)基础上?/p>
prim_inet没有官方文档Q可以认为是对底层socket的直接包装。淘?a target="_blank">yufeng_(d)q是otp内部实现的细?是针对Erlang库开发者的private moduleQ底层模块,不推荐用。但?a target="_blank">Building a Non-blocking TCP server using OTP principlesC中演CZ(jin)prim_inet操作Socket异步Ҏ(gu)?/p>
一般来_(d)需要一个单独进E进行客L(fng)套接字监听,每一个子q程q行处理来自具体客户端的socketh?/p>
?a target="_blank">Building a Non-blocking TCP server using OTP principlesC中,子进E用gen_fsm处理Q很巧妙的结合状态机和消息事Ӟ值得学习(fn)?/p>
?a target="_blank">Erlang: A Generalized TCP Server文章中,作者也是用此模式Q但子进E不W合OTP规范Q因此个Z是一个很好的实践模式?/p>
易的一对一监督q程Q用来创Zl动态子q程。对于需要ƈ发处理多个请求的服务器较为合适。比如socket 服务端接受新的客L(fng)q接h以后Q需要动态创Z个新的socketq接处理子进E。若遵守OTP原则Q那是子监督进E?/p>
也是Z{active, once}模式Q但d的等待下一个客L(fng)q接的Q务被抛给?jin)子监督q程?/p>
看一下入口tcp_server_app?/p>
d端口Q然后启动主监督q程Q此时还?sh)?x)监听处理客户端sockethQ,紧接着启动子监督进E,开始处理来自客L(fng)的socket的连接?/p>
监督q程tcp_server_sup也很单:(x)
需要注意的是,只有调用start_child函数Ӟ才真正调用tcp_server_handler:start_link([LSock])函数?/p>
tcp_server_handler的代码也不复杂:(x)
代码很精巧,有些技巧在里面。子监督q程调用start_link函数Qinit?x)返回{ok, #state{lsock = Socket}, 0}. 数字0代表?jin)timeout数|意味着gen_server马上调用handle_info(timeout, #state{lsock = LSock} = State)函数Q执行客L(fng)socket监听Q阻塞于此,但不?x)?jing)响在此模式下其它函数的调用。直到有客户端进来,然后启动一个新的子监督q程tcp_server_handlerQ当前子监督q程解除d?/p>
q个实现师从于Non-blocking TCP server using OTP principles一文,但子q程改ؓ(f)?jin)gen_server实现?/p>
看一看入口,很简单的Q?/p>
监督q程代码Q?/p>
{略不一Pone_for_one包括?jin)一个监听进Etcp_listenerQ还包含?jin)一个tcp_client_supq程?simple_one_for_one{略)
tcp_listener单独一个进E用于监听来自客L(fng)socket的连?
很显?dng)接收客户端的q接之后Q{交给tcp_client_handler模块q行处理Q?/p>
和标准APIҎ(gu)一下,可以感受到异步IO的好处?/p>
通过不同的模式,单实C个基于Erlang OTP的TCP服务器,也是学习(fn)ȝQ不至于忘记?/p>
(zhn)若有更好的Q欢q告知,谢谢?/p>