[Netty] Fossil - Practice Netty Server
本文是基于五年前Back-End开发的一些项目实践回忆,本文涉及netty以及相关的配套知识。
当前的Android开发套路,前端已经被web攻占,后端过去的开发经验在当前还保留了一些实用价值。
那还是2012年的第一场雪,时间过的好快呦。
mysql + phpmyadmin
导入已有数据库:
unsw@unsw-UX303UB$ mysql -u root -p #登录 Enter password: Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 44 Server version: 5.5.59-0ubuntu0.14.04.1 (Ubuntu) Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> source /root/android-workplace/netty-server/db-mysql/localhost-testdb.sql #导入数据库
Apache安装了么?
验证apache2.0安装是否完成,
在浏览器中地址栏内输入http://localhost/或者http://127.0.0.1打开。
配置phpmyadmin:
Ref: http://blog.csdn.net/kingszelda/article/details/38794259
安装完成,浏览器访问http://localhost/phpmyadmin并不能打开数据库管理界面,是因为phpmyadmin文件夹不在/var/www/html/文件夹下,这时候用软连接就好。
cd /var/www/html sudo ln -s /usr/share/phpmyadmin
到此为止,数据库就准备好了!
How to make one project on java using netty and protobuf for the communication between client and server on ubuntu 14.04?
netty and protobuf
-
Protobuf
Ref: 在Android中使用Protocol Buffers

Ref: ubuntu下编译protobuf error解决方法。
unsw@unsw-UX303UB$ protoc --version libprotoc 3.5.1
看这个比较好,当前先关心back-end部分。
syntax = "proto2"; // <-- 记得加上,否则有warming
package tutorial; //以一个包声明开始,这用于防止不同项目间的命名冲突 option java_package = "com.example.tutorial"; //指定生成的类应该放在什么Java包名下 option java_outer_classname = "AddressBookProtos"; //定义应该包含这个文件中所有类的类名 message Person { required string name = 1; required int32 id = 2; optional string email = 3; enum PhoneType { MOBILE = 0; HOME = 1; WORK = 2; } message PhoneNumber { required string number = 1; optional PhoneType type = 2 [default = HOME]; } repeated PhoneNumber phone = 4; //想象为动态大小的数组 } message AddressBook { repeated Person person = 1; }
执行命令:
protoc -I=$SRC_DIR --java_out=$DST_DIR $SRC_DIR/addressbook.proto
执行后生成:com/example/tutorial/AddressBookProtos.java
在此不纠结该生成文件的内容;
之后,如何使用这个protobuf格式的java文件?
标准的消息方法
每个消息和builder类还包含大量的其它方法,来让你检查或管理整个消息,包括:
-
- isInitialized() : 检查是否所有的required字段都已经被设置了。
- toString() : 返回一个人类可读的消息表示,对调试特别有用。
- mergeFrom(Message other): (只有builder可用) 将 other 的内容合并到这个消息中,覆写单数的字段,附接重复的。
- clear(): (只有builder可用) 清空所有的元素为空状态。
这些方法实现由所有的Java消息和builders所共享的 Message 和 Message.Builder 接口。更多信息,请参考 Message的完整API文档。
解析和序列化
最后,每个protocol buffer类都有使用protocol buffer 二进制格式写和读你所选择类型的消息的方法。这些方法包括:
-
- byte[] toByteArray();: 序列化消息并返回一个包含它的原始字节的字节数组。
- static Person parseFrom(byte[] data);: 从给定的字节数组解析一个消息。
- void writeTo(OutputStream output);: 序列化消息并将消息写入 OutputStream。
- static Person parseFrom(InputStream input);: 从一个 InputStream 读取并解析消息。
这些只是解析和序列化提供的一些选项。再次,请参考 Message API 参考 来获得完整的列表。
写消息
现在让我们试着使用protocol buffer类。你想要你的地址簿应用能够做的第一件事情是将个人详情写入地址簿文件。要做到这一点,你需要创建并放置你的protocol buffer类的实例,然后将它们写入一个输出流。
这里是一个程序,它从一个文件读取一个AddressBook,基于用户输入给它添加一个新Person,并再次将新的AddressBook写回文件。直接调用或引用由protocol编译器生成的代码的部分都被高亮了。
import com.example.tutorial.AddressBookProtos.AddressBook; import com.example.tutorial.AddressBookProtos.Person; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.InputStreamReader; import java.io.IOException; import java.io.PrintStream; class AddPerson { // This function fills in a Person message based on user input. static Person PromptForAddress(BufferedReader stdin, PrintStream stdout) throws IOException {
Person.Builder person = Person.newBuilder(); stdout.print("Enter person ID: "); person.setId(Integer.valueOf(stdin.readLine())); stdout.print("Enter name: "); person.setName(stdin.readLine()); stdout.print("Enter email address (blank for none): "); String email = stdin.readLine(); if (email.length() > 0) { person.setEmail(email); } while (true) { stdout.print("Enter a phone number (or leave blank to finish): "); String number = stdin.readLine(); if (number.length() == 0) { break; } Person.PhoneNumber.Builder phoneNumber = Person.PhoneNumber.newBuilder().setNumber(number); stdout.print("Is this a mobile, home, or work phone? "); String type = stdin.readLine(); if (type.equals("mobile")) { phoneNumber.setType(Person.PhoneType.MOBILE); } else if (type.equals("home")) { phoneNumber.setType(Person.PhoneType.HOME); } else if (type.equals("work")) { phoneNumber.setType(Person.PhoneType.WORK); } else { stdout.println("Unknown phone type. Using default."); } person.addPhone(phoneNumber); } return person.build(); } // Main function: Reads the entire address book from a file, // adds one person based on user input, then writes it back out to the same // file. public static void main(String[] args) throws Exception { if (args.length != 1) { System.err.println("Usage: AddPerson ADDRESS_BOOK_FILE"); System.exit(-1); } AddressBook.Builder addressBook = AddressBook.newBuilder(); // Read the existing address book. try { addressBook.mergeFrom(new FileInputStream(args[0])); } catch (FileNotFoundException e) { System.out.println(args[0] + ": File not found. Creating a new file."); } // Add an address. addressBook.addPerson(PromptForAddress(new BufferedReader(new InputStreamReader(System.in)), System.out)); // Write the new address book back to disk. FileOutputStream output = new FileOutputStream(args[0]); addressBook.build().writeTo(output); output.close(); } }
读消息
import com.example.tutorial.AddressBookProtos.AddressBook; import com.example.tutorial.AddressBookProtos.Person; import java.io.FileInputStream; import java.io.IOException; import java.io.PrintStream; class ListPeople { // Iterates though all people in the AddressBook and prints info about them. static void Print(AddressBook addressBook) { for (Person person: addressBook.getPersonList()) { System.out.println("Person ID: " + person.getId()); System.out.println(" Name: " + person.getName()); if (person.hasEmail()) { System.out.println(" E-mail address: " + person.getEmail()); } for (Person.PhoneNumber phoneNumber : person.getPhoneList()) { switch (phoneNumber.getType()) { case MOBILE: System.out.print(" Mobile phone #: "); break; case HOME: System.out.print(" Home phone #: "); break; case WORK: System.out.print(" Work phone #: "); break; } System.out.println(phoneNumber.getNumber()); } } } // Main function: Reads the entire address book from a file and prints all // the information inside. public static void main(String[] args) throws Exception { if (args.length != 1) { System.err.println("Usage: ListPeople ADDRESS_BOOK_FILE"); System.exit(-1); } // Read the existing address book. AddressBook addressBook = AddressBook.parseFrom(new FileInputStream(args[0])); Print(addressBook); } }
Netty
以上只是帮助形成认识,再来看Netty是怎么回事?如何将protobuf嵌套入该框架中。
Netty是什么?
1)本质:JBoss做的一个Jar包 // JBoss:是一个基于J2EE的开放源代码的应用服务器。
2)目的:快速开发高性能、高可靠性的网络服务器和客户端程序
3)优点:提供异步的、事件驱动的网络应用程序框架和工具
通俗的说:一个好使的处理Socket的东东
远古: java.net + java.io 近代: java.nio 其他: Mina,Grizzly
Netty只是网络通信框架,把Java Socket的API又封装了一次,使得你可以用最少的代码来完成网络通信这一任务。
RPC(Remote Promote Call) 一种进程间通信方式。允许像调用本地服务一样调用远程服务。

此处只关心对该框架的使用,体验其为何方便。
- Ref: Netty框架浅析--内附Android实现demo,一篇不错的入门文章,了解基本概念。
- 简洁的ppt介绍:http://vdisk.weibo.com/s/vm2qxmlIJWRl
- 博客例子demo:https://www.cnblogs.com/liuming1992/p/4758532.html
Server代码
1) 监听连接;
2) 然后,具体的处理客户端连接的代码。
Client代码
1) 具体的连接代码
2) 连接成功后,具体的通信代码
服务器如何监听?
package com.gerry.netty.server; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; public class EchoServer { private final int port; public EchoServer(int port) { this.port = port; } public void start() throws Exception { EventLoopGroup group = new NioEventLoopGroup(); try { ServerBootstrap sb = new ServerBootstrap(); sb.group(group) // 绑定线程池 .channel(NioServerSocketChannel.class) // 指定使用的channel .localAddress(this.port)// 绑定监听端口 .childHandler(new ChannelInitializer<SocketChannel>() { // 绑定客户端连接时候触发操作 @Override protected void initChannel(SocketChannel ch) throws Exception { System.out.println("connected...; Client:" + ch.remoteAddress()); ch.pipeline().addLast(new EchoServerHandler()); // 触发操作 ----> } }); ChannelFuture cf = sb.bind().sync(); // 服务器异步创建绑定 System.out.println(EchoServer.class + " started and listen on " + cf.channel().localAddress()); cf.channel().closeFuture().sync(); // 关闭服务器通道 } finally { group.shutdownGracefully().sync(); // 释放线程池资源 } } public static void main(String[] args) throws Exception { new EchoServer(65535).start(); // 启动 } }
触发操作 ---->
package com.gerry.netty.server; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; public class EchoServerHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { System.out.println("server channelRead...; received:" + msg); ctx.write(msg); } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { System.out.println("server channelReadComplete.."); // 第一种方法:写一个空的buf,并刷新写出区域。完成后关闭sock channel连接。 ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE); //ctx.flush(); // 第二种方法:在client端关闭channel连接,这样的话,会触发两次channelReadComplete方法。 //ctx.flush().close().sync(); // 第三种方法:改成这种写法也可以,但是这中写法,没有第一种方法的好。 } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { System.out.println("server occur exception:" + cause.getMessage()); cause.printStackTrace(); ctx.close(); // 关闭发生异常的连接 } }
客户端如何监听?
package com.gerry.netty.client; import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import java.net.InetSocketAddress; public class EchoClient { private final String host; private final int port; public EchoClient() { this(0); } public EchoClient(int port) { this("localhost", port); } public EchoClient(String host, int port) { this.host = host; this.port = port; } public void start() throws Exception { EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap b = new Bootstrap(); b.group(group) // 注册线程池 .channel(NioSocketChannel.class) // 使用NioSocketChannel来作为连接用的channel类 .remoteAddress(new InetSocketAddress(this.host, this.port)) // 绑定连接端口和host信息 .handler(new ChannelInitializer<SocketChannel>() { // 绑定连接初始化器 @Override protected void initChannel(SocketChannel ch) throws Exception { System.out.println("connected..."); ch.pipeline().addLast(new EchoClientHandler()); // 触发操作 --> } }); System.out.println("created.."); ChannelFuture cf = b.connect().sync(); // 异步连接服务器 System.out.println("connected..."); // 连接完成 cf.channel().closeFuture().sync(); // 异步等待关闭连接channel System.out.println("closed.."); // 关闭完成 } finally { group.shutdownGracefully().sync(); // 释放线程池资源 } } public static void main(String[] args) throws Exception { new EchoClient("127.0.0.1", 65535).start(); // 连接127.0.0.1/65535,并启动 } }
连接成功后,具体的通信代码 (触发操作)
package com.gerry.netty.client; import java.nio.charset.Charset; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.util.CharsetUtil; public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { System.out.println("client channelActive.."); ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!", CharsetUtil.UTF_8)); // 必须有flush // 必须存在flush // ctx.write(Unpooled.copiedBuffer("Netty rocks!", CharsetUtil.UTF_8)); // ctx.flush(); } @Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { System.out.println("client channelRead.."); ByteBuf buf = msg.readBytes(msg.readableBytes()); System.out.println("Client received:" + ByteBufUtil.hexDump(buf) + "; The value is:" + buf.toString(Charset.forName("utf-8"))); //ctx.channel().close().sync();// client关闭channel连接 } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }
后记:
本文只是大概的带你了解这套方案,具体到细节,还会涉及到许多软件工程的知识,之后再讲。

浙公网安备 33010602011771号