完全是零基礎(chǔ)開始的后端開發(fā)吨掌,所以該系列筆記(或者稱為教程也可以)是完全假設(shè)沒有任何預(yù)備知識的,但是前提是:1. Java基礎(chǔ);2. maven配置知識(gradle);
環(huán)境配置
本項(xiàng)目基于maven進(jìn)行學(xué)習(xí)啟動(dòng)析砸,我們需要用到的工具和依賴:
- maven安裝與配置(詳見上一篇)
- eclipe(IDE for Java web)
- JDK 1.7 +
- netty latest (netty download)
- telnet(linux/unix 默認(rèn)可用,windows需配置)
配置十分容易爆袍,就是將相關(guān)的jar包放置到lib目錄中首繁,同時(shí)在pom.xml中添加依賴即可。
在pom.xml中添加依賴如下:
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>5.0.0.Alpha2</version>
</dependency>
項(xiàng)目說明
此次教程旨在通過netty搭建一個(gè)本地服務(wù)器陨囊,采用telnet連接的方式實(shí)現(xiàn)向服務(wù)端口發(fā)送消息弦疮,驗(yàn)證客戶端和服務(wù)器的連通性,在服務(wù)端實(shí)時(shí)打印出客戶端發(fā)送過來的信息蜘醋,借此對netty的用法有一個(gè)初步的理解胁塞。
同時(shí)為了進(jìn)階netty,還有以下資料參考:
先扔幾個(gè)參考學(xué)習(xí)的網(wǎng)頁:
netty 官方API: netty link
netty 中文指南:netty 中文
netty 優(yōu)秀博客netty 入門
關(guān)于NIO基礎(chǔ)的知識:
https://my.oschina.net/andylucc/blog/614295
http://www.cnblogs.com/dolphin0520/p/3919162.html
http://blog.csdn.net/wuxianglong/article/details/6604817
服務(wù)代碼構(gòu)建
服務(wù)器的作用是根據(jù)客戶端發(fā)送過來的信息進(jìn)行解析后執(zhí)行對應(yīng)的事務(wù)處理策略压语,姑且我們將這個(gè)稱之為“事務(wù)策略”闲先。
不同的事務(wù)策略是為了滿足客戶端各種請求(比如:數(shù)據(jù)庫增刪改查、文件上傳下載等)无蜂,而通過定義協(xié)議,通過特定的請求字段及參數(shù)等蒙谓,發(fā)送到服務(wù)器進(jìn)行特定事務(wù)的執(zhí)行并返回結(jié)果斥季。
在本例中,則通過一個(gè)簡單的事務(wù)策略實(shí)例-打印客戶端消息。
創(chuàng)建打印客戶端消息的事務(wù)策略
package geekfish.NettyServer;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCountUtil;
public class SimpleServerTestHandler extends ChannelHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// get the msg from clients
try {
ByteBuf in = (ByteBuf) msg;
System.out.println("the msg from client is :" + in.toString(CharsetUtil.UTF_8) + " from "
+ ctx.hashCode());
} finally {
// 我們必須要對引用計(jì)數(shù)的對象進(jìn)行手動(dòng)釋放
ReferenceCountUtil.release(msg);
}
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
// TODO Auto-generated method stub
super.channelReadComplete(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
// TODO Auto-generated method stub
super.exceptionCaught(ctx, cause);
}
}
如我們之前所說酣倾,當(dāng)客戶端發(fā)送特定的請求時(shí)舵揭,我們在服務(wù)器端接收到客戶端報(bào)文后并進(jìn)行解析從而確定選用哪一個(gè)事務(wù)策略,在此我們引入兩個(gè)問題:
- 如何制定請求類型和在服務(wù)器端解析
在此躁锡,我們簡化了流程午绳,只要客戶端與服務(wù)器建立了連接,那么服務(wù)器就會(huì)將客戶端發(fā)送過來的信息無腦地打印出來映之,并不涉及多業(yè)務(wù)類型的解析拦焚;
- 誰來負(fù)責(zé)接收并處理客戶端信息
因此,僅有一個(gè)事務(wù)策略肯定是不夠的杠输,需要有主體去執(zhí)行才行赎败,可以想象這個(gè)主體主要負(fù)責(zé)的工作:接收所有客戶端信息、解析客戶端信息蠢甲、執(zhí)行客戶端請求的任務(wù)僵刮、返回執(zhí)行結(jié)果給客戶端。所以我們還有一個(gè)主體需要實(shí)現(xiàn)鹦牛,這里我們姑且將之稱為“服務(wù)端主體”搞糕。
創(chuàng)建服務(wù)端主體
package geekfish.NettyServer;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
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 MainServer {
private int port;
public MainServer(int port) {
this.port = port;
};
public void run() throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
System.out.println("ready to run port =>" + port);
try {
// 我們需要?jiǎng)?chuàng)建一個(gè)ServerBootstrap啟動(dòng)NIO服務(wù)
ServerBootstrap bootstrap = new ServerBootstrap();
// 我們需要設(shè)置boss Group 和 worker Group
bootstrap = bootstrap.group(bossGroup, workerGroup);
// 設(shè)置channel
bootstrap = bootstrap.channel(NioServerSocketChannel.class);
// 我們通過增加pipeline的方式給channel增加事務(wù)處理監(jiān)聽
bootstrap = bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// 我們可以在此處增加各種事務(wù)處理的監(jiān)聽,比如xxxServerHandler
ch.pipeline().addLast(new SimpleServerTestHandler());
// you can still add other handlers here
}
});
// 我們可以在此處進(jìn)行參數(shù)的設(shè)置
bootstrap = bootstrap.option(ChannelOption.SO_BACKLOG, 128);
// 設(shè)置子連接的參數(shù)
bootstrap = bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
// 綁定端口并啟動(dòng)接收客戶端信息
ChannelFuture channelFuture = bootstrap.bind(port).sync();
// 一直等待循環(huán)接收信息直到socket關(guān)閉
channelFuture.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
//the main entrance to run this instance
public final static void main(String[] args) throws Exception{
int port = 8080;
if(args.length > 0)
port = Integer.parseInt(args[0]);
new MainServer(port).run();
System.out.println("the server start listening...");
}
}
代碼驗(yàn)證測試
到此為止曼追,簡化版的項(xiàng)目已經(jīng)基本完成窍仰,現(xiàn)在既可以通過talnet連接進(jìn)行簡單會(huì)話測試。
- 啟動(dòng)運(yùn)行 服務(wù)端主體拉鹃;
run MainServer,當(dāng)然此處你可以帶上端口號參數(shù)辈赋,如果懶到啥都不干,那就是8080了膏燕,記住這個(gè)端口號钥屈,因?yàn)榭蛻舳诵枰c這個(gè)端口創(chuàng)建連接;
- 在terminal中創(chuàng)建連接坝辫;
telnet localhost 8080
當(dāng)然這里的8080需要替換為你當(dāng)前啟動(dòng)的端口號篷就,當(dāng)連接創(chuàng)建之后,則可以在terminal會(huì)話窗口內(nèi)向服務(wù)端發(fā)送消息啦近忙,此時(shí)打開eclipse的控制臺就可以看到每一條從客戶端發(fā)過來的信息竭业。
如果使用的是windows,注意windows下默認(rèn)未開啟telnet連接及舍,所以直接在cmd中輸入以上指令可能無法生效未辆,需要去到'控制面板>>程序>>打開或者關(guān)閉windows功能'里面勾選上'telnet客戶端'。
寫在最后的話
樓主是計(jì)算機(jī)視覺這塊的锯玛,所以比較偏重于應(yīng)用層咐柜,對于前后端的開發(fā)基本算是零起步兼蜈,但是通過接觸前后端開發(fā),最深的感觸就是各種各樣并且飛速迭代的框架拙友,開發(fā)者入門時(shí)可能會(huì)遇到各種選擇問題为狸,但是從樓主的經(jīng)歷來看,框架是封裝好的工具遗契,切不可不求甚解地強(qiáng)記框架API辐棒,而是要大致理解后端框架共性的業(yè)務(wù)流程及模塊劃分。
由于走過彎路牍蜂,沒有使用任何框架上來就是繼承Servlet去實(shí)現(xiàn)漾根,雖然最后堆了一坨代碼(包括自定義json封裝解析類、數(shù)據(jù)庫操作類捷兰、文件操作類等),所以即使實(shí)現(xiàn)了簡單的后臺功能立叛,但是對于高并發(fā)、數(shù)據(jù)庫高性能開發(fā)等均一無所知贡茅。然而與此同時(shí)秘蛇,因?yàn)镈IY過一個(gè)這樣簡易的結(jié)構(gòu),麻雀雖小顶考,但是也具備基本必要模塊赁还,所以在此基礎(chǔ)上,再去看各大流行框架驹沿,就會(huì)禁不住思考各個(gè)模塊對應(yīng)的實(shí)現(xiàn)細(xì)節(jié)艘策,這一點(diǎn)對上手新框架以及對現(xiàn)有框架的理解都是挺有幫助的。
最后渊季,希望這個(gè)教程能夠堅(jiān)持更新和創(chuàng)造一定的價(jià)值朋蔫。