TCP服務
- 傳輸控制協議,網絡傳輸層
-
OSI模型
: 物理層 數據鏈路層 網絡層 傳輸層 會話層 表示層 應用層
傳輸之前先要三次握手形成會話,一個套接字socket只用于一個服務
創建TCP服務
var net = require('net');
//使用net.createServer(listener)創建,**listener是連接事件connection的偵聽器**
var server = net.createServer(function(socket) {
socket.on('data', function(data) {
socket.write("hello");
});
socket.on('end', function() {
console.log('斷開');
});
socket.write("hello111:\n");
});
server.listen(8124, function() {
console.log('server bound');
});
//或者
var server = net.createServer();
server.on('connection', function(socket) { //新的連接
});
server.listen(8124);
//可以使用$ telnet 127.0.0.1 8124,或者使用net模塊構建客戶端
var net = require('net');
var client = net.connect({ //
port: 8124
}, function() { //'connect' listener
console.log('client connected');
client.write('world!\r\n');
});
client.on('data', function(data) {
console.log(data.toString());
client.end();
});
client.on('end', function() {
console.log('client disconnected');
});
也可以對Domain Socket監聽 server.listen('/tmp/echo.sock');
使用 $ nc -U /tmp/echo.sock 測試
服務器事件
使用net.createServer()創建的服務器是個EventEmitter實例
- listening 在調用server.lisren()綁定端口或者Domain Socket后觸發server.listen(port,listeningListener)
- connection 每個客戶端套接字連接到服務端時觸發 net.createServer(),最后一個參數傳遞
- close 當服務器關閉時觸發 在調用server.close()后,服務器會停止接受新的套接字,但當前的連接不會斷,直到所有連接都斷后觸發
- error 服務器發生異常時觸發,如果不幀聽error事件,會拋出異常
連接事件
服務器可以同時與多個客戶端保持連接,每個連接都是可讀寫的stream對象,用于服務器和客戶端通信
- data 當一端調用write()發送數據時,另一端觸發data事件
- end 任意一端發送了FIN數據時觸發
- connect 用于客戶端,當套接字與服務端連接成功時觸發
- error 發生異常
- close 套接字完全關閉時觸發
- timeout 連接在一定時間內部活躍,觸發
//使用pipe管道 管道提供了一個輸出流到輸入流的機制。通常我們用于從一個流中獲取數據并將數據傳遞到另外一個流中。
var net = require('net');
var server = net.createServer(function(socket) {
socket.write('Echo server\r\n');
socket.pipe(socket);//
});
server.listen(1337, '127.0.0.1');
Nagle算法
:將tcp中的小數據包緩存合并到一定數量或時間后發出,避免浪費網絡資源,但數據可能被延遲。tcp默認啟動Nagle算法,可以調用socket.setNoDelay(true)關閉,關閉后一端調用write(),另一端可能將多個小數據包合并后觸發一次data
UDP服務
- 用戶數據包協議,網絡傳輸層
- 一個套接字可以和多個UDP服務通信,無須連接,資源消耗低,處理快速靈活
- 可能丟包,應用在低丟包不影響的的場景,視頻、DNS
創建UDP套接字
UDP套接字可以做客戶端也可做服務端
- 作為服務端 調用dgram.bind(port,[address])方法對網卡和端口綁定
var dgram = require('dgram');
var server = dgram.createSocket('udp4');
server.on("message", function(msg, rinfo) {
console.log("server got: " + msg + " from " +
rinfo.address + ":" + rinfo.port);
});
server.on("listening", function() {
var address = server.address();
console.log("server listening " + address.address + ":" + address.port);
});
server.bind(41234);//綁定完成后觸發listening事件
- 作為客戶端:使用dgram.send(buf, offset, length, port, address, [callback])發送消息到網絡;參數對應含義,buffer,buffer偏移,buffer長度,目標端口,目標地址,發送完成后的回調。它可以隨意發送數據到網絡,tcp需要重新通過套接字構建新連接
var dgram = require('dgram');
var message = new Buffer("Node.js");
var client = dgram.createSocket("udp4");
client.send(message, 0, message.length, 41234, "localhost", function(err, bytes) {
client.close();
});
UPD套接字事件
- UDP是個EventEmitter實例使用更簡單,TCP是個Stream實例
- message UDP套接字幀聽網卡端口后,接受數據時觸發,觸發攜帶的數據為Buffer對象和遠程地址
- listening UDP開始幀聽時觸發
- close 調用close()時觸發,并不再觸發message事件
- error 異常時觸發,不幀聽則直接拋出,進程退出
構建HTTP服務
- 超文本傳輸協議 HyperText Transfer Protocol
- 構建在TCP之上 屬于應用層協議 B/S模式,目前最知名的標準RFC 2616
通常的http通信的信息分三部分,TCP的3次握手,客戶端向服務器發送請求報文,服務器完成處理后向客戶端發送響應內容。
瀏覽器其實是http的代理,將用戶的行為轉化為http請求發送給服務端,服務端處理請求然后發送響應報文給代理,代理解析報文再展示給用戶。http服務只做處理http請求和發送http響應
http模塊
- 繼承自TCP服務(net模塊)
- 能夠與多個客戶端保持連接,因為采用事件驅動,不用給每個連接創建額外的線程、進程,內存占用低,能高并發
- tcp以connection為單位服務,http以request為單位服務
http模塊將tcp連接的讀操作封裝為ServerRequest對象,報文頭部使用http_parser解析然后放在req.headers上傳遞給業務邏輯。報文體部分為只讀流對象,需要在數據流結束后轉字符串
function(req, res) {
// console.log(req.headers);
var buffers = [];
req.on('data', function(trunk) {
buffers.push(trunk);
}).on('end', function() {
var buffer = Buffer.concat(buffers); // TODO
res.end('Hello world');
});
}
//express中使用bodyParse,會自動解析,但要求請求頭添加Content-Type=application/json
封裝對底層連接的寫操作為ServerResponse對象
影響響應報文頭api為res.setHeader()和res.writeHead(),可以調用res.setHeader()進行多次設置,只要調用writeHead后,報頭才會寫入連接
res.writeHead(200, {'Content-Type': 'text/plain'});
設置報文體API為write(),end();end()會先調用write()發送數據,再發信號告知響應結束。
一旦開始了數據發送,writeHead()和setHeader()將不再生效
結束時要調用res.end(),否則客戶端將一直處于等待狀態,無論異常與否
http服務端事件
http服務器是個EventEmitter實例
- connection 客戶端與服務器建立底層的tcp連接時觸發。連接開啟keep-alive,可以在多次請求響應之間使用
- request 當請求數據發送到服務端,在解析出http請求頭后觸發該事件
- close 調用server.close()方法停止接受新連接,當已有的連接也都斷開后觸發,可以給server.close()傳個回調來快速注冊該事件
- checkContinue 某些客戶端在發送大數據時,會先發一個頭部帶Expect: 100-continue的請求,服務器接收后觸發checkContinue。如果不監聽,就自動回復100 Continue。如果不接收就響應400 Bad Request。注意,在客戶端收到100 Continue后重新發起請求才會觸發request
- connect 客戶端發起connect請求時觸發,通常在http代理時才會發起connect,如果不監聽,發起該請求的連接會關閉
- upgrade 客戶端要升級連接協議時會在請求頭部帶上upgrade,服務端接收后觸發,如果不監聽,發起該請求的連接會關閉
- clientError 連接的客戶端觸發error事件,會傳遞到服務器端并觸發
http客戶端
- http.request(options, connect) 構建http客戶端
var options = {
hostname: '127.0.0.1',
port: 1334,
path: '/',
method: 'GET',
localAddress //建立連接的本地網卡
socketPath //Domain套接字路徑
headers //請求頭對象
auth //Basic認證 將被計算成請求頭的Authorization部分
};
// 用request 包請求 不用自己來處理on end
var req = http.request(options, function(res) {
console.log('STATUS: ' + res.statusCode);
console.log('HEADERS: ' + JSON.stringify(res.headers));
res.setEncoding('utf8');
res.on('data', function(chunk) {
console.log(chunk);
});
});
req.end();
報文體的內容通過請求對象的write()寫入,end()告知報文結束
客戶端clientRequest(req)解析完響應頭就觸發response,并傳遞ClientResponse以供操作,之后報文體以只讀流提供)
http代理
- 為了重用tcp連接(在keeplive時,一個tcp連接可以多次用于請求)
- http里包含一個客戶端代理對象http.globalAgent,它管理每個服務端(host+port)創建的連接并創建一個連接池,默認每個服務器端5個連接
在通過ClientRequest調用http請求時會走代理。可以在options中傳遞agentX修改連接限制
var agent = new http.Agent({
maxSockets: 10
});
var options = {
hostname: '127.0.0.1',
port: 1334,
path: '/',
method: 'GET',
agent: agent //設置為false就不受連接池限制
};
http客戶端事件
- response 客戶端得到響應后觸發
- socket 當連接池中建立的連接分配給當前的對象時觸發
- upgrade 客戶端向服務端發起upgrade 服務端響應101 Switching Protocols時觸發
- continue 客戶端想發大數據,頭部帶Expect: 100-continue,服務端同意并響應100 continue時觸發