在學(xué)習(xí)node的時(shí)候都會(huì)練習(xí)做一個(gè)聊天室的項(xiàng)目,主要使用socket.io模塊和http模塊坯屿。這里我們使用更加原始的方式去寫一個(gè)在命令行聊天的聊天室。
http模塊巍扛,socket.io都是高度封裝之后的模塊领跛,我們使用更加原始的net模塊來(lái)做。
socket
做聊天室撤奸,我們首先要了解一下socket吠昭,用百度百科上的定義:網(wǎng)絡(luò)上的兩個(gè)程序通過(guò)一個(gè)雙向的通信連接實(shí)現(xiàn)數(shù)據(jù)的交換,這個(gè)連接的一端稱為一個(gè)socket胧瓜。socket對(duì)TCP/IP封裝提供網(wǎng)絡(luò)開(kāi)發(fā)的接口矢棚,提供網(wǎng)絡(luò)通信的接口。詳細(xì)可以看看各種百科的資料府喳。
服務(wù)端
1.基本接口介紹
創(chuàng)建服務(wù)端
const server = net.createServer((socket) => {
//這里的毀掉函數(shù)中的參數(shù)就是一個(gè)socket
}
//監(jiān)聽(tīng)端口和主機(jī)
server.listen({
port:4433,
host:'127.0.0.1'//要想別人訪問(wèn)到蒲肋,要寫服務(wù)啟動(dòng)所在機(jī)子的ip地址,默認(rèn)localhost
}, () => {
//這里是啟動(dòng)成功后的回調(diào)
console.log('server bound')
})
服務(wù)創(chuàng)建成功之后钝满,socket可以提供客戶端(訪問(wèn)者)的一些信息兜粘,這里主要用的兩個(gè)屬性
socket.remoteAddress :客戶端的ip地址
socket.remotePort:客戶端訪問(wèn)的接口,這里是隨機(jī)分配弯蚜,注意這里的接口跟我們創(chuàng)建服務(wù)的接口不同孔轴,表示的意義也不一樣建立網(wǎng)絡(luò)通信連接至少要一對(duì)端口號(hào),服務(wù)端監(jiān)聽(tīng)的端口是為了跟服務(wù)端所在機(jī)子的其他服務(wù)區(qū)分碎捺,客戶端訪問(wèn)這個(gè)端口的時(shí)候自然也要區(qū)分路鹰,分配不同端口
socket的event事件
//監(jiān)聽(tīng)客戶端發(fā)送過(guò)來(lái)的消息
socket.on('data', (data) => {
//這里data是一個(gè)buffer要轉(zhuǎn)化為字符串贷洲,然后去一下兩端空格
let receive = JSON.parse(data.toString().trim());
})
//給客戶端發(fā)送消息
socket.write(string[,encoding]);
錯(cuò)誤處理error
socket.on('error', (err) => {
//在測(cè)試中發(fā)現(xiàn)晋柱,不去監(jiān)聽(tīng)這個(gè)錯(cuò)誤事件优构,當(dāng)你客戶端掉線之后服務(wù)端也會(huì)斷開(kāi)所以做一些錯(cuò)誤處理
})
2.聊天設(shè)計(jì)
聊天過(guò)程我們分三部分
1.登錄
2.跟所有人聊
3.跟特定對(duì)象聊
首先我們確定聊天信息的發(fā)送格式,就像http里面有頭部趣斤,內(nèi)容等俩块。我們發(fā)送的信息,應(yīng)該包括發(fā)送者名字浓领,給誰(shuí)發(fā)以及發(fā)送的消息玉凯,設(shè)定為一下格式
const send = {
name:"",
message:"",
to:""
}
服務(wù)端要根據(jù)給誰(shuí)發(fā)做不同的處理
1.登錄(login):我們要給所有的人發(fā)信息,XXX登錄了聊天室联贩,還要記錄這個(gè)訪問(wèn)的socket漫仆,以便于后續(xù)給特定的人發(fā)消息,格式{username:socket}
2.跟所有人聊天(server):除了發(fā)消息本人要給其他人發(fā)送
3.跟某一個(gè)人聊天(client):找到相應(yīng)的socket發(fā)送消息
socket.on('data', (data) => {
let receive = JSON.parse(data.toString().trim());
switch(receive.to){
case "login":
DealInfo.login(receive, socket);
break;
case "server":
DealInfo.server(receive, socket);
break;
default:
DealInfo.client(receive, socket);
break;
}
})
之前我們有監(jiān)聽(tīng)socket的錯(cuò)誤處理泪幌,也就是當(dāng)用戶掉線的時(shí)候盲厌,我們就可以把記錄的socket刪除掉,并提示當(dāng)前用戶數(shù)祸泪。這里可以從記錄的socket個(gè)數(shù)求取吗浩,也可以根據(jù)以下方法
server.getConnections((err, count) => {
if(err){
throw err;
}
console.log(`${deleteKey}下線了 當(dāng)前在線人數(shù)${count}`);
})
客戶端
1.基本接口介紹
創(chuàng)建socket連接
const client = net.connect({
port:4433,
host:'127.0.0.1'//默認(rèn)localhost
}, () => {
//連接成功之后的回調(diào)
})
client的事件
//監(jiān)聽(tīng)服務(wù)端發(fā)送過(guò)來(lái)的信息
client.on('data', (data) => {
//這里data是一個(gè)buffer
let receive = JSON.parse(data.toString().trim());
}).on('error', (err) => {
//錯(cuò)誤處理
})
//給服務(wù)端發(fā)送消息
client.wirte(string[, encoding]);
2.聊天設(shè)計(jì)
基本設(shè)置
登錄:要輸入聊天名再去連接客戶端
群聊或者跟某一個(gè)人聊只需要區(qū)分用戶即可,跟某一個(gè)人聊的輸入格式為name:message
聊天格式:name>message
2.1用戶名
這里我們使用readline這個(gè)模塊
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question('What is your name >', (name) => {
//回調(diào)中的參數(shù)就所輸入的信息没隘,當(dāng)輸入非空的時(shí)候作為用戶名懂扼,之后按我們的聊天格式輸出預(yù)先樣式
//設(shè)立設(shè)定prompt的輸出內(nèi)容,只要調(diào)用rl.prompt()即可
rl.setPrompt(`${name.trim()}>`);
rl.prompt();
}
監(jiān)聽(tīng)用戶輸入
rl.on('line', (line) => {
let stdinInfo = line.trim().split(':');
if(stdinInfo.length == 2){
//跟某個(gè)用戶聊天
send.to = stdinInfo[0];
send.message = stdinInfo[1];
}else{
//跟所有人聊天
send.to = "server";
send.message = stdinInfo[0];
}
client.write(JSON.stringify(send));
rl.prompt();
})
詳細(xì)代碼地址:https://github.com/Stevenzwzhai/node-socket-chatroom