Netty網路程式設計詳解
Netty網路程式設計詳解
netty簡介:
Netty是一個非同步的,事件驅動的網路程式設計框架和工具,使用Netty可以快速開發出可維護的,高效能、高擴充套件能力的協議服務及其客戶端應用。
也就是說,Netty是一個基於NIO的客戶,伺服器端程式設計框架,使用Netty可以確保你快速和簡單的開發出一個網路應用,例如實現了某種協議的客戶,服務端應用。Netty相當簡化和流線化了網路應用的程式設計開發過程,例如,TCP和UDP的socket服務開發。
“快速”和“簡單”並不意味著會讓你的最終應用產生維護性或效能上的問題。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