首先确保本地环境已经安装了 node 环境
建立项目文件夹,我的是 node,以下操作都是在 node 文件夹下面操作的
然后安装 express 框架
npm install --save express@4.15.2
安装 socket.io 模块
npm install --save socket.io
我的 demo 也是安装手册上来的,
注意要点
在 vue 中的 created(){}
里面的变量,你在 data
里初始值是什么,这个变量的值就永远是什么,这个个坑跟 vue 的生命周期有关。所有在做一对一聊天,房间聊天的处理中,监听聊天通道的时候需要注意聊天对象的ID号和房间号的选择,其实你可以把这个监听放在选择聊天对象或者房间的点击事件中
- index.js
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var config = require('config'); // 学习模块的引入
app.get('/', function (req, res) {
// res.send('<h1>Hello World</h1>');
res.sendFile(__dirname + '/index.html');
});
// 监听连接处理
io.on('connection', socket => {
// socket.on('chat message', function (msg) {
// console.log('message:' + msg);
// });
//
socket.on('disconnect', function () {
console.log('user disconnected');
})
// 连接后,监听 chat message 通道
socket.on('chat message', msg => {
// 这个 msg 是熊 index.html 中发送过来的 io().emit('chat message', $('#m').val());
// io.emit 是往某个通道发送信息,第一个参数是通道,第二个参数是信息 这个与 socket.on 监听的通道可以不一致
io.emit('send message', msg);
});
})
http.listen(config['socket_port'], function () {
console.log('listening on *:3000');
})
- inde.html
<!doctype html>
<html>
<head>
<title>Socket.IO chat</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font: 13px Helvetica, Arial;
}
form {
background: #000;
padding: 3px;
position: fixed;
bottom: 0;
width: 100%;
}
form input {
border: 0;
padding: 10px;
width: 90%;
margin-right: .5%;
}
form button {
width: 9%;
background: rgb(130, 224, 255);
border: none;
padding: 10px;
}
#messages {
list-style-type: none;
margin: 0;
padding: 0;
}
#messages li {
padding: 5px 10px;
}
#messages li:nth-child(odd) {
background: #eee;
}
</style>
<!--<script src="/socket.io/socket.io.js"></script>-->
<script src="/socket.io/socket.io.js"></script>
<script src="https://code.jquery.com/jquery-1.11.1.js"></script>
<script>
$(function () {
var socket = io();
$('form').submit(function (e) {
e.preventDefault(); // prevents page reloading
// 向某个通道发送信息
socket.emit('chat message', $('#m').val());
$('#m').val('');
return false;
});
// 监听 chat message 通道
socket.on('send message', msg => {
$('#messages').append($('<li>').text(msg));
// 上下滑动屏幕
window.scrollTo(0, document.body.scrollHeight);
});
});
</script>
</head>
<body>
<ul id="messages"></ul>
<form action="">
<input id="m" autocomplete="off"/>
<button>Send</button>
</form>
</body>
</html>
- configs.js
module.exports = {
'socket_port': '3000',
}
程序讲解
首先你需要对 nodejs 语法要有基本的了解,如果不了解,我建议看 菜鸟手册
然后我们要知道,nodejs 是基于事件驱动的语法,也就是有事件,才会有相应的触发。
事件可以理解为事件就是客户点击了某个url,触发就是我们对某个路由的控制器做的相应处理
此处的触发,就比如 io.on('connection',function(socket){})
, 此处的 on()
就相当于是监听 connection
是固定的参数,监听到有客户端连接的意思,
on
的第二个参数就是连接后做的处理,相当于是控制器
程序执行流程讲解
在 index.js
中首 io模块
先监听了 connection连接
的处理,连接后监听了 'chat message'消息通道
在 index.html
中,往 chat message
通道发送信息 socket.emit('chat message', $('#m').val());
这个时候 index.js
中的 socket.on('chat message', msg => {})
监听到有消息发送过来,这里的 msg
就是 index.html 中发送的消息。
收到消息后,我们需要把消息发送到某个通道,此处的通道名字我用的是 sen message
,io.emit('send message', msg);
,我把这个消息发送到 send message 通道
我们在 index.html 前端页面监听了 send message 通道 socket.on('send message',msg=>{})
,然后对监听的数据做处理即可
以上情况,我们是针对群聊处理的。也就是每个人都是监听的
send message
通道,发送信息都是发送到了chat message
通道,如果需要私聊,或者指定房间,需要更改这两个参数即可
一对一聊天
- index.html
<body>
<h1>请选择聊天的对象</h1>
<select name="username" id="choose">
<option value="">null</option>
<option value="test1">test1</option>
<option value="test2">test2</option>
<option value="test3">test3</option>
<option value="test4">test4</option>
<option value="test5">test5</option>
</select>
<button id="b_click">ok</button>
<ul id="messages"></ul>
<form action="">
<input id="m" autocomplete="off"/>
<button>Send</button>
</form>
<script>
var to = '';
$('#b_click').click(function () {
to = $('#choose').val();
});
var username = prompt('请输入你的名字', '');
// 聊天对象
if (to == '') {
to = username;
}
$(function () {
var socket = io();
$('form').submit(function (e) {
e.preventDefault(); // prevents page reloading
// 向某个通道发送信息
var data = $('#m').val();
var msg = {username: username, to: to, data: data};
socket.emit('chat message', msg);
$('#m').val('');
return false;
});
// 监听 chat message 通道
socket.on(to, msg => {
// 如果 发送者和接收者都是自己,表示还没有开启对话,将对话对象更细昵称 msg.username
if (username == to) {
to = msg.username;
}
$('#messages').append($('<li>').text(msg.data));
// 上下滑动屏幕
window.scrollTo(0, document.body.scrollHeight);
});
});
</script>
</body>
- index.js
同上面的 index.js 此处只展示改变代码的部分
...
// 监听连接处理
io.on('connection', socket => {
// 连接后,监听 chat message 通道
socket.on('chat message', msg => {
io.emit(msg.to, msg);
});
})
...
-
思路分析
我们在 index.html 页面中监听的信息通道都是to 参数的值,这个通道在 demo 中,其实就是test
在long
主动跟test
聊天,在 long 的页面中username=long to =test
在 test 页面中username 和 to 的值都是 test
所以两人都是监听的 test 通道,都可以接收到信息 -
不适用 express 框架也可以的
var fs = require('fs');
var app = require('http').createServer(function (req, res) {
fs.readFile(__dirname + '/index.html', function (err, data) {
if (err) {
res.writeHead(500);
return res.end('Error loading index.html');
}
res.writeHead(200);
res.end(data);
})
})
io = require('socket.io')(app);
// 监听连接处理
io.on('connection', socket => {
socket.on('disconnect', function () {
console.log('user disconnected');
})
// 连接后,监听 chat message 通道
socket.on('chat message', msg => {
io.emit(msg.to, msg);
// 如果发送人和接收人都是同一个人,则不需要重复发送给自己
if (msg.to != msg.username) {
io.emit(msg.username, msg);
}
});
})
app.listen(3000, function () {
console.log('listening on *:3000');
})
某人上线下线的提醒
- index.html
var username = prompt('请输入你的名字', '');
var socket = io();
socket.emit('coming', username); // 在输入名字之后,将新用户推送到 coming 通道
- index.js
// 监听连接处理
io.on('connection', socket => {
// 连接后,监听 chat message 通道
socket.on('coming', msg => {
socket.name = msg; // 这个通道有消息过来的时候,将他赋值给 socket 的一个属性
,因为 disconnect 与这个不在这个作用域,所以要放在 socket 的属性上
console.log(msg + '上线了');
});
socket.on('disconnect', function () {
// 这里可以写自己的逻辑业务,比如通知某个房间的人
console.log(socket.name + '离开了');
})
})
vue 实现一对一聊天
首先后端的服务还是使用上面的 index.js
去处理
在前端
- vue/index.html 中引入 socket.io.js 文件
<div id="app"></div>
<script src="http://localhost:3000/socket.io/socket.io.js"></script>
<script type="text/javascript">
const socket = io.connect('http://localhost:3000');
</script>
先引入后端的 socket.io.js 文件,就可以获取 io 这个对象。我们预定义 socket 变量方便程序中使用
- vue/src/router/index.js 定义聊天路由
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
import Chat from '@/components/Chat'
import ChatList from '@/components/ChatList'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'HelloWorld',
component: HelloWorld
},{
path: '/chat',
name: 'Chat',
component: Chat
},{
path: '/chatlist',
name: 'ChatList',
component: ChatList
}
]
})
- vue/src/components/ChatList.vue 选择聊天对象列表,这步不重要
<template>
<!-- 用户列表 -->
<div>
<h2>聊天对象用户列表</h2>
<ul>
<li v-for="(username,index) in usernameList">
<!--<router-link to="/chat">-->
<button @click="$router.push({path:'/chat',query: {username,index}})">{{ username }}</button>
<!--</router-link>-->
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
usernameList: [
'test1',
'test2',
'test3',
'test4',
'test5',
],
}
}
}
</script>
- vue/src/components/Chat.vue 聊天页面
<template>
<div>
<ul id="messages">
<li v-for="msg in msgList" :class="[msg.username === username ?'right':'left']">{{
msg.username+':'+msg.content }}
</li>
</ul>
<form action="">
<input id="m" autocomplete="off" v-model="content">
<button @click="sendMessage">{{ sendInfo }}</button>
</form>
</div>
</template>
<script>
export default {
data() {
return {
content: '',
sendInfo: "发送",
msgList: [],
username: '',
chatTo: '',
left: false,
right: false,
}
},
methods: {
sendMessage: function (e) {
e.preventDefault();
var msg = {username: this.username, chatTo: this.chatTo, content: this.content}
socket.emit('one to one message', msg);
this.content = '';
}
},
created() {
this.username = prompt('请输入你的名字');
var query = this.$route.query;
if (Object.keys(query).length === 0) {
this.chatTo = this.username;
// alert('请选择聊天对象');
} else {
this.chatTo = query.username;
}
// 监听
socket.on(this.chatTo, msg => {
this.msgList.push({
username: msg.username,
chatTo: msg.chatTo,
content: msg.content,
});
// 上下滑动屏幕
window.scrollTo(0, document.body.scrollHeight);
});
},
}
</script>
<style>
form {
background: #000;
padding: 3px;
position: fixed;
bottom: 0;
width: 100%;
}
form input {
border: 0;
padding: 10px;
width: 70%;
margin-right: .5%;
}
form button {
width: 20%;
background: rgb(130, 224, 255);
border: none;
padding: 10px;
}
#messages {
list-style-type: none;
}
#messages li {
padding: 5px 10px;
width: 50%;
}
#messages li:nth-child(odd) {
background: #eee;
}
.left {
float: left;
}
.right {
float: right;
}
</style>
image.png
多对多聊天(房间聊天,群聊)
- 后端 index.js
....
// 监听连接处理
io.on('connection', socket => {
// 私聊通道监听
socket.on('one to one message', msg => {
// 发送给私聊对象
io.emit(msg.chatTo, msg);
});
// f房间聊天室监听
socket.on('chat room message', msg => {
// 发送给某个房间
io.emit('chat room receive' + msg.roomNum, msg)
});
})
....
- ChatRoom.vue
<template>
<div>
<ChooseRoom v-bind:roomList="roomList" v-if="!roomNum" v-on:chooseRoom="chooseRoom"></ChooseRoom>
<h1>进入聊天室</h1>
<ul id="messages">
<li v-for="msg in msgList" :class="[msg.username === username ?'right':'left']">{{
msg.username+':'+msg.content }}
</li>
</ul>
<form action="">
<input id="m" autocomplete="off" v-model="content">
<button @click="sendMessage">{{ sendInfo }}</button>
</form>
</div>
</template>
<script>
import ChooseRoom from './ChooseRoom'
export default {
data() {
return {
content: '',
sendInfo: "发送",
msgList: [],
username: '',
roomNum: '',
left: false,
right: false,
roomList: [
{id: '1', name: '1号房间'},
{id: '2', name: '2号房间'},
{id: '3', name: '3号房间'},
{id: '4', name: '4号房间'},
{id: '5', name: '5号房间'},
],
}
},
components: {
ChooseRoom
},
methods: {
// 发送信息
sendMessage: function (e) {
e.preventDefault();
var msg = {username: this.username, roomNum: this.roomNum, content: this.content}
socket.emit('chat room message', msg);
this.content = '';
},
chooseRoom: function (id) {
this.roomNum = id;
socket.on('chat room receive'+this.roomNum, msg => {
this.msgList.push({
username: msg.username,
content: msg.content,
});
// 上下滑动屏幕
window.scrollTo(0, document.body.scrollHeight);
});
}
},
created() {
this.username = prompt('请输入你的名字');
},
}
</script>
- ChooseRoom.vue
<template>
<div>
<h1>选择房间</h1>
<ul>
<li v-for="room in roomList">
<button @click="$emit('chooseRoom',room.id)">{{ room.name }}</button>
</li>
</ul>
</div>
</template>
<script>
export default {
props: ['roomList'],
}
</script>
在一个小图表上显示有多少条未读数据的做法是,直接监听相关的端口,j每次有消息通知的时候将一个变量加1即可
data() {
return {
msgCount: 0
}
},
created() {
// socket.ChatList = [];
// 一对一聊天
socket.on(this.username, msg => {
this.msgCount++;
// socket.ChatList.push({
// username: msg.username,
// chatTo: msg.chatTo,
// content: msg.content,
// });
});
}
如果想点击小图标后显示聊天的内容,可以在上面的处理中将接受的数据赋值给 socket 的一个自定义变量,这样的话,跨页面也是可以获取到这个变量的
一对一聊天,显示聊天对象列表和内容
-
思路,聊天用户发送信息的时候,在后端使用 redis 的 rpush 保存数据,并将聊天人使用 sadd 存入 set ,其实也可以使用 hash 的 hincryby 每次将聊天记录加1 ,但是我看 nodejs 的 redis 没有这个操作指令。
对了,用户一对一的通道名子,我根据两人的名字比较,大的值在前面。js 语法也是可以直接比较字符串的,这样就可以统一通道规则 -
前端首页 index.vue
<template>
<div class="hello">
<!--<button @click="toChat($router,username,chatTo)">-->
<!--<h1>未查看信息条数{{ msgCount }}</h1>-->
<!--</button>-->
<h1>聊天列表</h1>
<ul>
<li v-for="(chatName,index) in chatList" :key="index">
<button @click="toChat($router,username,chatName)">
<h1>{{ chatName }}</h1>
</button>
</li>
</ul>
</div>
</template>
<script>
import originJsonp from '../jsonp';
export default {
name: 'HelloWorld',
data() {
return {
msgCount: 0,
username: '',
chatTo: '',
chatList: [], // 聊天人列表
}
},
methods: {
toChat: function ($router, username, chatTo) {
let chatSocket = username > chatTo ? username + '-' + chatTo : chatTo + '-' + username;
// 也可以 let arr = [username,chatTo];
// let chatSocket = arr.sort().join('_');
$router.push({path: '/chat', query: {username, chatTo, chatSocket}})
}
},
created() {
var vm = this;
if (this.username === '') {
this.username = this.chatTo = prompt('请输入你的名字');
}
// 登陆后,获取此用户通道的未读信息
originJsonp({
"method": 'get chat list',
"username": vm.username,
}).then(function (response) {
console.log(response);
vm.chatList = response
})
// 一对一聊天
socket.on(this.username, msg => {
this.msgCount++;
});
}
}
</script>
- 前端聊天页面 chat.vue
<template>
<div>
<h1>私聊</h1>
<ul id="messages">
<li v-for="msg in msgList" :class="[msg.username === username ?'right':'left']">
{{ msg.username+':'+msg.content }}
</li>
</ul>
<form action="">
<input id="m" autocomplete="off" v-model="content">
<button @click="sendMessage">{{ sendInfo }}</button>
</form>
</div>
</template>
<script>
import originJsonp from '../jsonp';
export default {
props: ['chatList'],
data() {
return {
content: '',
sendInfo: "发送",
msgList: [],
username: '',
chatTo: '',
left: false,
right: false,
chatSocket: '', // 两人的聊天通道
}
},
methods: {
sendMessage: function (e) {
e.preventDefault();
var msg = {
username: this.username,
chatTo: this.chatTo,
content: this.content,
chatSocket: this.chatSocket
}
socket.emit('one to one message', msg);
this.content = '';
}
},
created() {
var vm = this;
var query = this.$route.query;
if (Object.keys(query).length === 0) {
this.username = prompt('请输入你的名字');
// 如果直接进来的 用户和聊天对象都是自己
this.chatTo = this.username;
} else {
// 否则是从 chatList 页面进来的
this.chatTo = query.chatTo;
if (this.username === '' && !query.username) {
// 从 ChatList 页面进来的参数有 ?chatTo=test1&index=0
this.username = prompt('请输入你的名字');
} else {
// 从首页进来的参数有 ?username=test1&chatTo=test1
this.username = query.username;
// todo ajax 请求redis 接口 赋值给 this.msgList
this.chatSocket = vm.username > vm.chatTo ? vm.username + '-' + vm.chatTo : vm.chatTo + '-' + vm.username;
}
}
if (!this.chatSocket) {
// 两人的聊天通道 从大到校排序
this.chatSocket = vm.username > vm.chatTo ? vm.username + '-' + vm.chatTo : vm.chatTo + '-' + vm.username;
}
originJsonp({
"method": "one to one message",
"username": vm.username,
"chatSocket": this.chatSocket,
})
.then(function (response) {
response.forEach(function (value, index) {
// 原来存的是json 字符串 需要转换成对象
response[index] = JSON.parse(value);
})
vm.msgList = response
})
.catch(function (error) {
console.log(error);
});
// 一对一聊天
socket.on(this.chatSocket, msg => {
this.msgList.push({
username: msg.username,
chatTo: msg.chatTo,
content: msg.content,
});
// 上下滑动屏幕
window.scrollTo(0, document.body.scrollHeight);
});
},
}
</script>
<style>
form {
background: #000;
padding: 3px;
position: fixed;
bottom: 0;
width: 100%;
}
form input {
border: 0;
padding: 10px;
width: 70%;
margin-right: .5%;
}
form button {
width: 20%;
background: rgb(130, 224, 255);
border: none;
padding: 10px;
}
#messages {
list-style-type: none;
}
#messages li {
padding: 5px 10px;
width: 50%;
}
#messages li:nth-child(odd) {
background: #eee;
}
.left {
float: left;
}
.right {
float: right;
}
</style>
- 后端 index.js
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var redis = require('redis');
var client = redis.createClient(6379, '127.0.0.1');
const ONE_TO_ONE = 'one to one message'; // 一对一聊天
const ROOM_CHAT = 'chat room receive'; // 房间聊天
const SET_CHAT_LIST = 'chat list'; // 聊天对象列表
app.get('/', function (req, res) {
res.sendFile(__dirname + '/index.html');
});
// 监听连接处理
io.on('connection', socket => {
// 私聊通道监听
socket.on(ONE_TO_ONE, msg => {
console.log(msg.chatSocket);
// 发送给私聊对象
io.emit(msg.chatSocket, msg);
// 将聊天语句存入 redis
client.rpush(ONE_TO_ONE + msg.chatSocket, JSON.stringify(msg), function (err, data) {
console.log(data)
})
// 将聊天人发送给聊天对象的 set 集合中
client.sadd(SET_CHAT_LIST + msg.chatTo, msg.username, function (err, data) {
console.log(data)
});
});
// f房间聊天室监听
socket.on('chat room message', msg => {
// 发送给某个房间
io.emit(ROOM_CHAT + msg.roomNum, msg)
// 将聊天语句存入 redis
client.rpush(ROOM_CHAT + msg.roomNum, JSON.stringify(msg), function (err, data) {
console.log(data)
})
});
})
http.listen(3000, function () {
console.log('listening on *:3000');
})
- 后端PHP 接口 redis.php
<?php
header('Access-Control-Allow-Origin:*'); // 单个域名处理
define('SET_CHAT_LIST', 'chat list'); // 聊天列表
//连接本地的 Redis 服务
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
//查看服务是否运行
if (!isset($_GET['method'])) {
echo $_GET['callback'] . "(请传入请求参数)";
}
$username = isset($_GET['username']) ? $_GET['username'] : ''; // 用户名
$method = isset($_GET['method']) ? $_GET['method'] : ''; // 请求方法
$chatSocket = isset($_GET['chatSocket']) ? $_GET['chatSocket'] : ''; // 请求方法
$chatTo = isset($_GET['chatTo']) ? $_GET['chatTo'] : ''; // 请求方法
$arr = [];
switch ($method) {
case 'one to one message': // 一对一聊天
$arr = $redis->lRange($method . $chatSocket, 0, -1);
break;
case 'set chat list': //
$chatTo = $_GET['chatTo'];
$arr = $redis->hIncrBy($method . $username, $chatTo, HASH_INC_BY);
break;
case 'get chat list': // 获取聊天列表
$arr = $redis->sMembers(SET_CHAT_LIST . $username);
break;
}
$result = json_encode($arr);
$callback = $_GET['callback'];
echo $callback . "($result)";
-
我这里设计的是只要发送信息,node 后端就直接使用 redis 的 sadd 存入集合,但是这样可能没必要,因为只需要存入一次就可以了,改善方式,可以是在进入本聊天页面的时候,直接请求一个 php 接口,将聊天用户存入 sadd 。但是这样就是还没有聊天就存入了聊天对象,好像也不合适
image.png
image.png
image.png
网友评论