본문으로 바로가기

TCP Socket Router 예제를 통한 Electron Application 작성방법에 대한 연재가 완료되었습니다.

 

Electron + Vue.js 애플리케이션 만들기

1. Electron 프로젝트 생성하기(Create Electron Application Project)

2. Electron IPC 통신(Electron Architecture, IPC Main/IPC Renderer)

3. TCP Router 기능 구현(Implements TCP Router Communication)

4. 화면 UI 구성(Main Window UI)

5. 환경설정 구현하기(Preferences Window)

6. 메뉴 사용하기(Application Menu & Context Menu)

7. 시스템 트레이 사용 및 창 최소화(System Tray & Minimize to Tray)

8. Bootstrap Vue와 Font Awesome 사용하기(Using Bootstrap Vue & Font Awesome)

9. Dark Layout과 Frameless Window(Dark Layout & Frameless Window)

10. 빌드 및 배포, 자동 업데이트(Build, Auto Updater)

 

 

이번 포스트에서는 Node.js에서 TCP 서버와 클라이언트 그리고 Router 기능을 구현하고 Electron 애플리케이션에서 실행합니다. 본 블로그의 포스트에서 이미 TCP서버와 클라이언트 모듈의 모듈화 하는 글을 올렸습니다. 아래의 예제는 이 서버와 크라이언트 모듈을 사용하여 Router 기능으로 확장합니다.

 

TCP Socket Server & Client by Node.js

이번 포스트에서는 TCP Socket Server & Client 구현하는 방법에 대해 설명합니다. Node.js 에서 net 모듈에 대한 API 정의는 아래 사이트에서 확인하십시오. http://nodejs.sideeffect.kr/docs/v0.10.7/api/net.h..

parandol.tistory.com

 

TCP Router 구현 하기

TCP Router 기능 설명

구현하려는 TCP Router는 TCP 접속을 다른 TCP 접속으로 연결을 중계해주는 기능입니다. 신규 클라이언트 접속이 들어오면 다른 서버로 연결을 설정하고 클라이언트에서 데이터를 수신하면 서버로 재전송하고 반대로 서버의 응답 데이터를 클라이언트에 다시 전송합니다. 일반적인 프락시 기능과 유사합니다.

 

Proxy Server

 

TCP Router 기능 구현

TCPSocketServer의 기능을 확장하여 TCPSocketRouter 객체를 생성하고 TCPSocketClient의 기능을 확장하여 TCPRouterClient 객체를 생성합니다. TCPSocketRouter에 새로운 클라이언트 접속이 들어오면 신규로 TCPRouterClient를 통해 서버로 TCP 연결을 생성합니다.

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

class TCPRouterClient extends TCPSocketClient {
    constructor({type, remote, port, host, timeout}) {
        super({type, port, host, timeout});
        this.remote = remote;
    }
    
    handleConnection(socket) {
        // console.log("Client connected.....................");
        socket.setKeepAlive(true);
    };

    handleData(data) {
        this.remote.write(data);
        console.log("Client Read : %d, Writtern : %d ", this.socket.bytesRead, this.socket.bytesWritten);
        console.log("Remote Read : %d, Writtern : %d ", this.remote.bytesRead, this.remote.bytesWritten);
    };

    handleClose() {
        console.log("Client to Server Socket Closed........ with Client " + this.remote.uuid);
        this.remote.end();
    }
}

class TCPSocketRouter extends TCPSocketServer {
    constructor({type, listen, host, port, timeout}) {
        super({type, port : listen, timeout});
        this.host = host;
        this.port = port;
        this.timeout = timeout;
        this.buffer = {};

        this.connections = {};
        this.counts = {};

        this.clientIndex = 0;
    }

    // starting point <---(client:Socket)---> router <---(server:TCPRouterClient)---> destination
    handleConnection(client) {
        console.log("=================================== TCPSocketRouter ===================================");
        //console.log('Client connection (Echo Server): ' + JSON.stringify(client));
        console.log('Client connection (Router Server)');

        // client.uuid = require("uuid/v4")();
        client.uuid = "client-" + (++this.clientIndex);
        // console.log('client.uuid : ' + client.uuid);

        this.connections[client.uuid] = {client};

        const server = new TCPRouterClient({type : this.type, remote: client, port: this.port, host : this.host, timeout : this.timeout});
        server.on("connect", () => {
            this.connections[client.uuid] = {client, server : server.socket};

            const clientSocket = client;
            const serverSocket = server.socket;

            console.log('Buffer size : ' + serverSocket.bufferSize);

            clientSocket.setKeepAlive(true);
            serverSocket.setKeepAlive(true);

            // 접속 클라이언트 정보 추가
            const addr = clientSocket.remoteAddress;
            var count = this.counts[addr]
            if(!count || count == 0) {
                this.counts[addr] = 1;
            } else {
                this.counts[addr]++;
            }

            // 버퍼에 값이 있을 경우 값 전송
            var arr = this.buffer[client.uuid];
            if(arr) {
                let data = arr.shift();
                while(data) {
                    serverSocket.write(data);
                    data = arr.shift();
                }
            }
            
            console.log(
                "TCP Forwarding %s:%s <--> %s:%s START",
                clientSocket.remoteAddress, clientSocket.remotePort,
                serverSocket.remoteAddress, serverSocket.remotePort
            );

            this.print();
        });
    }

    handleClose(client) {
        const uuid = client.uuid;
        const connection = this.connections[uuid];
        const clientSocket = connection.client;
        const serverSocket = connection.server;

        console.log(
            "TCP Forwarding %s:%s <--> %s:%s STOP",
            clientSocket.remoteAddress, clientSocket.remotePort,
            serverSocket.remoteAddress, serverSocket.remotePort
        );
        
        // 접속 클라이언트 정보를 업데이트
        const addr = clientSocket.remoteAddress;
        var count = this.counts[addr]
        if(count <= 1) {
            delete this.counts[addr];
        } else {
            this.counts[addr]--;
        }

        this.connections[uuid] = null;
        delete this.connections[uuid];

        // 원격지 서버와의 접속 종료
        serverSocket.end();

        this.print();
    }

    handleData(client, data) {
        console.log("Server Socket Read : %d, Writtern : %d ", client.bytesRead, client.bytesWritten);
        const uuid = client.uuid;
        const connection = this.connections[uuid];
        if(connection && connection.server) {
            connection.server.write(data);
        } else {
            var arr = this.buffer[uuid];
            if(arr) {
                this.buffer[uuid].push(data);
            } else {
                this.buffer[uuid] = [];
                this.buffer[uuid].push(data);
            }
        }
        this.print();
    };

    print() {
        for(const key in this.counts) {
            console.log("Client [%s] : %d", key, this.counts[key]);
        }
        for(const key in this.connections) {
            const connection = this.connections[key];
            if(connection) {
                // console.log("Client [%s], Sent : %d, Received : %d ", key, connection.sendBytes, connection.receiveBytes);
                if(connection.server && connection.server) {
                  console.log("Client [%s], Sent : %d, Received : %d ", key, connection.client.bytesRead, connection.server.bytesRead);
                // } else {
                //   console.log("Client [%s], Received : %d, Sent : 0 ", key, connection.client.bytesWritten);
                }
            }
        }
    }
}
// export default TCPSocketRouter;
module.exports = TCPSocketRouter;

 

TCP Router Manager 기능 구현

여러 개의 Router기능을 동시에 실행하기 위하여 관리하는 객체를 생성합니다. 설정 정보를 배열로 받아서 여러 개의 TCPSocketRouter를 실행합니다. 아래에 예시에서 서버의 중지 및 재시작 등의 기능 구현 및 상태 등을 표시하기 위한 기능은 아직 구현되지 않았습니다.

const TCPSocketRouter = require('./tcp-socket-router'); 

class TCPRouterManager {
    constructor(env) {
        this.init(env);
    }
    
    init(env) {
        env.forEach(({type, listen, port, host, timeout}) => {
            new TCPSocketRouter({type, listen, port, host, timeout});
        });
    };
}

 

TCP Router 실행하기

const TCPRouterManager = require('./tcp-router-manager'); 

const env = [
    {
        listen : 3306,
        host : "192.168.1.24",
        port : 3306
    },
    // {
    //     listen : 9010,
    //     host : "localhost",
    //     port : 9090
    // }
];

new TCPRouterManager (env);

 

Electron에 TCP Router 기능 붙이기

TCPSocketServer 확장

events.EventEmitter 모듈을 확장하여 이벤트 시점을 구체화하였습니다.

import events from "events";

var net = require('net');

class TCPSocketServer extends events.EventEmitter {
    constructor({listen}) {
        super();
        this.listen = listen;
    }

    startup() {
        const _this = this;
        // const server = (this.type === "tls") ? tls.createServer((client) => { }) : net.createServer((client) => { });
        const server = net.createServer((client) => { });
        
        // console.log('Server listening: ' + JSON.stringify(server.address()));
        // Emitted when the server has been bound after calling server.listen().
        // server.listen ()을 호출 한 후 서버가 바인드 될 때 발생합니다.
        server.on('listening', () => {
            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.listen, this.host);
            //     }, 1000);
            // }
            // this.error = err;
            // console.log("........... error port : " + JSON.stringify(err));
            this.emit("error", err);
        });

        server.listen(this.listen, () => {

        });

        this.server = server;
        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;

TCPSocketClient 확장

events.EventEmitter 모듈을 확장하여 이벤트 시점을 구체화하였습니다.

import events from "events";

var net = require('net');

class TCPSocketClient extends events.EventEmitter {
    constructor({port, host}){
        super();
        this.socket = this.create({port, host});
    }

    create({port, host}) {
        const _this = this;
        const client = net.connect({port, host});

        client.on('connect', () => {
            this.socket = client;
            this.isConnected = true;
            _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;

TCPSocketRouter 확장

접속한 클라이언트 수와 전송 데이터량을 반환하는 함수를 추가하였습니다.

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

class TCPRouterClient extends TCPSocketClient {
    constructor({remote, port, host}) {
        super({port, host});
        this.remote = remote;
    }
    
    handleConnection(socket) {
        socket.setKeepAlive(true);
    };

    handleData(data) {
        this.remote.write(data);
        console.log("Client Read : %d, Writtern : %d ", this.socket.bytesRead, this.socket.bytesWritten);
        console.log("Remote Read : %d, Writtern : %d ", this.remote.bytesRead, this.remote.bytesWritten);
        this.emit("transfered");
    };

    handleClose() {
        console.log("Client to Server Socket Closed........ with Client " + this.remote.uuid);
        this.remote.end();
    }
}

class TCPSocketRouter extends TCPSocketServer {
    constructor({listen, host, port}) {
        super({listen});

        this.host = host;
        this.port = port;
        this.buffer = {};

        this.connections = {};
        this.counts = {};
        this.transferBytes = {};

        this.clientIndex = 0;
    }

    // starting point <---(client:Socket)---> router <---(server:TCPRouterClient)---> destination
    handleConnection(client) {
        console.log('Client connection (Router Server)');

        // client.uuid = require("uuid/v4")();
        client.uuid = "client-" + (++this.clientIndex);
        // console.log('client.uuid : ' + client.uuid);

        this.connections[client.uuid] = {client};

        const server = new TCPRouterClient({type : this.type, remote: client, port: this.port, host : this.host, timeout : this.timeout});
        server.on("connect", () => {
            this.connections[client.uuid] = {client, server : server.socket};

            const clientSocket = client;
            const serverSocket = server.socket;

            // console.log('Buffer size : ' + serverSocket.bufferSize);

            clientSocket.setKeepAlive(true);
            serverSocket.setKeepAlive(true);

            // 접속 클라이언트 정보 추가
            const addr = clientSocket.remoteAddress;
            var count = this.counts[addr]
            if(!count || count == 0) {
                this.counts[addr] = 1;
            } else {
                this.counts[addr]++;
            }

            // 버퍼에 값이 있을 경우 값 전송
            var arr = this.buffer[client.uuid];
            if(arr) {
                let data = arr.shift();
                while(data) {
                    serverSocket.write(data);
                    data = arr.shift();
                }
            }
            
            console.log(
                "TCP Forwarding %s:%s <--> %s:%s START",
                clientSocket.remoteAddress, clientSocket.remotePort,
                serverSocket.remoteAddress, serverSocket.remotePort
            );

            this.emit("connected", this.count());
            this.print();
        });
        server.on("transfered", () => {
            this.emit("transfered", this.bytes());
        });
    }

    handleClose(client) {
        const uuid = client.uuid;
        const connection = this.connections[uuid];
        const clientSocket = connection.client;
        const serverSocket = connection.server;

        console.log(
            "TCP Forwarding %s:%s <--> %s:%s STOP",
            clientSocket.remoteAddress, clientSocket.remotePort,
            serverSocket.remoteAddress, serverSocket.remotePort
        );

        // 접속 클라이언트 정보를 업데이트
        const addr = clientSocket.remoteAddress;
        var count = this.counts[addr]
        if(count <= 1) {
            delete this.counts[addr];
        } else {
            this.counts[addr]--;
        }

        this.connections[uuid] = null;
        delete this.connections[uuid];

        // 원격지 서버와의 접속 종료
        serverSocket.end();

        this.emit("closed", this.count());
        this.print();
    }

    handleData(client, data) {
        console.log("Server Socket Read : %d, Writtern : %d ", client.bytesRead, client.bytesWritten);
        const uuid = client.uuid;
        const connection = this.connections[uuid];
        if(connection && connection.server) {
            connection.server.write(data);
        } else {
            var arr = this.buffer[uuid];
            if(arr) {
                this.buffer[uuid].push(data);
            } else {
                this.buffer[uuid] = [];
                this.buffer[uuid].push(data);
            }
        }

        this.emit("transfered", this.bytes());
        this.print();
    };

    print() {
        for(const key in this.counts) {
            console.log("Client [%s] : %d", key, this.counts[key]);
        }
        for(const key in this.connections) {
            const connection = this.connections[key];
            if(connection) {
                if(connection.server && connection.server) {
                    console.log("Client [%s], Sent : %d, Received : %d ", key, connection.client.bytesRead, connection.server.bytesRead);
                }
            }
        }
    }

    count() {
        let count = 0;
        for(const key in this.counts) {
            count += this.counts[key];
        }
        return count;
    }

    bytes() {
        let serverReadBytes = 0;
        let serverWriteBytes = 0;
        let clientReadBytes = 0;
        let clientWriteBytes = 0;
        
        for(const key in this.connections) {
            const conn = this.connections[key];
            
            serverReadBytes += conn.server.bytesRead || 0,
            serverWriteBytes += conn.server.bytesWritten || 0,
            clientReadBytes += conn.client.bytesRead || 0,
            clientWriteBytes += conn.client.bytesWritten || 0
        }

        return {
            serverReadBytes,
            serverWriteBytes,
            clientReadBytes,
            clientWriteBytes
        };
    }
}

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

TCPRouterManager 확장

events.EventEmitter 모듈을 확장하여 이벤트 시점을 구체화하였으며 접속한 클라이언트 수와 전송 데이터량, 상태를 반환하는 함수를 추가하였습니다.

import events from "events";

import TCPSocketRouter from "./tcp-socket-router";

export default (() => {
    class TCPRouterManager extends events.EventEmitter {
        constructor() {
            super();
            this.routers = {};
        }
        
        execute(env) {
            if(!TCPRouterManager.instance) return;

            env.forEach(({listen, port, host}) => {
                const key = host + ":" + port;

                try {
                    const router = new TCPSocketRouter({listen, port, host});
    
                    router.on("transfered", (bytes) => {
                        this.emit("transfered", {key, bytes});
                    });
                    router.on("connected", (count) => {
                        this.emit("connected", {key, count});
                    });
                    router.on("closed", (count) => {
                        this.emit("closed", {key, count});
                    });
                    router.on("error", (error) => {
                        this.emit("error", {key, error});
                    })
    
                    router.startup();
                    this.routers[key] = router;
                } catch(err) {
                    console.log(err);
                }
            });
        };

        bytes() {
            let ret = {};
            for(const key in this.routers) {
                ret[key] = this.routers[key].bytes();
            }
            return ret;
        }

        count() {
            let ret = {};
            for(const key in this.routers) {
                ret[key] = this.routers[key].count();
            }
            return ret;
        }

        state() {
            let ret = {};
            for(const key in this.routers) {
                ret[key] = {
                    count : this.routers[key].count(),
                    bytes : this.routers[key].bytes()
                }
            }
            return ret;
        }
    }
    
    return {
        getInstance() {
            if(!TCPRouterManager.instance) {
                TCPRouterManager.instance = new TCPRouterManager();
            }
            return TCPRouterManager.instance;
        }
    };
})();

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

TCP Router 실행하기

/src/main/index.js의 init() 함수에서 ipcMain 채널을 등록하고 TCP Router를 실행하는 코드입니다. 화면이 아직 초기화되지 않았을 때는 errorQueue에 값을 저장하고 있다가 화면 초기화 시에 출력하도록 처리하였습니다.

import TCPRouterManager from './core/tcp-router-manager'
// const TCPRouterManager = require('./core/tcp-router-manager'); 

let mainWindow = null;
function init() {

......(중간 생략)......

  const errorQueue = [];
  let pushRenderer = null;
  ipcMain.on("tcp-router-state-init", (event, args) => {
    // console.log(args);
    pushRenderer = event.sender;
    if(errorQueue.length > 0) {
      while(errorQueue.length > 0) {
        const msg = errorQueue.shift();
        pushRenderer.send("tcp-router-error", msg);
      }
    }
  });
  ipcMain.once("tcp-router-state-end", (event, args) => {
    // console.log(args);
    pushRenderer = null;
  });



  const env = [
    {
        listen : 3307,
        host : "192.168.1.24",
        port : 3306
    },
    // {
    //     listen : 9010,
    //     host : "localhost",
    //     port : 9090
    // }
  ];
  const manager = new TCPRouterManager.getInstance();
  manager.on("transfered", ({key, bytes}) => {
      if(pushRenderer) {
        pushRenderer.send("tcp-router-state", manager.state());
      }
  });
  manager.on("connected", ({key, count}) => {
      if(pushRenderer) {
        pushRenderer.send("tcp-router-state", manager.state());
      }
  });
  manager.on("closed", ({key, count}) => {
      if(pushRenderer) {
        pushRenderer.send("tcp-router-state", manager.state());
      }
  });
  manager.on("error", ({key, error}) => {
    const msg = (error.code == 'EADDRINUSE') ? "Error, listen port(" + error.port + ") already in use." : error;
    if(pushRenderer) {
      pushRenderer.send("tcp-router-error", msg);
    } else {
      errorQueue.push(msg);
    }
  });
  manager.execute(env);


......(이하 생략)......

TCP Router 정보 출력

상태 정보와 오류 메시지를 출력하기 위해 "tcp-router-state", "tcp-router-state-init", "tcp-router-state-end" 채널을 사용하여 데이터를 송수신하고 ipcMain 프로세스로부터 수신된 메시지는 tcpRouterError, tcpRouterState 변수값을 통해 화면에 출력하는 코드입니다.

<template>
  <div id="wrapper">
    <img id="logo" src="~@/assets/logo.png" alt="electron-vue">
    <main>
      ......(중간 생략)......

        <div class="doc" style="margin-top:16px;">
          <div class="title alt">IPC(Inter-Process Communication)</div>
          <div class="items">
            <div class="item" v-for="(value, key) in tcpRouterState ">
              <div class="name">{{ key }}</div>
              <div class="value">{{ value.count }}</div>
              <div class="value">{{ value.bytes.clientReadBytes }} -> client <- {{ value.bytes.clientWriteBytes }} ROUTER {{ value.bytes.serverWriteBytes }} -> server <- {{ value.bytes.serverReadBytes }}</div>
            </div>
          </div>
          <div>{{ tcpRouterError }}</div>
        </div>
        
      ......(중간 생략)......
      
    </main>
  </div>
</template>

<script>
import { ipcRenderer } from 'electron'

  export default {
    name: 'landing-page',
    data() {
      return {
        tcpRouterError : "",
        tcpRouterState: {},
      }
    },
    created() {
        ipcRenderer.on("tcp-router-state", (event, args) => {
          // console.log(args);
          this.tcpRouterState = args;
        });
        ipcRenderer.on("tcp-router-error", (event, args) => {
          // console.log(args);
          this.tcpRouterError = args;
        });
        
        ipcRenderer.send("tcp-router-state-init", "");
    },
    destroyed() {
        ipcRenderer.send("tcp-router-state-end", "");
    }
  }
</script>

......(이하 생략)......

 

출력 화면(정상)

아래 화면은 TCP Router 실행 시 서버 정보와 현재 접속한 클라이언트수, 데이터 전송량을 출력한 예시입니다.

출력 화면(오류)

 

오류 메시지 출력

 

글을 마치며

지금까지 Electron 애플리케이션에 RCP Router 기능을 붙여보았습니다. ipcMain, ipcRenderer 모듈의 사용방법과

events.EventEmitter 모듈 기능을 적절하게 활용한다면 초보자 개발자(적어도 웹 애플리케이션 개발자)도 웬만한 네이티브 애플리케이션 작성이 가능할 것입니다.

 

다음 시간에는 메일 화면의 GUI를 구성하고 조금 더 구체적으로 정보를 출력해보고 다이얼로그를 이용하여 환경설정 화면을 작성해보겠습니다.

 

참고자료

 

소스코드

본 포스트 관련 소스코드는 여기에서 다운로드 가능합니다.

 

 

 

 

728x90