基于netty框架的Socket传输
一、Netty框架介绍
什么是netty?先看下百度百科的解释:
为什么好多大公司都在使用netty框架?主要是基于netty框架的以下几个特点决定的:
1)健壮性,2)功能齐全,3)可定制,4)扩展性
二、框架优点
传统的RPC性能差,主要是由于客户端和远程调用采用了同步阻塞IO线程,当客户端的并发压力增大后,同步阻塞会由于频繁的等待导致I/O线程堵塞,线程无法高效的工作,IO处理能力自然会降低。影响性能的三个因素:第一,IO模型,IO模型在一定程度上决定了框架的性能。第二、协议,如:HTTP、TCP/IP等,协议选择的不同,性能模型也不同,通常情况下,内部私有协议的性能比较优,这是由于内部设计决定的。第三、线程,数据报文的接收、读取、编码、解码等,线程模型的不同,性能也不同。相比于传统的RPC框架,netty的优点主要体现在以下几个方面:
- API使用简单,封装非常完善,开发门槛低
- 功能上强大,预置了多种编码解码功能,多种协议支持
- 定制能力强,可以对ChannelHandler对通信框架灵活扩展
- 性能高,Reactor线程模型调度,ChannelFuture-Listener,通过Listener机制主动推送结果
- 版本成熟稳定,社区活跃,版本更新快,出现的Bug会被很快的修复,同时,有心功能的加入,经历了大规模的商业应用考验,质量的到了充分的验证。已经广泛应用到互联网、大数据、企业应用、电信软件、网络游戏等热门行业,他可以满足不同的商业标准。
三、Netty架构分析
Netty是一个基于三层网络架构模型的框架,三层网络架构分析包括调度层、链条传递层以及业务逻辑层。
- Reactor通信调度层,是一个模型,
NIO线程池组件{
监听网络读写连接
业务调度处理
NIO,AIO,配合NIO通道NioSocketChannel组件
}
Netty通过内部select巡查机制,能够实现IO多路复用,通过把多个IO阻塞复用到同一个select的阻塞上,从而能够使系统即使在单线程的情况下,也能够同时处理多个请求。这样就使得netty实现了IO多路复用的优势,与传统多线程相比,大大减少了系统的开销,因为系统不必创建新的线程和销毁线程了,减少了系统的维护难度,节省了资源。
ByteBuffer池化支持,不用手动切换标志位,实现零拷贝。传统的Socket读写,基本是使用堆内存进行,即jvm事先会把堆内存拷贝到内存中,然后再写入Socket,而netty采用的是DIRECT BUFFERS,不需要经过jvm内存拷贝,在堆外内存直接进行Socket读写,这样就少了一次缓冲区的内存拷贝,从而实现零拷贝。
2.Pipleline职责链条传递
拦截处理向前向后事件,外部传入的消息包对象,有POJO信息抽象,上层也只需要处理逻辑,类似SpringIOC处理BeanDefince。不同的Handler节点的功能也不同,通常情况下需要编码解码等,它可以完成外部协议到内部POJO对象的转化,这样上层只需要关注业务逻辑,不需要知道底层的协议和线程模型,从而实现解耦。
3.构建逻辑业务处理单元
底层的协议隔离,上层处理逻辑框架并不需要关心底层协议是什么。Netty框架的分层设计使得开发人员不需要关注协议框架的实现,只需要关注服务层的业务逻辑开发即可,实现了简单化。
之前有个项目是基于传统Socket和线程池的技术实现的,但是在高并发的时候发现并发能力不足,压测的时候发现TPS达不到理想值,所以经过考虑,决定使用netty框架来解决此问题。同样,netty框架也分为客户端和服务端,经过整理,先写一个demo初探netty框架,下面是代码的实现过程。
首先是服务端,服务端包含两个方面,第一、服务端Server的主要作用就是通过辅助引导程序,设置NIO的连接方式处理客户端请求,通过绑定特定端口、设定解码方式以及监听来实现整个线程的处理请求;第二、服务端Handler需要继承ChannelInboundHandlerAdapter类,handler类的主要作用是读取客户端数据,处理业务,抛出异常,响应客户端请求。代码如下:
服务端Server:
public class Server {
private static Log logger = LogFactory.getLog(Server.class);
private int port;
public Server(int port) {
super();
this.port = port;
}
public void start(){
ServerBootstrap b = new ServerBootstrap();//引导辅助程序
EventLoopGroup group = new NioEventLoopGroup();//通过nio方式来接收连接和处理请求
try {
b.group(group);
b.channel(NioServerSocketChannel.class);//设置nio类型的channnel
b.localAddress(new InetSocketAddress(port));//设置监听端口
//b.option(ChannelOption.SO_BACKLOG, 2048);
b.childHandler(new ChannelInitializer<SocketChannel>() {//有连接到达时会创建一个channel
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//注册handler
ch.pipeline().addLast(new ByteArrayDecoder());
ch.pipeline().addLast(new ByteArrayEncoder());
ch.pipeline().addLast(new StringEncoder(Charset.forName("UTF-8")));
ch.pipeline().addLast(new ServerHandler());
}
});//.option(ChannelOption.SO_BACKLOG, 2048).childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture f = b.bind().sync();//配置完成,开始绑定server,通过调用sync同步方法阻塞直到绑定成功
logger.info(Server.class.getName()+"开始监听:"+f.channel().localAddress());
f.channel().closeFuture().sync();//应用程序会一直等待直到channel关闭
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
//关闭EventLoopGroup,释放掉所有资源包括创建的线程
group.shutdownGracefully().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
服务端Handler
public class ServerHandler extends ChannelInboundHandlerAdapter {
private static Log logger=LogFactory.getLog(ServerHandler.class);
@Override
public void channelActive(ChannelHandlerContext ctx){
logger.info(ctx.channel().localAddress().toString()+"通道活跃....");
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
logger.error(ctx.channel().localAddress().toString()+"通道不活跃....");
}
/**
*
* 读取客户端传过来的消息
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//业务处理类
logger.info("开始业务处理....");
new SocketController(ctx,msg).run();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//出现异常,关闭连
logger.error("服务端出现异常:"+cause.getMessage(),cause);
ctx.close();
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
logger.info("服务端完成请求!");
ctx.flush();
}
}
客户端代码
客户端主要是用来向服务端发送数据,同样包含两个方面,第一、Client主要通过设定端口和IP和服务器建立连接,进行数据包的编码;第二、ClientHandler 需要继承 SimpleChannelInboundHandler<ByteBuf>类,针对不同的传输方式,继承不同的类,handler类同样处理业务请求,响应服务端的请求。代码如下:
客户端Client:
public class Client {
private static Log logger=LogFactory.getLog(Client.class);
private String host;
private int port;
public Client(String host, int port) {
super();
this.host = host;
this.port = port;
}
public void connect(){
EventLoopGroup workGroup=new NioEventLoopGroup();
Bootstrap bootstrap=new Bootstrap();
bootstrap.group(workGroup);
bootstrap.channel(NioSocketChannel.class);
bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
logger.info("客户端触发连接......");
ch.pipeline().addLast(new StringEncoder(Charset.forName("UTF-8")));
ch.pipeline().addLast(new ClientHandler());
}
});
//客户端开始连接
try {
logger.info("连接到服务器......");
ChannelFuture future=bootstrap.connect(host,port).sync();
//等待连接关闭
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
workGroup.shutdownGracefully();
}
}
}
客户端Handler:
public class ClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
private static Log logger=LogFactory.getLog(ClientHandler.class);
/**
* 向服务端发送消息
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
logger.info(ctx.channel().localAddress().toString()+"客户点活跃...");
//向服务端写字符串
logger.info("客户端连接服务端,开始发送数据.....");
String string ="hello server!";
System.out.println("发送数据为:"+string);
ByteBuf buf=ctx.alloc().buffer(4*string.length());
buf.writeBytes(string.getBytes());
ctx.writeAndFlush(buf);
logger.info("发送完毕...");
}
/**
* 读取服务端返回来的消息
*/
@Override
protected void channelRead0(ChannelHandlerContext arg0, ByteBuf in) throws Exception {
logger.info("开始接受服务端数据");
byte[] b=new byte[in.readableBytes()];
in.readBytes(b);
String string=new String(b);
logger.info("服务端发送的数据为:"+string);
in.release();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
logger.info("客户端异常:"+cause.getMessage(),cause);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
logger.info("客户端完成请求....");
ctx.flush();
}
}
服务端启动:
public class ServerMain {
private static Log logger=LogFactory.getLog(ServerMain.class);
private static Server server =new Server(55550);
public static void main(String[] args) {
logger.info("服务端启动.......");
server.start();
}
}
客户端启动类:
public class Test {
private static Client client = new Client("127.0.0.1", 55550);
public static void main(String[] args) throws UnknownHostException, IOException {
client.connect();
}
}
测试结果:
服务端:

客户端:

总结:
以上只是一个netty框架初探的小Demo,学习使用netty框架的开始,这里面涉及到了很多的技术以及非常多的组件,比如:Channels、Callbacks、Futures、Events和handlers等等,需要进一步的学习,另外,消息的编码解码、粘包、拆包的方式方法、消息格式的转换以及报文格式大小限制都需要进一步的研究学习。
基于netty框架的Socket传输的更多相关文章
- 基于netty框架的socket长连接负载均衡解决方案
socket通讯的单机瓶颈 物联网的项目socket使用方式有两种: 短连接的socket请求 维持socket长连接的请求 对于socket短链接来说就好比是http请求,请求服务器,服务器返回数据 ...
- 基于netty框架的轻量级RPC实现(附源码)
前言 Rpc( Remote procedure call):是一种请求 - 响应协议.RPC由客户端启动,客户端向已知的远程服务器发送请求消息,以使用提供的参数执行指定的过程.远程服务器向客户端发送 ...
- 【转】彻底搞透Netty框架
本文基于 Netty 4.1 展开介绍相关理论模型,使用场景,基本组件.整体架构,知其然且知其所以然,希望给大家在实际开发实践.学习开源项目方面提供参考. Netty 是一个异步事件驱动的网络应用程序 ...
- 一款基于Netty开发的WebSocket服务器
代码地址如下:http://www.demodashi.com/demo/13577.html 一款基于Netty开发的WebSocket服务器 这是一款基于Netty框架开发的服务端,通信协议为We ...
- 基于netty轻量的高性能分布式RPC服务框架forest<上篇>
工作几年,用过不不少RPC框架,也算是读过一些RPC源码.之前也撸过几次RPC框架,但是不断的被自己否定,最近终于又撸了一个,希望能够不断迭代出自己喜欢的样子. 顺便也记录一下撸RPC的过程,一来作为 ...
- 基于Java Netty框架构建高性能的部标808协议的GPS服务器
使用Java语言开发一个高质量和高性能的jt808 协议的GPS通信服务器,并不是一件简单容易的事情,开发出来一段程序和能够承受数十万台车载接入是两码事,除去开发部标808协议的固有复杂性和几个月长周 ...
- 基于Java Netty框架构建高性能的Jt808协议的GPS服务器(转)
原文地址:http://www.jt808.com/?p=971 使用Java语言开发一个高质量和高性能的jt808 协议的GPS通信服务器,并不是一件简单容易的事情,开发出来一段程序和能够承受数十万 ...
- 《Java 编写基于 Netty 的 RPC 框架》
一 简单概念 RPC: ( Remote Procedure Call),远程调用过程,是通过网络调用远程计算机的进程中某个方法,从而获取到想要的数据,过程如同调用本地的方法一样. 阻塞IO :当阻塞 ...
- java编写基于netty的RPC框架
一 简单概念 RPC:(Remote Procedure Call),远程调用过程,是通过网络调用远程计算机的进程中某个方法,从而获取到想要的数据,过程如同调用本地的方法一样. 阻塞IO:当阻塞I/O ...
随机推荐
- shopnc验证码显示不了
data/config文件编码问题,要utf-8无bom
- System包含的信息
System类中的属性值 System.getProperty()方法大全 System.out.println("java版本号:" + System.getProperty(& ...
- linux_通配符
通配符和正则表达式区别? 通配符用在用户命令行bash环境,而正则表达式用于linux三剑客(awk, sed, grep) 那,有哪些通配符? * 所有字符 五星 ls *.txt # 列举目 ...
- linkin大话数据结构--数组
数组概述:如何正确理解数组?数组也是一种类型 数组是多个相同类型数据的组合,实现对这些数据的统一管理.数组属引用类型,数组型数据是对象(Object),数组中的每个元素相当于该对象的成员变量数组中的元 ...
- win10预览版无开始菜单解决方案
1.按下Win+R键打开“运行”程序,键入gpedit.msc 回车以打开本地组策略编辑器 2.调到图示位置将windows设置->安全设置->本地策略->安全选项->“用户账 ...
- PHP date()函数详解
date (PHP 4, PHP 5) date - 格式化一个本地时间/日期 说明¶ string date ( string $format [, int $timestamp ] ) 返回将整数 ...
- git常用命令,学git代码管理
下面是我整理的常用 Git 命令清单.几个专用名词的译名如下. Workspace:工作区 Index / Stage:暂存区 Repository:仓库区(或本地仓库) Remote:远程仓库 一. ...
- MySql-5.7.17-20解压缩版安装配置
MySql-5.7.XXX解压缩版安装配置 1.mysql-5.7.20是解压版免安装的,版本下载地址:http://dev.mysql.com/downloads/mysql/ 如下图 2.解压 ...
- JAVA 调用 R 语言之升华篇
由于项目的需要,最近做了一个javaWeb调用R的组件,在这里,我把自己走的一些弯路给大家总结一下: 一.选取什么插件作为java和R之间的通信? 1. 在传统的方式中,大致可以分为两类:一类是JRI ...
- Linux下php+imagemagick支持webp格式的图片
摘要 ImageMagick是一款功能强大的图片处理工具包,很多互联网应用中都会涉及到图片处理工作,比如切割.缩放.水印.格式转换等.ImageMagick就是一个理想的工具包. 安装基础依赖 先检查 ...