A nodejs library to wrap TCP/UDP packets between a server and client
Since you can never be sure of a data event being the full message, you may find yourself getting half packets, or even multiple packets at a time, and not being able to distinguish where a packet starts or ends.
This lib is intended to simplify this:
npm install socket-packet
import SocketPacket from 'socket-packet'
// Once you have a socket, or for example:
// const socket = new Socket()
SocketPacket.bind(socket)
socket.on('packet', packet => {
if (packet === 'ping') {
socket.send('pong')
}
})
Binds socket-packet
to an instance of a socket. This will attach the necessary logic to the socket for easy packet handling and sending.
- socket: <Object> instance of a net.Socket
- logger: {optional} <Object> instance of a winston or similar logger
- opts: {optional} <Object> with any customized options for SocketPacket
- type: {optional} <string> - see type
- startsWith: {optional} <string> - see startsWith
- endsWith: {optional} <string> - see endsWith
- encoding: {optional} <string> - see encoding
- packetStringifier: {optional} <function> - see packetStringifier
- packetParser: {optional} <function> - see packetParser
SocketPacket.bind(socket, logger, opts)
socket-packet
adds a listener to the data
event of the socket, "unwraps" the payload into their separate packets, and for each valid packet contained, emits the packet
event
socket.on('packet', packet => {
if (packet === 'ping') {
socket.dispatch('pong')
}
})
socket-packet
emits error
when encountering errors related to un/packaging messages, examples:
- on receiving a message which doesnt conform to the expected start and end wrapping
- on error from invocation of packetStringifier and packetParser
socket.on('error', error => {
// handle error
})
socket-packet
binds a .dispatch
function to the socket. Using this method will package the provided data/message and then write it to the socket
- port: {optional} <number> UDP specific, not to be used with TCP, see dgram.send()
- address: {optional} <string> UDP specific, not to be used with TCP, see dgram.send()
- callback: {optional} <function> once the data has been flushed on the socket, this callback will be invoked, as expected when using net.Socket.write()
socket.dispatch('hello')
socket.dispatch('ping')
socket.dispatch({ hello: 'world'})
NB: although you are dispatching a JSON object, when it arrives on the other side of the socket, it would be a string/buffer ... You can use the packetParser on the other end of the socket to parse it as JSON; remember to use the packetStringifier to safely get a stringified version of the JSON object to write to the socket
The type of socket being bound to. Options are:
tcp
- Defaultudp
This library is all about packaging messages/data being transferred over a socket. Packages are wrapped with a default starting value -!@@!-
, which is used to indicate the start of a new packet. If the server/client prefixes packets with a different value, be sure to set this to the same value
This library is all about packaging messages/data being transferred over a socket. Packages are wrapped with a default ending value -@!!@-
, which is used to indicate the end of a new packet. If the server/client suffixes packets with a different value, be sure to set this to the same value
The encoding to use when parsing/stringifying packets, default is'utf8' Caveat: if you use socket.setEncoding() and it does not match this encoding, problems may be experienced ... I have not yet tested this
This function is used when socket.dispatch(data[, cb]) is invoked to stringify the message/data object before writing to the socket ... The default returns the packet as is, as a string:
opts = {
packetStringifier: packet => packet && packet.toString()
}
if, for example, all packets are expected to be application/json
, you can use this:
opts = {
packetStringifier: packet => packet && JSON.stringify(packet)
}
This function is used when the data
event is emitted and a packet is extracted to parse the packet to a specified format ... The default dispatches the packet as is, as a string:
opts = {
packetParser: packet => packet && packet.toString()
}
if, for example, all packets are expected to be application/json
, you can use this:
opts = {
packetParser: packet => packet && JSON.parse(packet)
}
server.js:
import net from 'net'
import SocketPacket from 'socket-packet'
/* OR */
const net = require('net')
const SocketPacket = require('socket-packet')
const port = process.env.PORT || 8080
const host = process.env.HOST || 'localhost'
const server = net.createServer(socket => {
console.log('Client connected')
SocketPacket.bind(socket)
socket.on('packet', packet => {
switch (packet) {
case 'ping':
console.log('ping received')
setTimeout(() => {
socket.dispatch('pong', () => {
console.log('pong dispatched')
})
}, 500)
break
default:
console.log('Unhandled packet received: ', packet)
}
})
socket.on('error', err => {
console.log('Error:', err)
})
socket.on('end', () => {
console.log('Client ended')
})
socket.on('close', hadError => {
console.log(`Client disconnected (with error: ${hadError})`)
})
})
server.listen(port, host, () => {
const { address, port } = server.address()
console.log(`Server started on ${address}:${port}`)
})
client.js:
import net from 'net'
import SocketPacket from 'socket-packet'
/* OR */
const net = require('net')
const SocketPacket = require('socket-packet')
const port = process.env.PORT || 8080
const host = process.env.HOST || 'localhost'
const client = net.createConnection({ port, host }, () => {
console.log('connection established')
SocketPacket.bind(client)
})
client.on('packet', packet => {
switch (packet) {
case 'pong':
console.log('pong received')
break
default:
console.log('Unhandled packet received: ', packet)
}
})
client.on('error', err => {
console.log('Error:' + err)
})
client.on('close', hasError => {
console.log(`Connection closed (has error: ${!!hasError})`)
clearInterval(interval)
})
const interval = setInterval(() => {
client.dispatch('ping', () => {
console.log('ping dispatched')
})
}, 2000)
server.js:
import dgram from 'dgram'
import SocketPacket from 'socket-packet'
/* OR */
const dgram = require('dgram')
const SocketPacket = require('socket-packet')
const port = process.env.PORT || 9090
const host = process.env.HOST || 'localhost'
const serverSocket = dgram.createSocket({ type: 'udp4', sendBufferSize: 8192 })
SocketPacket.bind(serverSocket, null, { type: 'udp4' })
serverSocket.on('packet', (packet, rInfo) => {
switch (packet) {
case 'ping':
console.log('ping received')
serverSocket.dispatch('pong', rInfo.port, rInfo.address, () => {
console.log('pong dispatched')
})
break
default:
console.log(`Unknown message received: ${packet}`)
break
}
})
serverSocket.on('error', err => {
console.log('Server error:', err)
})
serverSocket.on('end', () => {
console.log('Server ended')
})
serverSocket.on('close', hadError => {
console.log('Server closed')
})
serverSocket.bind(port, host, () => {
const { address, port } = serverSocket.address()
console.log(`Server running on ${address}:${port}`)
})
client.js:
import net from 'net'
import SocketPacket from 'socket-packet'
/* OR */
const dgram = require('dgram')
const SocketPacket = require('socket-packet')
const port = process.env.PORT || 9091
const host = process.env.HOST || 'localhost'
const serverPort = process.env.SERVER_PORT || 9090
const serverHost = process.env.SERVER_HOST || 'localhost'
const clientSocket = dgram.createSocket({ type: 'udp4', sendBufferSize: 8192 })
SocketPacket.bind(clientSocket, null, { type: 'udp4' })
clientSocket.on('packet', packet => {
switch (packet) {
case 'pong':
console.log('pong received')
break
default:
console.log(`Unknown message received: ${packet}`)
break
}
})
clientSocket.on('error', err => {
console.log('Client error:', err)
})
clientSocket.on('end', () => {
console.log('Client ended')
})
clientSocket.on('close', hadError => {
console.log('Client closed')
})
clientSocket.bind(port, host, () => {
const { address, port } = clientSocket.address()
console.log(`Client running on ${address}:${port}`)
setInterval(() => {
clientSocket.dispatch('ping', serverPort, serverHost, () => {
console.log('ping dispatched')
})
}, 2000)
})
Caveats:
- Please make sure you know about UDP length limits and how nodejs errors out when misconfigured. UDP has a min and max length per os. Please ensure you use safe values, else socket-packet will throw errors (as thrown by nodejs' udp module). You may notice that I used
8192
in my examples: I feel this value is a safe value all round, but you may use your own defined value