网络通信其实就是干这么几件事
1.建立连接
2.客户端发送数据
3.服务器接受到数据,然后根据数据内容进行处理,然后返回数据
4.不停重复2,3
5.关闭连接
我们为什么要用netty来搭建服务器呢?或者说netty为什么成为了高人气的服务器框架呢?
下面我们就来仔细研究一下吧。
在网络编程中,我们至少得有一台服务器,有一台客户端,两者建立连接,然后进行通信。
在早期,我们的服务器都是采用bio的方式进行交流的。
//服务器端代码
public class Server {
protected static final String ip = "127.0.0.1";
protected static final int port = 6010;
private static void start() {
try {
ServerSocket server = new ServerSocket(port);//1
while(true) {
Socket client = server.accept();//2
System.out.println("accept");
new Thread(new ServerHandler(client)).start();//3
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
start();
}
}
//服务器处理逻辑
public class ServerHandler implements Runnable {
private Socket client;
public ServerHandler(Socket client) {
this.client = client;
}
public void run() {
byte[] buffer = new byte[1024];
while (true) {
int read = 0;
try {
read = client.getInputStream().read(buffer);
if (!(read > 0)) break;
System.out.println(new String(buffer,0, read));
client.getOutputStream().write(buffer,0,read);
} catch (IOException e) {
e.printStackTrace();
return;
}
}
}
}
//客户端代码
public class Client {
public static void main(String[] args) {
start();
}
private static void start() {
try {
Socket socket = new Socket();
socket.connect(new InetSocketAddress(Server.ip, Server.port));
Thread.sleep(1000);//模拟客户端阻塞
socket.getOutputStream().write("hello world".getBytes());
byte[] buffer = new byte[1024];
while (socket.getInputStream().read(buffer) > 0) {
System.out.println(new String(buffer));
buffer = new byte[1024];
}
socket.close();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
上面这就是用BIO实现的服务器和客户端通信,可见服务器和客户端之间是能正常通信,处理业务的,人们在实践中发现,大量的连接其实不是一直在传输消息的,而是阻塞状态。但是以BIO的方式来做的话,每一个客户端连接上来都需要创建一个线程来与客户端保持连接。当有大量连接时,服务器需要浪费大量的内存来保持这些连接(分配给线程)。
这个时候出现了NIO技术,即在请求数据的时候,线程不再阻塞了。
如果数据没有准备好,那么当线程请求时会直接收到没有准备好的信号。当然准备好的话,就会收到准备好了的信号。
所以我们就能用一个线程,将全部的连接保存下来,然后挨个去轮询,当找到某个连接数据准备好后就新建线程来处理这个连个连接。
这个时候我们就不需要在新建大量线程去保持连接了,只需要一个或者几个(当连接数太多的时候,可以适当多几个)就可以维持大量的连接,又因为大量的连接其实是未准备好的,所以处理的连接数据的线程数量不会太多。
在此基础上,操作系统提供了select,poll,epoll来处理上面保持连接的问题。当这些连接准备好你注册好的事件的时候,就会返回,你能拿到与之对应的连接,然后通过连接拿到数据进行处理。这就是java的NIO的本质。
//NIO实现的服务器端代码
public class Server {
protected static final String ip = "127.0.0.1";
protected static final int port = 6010;
public static void main(String[] args) {
start();
}
private static void start() {
try {
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.bind(new InetSocketAddress(ip, port));
ssc.configureBlocking(false);
Selector selector = Selector.open();
ssc.register(selector, SelectionKey.OP_ACCEPT);
while(true) {
selector.select(); //阻塞方法 若返回则说明有 感兴趣的事件准备好了
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = keys.iterator();
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
ByteBuffer sendBuffer = ByteBuffer.allocate(1024);
while (keyIterator.hasNext()) {//处理客户端连接
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
ssc = (ServerSocketChannel) key.channel();
SocketChannel clientChannel = ssc.accept();
clientChannel.configureBlocking(false);
clientChannel.register(selector, SelectionKey.OP_READ);
System.out.println("有客户端连接!");
}
if (key.isReadable()) {
SocketChannel socketChannel = (SocketChannel) key.channel();
readBuffer.clear();//清除缓冲区
int numRead;
try{
numRead = socketChannel.read(readBuffer);
if (numRead == -1) {
socketChannel.close();
key.cancel();
continue;
}
}catch (IOException e){
key.cancel();
socketChannel.close();
continue;
}
String msg = new String(readBuffer.array(),0,numRead);
System.out.println("接收到消息" + msg);
socketChannel.register(selector,SelectionKey.OP_WRITE);
}
if (key.isWritable()) {
String msg = "hello";
SocketChannel channel = (SocketChannel) key.channel();
System.out.println("发送消息:" + msg);
sendBuffer.clear();
sendBuffer.put(msg.getBytes());
sendBuffer.flip();//由写变为读,反转
channel.write(sendBuffer);
channel.register(selector,SelectionKey.OP_READ); //注册读操作
}
keyIterator.remove(); //移除当前的key
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//客户端
public class Client {
protected static final String ip = "127.0.0.1";
protected static final int port = 6010;
public static void main(String[] args) {
start();
}
private static void start() {
try {
SocketChannel sc = SocketChannel.open();
sc.connect(new InetSocketAddress(ip, port));//建立连接
ByteBuffer readBuffer = ByteBuffer.allocate(1024);//缓冲区
ByteBuffer sendBuffer = ByteBuffer.allocate(1024);
sendBuffer.put("hello".getBytes());//将要发送的数据写入buffer
sc.write(sendBuffer);//发送消息
int length = sc.read(readBuffer);//读取信息
String s = new String(readBuffer.array(), 0, length);
System.out.println(s);
sc.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端可以用BIO的客户端,也可以NIO的SocketChannel来实现。
运行效果如下
用原生NIO来写代码实在是太过繁琐,而且很容易出现一些不知名的bug(因为知识的漏洞)。所以有人将原生NIO进行的封装,netty这个项目就此诞生了。
用netty来实现服务器和客户端
//服务器端
public class NettyServer {
private static final String IP = "127.0.0.1";
private static final int PORT = 6010;
private static final int BOSSNUM = Runtime.getRuntime().availableProcessors() * 2;
private static final int WKNUM = 100;
private static final EventLoopGroup bossGroup = new NioEventLoopGroup(BOSSNUM);
private static final EventLoopGroup workGroup = new NioEventLoopGroup(WKNUM);
public static void start() throws InterruptedException {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workGroup).channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<Channel>() {
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4));
pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
pipeline.addLast(new TCPChannelHandler());
}
});
ChannelFuture channelFuture = serverBootstrap.bind(IP, PORT).sync();
channelFuture.channel().closeFuture().sync();
System.out.println("start");
}
public static void main(String[] args) throws InterruptedException {
start();
}
}
//服务器端业务处理代码
public class TCPChannelHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("Channel Active");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("收到" + msg);
ctx.channel().writeAndFlush("Hello Clent");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
}
}
//客户端
public class NettyClient implements Runnable{
public void run() {
NioEventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new LengthFieldPrepender(4));
pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
pipeline.addLast(new TCPClient());
}
});
try {
ChannelFuture sync = bootstrap.connect("127.0.0.1", 6010).sync();
sync.channel().writeAndFlush("hello");
Thread.sleep(100);
group.shutdownGracefully();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Thread thread = new Thread(new NettyClient());
thread.start();
}
}
//客户端业务逻辑处理代码
public class TCPClient extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("active");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("receive: " + msg);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
}
}
Netty封装了NIO,降低了NIO实现网络编程的难度,对比代码量就可以看出。而且在代码的可读性上也大大提高了。
不是说NIO一定比BIO好,只是说现在网络环境是连接数量多,传输数据量少(浏览网页)为主,NIO更加适合,假如你的服务器连接人数不多,又是传输数据量比较大的长连接的话,那么BIO肯定比NIO更加适合你。
我说这句话的目的是希望大家不会要盲目推崇NIO,具体问题具体分析。
而Netty是一个基于多路复用io模型的NIO网络编程框架,屏蔽了很多NIO中的陷进,能更加容易的搭建自己的服务器(不一定是http服务器,可以自己实现自己的协议。比如dubbo)。
下一篇正式进入Netty的学习。