Netty网络编程详解
netty简介:
Netty是一个异步的,事件驱动的网络编程框架和工具,使用Netty可以快速开发出可维护的,高性能、高扩展能力的协议服务及其客户端应用。
也就是说,Netty是一个基于NIO的客户,服务器端编程框架,使用Netty可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户,服务端应用。Netty相当简化和流线化了网络应用的编程开发过程,例如,TCP和UDP的socket服务开发。
“快速”和“简单”并不意味着会让你的最终应用产生维护性或性能上的问题。Netty是一个吸收了多种协议的实现经验,这些协议包括FTP,SMPT,HTTP,各种二进制,文本协议,并经过相当精心设计的项目,最终,Netty成功的找到了一种方式,在保证易于开发的同时还保证了其应用的性能,稳定性和伸缩性。
一些用户可能找到了某些同样声称具有这些特性的编程框架,因此你们可能想问Netty又有什么不一样的地方。这个问题的答案是Netty项目的设计哲学。从创立之初,无论是在API还是在其实现上Netty都致力于为你提供最为舒适的使用体验。虽然这并不是显而易见的,但你终将会认识到这种设计哲学将令你在阅读本指南和使用Netty时变得更加得轻松和容易。
我的第一个netty程序HelloWorld
服务端代码包含一下类
NettyServer 服务端入口
MyChannelInitializer 继承ChannelInitializer<SocketChannel>通道初始化程序
MyServerHandler继承ChannelHandlerAdapter服务处理程netty5默认回调函数channelRead
SecureChatSslContextFactory ssl加密工厂类加密文件通过jdk的keytool生成(稍后介绍)
NettyServer:
package org.netty.server;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class NettyServer {
Log log = LogFactory.getLog(NettyServer.class);
private int port;
public static void main(String[] args) throws Exception {
int port = 8000;
new NettyServer(port).run();
}
public NettyServer(int port) {
this.port = port;
}
public void run() throws Exception {
log.info("netty Server 开始启动服务");
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new MyChannelInitializer());
Channel ch = b.bind(port).sync().channel();
log.info("netty Server 服务启动成功 端口:"+port);
ch.closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
MyChannelInitializer:
package org.netty.server;
import javax.net.ssl.SSLEngine;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpContentCompressor;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.WriteTimeoutHandler;
public class MyChannelInitializer extends ChannelInitializer<SocketChannel> {
Log log = LogFactory.getLog(MyChannelInitializer.class);
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
if (HttpServerConfig.getConfig().getSsl()) {
log.info("启动ssl访问通过https方式");
SSLEngine engine = SecureChatSslContextFactory.getServerContext().createSSLEngine();
engine.setNeedClientAuth(false);
engine.setUseClientMode(false);
engine.setWantClientAuth(false);
engine.setEnabledProtocols(new String[] { "SSLv2Hello", "SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2" });
pipeline.addLast("ssl", new SslHandler(engine));
}
// 40秒没有数据读入,发生超时机制
pipeline.addLast(new ReadTimeoutHandler(40));
// 40秒没有输入写入,发生超时机制
pipeline.addLast(new WriteTimeoutHandler(40));
/**
* http-request解码器 http服务器端对request解码
*/
pipeline.addLast("decoder", new HttpRequestDecoder());
/**
* http-response解码器 http服务器端对response编码
*/
pipeline.addLast("encoder", new HttpResponseEncoder());
/**
* HttpObjectAggregator会把多个消息转换为一个单一的FullHttpRequest或是FullHttpResponse。
*/
// 1048576
// 1024*1024*64
pipeline.addLast("aggregator", new HttpObjectAggregator(1024 * 1024 * 8));
/**
*压缩 Compresses an HttpMessage and an HttpContent in gzip or deflate
* encoding while respecting the "Accept-Encoding" header. If there is
* no matching encoding, no compression is done.
*/
pipeline.addLast("deflater", new HttpContentCompressor());
//回调处理类MyServerHandler
pipeline.addLast("handler", new MyServerHandler());
}
}
MyServerHandler:
package org.netty.server;
import java.net.URI;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.handler.codec.http.multipart.Attribute;
import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory;
import io.netty.handler.codec.http.multipart.HttpDataFactory;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.EndOfDataDecoderException;
import io.netty.handler.codec.http.multipart.InterfaceHttpData;
import io.netty.handler.codec.http.multipart.InterfaceHttpData.HttpDataType;
import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCountUtil;
public class MyServerHandler extends ChannelHandlerAdapter {
Log log = LogFactory.getLog(MyServerHandler.class);
private String buf = "";
private StringBuilder requestbuffer = new StringBuilder();
private Map<String, String> origin_params;
Gson gson = new Gson();
private HttpPostRequestDecoder decoder;
private static final HttpDataFactory factory = new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE); // Disk
@Override
public void channelReadComplete(ChannelHandlerContext ctx)
{
if (decoder != null)
{
decoder.cleanFiles();
}
ctx.flush();
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
log.info("请求处理开始");
buf = "";
HttpRequest request = (HttpRequest) msg;
boolean isSSl=HttpServerConfig.getConfig().getSsl();
String type="http";
if(isSSl){
type="https";
}
try {
URI uri = new URI(request.uri());
log.info("请求uri:"+uri.getPath());
//每次请求都会有这个请求的,直接return就可以了
if (uri.getPath().equals("/favicon.ico"))
{
ctx.close();
return;
}
System.out.println(uri.getPath());
if (request.method().equals(HttpMethod.GET)) {
log.info("get请求!");
origin_params = getHttpGetParams(request);
if(origin_params!=null){
log.info("请求参数:"+origin_params);
buf="你好:"+origin_params.get("name")+",你发起了"+type+"的get请求";
}
} else if (request.method().equals(HttpMethod.POST)) {
log.info("Post请求!");
Map<String, String> url_params = getHttpGetParams(request);
if (msg instanceof HttpContent) {
HttpContent httpContent = (HttpContent) msg;
ByteBuf content = httpContent.content();
String params = content.toString(CharsetUtil.UTF_8);
requestbuffer.append(params);
origin_params = getJsonParams(params);
if (origin_params != null) {
log.info("请求参数:"+origin_params);
buf = "你好:" + origin_params.get("name") + ",你发起了" + type + "的post请求";
}
if (origin_params == null) {
origin_params = getHttpPostParams(request);
System.out.println(origin_params);
}
} else {
origin_params = getHttpPostParams(request);
}
if (origin_params != null && url_params != null) {
origin_params.putAll(url_params);
}
}
writeResponse(request, ctx);
} catch (Exception e) {
e.printStackTrace();
} finally {
ReferenceCountUtil.release(msg);
}
}
private Map<String, String> getHttpGetParams(HttpRequest request) {
return getQueryParams(request.uri());
}
private Map<String, String> getQueryParams(String params) {
QueryStringDecoder queryStringDecoder = new QueryStringDecoder(params);
Map<String, String> paramsMap = new HashMap<String, String>();
for (Entry<String, List<String>> p : queryStringDecoder.parameters().entrySet()) {
String key = p.getKey().trim();
List<String> vals = p.getValue();
if (vals.size() > 0) {
String value = vals.get(0);
requestbuffer.append(key + ":" + value + "\n");
paramsMap.put(key, value);
}
}
return paramsMap;
}
private Map<String, String> getJsonParams(String params) {
try {
return gson.fromJson(params, new TypeToken<Map<String, String>>() {
}.getType());
} catch (Exception e) {
return null;
}
}
private Map<String, String> getHttpPostParams(HttpRequest request) throws Exception {
Map<String, String> origin_params = new HashMap<String, String>();
boolean decodeflag = false;
decoder = new HttpPostRequestDecoder(factory, request);
try {
while (decoder.hasNext()) {
InterfaceHttpData interfaceHttpData = decoder.next();
if (interfaceHttpData != null) {
try {
/**
* HttpDataType有三种类型 Attribute, FileUpload,
* InternalAttribute
*/
if (interfaceHttpData.getHttpDataType() == HttpDataType.Attribute) {
Attribute attribute = (Attribute) interfaceHttpData;
String value = attribute.getValue();
String key = attribute.getName();
requestbuffer.append(key + ":" + value + "\n");
origin_params.put(key, value);
}
} catch (Exception e) {
System.out.println("请求参数异常!" + e.getMessage());
} finally {
interfaceHttpData.release();
}
}
}
} catch (EndOfDataDecoderException e1) {
decodeflag = true;
} catch (Exception e) {
System.out.println("解释HTTP POST协议出错:" + e.getMessage());
throw e;
}
if (decodeflag)
return origin_params;
return null;
}
/**
*将结果返回给用户
*
* @param currentObj
* @param ctx
* @return
*/
private void writeResponse(HttpObject currentObj, ChannelHandlerContext ctx) {
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
currentObj.decoderResult().isSuccess() ? HttpResponseStatus.OK : HttpResponseStatus.BAD_REQUEST,
Unpooled.copiedBuffer(buf.toString(), CharsetUtil.UTF_8));
ChannelFuture future = ctx.writeAndFlush(response);
log.info("请求响应处理成功");
future.addListener(ChannelFutureListener.CLOSE);
//requestbuffer.append("\n---------------服务器主动关闭远程链接.---------------------");
}
}
SecureChatSslContextFactory:
package org.netty.server;
import java.io.InputStream;
import java.security.KeyStore;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
public final class SecureChatSslContextFactory {
private static final String PROTOCOL = "SSL";
private static final SSLContext SERVER_CONTEXT;
/**
*证书文件生成方式
*
* keytool -genkey -keysize 2048 -validity 365 -keyalg RSA -keypass 123456 -storepass 123456 -keystore F:lichengdu.jks
* keytool为JDK提供的生成证书工具
* -keysize 2048密钥长度2048位(这个长度的密钥目前可认为无法被暴力破解)
* -validity 365证书有效期365天
* -keyalg RSA使用RSA非对称加密算法
* -keypass 123456密钥的访问密码为123456
* -storepass 123456密钥库的访问密码为123456(通常都设置一样,方便记)
* -keystore F:lichengdu.jks指定生成的密钥库文件为F:lichengdu.jks
*完了之后就拿到了F:lichengdu.jks
*/
private static String SERVER_KEY_STORE = "lichengdu.jks";
//证书密码storepass
private static String SERVER_KEY_STORE_PASSWORD = "123456";
static {
SSLContext serverContext;
try {
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
//获得生成的jks签名证书
InputStream ksInputStream = SecureChatSslContextFactory.class.getClassLoader()
.getResourceAsStream(SERVER_KEY_STORE);
ks.load(ksInputStream, SERVER_KEY_STORE_PASSWORD.toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, SERVER_KEY_STORE_PASSWORD.toCharArray());
serverContext = SSLContext.getInstance(PROTOCOL);
serverContext.init(kmf.getKeyManagers(), null, null);
} catch (Exception e) {
throw new Error("Failed to initialize the server-side SSLContext", e);
}
SERVER_CONTEXT = serverContext;
}
public static SSLContext getServerContext() {
return SERVER_CONTEXT;
}
private SecureChatSslContextFactory() {
// Unused
}
}
好了到此一个http和https的服务端就已经全部写完了其中还有一些配置文件的读取这里就不再赘述具体代码可以到我的csdn下载
启动 NettyServer类中的main方法通过浏览器访问测试 成功访问
写一个客户
HttpConnection 模拟浏览器访问服务类
HttpConnectionTest Junit4测试类
具体代码:
HttpConnection:
package org.netty.client;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLConnection;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
public class HttpConnection {
private static class TrustAnyTrustManager implements X509TrustManager
{
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException
{
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException
{
}
public X509Certificate[] getAcceptedIssuers()
{
return new X509Certificate[] {};
}
}
private static class TrustAnyHostnameVerifier implements HostnameVerifier
{
public boolean verify(String hostname, SSLSession session)
{
return true;
}
}
/**
*向指定URL发送POST方法的请求(Https 模式)
* @param url发送请求的URL
* @param content发送的数据
* @param charset字符集
* @return所代表远程资源的响应结果
* @throws NoSuchAlgorithmException
* @throws KeyManagementException
* @throws IOException
*/
public static String doSslPost(String url, String content, String charset) throws NoSuchAlgorithmException, KeyManagementException, IOException
{
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new java.security.SecureRandom());
URL console = new URL(url);
HttpsURLConnection conn = (HttpsURLConnection) console.openConnection();
conn.setSSLSocketFactory(sc.getSocketFactory());
conn.setHostnameVerifier(new TrustAnyHostnameVerifier());
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setRequestMethod("POST");
conn.setConnectTimeout(30000);
conn.setReadTimeout(30000);
conn.connect();
DataOutputStream out = new DataOutputStream(conn.getOutputStream());
out.write(content.getBytes(charset));
// flush输出流的缓冲
out.flush();
out.close();
InputStream is = conn.getInputStream();
if (is != null)
{
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = is.read(buffer)) != -1)
{
outStream.write(buffer, 0, len);
}
is.close();
return new String(outStream.toByteArray(), "utf-8");
}
return null;
}
/**
*向指定URL发送GET方法的请求(Https 模式)
* @param url发送请求的URL 包含请求参数,请求参数应该是 name1=value1&name2=value2的形式。
* @param charset字符集
* @return所代表远程资源的响应结果
* @throws NoSuchAlgorithmException
* @throws KeyManagementException
* @throws IOException
*/
public static String doSslGet(String url, String charset) throws NoSuchAlgorithmException, KeyManagementException, IOException
{
InputStream is = null;
try
{
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new java.security.SecureRandom());
URL console = new URL(url);
HttpsURLConnection conn = (HttpsURLConnection) console.openConnection();
conn.setSSLSocketFactory(sc.getSocketFactory());
conn.setHostnameVerifier(new TrustAnyHostnameVerifier());
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setRequestMethod("GET");
conn.setConnectTimeout(30000);
conn.setReadTimeout(30000);
conn.connect();
is = conn.getInputStream();
if (is != null)
{
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = is.read(buffer)) != -1)
{
outStream.write(buffer, 0, len);
}
try
{
return new String(outStream.toByteArray(), "utf-8");
}
catch (Exception e)
{
throw new RuntimeException("发送GET请求出现异常.");
}
finally
{
is.close();
is = null;
outStream.close();
outStream = null;
}
}
}
catch (Exception e)
{
throw new RuntimeException("发送GET请求出现异常.");
}
finally
{
if (is != null)
is.close();
}
return null;
}
/**
*向指定URL发送GET方法的请求
* @param url发送请求的URL
* @param param请求参数,请求参数应该是 name1=value1&name2=value2的形式。
* @return URL所代表远程资源的响应结果
*/
public static String doGet(String url, String param)
{
String result = "";
BufferedReader in = null;
try
{
String urlNameString = url + "?" + param;
URL realUrl = new URL(urlNameString);
//打开和URL之间的连接
URLConnection connection = realUrl.openConnection();
//设置通用的请求属性
connection.setRequestProperty("accept", "*/*");
connection.setRequestProperty("connection", "Keep-Alive");
connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
connection.setConnectTimeout(30000);
connection.setReadTimeout(30000);
//建立实际的连接
connection.connect();
//定义 BufferedReader输入流来读取URL的响应
in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
while ((line = in.readLine()) != null)
{
result += line;
}
}
catch (Exception e)
{
throw new RuntimeException("发送GET请求出现异常.");
}
//使用finally块来关闭输入流
finally
{
try
{
if (in != null)
in.close();
}
catch (Exception e2)
{
}
}
return result;
}
/**
*向指定 URL 发送POST方法的请求
* @param url发送请求的 URL
* @param param请求参数,请求参数应该是 name1=value1&name2=value2的形式。
* @return所代表远程资源的响应结果
*/
public static String doPost(String url, String param)
{
PrintWriter out = null;
BufferedReader in = null;
String result = "";
try
{
URL realUrl = new URL(url);
//打开和URL之间的连接
URLConnection conn = realUrl.openConnection();
//设置通用的请求属性
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
//发送POST请求必须设置如下两行
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setConnectTimeout(30000);
conn.setReadTimeout(30000);
conn.connect();
//获取URLConnection对象对应的输出流
out = new PrintWriter(conn.getOutputStream());
//发送请求参数
out.print(param);
// flush输出流的缓冲
out.flush();
//定义BufferedReader输入流来读取URL的响应
in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
while ((line = in.readLine()) != null)
{
result += line;
}
}
catch (Exception e)
{
throw new RuntimeException("发送POST请求出现异常.");
}
//使用finally块来关闭输出流、输入流
finally
{
try
{
if (out != null)
out.close();
if (in != null)
in.close();
}
catch (IOException ex)
{
}
}
return result;
}
}
HttpConnectionTest:
package org.netty.client;
import org.junit.Ignore;
import org.junit.Test;
public class HttpConnectionTest {
private final String TEST_URL="127.0.0.1:8000";
private final String TEST_JSON="{\"name\":\"lichengdu\"}";
private final String TEST_CHARSET="UTF-8";
@Test
@Ignore
public void doSslPostTest(){
try {
String reulst=HttpConnection.doSslPost("https://"+TEST_URL, TEST_JSON, TEST_CHARSET);
System.out.println(reulst);
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
@Ignore
public void doSslGetTest(){
try {
String reulst=HttpConnection.doSslGet("https://"+TEST_URL+"?name=lichengdu", TEST_CHARSET);
System.out.println(reulst);
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
@Ignore
public void doGetTest(){
try {
String reulst=HttpConnection.doGet("http://"+TEST_URL, "name=lichengdu");
System.out.println(reulst);
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
public void doPostTest(){
try {
String reulst=HttpConnection.doPost("http://"+TEST_URL, TEST_JSON);
System.out.println(reulst);
} catch (Exception e) {
e.printStackTrace();
}
}
}
至此客户端代码编写完毕
测试成功!
修改配置文件让服务端用SSL方式启动
测试成功!
JUnit4代码测试
全部测试完成本!
源码下载地址:http://download.csdn.net/download/qq_29582193/9897609
开发过程常见问题
待补充........