This error occurs when attempting to write data to a Node.js net.Socket before the connection is established. The socket must successfully connect to a remote server before any data can be sent. This typically happens when write() is called immediately without waiting for the "connect" event.
In Node.js, a net.Socket is a duplex stream used for TCP network communication. The socket lifecycle has distinct states: initial, connecting, connected, and closed. Writing data to a socket requires an established connection. If you call write() before the socket has successfully connected (not just created), Node.js throws this error because the socket doesn't have an open channel to send data through. This is a common mistake when learning Node.js networking, where developers assume that creating a socket automatically connects it. In reality, you must explicitly call connect() and wait for the "connect" event before writing data.
Always wait for the socket to connect before attempting to write data. Use the "connect" event listener:
const net = require('net');
const socket = new net.Socket();
socket.connect(3000, '127.0.0.1', () => {
// Connection established - safe to write now
console.log('Socket connected');
socket.write('Hello from client');
});
socket.on('data', (data) => {
console.log('Received:', data.toString());
socket.destroy();
});
socket.on('error', (err) => {
console.error('Socket error:', err.message);
});
socket.on('close', () => {
console.log('Socket closed');
});The callback passed to connect() is called when the connection is established.
For more flexibility, listen for the "connect" event separately:
const net = require('net');
const socket = new net.Socket();
// Set up listeners before connecting
socket.on('connect', () => {
console.log('Connected to server');
// Now it's safe to write
socket.write('Hello server');
});
socket.on('data', (data) => {
console.log('Response:', data.toString());
});
socket.on('error', (err) => {
console.error('Socket error:', err.code, err.message);
});
socket.on('close', () => {
console.log('Connection closed');
});
// Initiate connection
socket.connect(3000, 'localhost');This pattern gives you more control over error handling and event management.
Add error handling to detect connection failures:
const net = require('net');
const socket = new net.Socket();
socket.on('connect', () => {
console.log('Connected');
socket.write('Data');
});
socket.on('error', (err) => {
// Check specific error codes
if (err.code === 'ECONNREFUSED') {
console.error('Connection refused - server not running');
} else if (err.code === 'ETIMEDOUT') {
console.error('Connection timeout');
} else if (err.code === 'ENOTFOUND') {
console.error('Host not found');
} else {
console.error('Connection error:', err.message);
}
// Don't attempt to write if connection failed
});
socket.connect(3000, 'localhost');Never call write() in the error handler or if connection fails.
Check if the socket is writable before writing:
const net = require('net');
const socket = new net.Socket();
function safeWrite(data) {
if (socket.writable && socket.readyState === 'open') {
socket.write(data);
} else {
console.error('Socket not ready for writing');
console.log('Writable:', socket.writable);
console.log('Ready state:', socket.readyState);
}
}
socket.on('connect', () => {
console.log('Socket connected and ready');
safeWrite('Hello');
});
socket.on('error', (err) => {
console.error('Socket error:', err.message);
});
socket.connect(3000, '127.0.0.1');
// This would fail:
// safeWrite('Before connect'); // Not ready yetCheck the writable property to ensure the socket is in a valid state.
Modernize your approach with promises:
const net = require('net');
function connectSocket(host, port) {
return new Promise((resolve, reject) => {
const socket = new net.Socket();
socket.on('connect', () => {
resolve(socket);
});
socket.on('error', (err) => {
reject(err);
});
socket.connect(port, host);
});
}
async function main() {
try {
const socket = await connectSocket('localhost', 3000);
console.log('Connected');
// Safe to write now
socket.write('Hello from async client');
socket.on('data', (data) => {
console.log('Response:', data.toString());
socket.destroy();
});
} catch (err) {
console.error('Failed to connect:', err.message);
}
}
main();This pattern is cleaner and prevents accidental premature writes.
Before debugging client code, ensure the server is running and listening:
// Simple echo server for testing
const net = require('net');
const server = net.createServer((socket) => {
console.log('Client connected');
socket.on('data', (data) => {
console.log('Received:', data.toString());
socket.write('Echo: ' + data.toString());
});
socket.on('error', (err) => {
console.error('Socket error:', err.message);
});
socket.on('close', () => {
console.log('Client disconnected');
});
});
server.listen(3000, '127.0.0.1', () => {
console.log('Server listening on port 3000');
});Run this server in one terminal, then connect clients to it. Test with:
# In another terminal
node client.jsSocket States in Node.js
A socket has different states accessible via socket.readyState:
- 'opening' - connection in progress
- 'open' - connection established (safe to write)
- 'readOnly' - can't write (half-closed)
- 'writeOnly' - can write but not read (unusual)
- 'closed' - connection closed
Always check socket.writable property to safely determine if writing is allowed.
Handling Half-Open Connections
Sometimes a socket can be in a half-closed state where reading is closed but writing is still possible:
socket.on('end', () => {
// Remote end closed the connection
// You can still write() but shouldn't
socket.end(); // Gracefully close our end
});
socket.on('close', () => {
// Both directions closed - no more writes
});Backpressure and Buffering
If you write large amounts of data, check the return value:
socket.on('connect', () => {
const buffer = Buffer.alloc(1024 * 1024); // 1MB
const canContinue = socket.write(buffer);
if (!canContinue) {
console.log('Socket buffer full, waiting for drain');
}
});
socket.on('drain', () => {
// Internal buffer has been flushed
// Safe to write more
socket.write(moreData);
});Worker Threads and Socket Sharing
Be careful when sharing sockets across worker threads:
const { Worker } = require('worker_threads');
// Socket must not be shared between threads
// Instead, communicate via message passing
const worker = new Worker('./worker.js');
worker.on('message', (msg) => {
if (msg.type === 'write') {
socket.write(msg.data);
}
});TLS/SSL Socket Connection
For secure connections, the same principle applies:
const tls = require('tls');
const options = {
host: 'example.com',
port: 443,
rejectUnauthorized: false // Only for testing!
};
const socket = tls.connect(options, () => {
// Connection established and TLS handshake complete
socket.write('GET / HTTP/1.0\r\nHost: example.com\r\n\r\n');
});
socket.on('data', (data) => {
console.log(data.toString());
socket.end();
});Testing Socket Connections
Use nc (netcat) or telnet to manually test socket servers:
# Test if server is listening
nc -zv localhost 3000
# Interactive connection test
nc localhost 3000
# Type messages and they'll be echoed back
# Or use telnet
telnet localhost 3000This helps distinguish between code issues and server availability issues.
Error: EMFILE: too many open files, watch
EMFILE: fs.watch() limit exceeded
Error: Middleware next() called multiple times (next() invoked twice)
Express middleware next() called multiple times
Error: Worker failed to initialize (worker startup error)
Worker failed to initialize in Node.js
Error: EMFILE: too many open files, open 'file.txt'
EMFILE: too many open files
Error: cluster.fork() failed (cannot create child process)
cluster.fork() failed - Cannot create child process