== Scenarios used in the project ==
Account scan code login, wechat scan code authorization, message real-time reminder, configuration results response, client data synchronization… In the previous project, I used round robin to do instant communication, which was inefficient and wasted resources. Later several projects began to use Websocket with KOA and Redis to achieve it. Now I have a thorough understanding of the whole instant communication implementation process.
Prerequisite: Packages to be installed
- Koa == was created by the same people behind Express and is much more elegant and clean to use than Node
- Navigation of koa – the router = = is routing, can be thought of in the actual application node interface www.npmjs.com/package/koa…
- Koa – websocket = = koa middleware encapsulation of websocket www.npmjs.com/package/koa…
- Redis = = we used here is subscribe to release function of redis www.npmjs.com/package/red…
- Dotenv = = get project of environment variables can also be front end in the config file. The js wrote death in www.npmjs.com/package/dot…
First, set up the basic socket service
The setup method is similar to node service, with more KOA-WebSocket middleware
const Koa = require('koa')
const router = require('koa-router') ()const websockify = require('koa-websocket')
const app = websockify(new Koa())
// Create a socket interface
router.all('/kapi/socket/init', async ctx => {
const { channel } = ctx.query // The channel id will be passed by the client interface
console.log(channel)
ctx.websocket.send('message')})// The registered route allows the use of middleware
app.ws.use(router.routes()).use(router.allowedMethods())
// Dynamic port number can be used after the port number
app.listen(3131, () =>
console.log(`socket listening on port ${Config.socket.port}`)
)
Copy the code
Link to subscribe to redis channel
On the basis of socket interface, added redis link, channel subscription, here only show the key code, will be done later
const Koa = require('koa')
const router = require('koa-router') ()const websockify = require('koa-websocket')
const app = websockify(new Koa())
const redis = require('redis')
// As with databases, these parameters are required to link redis channels
// Use dotenv dynamic fetch
const redisConfig = {
host: '18.8.1.3',
port: '32',
password: '1232343',
db: 4
}
// Create a socket interface
router.all('/kapi/socket/init', async ctx => {
const { channel } = ctx.query // Accept the channel id passed by the client and subscribe to Redis
console.log(channel)
Redis HTTP: / / https://www.npmjs.com/package/redis / / link
let client = redis.createClient(redisConfig)
// Subscribe to redis channel
client.subscribe(channel)
// Receive the message
client.on('message', async (channel, message) => {
console.log(
'Received subscribe message, channel [%s] message [%s]',
channel,
message
)
// The message is received and returned to the client through the interface
await ctx.websocket.send(message)
})
})
// The registered route allows the use of middleware
app.ws.use(router.routes()).use(router.allowedMethods())
// Dynamic port number can be used after the port number
app.listen(3131, () =>
console.log(`socket listening on port ${Config.socket.port}`)
)
Copy the code
Client call
Do not forget to start the socket before the client call, you can use PM2 or NPM to run koA files, showing the key code, as to listen for error, open, close
import React, { useState, useEffect, UseCallback} from 'react' import createSocket from 'SRC/API /socket' // Import socket export default ()=>{const [mess, setMess] = useState(' no message ') const [channel] = useState(2) const sendSocket = useCallback(() => {// Create socket request, The node on the interface, the channel id wsServer = new WebSocket (` localhost: 3131 / kapi/socket/init? Channel = ${channel} `) / / listening news return wsServer addEventListener (' message ', socketMessage) const socketMessage = (event) => { const data = JSON.parse(event.data) if(data.code===200){ The console. The log (data) setmess (data) / / get the corresponding message to cancel listening wsServer. RemoveEventListener (' message 'socketMessage)}}}, UseEffect (() => {sendSocket()}, [sendSocket]) return (<div>{mess}</div>)}Copy the code
Fourth, splitting and sorting refinement
First, split the original socket service and then refine it
const Koa = require('koa')
const router = require('koa-router') ()const websockify = require('koa-websocket')
const app = websockify(new Koa())
const Config = require('.. /.. /config/const')
const socketApiRoutes = require('./route')
// socket route
socketApiRoutes(router)
// Register the route
app.ws.use(router.routes()).use(router.allowedMethods())
app.listen(Config.socket.port, () =>
console.log(`socket listening on port ${Config.socket.port}`)
)
Copy the code
2.redis.js
const redis = require('redis')
const dotenv = require('dotenv').config({
path: process.env.NODE_ENV == 'production' ? '.env' : '.env.local'
})
// The parameter above is written dead, can be obtained dynamically
const ENV = dotenv.parsed
const redisConfig = {
host: ENV.REDIS_HOST,
port: ENV.REDIS_PORT,
password: ENV.REDIS_PASSWORD,
db: ENV.REDIS_DB
}
// http://redis.js.org/
const createRedisClient = (channel, callback) => {
// https://www.npmjs.com/package/redis
let client = redis.createClient(redisConfig)
/ / subscribe
client.subscribe(channel)
// Listen for Redis ready
client.on('ready', () => {
console.log(
'Redis [%s:%s/%s] is connected and ready for subscribe channel [%s] use.',
redisConfig.host,
redisConfig.port,
redisConfig.database,
channel
)
})
// Listen on redis connect
client.on('connect', () => {
console.log('Redis connect')})// Receive the message
client.on('message', async (channel, message) => {
console.log(
'Received subscribe message, channel [%s] message [%s]',
channel,
message
)
await callback(channel, message)
})
// Listen on redis connect
client.on('reconnecting', err => {
console.log('Redis reconnecting:' + err)
})
// Listen for errors in redis
client.on('error', err => {
console.log('Redis Error:' + err)
})
// Listen for redis subscription events
client.on('subscribe', (channel, count) => {
console.log(
'client subscribed to ' + channel + ', ' + count + ' total subscriptions')})// Listen for redis unsubscribe events
client.on('unsubscribe', (channel, count) => {
console.log(
'client unsubscribed from' +
channel +
', ' +
count +
' total subscriptions')})return client
}
module.exports = createRedisClient
Copy the code
3.route.js
const createRedisClient = require('./redis')
const socketApiRoutes = router => {
router.all('/kapi/socket/init', async ctx => {
const { channel } = ctx.query
console.log(channel)
createRedisClient(channel, (channel, message) => {
console.log(`on message channel: ${channel}`)
waitForSocketConnection(ctx.websocket, () => {
// Wait for the connection to open before sending a message
ctx.websocket.send(message)
})
})
function waitForSocketConnection(socket, callback) {
setTimeout(() => {
if (socket.readyState == 1) {
if(callback ! =null) {
callback()
}
} else {
waitForSocketConnection(socket, callback)
}
}, 1000)}// Listen for web messages
ctx.websocket.on('message', function(res) {
// console.log('ctx websocket web message', res)
})
// Listen for close events
ctx.websocket.on('close', function() {
// client.unsubscribe()
console.log('ctx websocket close')
})
ctx.websocket.body = {
status: true}})}module.exports = socketApiRoutes
Copy the code
Second, encapsulate the client socket call
import { isSupportSocket } from 'src/utils/util'
interface ScoketEvent { open? : () = >voidmessage? : (event: MessageEvent) =>voiderror? : () = >voidclose? : () = >void
}
constcreateSocket = (pathname: string, eventOption? : ScoketEvent) => { let wsServer: WebSocket |null = null
if (isSupportSocket()) {
/ / request socket
wsServer = new WebSocket(
`${window.location.protocol === 'https:' ? 'wss' : 'ws'} :/ / ${
window.location.host
}${pathname}`
)
// Initialize the socket event
let _interval: NodeJS.Timer | null = null
let _setTimeout: NodeJS.Timer | null = null
if (wsServer) {
const socketOpen = () => {
if (eventOption && eventOption.open) {
eventOption.open()
}
// Heartbeat check
wsServer && wsServer.send('socket heart check')
_interval = setInterval(() => {
wsServer && wsServer.send('socket heart check')},30 * 1000)}const socketMessage = (event: MessageEvent) => {
if (eventOption && eventOption.message) {
eventOption.message(event)
console.log(event, 'socketMessage')}}const socketError = () => {
if (eventOption && eventOption.error) {
eventOption.error()
}
if (_interval) clearInterval(_interval)
wsServer = null
console.log('websocket error')}const socketClose = () => {
if (eventOption && eventOption.close) {
eventOption.close()
}
if (wsServer) {
wsServer.removeEventListener('open', socketOpen)
wsServer.removeEventListener('message', socketMessage)
wsServer.removeEventListener('error', socketError)
}
if (_interval) clearInterval(_interval)
if (_setTimeout) clearTimeout(_setTimeout)
}
// Triggered when the connection is established
wsServer.addEventListener('open', socketOpen)
// Triggered when the listening message client receives data from the server
wsServer.addEventListener('message', socketMessage)
// Triggered when a communication error occurs
wsServer.addEventListener('error', socketError)
// Triggered when a communication error occurs
wsServer.addEventListener('close', socketClose)
}
} else {
console.error('This browser does not support sockets, please install a new version')}return wsServer
}
export default createSocket
Copy the code
Third, the client socket call
Call the socket encapsulated above in the page
import React, { useState, useEffect, useCallback } from 'react' import { RouteConfig } from 'react-router-config' import createSocket from 'src/api/socket' interface IProps extends RouteConfig {} export default (props: IProps) => {const [channel] = useState<number>(2) const [mess,setmess] = useState<string>(' no message ') const sendSocket = useCallback(() => { const wsServer: WebSocket | null = createSocket( `/kapi/socket/init? channel=${channel}`, { message: socketMessage } ) function socketMessage(event: MessageEvent) { const data = JSON.parse(event.data) console.log(data) setmess(data) wsServer && wsServer.close() } return () => { if (wsServer) { wsServer.removeEventListener('message', socketMessage) wsServer.close() } } }, [channel]) useEffect(() => { sendSocket() }, [sendSocket]) return ( <div>{mess}</div> ) }Copy the code
Finishing, I hope to help my friends.