본문으로 바로가기

728x90

이번 포스트에서는 TCP Socket Server & Client 구현하는 방법에 대해 설명합니다.

 

Node.js 에서 net 모듈에 대한 API 정의는 아래 사이트에서 확인하십시오.

http://nodejs.sideeffect.kr/docs/v0.10.7/api/net.html

 

net Node.js v0.10.7 Manual & Documentation

net# Stability: 3 - Stable The net module provides you with an asynchronous network wrapper. It contains methods for creating both servers and clients (called streams). You can include this module with require('net'); Stability: 3 - Stable net 모듈은 비동기적인 네트

nodejs.sideeffect.kr

 

간단한 TCP 소켓 서버/클라이언트 실습

서버 생성하기

tcp-socket-server.js 소스 코드

var net = require('net');
 
var server = net.createServer(function(client) {
    console.log('Client connection: ');
    console.log('   local = %s:%s', client.localAddress, client.localPort);
    console.log('   remote = %s:%s', client.remoteAddress, client.remotePort);
    
    client.setTimeout(500);
    client.setEncoding('utf8');
    
    client.on('data', function(data) {
        console.log('Received data from client on port %d: %s', client.remotePort, data.toString());
        
        writeData(client, 'Sending: ' + data.toString());
        console.log('  Bytes sent: ' + client.bytesWritten);
    });
    
    client.on('end', function() {
        console.log('Client disconnected');
    });
    
    client.on('error', function(err) {
        console.log('Socket Error: ', JSON.stringify(err));
    });
    
    client.on('timeout', function() {
        console.log('Socket Timed out');
    });
});

server.listen(9090, function() {
    console.log('Server listening: ' + JSON.stringify(server.address()));
    server.on('close', function(){
        console.log('Server Terminated');
    });
    server.on('error', function(err){
        console.log('Server Error: ', JSON.stringify(err));
    });
});

function writeData(socket, data){
  var success = socket.write(data);
  if (!success){
    console.log("Client Send Fail");
  }
}

 

클라이언트 생성하기

tcp-socket-client.js 소스 코드

var net = require('net'); 
 
function getConnection(){
    //서버에 해당 포트로 접속 
    var client = ""; 
    var recvData = [];  
    var local_port = ""; 
 
    client = net.connect({port: 9090, host:'localhost'}, function() {
        console.log("connect log======================================================================"); 
        console.log('connect success'); 
        console.log('local = ' + this.localAddress + ':' + this.localPort); 
        console.log('remote = ' + this.remoteAddress + ':' +this.remotePort); 
     
        local_port = this.localPort; 
     
        this.setEncoding('utf8'); 
        this.setTimeout(600000); // timeout : 10분 
        console.log("client setting Encoding:binary, timeout:600000" ); 
        console.log("client connect localport : " + local_port);
    }); 
 
    // 접속 종료 시 처리 
    client.on('close', function() { 
        console.log("client Socket Closed : " + " localport : " + local_port); 
    }); 
 
    // 데이터 수신 후 처리 
    client.on('data', function(data) { 
        console.log("data recv log======================================================================"); 
        recvData.push(data); 
        console.log("data.length : " + data.length);
        console.log("data recv : " + data);
        client.end();
    }); 
 
    client.on('end', function() { 
        console.log('client Socket End'); 
    }); 
     
    client.on('error', function(err) { 
        console.log('client Socket Error: '+ JSON.stringify(err)); 
    }); 
     
    client.on('timeout', function() { 
        console.log('client Socket timeout: '); 
    }); 
     
    client.on('drain', function() { 
        console.log('client Socket drain: '); 
    }); 
     
    client.on('lookup', function() { 
        console.log('client Socket lookup: '); 
    });  
    return client;
}
 
function writeData(socket, data){
  var success = !socket.write(data);
  if (!success){
      console.log("Server Send Fail");
  }
}
 
var client = getConnection();
writeData(client, "Echo Message....");

 

서버 실행 테스트

node tcp-socket-server.js 를 실행합니다.

Node.js TCP Socket Server

 

클라이언트 실행 테스트

node tcp-socket-client.js 를 실행합니다.

Node.js TCP Socket Client

 

 

TCP 소켓 서버/클라이언트 모듈화

서버 코드 모듈화 하기

서버의 동작에 필요한 로직은 Override의 속성을 사용하여 재정의하여 사용하도록 처리하였습니다. 주요 이벤트처리를 위한 handleConnection, handleClose, handleClose, handleData 핸들러 함수 및 서버중지를 위한 shutdown함수를 추가하였습니다.

// import events from "events";

var net = require('net');
// var tls = require('tls');

// class TCPSocketServer extends events.EventEmitter {
class TCPSocketServer {
    constructor({type, port, host, timeout}) {
        // super();

        this.type = type;
        this.port = port;
        this.host = host;
        this.timeout = timeout;
        this.server = this.create();
    }

    create() {
        const _this = this;
        // const server = (this.type === "tls") ? tls.createServer((client) => { }) : net.createServer((client) => { });
        const server = net.createServer((client) => { });

        server.listen(this.port, this.host, () => {
            // console.log('Server listening: ' + JSON.stringify(server.address()));
            // Emitted when the server has been bound after calling server.listen().
            // server.listen ()을 호출 한 후 서버가 바인드 될 때 발생합니다.
            server.on('listening', (socket) => {
                console.log('Server listening');
            });

            // Emitted when a new connection is made. socket is an instance of net.Socket.
            // 새로 연결했을 때 발생합니다. socket은 net.Socket의 인스턴스입니다.
            server.on('connection', (socket) => {
                // console.log('Client connection: ');
                // console.log('   local = %s:%s', client.localAddress, client.localPort);
                // console.log('   remote = %s:%s', client.remoteAddress, client.remotePort);
                
                // socket.setTimeout(this.timeout || 60000);
                // socket.setEncoding('utf8');
                
                socket.on('data', (data) => {
                    _this.handleData(socket, data);
                    // this.writeData(socket, 'Sending: ' + data.toString());
                    // console.log('  Bytes sent: ' + socket.bytesWritten);
                });
                
                // Emitted when the other end of the socket sends a FIN packet, thus ending the readable side of the socket.
                // By default (allowHalfOpen is false) the socket will send a FIN packet back and destroy its file descriptor once it has written out its pending write queue. However, if allowHalfOpen is set to true, the socket will not automatically end() its writable side, allowing the user to write arbitrary amounts of data. The user must call end() explicitly to close the connection (i.e. sending a FIN packet back).
                // 소켓의 다른 쪽 끝이 FIN 패킷을 보낼 때 발생하여 소켓의 읽을 수있는 쪽을 끝냅니다.
                // 기본적으로 (HalfOpen은 false 임) 소켓은 FIN 패킷을 다시 보내고 보류중인 쓰기 큐를 작성한 후 파일 디스크립터를 파기합니다. 그러나 allowHalfOpen을 true로 설정하면 소켓이 쓰기 가능한면을 자동으로 종료하지 않으므로 사용자가 임의의 양의 데이터를 쓸 수 있습니다. 연결을 닫으려면 (즉, FIN 패킷을 다시 보내려면) end ()를 명시 적으로 호출해야합니다.
                socket.on('end', () => {
                    // console.log('socket disconnected');
                    _this.handleClose(socket);
                });
                
                // Emitted when an error occurs. The 'close' event will be called directly following this event.
                // 오류가 발생하면 발생합니다. 'close'이벤트는이 이벤트 바로 다음에 호출됩니다.
                socket.on('error', (err) => {
                    console.log('Socket Error: ', JSON.stringify(err));
                });
                
                // Emitted if the socket times out from inactivity. This is only to notify that the socket has been idle. The user must manually close the connection.
                // 소켓이 비 활동으로 시간 종료되면 발생합니다. 이것은 소켓이 유휴 상태임을 알리기 위한 것입니다. 사용자는 연결을 수동으로 닫아야 합니다.
                socket.on('timeout', () => {
                    console.log('Socket Timed out');
                });

                // console.log('socket connection (TCP Socket Server): ' + socket);
                this.handleConnection(socket);
            });

            // Emitted when the server closes. If connections exist, this event is not emitted until all connections are ended.
            // 서버가 닫힐 때 발생합니다. 연결이 존재하면이 이벤트는 모든 연결이 종료 될 때까지 생성되지 않습니다.
            server.on('close', () => {
                console.log('Server Terminated');
            });

            // Emitted when an error occurs. Unlike net.Socket, the 'close' event will not be emitted directly following this event unless server.close() is manually called. See the example in discussion of server.listen().
            // 오류가 발생하면 발생합니다. net.Socket과 달리 server.close ()를 수동으로 호출하지 않으면 'close'이벤트가이 이벤트 바로 뒤에 생성되지 않습니다. server.listen ()에 대한 논의에서 예제를 참조하십시오.
            server.on('error', (err) => {
                console.log('Server Error: ', JSON.stringify(err));
                if (err.code == 'EADDRINUSE') {
                    console.log('Address in use, retrying...');
                    setTimeout(function () {
                        server.close();
                        server.listen(this.port, this.host);
                    }, 1000);
                }
            });
        });

        return server;
    }

    handleConnection(socket) {
        // console.log("Warning, redefinition of this method is required.");
        throw new Error("You have to implement the method do Something!");
    }

    handleClose() {
        // console.log("Warning, redefinition of this method is required.");
        throw new Error("You have to implement the method do Something!");
    }

    write(socket, data) {
        var success = socket.write(data);
        if (!success){
            console.log("Client Send Fail");
        }
    }
    
    handleData(socket, data) { 
        // console.log("Warning, redefinition of this method is required.");
        throw new Error("You have to implement the method do Something!");
    };

    shutdown() {
        this.server.close(() => {
        });
    }
}

// export default TCPSocketServer;
module.exports = TCPSocketServer;

 

클라이언트 코드 모듈화 하기

클라이언트 동작에 필요한 로직은 Override의 속성을 사용하여 재정의하여 사용하도록 처리하였습니다. 주요 이벤트처리를 위한 handleConnection, handleClose, handleClose, handleData 핸들러 함수 및 하위 구현 모듈에서 이벤트를 받기위한 on 인터페이스를 추가하였습니다.

var net = require('net'); 
// var tls = require('tls'); 

class TCPSocketClient {
    constructor({port, host, timeout}){
        this.socket = this.create({port, host, timeout});
    }

    create({type, port, host, timeout}) {
        this.timeout = timeout;

        const _this = this;
        // const client = (this.type === "tls") ? tls.connect({port, host}) : net.connect({port, host});
        const client = net.connect({port, host});

        client.on('connect', () => {
            // console.log("Client connected.....................");
            // console.log("Client Socket connected........ : " + " localport : " + client.localPort);
            this.socket = client;
            this.isConnected = true;

            // client.setTimeout(this.timeout || 60000); // timeout : 60초
            _this.handleConnection(client);
        })
    
        // 접속 종료 시 처리
        // Emitted once the socket is fully closed. The argument hadError is a boolean which says if the socket was closed due to a transmission error.
        // 소켓이 완전히 닫히면 발생합니다. hadError 인수는 전송 오류로 인해 소켓이 닫혔는지 여부를 나타내는 부울입니다.
        client.on('close', function() {
            this.isConnected = false;
            // console.log("Client Socket Closed........");
            //console.log("client Socket Closed........ : " + " localport : " + this.socket.localPort);
            _this.handleClose();
        });
    
        // 데이터 수신 후 처리
        // Emitted when data is received. The argument data will be a Buffer or String. Encoding of data is set by socket.setEncoding().
        // The data will be lost if there is no listener when a Socket emits a 'data' event.
        // 데이터가 수신 될 때 발생합니다. 인수 데이터는 버퍼 또는 문자열입니다. 데이터 인코딩은 socket.setEncoding ()에 의해 설정됩니다.
        // 소켓이 'data'이벤트를 생성 할 때 리스너가 없으면 데이터가 손실됩니다.
        client.on('data', (data) => {
            _this.handleData(data);
        });
        
        // Emitted when an error occurs. The 'close' event will be called directly following this event.
        // 오류가 발생하면 발생합니다. 'close'이벤트는이 이벤트 바로 다음에 호출됩니다.
        client.on('error', function(err) {
            console.log('Client Socket Error: '+ JSON.stringify(err));
        });
    
        // Emitted when the other end of the socket sends a FIN packet, thus ending the readable side of the socket.
        // By default (allowHalfOpen is false) the socket will send a FIN packet back and destroy its file descriptor once it has written out its pending write queue. However, if allowHalfOpen is set to true, the socket will not automatically end() its writable side, allowing the user to write arbitrary amounts of data. The user must call end() explicitly to close the connection (i.e. sending a FIN packet back).
        // 소켓의 다른 쪽 끝이 FIN 패킷을 보낼 때 발생하여 소켓의 읽을 수있는 쪽을 끝냅니다.
        // 기본적으로 (HalfOpen은 false 임) 소켓은 FIN 패킷을 다시 보내고 보류중인 쓰기 큐를 작성한 후 파일 디스크립터를 파기합니다. 그러나 allowHalfOpen을 true로 설정하면 소켓이 쓰기 가능한면을 자동으로 종료하지 않으므로 사용자가 임의의 양의 데이터를 쓸 수 있습니다. 연결을 닫으려면 (즉, FIN 패킷을 다시 보내려면) end ()를 명시 적으로 호출해야합니다.
        client.on('end', function() {
            // console.log('Client Socket End');
        });

        // Emitted if the socket times out from inactivity. This is only to notify that the socket has been idle. The user must manually close the connection.
        // 소켓이 비 활동으로 시간 종료되면 발생합니다. 이것은 소켓이 유휴 상태임을 알리기 위한 것입니다. 사용자는 연결을 수동으로 닫아야 합니다.
        client.on('timeout', function() {
            console.log('Client Socket timeout: ');
        });
        
        // Emitted when the write buffer becomes empty. Can be used to throttle uploads.
        // 쓰기 버퍼가 비게되면 발생합니다. 업로드를 조절하는 데 사용할 수 있습니다.
        client.on('drain', function() {
            // console.log('Client Socket drain: ');
        });
        
        // Emitted after resolving the host name but before connecting. Not applicable to Unix sockets.
        // 호스트 이름을 확인한 후 연결하기 전에 발생합니다. 유닉스 소켓에는 적용되지 않습니다.
        client.on('lookup', function() { 
            // console.log('Client Socket lookup: ');
        });

        return client;
    }

    handleConnection(socket) {
        // console.log("Warning, redefinition of this method is required.");
        throw new Error("You have to implement the method do Something!");
    }
    
    handleData(data) { 
        // console.log("Warning, redefinition of this method is required.");
        throw new Error("You have to implement the method do Something!");
    };
    
    handleClose() { 
        // console.log("Warning, redefinition of this method is required.");
        throw new Error("You have to implement the method do Something!");
    };

    write(data) {
        if(!this.isConnected) {
            console.log("Server Send Fail");
            return;
        }
        var success = !this.socket.write(data);
        if (!success){
            console.log("Server Send Fail");
        }
    }

    close() {
        this.socket.end();
    }

    on(type, callback) {
        this.socket.on(type, callback);
    }
}

// export default TCPSocketClient;
module.exports = TCPSocketClient;

에코 서버 테스트 하기

//import TCPSocketServer from "./tcp-socket-server";

const TCPSocketServer = require('./tcp-socket-server'); 

class EchoServer extends TCPSocketServer {
    constructor(port) {
        super({port});
    }

    handleConnection(client) {
        //console.log('Client connection (Echo Server): ' + JSON.stringify(client));
        console.log('Client connection (Echo Server)');
        const clients = this.server.getConnections((err, count) => {
            console.log("Clients count : " + count);
        });
        // console.log("Clients : " + JSON.stringify(clients));
    }

    handleData(client, data) { 
        this.write(client, data);
        console.log('Received data(' + client.bytesWritten + ' bytes) from client on port %d: %s', client.remotePort, data.toString());
    };

    handleClose(client) {
        console.log('Client disconnected');
    }
}

new EchoServer({ port : 9090 });

에코 클라이언트 테스트 하기

//import TCPSocketClient from "tcp-socket-client";

const TCPSocketClient = require('./tcp-socket-client'); 

class EchoClient extends TCPSocketClient {
    constructor({port, host, timeout}) {
        super({port, host, timeout});
    }
    
    handleConnection(socket) {
        console.log("connect log======================================================================"); 
        console.log('connect success'); 
        console.log('local = ' + socket.localAddress + ':' + socket.localPort); 
        console.log('remote = ' + socket.remoteAddress + ':' + socket.remotePort); 

        socket.setEncoding('utf8'); 
        socket.setTimeout(this.timeout || 10000); // timeout : 10분 
        console.log("Client setting Encoding:binary, timeout:" + (this.timeout || 10000));
        console.log("Client connect localport : " + socket.localPort);
    };
    
    handleData(data) { 
        console.log("data recv log======================================================================"); 
        console.log("data : " + data);
        console.log("data.length : " + data.length);
        console.log("data recv : " + data);
    };

    handleClose() {
    }
}

var client1 = new EchoClient({port: 9010, host:'192.168.0.100'});
client1.on("connect", () => {
    client1.write("Echo Message1 in Client1....");
    setTimeout(() => {
        client1.write("Echo Message2 in Client1....");
    }, 1500);
    setTimeout(() => {
        client1.close();
        console.log("-------------");
    }, 3000);
});


var client2 = new EchoClient({port: 9010, host:'localhost'});
client2.on("connect", () => {
    client2.write("Echo Message1 in Client2....");
    setTimeout(() => {
        client2.write("Echo Message2 in Client2....");
    }, 2000);
    setTimeout(() => {
        client2.close();
        console.log("-------------");
    }, 5000);
});

 

관련 사이트

 

 

 

728x90