JAVA程序員如何轉(zhuǎn)node_04

前言

這個(gè)系列的文章已經(jīng)拖了好久离例。我一直想著我應(yīng)該寫點(diǎn)什么比較好咏雌。想著想著就覺得算了凡怎,明天吧,可能明天就有新的思路赊抖。我應(yīng)該寫上手一些框架的步驟统倒?這可能比較簡單,剛?cè)腴T上手框架也確實(shí)容易對(duì)這門語言產(chǎn)生自己的印象氛雪。但是現(xiàn)在網(wǎng)站上這種教程沒有嗎房匆?我想,只要瀏覽一下cnode报亩,你能很快找到各種各樣的教程浴鸿。

我想分享的是一種體會(huì),一種從無快速上手的體會(huì)弦追,一種先入為主導(dǎo)致種種問題而產(chǎn)生的體會(huì)岳链,這才是我寫這些東西的初心【⒓可能他不會(huì)很容易懂掸哑,需要你稍微做過一點(diǎn)你才能知道我走過的這些坑是真實(shí)存在的约急。

稍微總結(jié)一下之前寫的文章內(nèi)容。第一篇苗分,我寫的是什么是異步厌蔽,以及在代碼層面上怎么實(shí)現(xiàn)異步。我也說了摔癣,是回調(diào)函數(shù)實(shí)現(xiàn)的異步奴饮。第二篇,我簡單說了一下node的異步處理邏輯择浊,使用的是事件循環(huán)機(jī)制戴卜。此外因?yàn)榛卣{(diào)函數(shù)會(huì)產(chǎn)生多層回調(diào),所以為了去除他近她,說了如何用promise將回調(diào)函數(shù)包裝成async叉瘩。第三篇,我介紹了node中的包管理工具npm的用法以及簡單的使用了一下koa來生成一個(gè)網(wǎng)頁服務(wù)應(yīng)用粘捎。

第四篇說什么呢薇缅,咱們繼續(xù)來說說網(wǎng)頁服務(wù)應(yīng)用,因?yàn)檫@是node后端程序員接觸的最多的一部分攒磨。


如果你寫的是一個(gè)javaweb應(yīng)用泳桦,最原始的就是使用servlet。首先你會(huì)寫一個(gè)servlet繼承httpServlet娩缰,然后在類的下面編寫doGet方法doPost方法等灸撰。寫完了這些你需要在web.xml中編寫urlPartern來將url對(duì)應(yīng)到你的servlet類中。這些弄完了你會(huì)將他打包拼坎,放在apache目錄下浮毯,開啟apache服務(wù)。這里面泰鸡,servlet就是mvc中的controller债蓝,web.xml實(shí)現(xiàn)的就是一個(gè)路由轉(zhuǎn)發(fā)的功能。

我們?cè)賮砜纯磏ode原生怎么實(shí)現(xiàn)一個(gè)網(wǎng)頁服務(wù)盛龄。

1饰迹、在一個(gè)目錄下新建一個(gè)文件app.js,輸入以下代碼

const http = require('http');
http.createServer((req,res) => {
    res.end('hello world');
}).listen(8888);
console.log('server is running on 127.0.0.1:8888');

2、命令行中node app.js 啟動(dòng)應(yīng)用余舶。

這樣瀏覽器訪問127.0.0.1:8888就可以看到hello world啊鸭。就這樣簡單的4行代碼(不算最后一行),就可以實(shí)現(xiàn)很高的qps了(每秒8000次左右)匿值,node默認(rèn)是單線程工作赠制,如果開啟多線程,那么就可達(dá)到一萬多的每秒請(qǐng)求千扔。作為參考憎妙,apache的qps大概在5000次库正,go和node差不多曲楚,Nginx可達(dá)幾萬厘唾。

要知道,網(wǎng)絡(luò)io是io龙誊,硬盤查詢也是io抚垃。對(duì)于網(wǎng)絡(luò)io,大家都是采用輪詢的方式掃描端口趟大,在這一處的io影響是不大的鹤树。我個(gè)人認(rèn)為,系統(tǒng)內(nèi)部的硬盤io才是node對(duì)于io處理的優(yōu)勢之處逊朽。舉個(gè)例子罕伯,同樣發(fā)送8000個(gè)請(qǐng)求,在沒有涉及硬盤存儲(chǔ)叽讳,直接從內(nèi)存獲取數(shù)據(jù)返回的時(shí)候追他,大家比較的就只是網(wǎng)絡(luò)的io。但如果這個(gè)時(shí)候請(qǐng)求涉及到數(shù)據(jù)的存儲(chǔ)岛蚤,這時(shí)候apache這種傳統(tǒng)同步服務(wù)器在單個(gè)請(qǐng)求中會(huì)阻塞到其他的請(qǐng)求邑狸,而如果是node的話就能進(jìn)行異步訪問從而達(dá)到并行處理的效果。

關(guān)于這一點(diǎn)我在第一篇中說過

使用node的話是非阻塞IO涤妒,調(diào)用了IO操作之后不要求數(shù)據(jù)直接就能返回单雾,cpu直接就開始處理下一個(gè)操作,等到了IO操作結(jié)束之后她紫,IO操作會(huì)去通知cpu執(zhí)行接下來的操作硅堆。這就使計(jì)算機(jī)的IO處理速度大大提升。

也就是說贿讹,如果增加了數(shù)據(jù)的存儲(chǔ)操作渐逃,可能node就是會(huì)變慢一點(diǎn)(7000)次左右,而apache會(huì)迅速降到(1000)次左右围详。

我們來看一眼這幾行代碼朴乖,其中最主要的就是這一句。

http.createServer((req,res) => {res.end('hello world'); })

其中(req,res) => {res.end('hello world'); }就是以下的縮寫

function func1(request,response) {
    res.end('hello world');
}

也就是說助赞,將寫一個(gè)帶有兩個(gè)參數(shù)的函數(shù)放入http.createServer()中就能生成一個(gè)服務(wù)器對(duì)象买羞。

http

為了不讓大家太迷糊,我盡量簡單講一下http模塊都做了什么雹食。

你將函數(shù)放入http.createServer()中之后畜普,http會(huì)給你生成一個(gè)服務(wù)器對(duì)象,一直監(jiān)聽著8888這個(gè)端口群叶,當(dāng)他發(fā)現(xiàn)端口有連接事件(connect)的時(shí)候吃挑,他按兵不動(dòng)(不會(huì)觸發(fā)你的那個(gè)函數(shù))钝荡。只有當(dāng)端口收到了一個(gè)有效請(qǐng)求的時(shí)候,這時(shí)候http會(huì)生成一個(gè)request對(duì)象和一個(gè)response對(duì)象舶衬,將這兩個(gè)對(duì)象放入你的函數(shù)之中埠通。你的函數(shù)處理完之后就會(huì)返回給原請(qǐng)求的地址。

有了這個(gè)逛犹,我們就可以在request對(duì)象中獲取我們需要的信息端辱,如get請(qǐng)求中地址欄攜帶的信息、post中body存放的信息虽画、請(qǐng)求地址等等舞蔽。有了這些你就可以實(shí)現(xiàn)一個(gè)網(wǎng)絡(luò)應(yīng)用了。

但是码撰,此時(shí)你的網(wǎng)絡(luò)應(yīng)用寫起來會(huì)零零散散渗柿,看起來像這樣。

const http = require('http');
const url = require('url');
const qs = require('querystring');
http.createServer((req,res) => {
    // console.log(req.url, req.method);
    const method = req.method;
    let { pathname: path, query } = url.parse(req.url);
    // GET /index1 請(qǐng)求
    if(path === '/index1' && method === 'GET') {
        query = qs.parse(query);
        res.end(`處理來自${method} ${path},數(shù)據(jù)為 ${JSON.stringify(query)} 的請(qǐng)求`);
        return ;
    }
    // POST /index2
    if(path === '/index2' && method === 'POST') {
        let data = '';
        req.on('data', (chunk) => {
            data += chunk;  
        });
        req.on('end', () => {
            res.end(`處理來自${method} ${path},數(shù)據(jù)為 ${JSON.stringify(qs.parse(data))}`)
        })
        return;
    }
    res.statusCode = 404;
    res.end('404 NOT FOUND')
}).listen(8888);
console.log('server is running on 127.0.0.1:8888');

理論上脖岛,所有的請(qǐng)求都會(huì)經(jīng)過咱們編寫的“這一層函數(shù)”朵栖。但,難道我們要在這里依次寫無數(shù)個(gè)ifelse來判斷request的各個(gè)參數(shù)鸡岗、路徑混槐,來決定response的各個(gè)響應(yīng)消息嗎?

不可能吧轩性,一個(gè)應(yīng)用中声登,你會(huì)有日志功能,配置功能揣苏,定時(shí)功能悯嗓,路由管理,mvc這些需求卸察。你全寫在一個(gè)文件中脯厨,那真的就是面向過程編程了,還不如直接用c語言去寫坑质。

之前我們說過合武,node中異步函數(shù)的調(diào)用雖然使用了async和await,但他內(nèi)部還是使用回調(diào)函數(shù)涡扼,每當(dāng)有一個(gè)事件出現(xiàn)稼跳,消息一定是隨著函數(shù)作為參數(shù)層層往下,再層層往上吃沪。這是node中一個(gè)特性汤善,我們能不能根據(jù)這個(gè)做點(diǎn)什么呢?

答案已經(jīng)呼之欲出了,利用回調(diào)函數(shù)會(huì)層層往下又層層往上的特點(diǎn)红淡,我們何不讓將邏輯布置成一層一層的不狮,讓請(qǐng)求每走一層就處理一部分邏輯?

koa中就是這樣在旱,我們稱之為洋蔥模型摇零。每一層就是一個(gè)中間件。

中間件

image

洋蔥模型是一個(gè)很不錯(cuò)的組織方式颈渊,他天然就實(shí)現(xiàn)了面向切片編程遂黍。你可以寫一個(gè)中間件终佛,讓某一部分請(qǐng)求通過俊嗽,這樣就不用在每一個(gè)請(qǐng)求中都調(diào)用一次。

當(dāng)然铃彰,我不是說洋蔥模型就是完美的绍豁,有很多地方依舊用起來會(huì)比較別扭,在某些特定的地方你依然會(huì)像以前一樣封裝成工具類這樣調(diào)用牙捉。但由于回調(diào)函數(shù)對(duì)中間件的天然支持竹揍,你能感覺到這種形式的編程還是能給你帶來很多不錯(cuò)的體驗(yàn)。

只是說的話會(huì)有點(diǎn)抽象邪铲,讓我們運(yùn)行一段這樣的代碼芬位。

const Koa = require('koa');
const app = new Koa();
async function middleWare1(ctx,next){
  console.log('----------middleWare1 start------------');
  await next();
  console.log('----------middleWare1 end------------');
}
async function middleWare2(ctx,next){
  console.log('----------middleWare2 start------------');
  await next();
  console.log('----------middleWare2 end------------');
}
async function middleWare3(ctx,next){
  console.log('----------middleWare3 start------------');
  ctx.body = 'hello world';
  console.log('----------middleWare3 end------------');
}
app.use(middleWare1);
app.use(middleWare2);
app.use(middleWare3);
app.listen(8888);
console.log('Server running at http://127.0.0.1:8888/');

訪問瀏覽器會(huì)看到輸出這樣的一段日志。這說明一個(gè)請(qǐng)求進(jìn)來會(huì)進(jìn)入層層的中間件带到,再層層的離開昧碉。

----------middleWare1 start------------
----------middleWare2 start------------
----------middleWare3 start------------
----------middleWare3 end------------
----------middleWare2 end------------
----------middleWare1 end------------

解釋一下,每個(gè)中間件都是一個(gè)函數(shù)揽惹,規(guī)定傳入的參數(shù)是contxt(上下文)和next(回調(diào)函數(shù)被饿,調(diào)用他就可以執(zhí)行下一個(gè)中間件);

應(yīng)用中要添加中間件搪搏,就要通過app.use(中間件方法)傳入狭握,應(yīng)用會(huì)自動(dòng)按照其傳入的順序執(zhí)行。

可以嘗試注釋掉其中的某一個(gè)await next()疯溺,看看結(jié)果是怎么樣的论颅。

在koa中,中間件大多都是單獨(dú)的模塊囱嫩。我們只需要添加到入口文件app.js中恃疯,讓app.use(middleware)添加到應(yīng)用中即可運(yùn)用。

比如最簡單的koa-router管理路由的挠说,我們可以看看他是如何管理我們上面原始的粗糙的http請(qǐng)求分發(fā)澡谭。 這個(gè)例子主要用到3個(gè)文件

// app.js   主要用于將中間件添加到應(yīng)用中
const Koa = require('koa');
const app = new Koa();
const router = require('./router');
var bodyParser = require('koa-bodyparser');

app.use(bodyParser());//加入這個(gè) 才可以解析post請(qǐng)求中參數(shù)
app
  .use(router.routes()) //將路由添加到應(yīng)用
  .use(router.allowedMethods());
app.listen(8888);
console.log('server is running on 127.0.0.1:8888');

// router.js 管理路由  
const Router = require('koa-router');
const index = require('./controller/index')
const  router = new Router();
router.get('/index1',index.index1); //路由一般與controller對(duì)應(yīng)
router.post('/index2',index.index2);
module.exports =router;

//index.js  具體處理邏輯的地方 controller層
async function index1(ctx, next) {
    const {method,path,query} = ctx.request;
    ctx.body = `處理來自${method} ${path},數(shù)據(jù)為 ${JSON.stringify(query)} 的請(qǐng)求`;
}
async function index2(ctx, next) {
    const {method,path,body} = ctx.request;
    ctx.body = `處理來自${method} ${path},數(shù)據(jù)為 ${JSON.stringify(body)} 的請(qǐng)求`;
}
module.exports = {
    index1,
    index2,
}

有了這樣的一個(gè)框架,我們就可以方便的對(duì)代碼進(jìn)行模塊化管理、分層管理蛙奖。

代碼已上傳到Zeeephr/koa-demo 潘酗,如果有需要可以看一看。

后記

這個(gè)系列后面可能就是一起看源碼了雁仲,但是不要慌仔夺,node中看源碼的體驗(yàn)非常好。node編程中有的時(shí)候甚至不用去查api或者百度哪里報(bào)錯(cuò)攒砖,直接在node_modules文件夾中點(diǎn)開就能看缸兔,最夸張的就是他還可以在引入的包中打斷點(diǎn),這樣你就能清晰地知道你的數(shù)據(jù)是如何走向的吹艇。

好了這一part就先講到這吧惰蜜,覺得有用的話可以點(diǎn)點(diǎn)贊,留下你的評(píng)論受神!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末抛猖,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子鼻听,更是在濱河造成了極大的恐慌财著,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,607評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件撑碴,死亡現(xiàn)場離奇詭異撑教,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)醉拓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門伟姐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人廉嚼,你說我怎么就攤上這事玫镐。” “怎么了怠噪?”我有些...
    開封第一講書人閱讀 164,960評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵恐似,是天一觀的道長。 經(jīng)常有香客問我傍念,道長矫夷,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,750評(píng)論 1 294
  • 正文 為了忘掉前任憋槐,我火速辦了婚禮双藕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘阳仔。我一直安慰自己忧陪,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著嘶摊,像睡著了一般延蟹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上叶堆,一...
    開封第一講書人閱讀 51,604評(píng)論 1 305
  • 那天阱飘,我揣著相機(jī)與錄音,去河邊找鬼虱颗。 笑死沥匈,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的忘渔。 我是一名探鬼主播高帖,決...
    沈念sama閱讀 40,347評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼辨萍!你這毒婦竟也來了棋恼?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,253評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤锈玉,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后义起,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拉背,經(jīng)...
    沈念sama閱讀 45,702評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評(píng)論 3 336
  • 正文 我和宋清朗相戀三年默终,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了椅棺。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,015評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡齐蔽,死狀恐怖两疚,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情含滴,我是刑警寧澤诱渤,帶...
    沈念sama閱讀 35,734評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站谈况,受9級(jí)特大地震影響勺美,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜碑韵,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評(píng)論 3 330
  • 文/蒙蒙 一赡茸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧祝闻,春花似錦占卧、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽舷蒲。三九已至,卻和暖如春友多,著一層夾襖步出監(jiān)牢的瞬間牲平,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評(píng)論 1 270
  • 我被黑心中介騙來泰國打工域滥, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留纵柿,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,216評(píng)論 3 371
  • 正文 我出身青樓启绰,卻偏偏與公主長得像昂儒,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子委可,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 背景 先介紹一下背景吧渊跋,筆者在大學(xué)期間主要學(xué)的是JAVA,但學(xué)的也不是很深着倾,就會(huì)基本的課設(shè)程度的網(wǎng)站編寫水平拾酝。找工...
    zeeephr閱讀 396評(píng)論 0 0
  • 前言 Koa 是運(yùn)行在 Node.js 中的 web 服務(wù)框架,小而美卡者。 Koa2 是 Koa 框架的最新版本蒿囤,K...
    let_Scott閱讀 5,775評(píng)論 2 28
  • 參考資料 https://chenshenhai.github.io/koa2-note/note/static/...
    JunChow520閱讀 10,486評(píng)論 1 8
  • Koa 學(xué)習(xí) 歷史 Express Express是第一代最流行的web框架,它對(duì)Node.js的http進(jìn)行了封...
    Junting閱讀 2,825評(píng)論 0 0
  • 1.簡書 koa是由Express原班人馬打造崇决,致力于成為一個(gè)更小材诽、更富有表現(xiàn)力、更健壯的Web框架恒傻。使用koa編...
    不去解釋閱讀 2,666評(píng)論 0 11