다. 키보드, 마우스 이벤트 송수신

이번에는 제어하는 호스트의 키보드와 마우스 이벤트를 전송하고 제어당하는 호스트에서 이를 수신하는 부분을 구현합시다.

먼저 제어하는 호스트의 키보드와 마우스 이벤트를 전송하는 부분을 구현합시다. 먼저 프로젝트에 SendEventClient 클래스를 추가하세요.

전송하고 수신할 메시지 종류를 열거형으로 정의합시다.

public enum MsgType
{
    MT_KDOWN = 1, MT_KEYUP, MT_M_LEFTDOWN,
    MT_M_LEFTUP, MT_M_RIGHTUP, MT_M_RIGHTDOWN,
    MT_M_MIDDLEDOWN, MT_M_MIDDLEUP, MT_M_MOVE
}
public class SendEventClient
{

상대측 IPEndPoint를 기억하는 멤버를 선언하세요.

    IPEndPoint ep;

생성자에서는 상대측 IP와 포트를 입력인자로 받아서 IPEndPoint 개체를 생성합니다.

    public SendEventClient(string ip, int port)
    {
        ep = new IPEndPoint(IPAddress.Parse(ip), port);
    }

키를 누른 메시지를 전송하는 메서드를 구현합시다.

    public void SendKeyDown(int key)
    {

먼저 전송할 메시지 종류를 버퍼에 기록합니다.

        byte[] data = new byte[9];
        data[0] = (byte)MsgType.MT_KDOWN;

그리고 누를 키를 byte 배열에 설정합니다. 맨 앞의 원소는 메시지 종류이므로 인덱스 1에서 4바이트에 설정해야 합니다. int 형식의 값을 byte 배열로 변환하여 설정합니다.

        Array.Copy(BitConverter.GetBytes(key), 0, data, 1, 4);

이제 버퍼를 전송합니다. 버퍼를 전송하는 부분은 별도의 메서드 SendData를 정의합시다.

        SendData(data);
    }
    private void SendData(byte[] data)
    {

먼저 TCP 소켓을 생성하여 연결합니다.

        Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream,
                                 ProtocolType.Tcp);
        sock.Connect(ep);

입력 인자로 받은 버퍼를 전송하고 연결을 끊습니다.

        sock.Send(data);
        sock.Close();
    }

키를 뗀 메시지를 전송하는 메서드도 정의합시다. 구현 방법은 SendKeyDown과 비슷합니다.

    public void SendKeyUp(int key)
    {

먼저 메시지 종류를 버퍼의 맨 앞에 설정합니다.

        byte[] data = new byte[9];
        data[0] = (byte)MsgType.MT_KEYUP;

그리고 키를 버퍼에 추가하여 전송합니다.

        Array.Copy(BitConverter.GetBytes(key), 0, data, 1, 4);
        SendData(data);
    }

마우스를 눌렀을 때의 메시지를 전송하는 메서드를 정의합시다.

    public void SendMouseDown(MouseButtons mouseButtons)
    {

버튼 종류에 따라 메시지 종류를 설정합니다.

        byte[] data = new byte[9];
        switch (mouseButtons)
        {
            case MouseButtons.Left:  data[0] = (byte)MsgType.MT_M_LEFTDOWN; break;
            case MouseButtons.Right:  data[0] = (byte)MsgType.MT_M_RIGHTDOWN; break;
            case MouseButtons.Middle  data[0] = (byte)MsgType.MT_M_MIDDLEDOWN; break;
        }

그리고 data를 전송합니다.

        SendData(data);
    }

마우스를 떼었을 때의 메시지를 전송하는 메서드를 정의합시다.

    public void SendMouseUp(MouseButtons mouseButtons)
    {

버튼 종류에 따라 메시지 종류를 설정한 후에 메시지를 전송합니다.

        byte[] data = new byte[9];
        switch (mouseButtons)
        {
            caseMouseButtons.Left:  data[0]=(byte)MsgType.MT_M_LEFTUP; break;
            case MouseButtons.Right:  data[0] = (byte)MsgType.MT_M_RIGHTUP; break;
            case MouseButtons.Middle:  data[0] = (byte)MsgType.MT_M_MIDDLEUP; break;
        }
        SendData(data);
    }

마우스 이동 메시지를 전송하는 메서드를 정의합시다.

    public void SendMouseMove(int x, int y)
    {

메시지 종류를 설정하고 좌표를 추가한 후에 메시지를 전송합니다.

        byte[] data = new byte[9];
        data[0] = (byte)MsgType.MT_M_MOVE;
        Array.Copy(BitConverter.GetBytes(x), 0, data, 1, 4);
        Array.Copy(BitConverter.GetBytes(y), 0, data, 5, 4);
        SendData(data);
    }
}

메시지를 수신하는 서버에서는 수신한 버퍼의 내용을 분석하는 부분이 필요합니다. 여기에서는 Meta 클래스를 정의하여 분석한 정보를 표현합시다.

public class Meta
{

수신한 메시지 종류를 속성으로 제공합시다.

    public MsgType Mt{    get;    private set;    }

누르거나 뗀 키를 속성으로 제공합시다.

    public int Key{    get;    private set;    }

현재 마우스 좌표를 속성으로 제공합시다.

    public Point Now{    get;    private set;    }

생성자는 수신한 버퍼를 입력 인자로 받습니다.

    public Meta(byte[] data)

    {

버퍼의 맨 앞에는 메시지 종류가 있습니다.

        Mt = (MsgType)data[0];

메시지 종류에 따라 키에 관한 메시지와 마우스 이동에 관한 메시지를 분석합니다. 마우스를 누르거나 떼었을 때는 메시지 종류 외에 다른 부가적인 메시지가 없습니다.

        switch (Mt)
        {
            case MsgType.MT_KDOWN:
            case MsgType.MT_KEYUP: MakingKey(data); break;
            case MsgType.MT_M_MOVE: MakingPoint(data); break;
        }
    }

마우스 이동일 때 현재 좌표를 분석하는 메서드를 정의합시다.

    private void MakingPoint(byte[] data)
    {

X 좌표는 버퍼 인덱스 1~4, Y 좌표는 버퍼 인덱스 5~8에 있습니다.

        Point now = new Point(0, 0);
        now.X = (data[4] << 24) + (data[3] << 16) + (data[2] << 8) + (data[1]);
        now.Y = (data[8] << 24) + (data[7] << 16) + (data[6] << 8) + (data[5]);
        Now = now;
    }

키보드를 누르거나 떼었을 때의 키를 분석하는 메서드를 정의합시다.

    private void MakingKey(byte[] data)
    {

키는 버퍼 인덱스 1~4에 있습니다.

        Key = (data[4] << 24) + (data[3] << 16) + (data[2] << 8) + (data[1]);
    }
}

키보드와 마우스 이벤트 발생 메시지를 수신하였을 때 이를 통보하기 위해 이벤트 방식을 사용할 것입니다. 이를 위해 RecvMEEventArgs 클래스를 추가하세요.

public class RecvKMEEventArgs : EventArgs
{

수신한 메시지를 분석한 Meta 개체를 속성으로 제공합시다.

    public Meta Meta{    get;    private set;    }

사용하기 쉽게 키와 좌표, 메시지 종류도 속성으로 제공합니다.

    public int Key
    {
        get{    return Meta.Key;    }
    }

    public Point Now
    {
        get{    return Meta.Now;    }
    }

    public MsgType MT
    {
        get{    return Meta.Mt;    }
    }

생성자에서는 분석한 Meta 개체를 입력 인자로 받아 속성을 설정합니다.

    internal RecvKMEEventArgs(Meta meta)
    {
        Meta = meta;
    }
}

이벤트 형식에 사용할 대리자를 정의합시다.

public delegate void RecvKMEEventHandler(object sender, RecvKMEEventArgs e);

이제 RecvEventServer 클래스를 정의합시다.

public class RecvEventServer
{

서버의 Listen 소켓을 멤버로 선언합니다.

    Socket lis_sock;

메시지를 수신하였을 때 처리를 위해 이벤트를 멤버로 선언합니다.

    public event RecvKMEEventHandler RecvedKMEvent = null;

클라이언트의 연결 요청과 수용을 위한 스레드를 멤버로 선언합시다.

    Thread th;

생성자 메서드는 자신의 IP와 포트를 입력 인자로 받습니다.

    public RecvEventServer(string ip, int port)
    {

입력 인자로 받은 정보로 IPEndPoint 개체를 생성하여 TCP 소켓을 생성, 네트워크 인터페이스와 결합 및 Back 로그 큐를 설정합니다.

        IPAddress ipaddr = IPAddress.Parse(ip);
        IPEndPoint ep = new IPEndPoint(ipaddr, port);
        lis_sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        lis_sock.Bind(ep);
        lis_sock.Listen(5);

클라이언트 연결 요청을 수용을 위한 스레드를 생성 및 가동합니다.

        ThreadStart ts = new ThreadStart(AcceptLoop);
        th = new Thread(ts);
        th.Start();
    }

클라이언트 연결 요청 및 수용하는 메서드를 구현합시다. 메서드 내에서 메시지를 수신하는 부분을 비동기 처리를 위해 대리자를 정의할게요.

    public delegate void ReceiveLoopDele(Socket dosock);
    void AcceptLoop()
    {
        Socket do_sock;

메시지를 수순하는 부분을 처리할 대리자 개체를 생성합니다.

        ReceiveLoopDele rld = new ReceiveLoopDele(ReceiveLoop);
        try
        {

클라이언트 연결 요청 및 수용하는 부분은 무한 반복합니다.

            while (true)
            {

ListenSocket의 Accept 메서드를 호출합니다.

                do_sock = lis_sock.Accept();

그리고 메시지를 수신하는 대리자를 수행합니다.

                rld.BeginInvoke(do_sock, null, null);
            }
        }

예외가 발생하면 Close 메서드를 호출하여 해제화합니다.

        catch
        {
            Close();
        }
    }

메시지를 수신하는 메서드를 구현합시다.

   void ReceiveLoop(Socket dosock)
    {

먼저 크기가 9인 버퍼를 생성하여 메시지를 수신합니다. 클라이언트 측에서는 전송할 메시지 종류에 관계없이 고정길이 버퍼에 데이터를 설정하여 보내게 구현하였습니다.

        byte[] buffer = new byte[9];
        int n = dosock.Receive(buffer);

메시지 수신 이벤트 핸들러가 있으면 이벤트를 발생합니다.

       if (RecvedKMEvent != null)
        {
            RecvedKMEvent(this, new RecvKMEEventArgs(new Meta(buffer)));
        }

소켓을 닫습니다.

        dosock.Close();
    }

메시지 수신 서버를 닫는 메서드를 제공합시다.

    public void Close()
    {

스레드와 Listen 소켓을 해제합니다.

        if (th != null){    th = null;    }
        if (lis_sock != null){    lis_sock.Close();    lis_sock = null;    }
    }
}
using System;
using System.Net;
using System.Net.Sockets;
using System.Windows.Forms;
namespace 원격제어기
{
    public enum MsgType
    {
        MT_KDOWN = 1, MT_KEYUP, MT_M_LEFTDOWN,
        MT_M_LEFTUP, MT_M_RIGHTUP, MT_M_RIGHTDOWN,
        MT_M_MIDDLEDOWN, MT_M_MIDDLEUP, MT_M_MOVE
    }
    public class SendEventClient
    {
        IPEndPoint ep;
        public SendEventClient(string ip, int port)
        {
            ep = new IPEndPoint(IPAddress.Parse(ip), port);
        }
        public void SendKeyDown(int key)
        {
            byte[] data = new byte[9];
            data[0] = (byte)MsgType.MT_KDOWN;
            Array.Copy(BitConverter.GetBytes(key), 0, data, 1, 4);
            SendData(data);
        }
        private void SendData(byte[] data)
        {
            Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, 
                                     ProtocolType.Tcp);
            sock.Connect(ep);
            sock.Send(data);
            sock.Close();
        }
        public void SendKeyUp(int key)
        {
            byte[] data = new byte[9];
            data[0] = (byte)MsgType.MT_KEYUP;
            Array.Copy(BitConverter.GetBytes(key), 0, data, 1, 4);
            SendData(data);
        }
        public void SendMouseDown(MouseButtons mouseButtons)
        {
            byte[] data = new byte[9];
            switch (mouseButtons)
            {
                case MouseButtons.Left:  data[0] = (byte)MsgType.MT_M_LEFTDOWN; break;
                case MouseButtons.Right: data[0] = (byte)MsgType.MT_M_RIGHTDOWN; break;
                case MouseButtons.Middle:data[0]=(byte)MsgType.MT_M_MIDDLEDOWN;break;
            }
            SendData(data);
        }
        public void SendMouseUp(MouseButtons mouseButtons)
        {
            byte[] data = new byte[9];
            switch (mouseButtons)
            {
                case MouseButtons.Left: data[0] = (byte)MsgType.MT_M_LEFTUP;  break;
                case MouseButtons.Right: data[0] = (byte)MsgType.MT_M_RIGHTUP; break;
                case MouseButtons.Middle: data[0] = (byte)MsgType.MT_M_MIDDLEUP; break;
            }
            SendData(data);
        }
        public void SendMouseMove(int x, int y)
        {
            byte[] data = new byte[9];
            data[0] = (byte)MsgType.MT_M_MOVE;
            Array.Copy(BitConverter.GetBytes(x), 0, data, 1, 4);
            Array.Copy(BitConverter.GetBytes(y), 0, data, 5, 4);
            SendData(data);
        }
    }
}

[소스 9.7] SendEventClient.cs

using System.Drawing;
namespace 원격제어기
{
    public class Meta
    {
        public MsgType Mt{    get;    private set;    }
        public int Key{     get;     private set;    }
        public Point Now{     get;     private set;     }
        public Meta(byte[] data)
        {
            Mt = (MsgType)data[0];
            switch (Mt)
            {
                case MsgType.MT_KDOWN:
                case MsgType.MT_KEYUP:
                    MakingKey(data); break;
                case MsgType.MT_M_MOVE:
                    MakingPoint(data); break;
            }
        }
        private void MakingPoint(byte[] data)
        {
            Point now = new Point(0, 0);
            now.X = (data[4] << 24) + (data[3] << 16) + (data[2] << 8) + (data[1]);
            now.Y = (data[8] << 24) + (data[7] << 16) + (data[6] << 8) + (data[5]);
            Now = now;
        }
        private void MakingKey(byte[] data)
        {
            Key = (data[4] << 24) + (data[3] << 16) + (data[2] << 8) + (data[1]);
        }
    }
}

[소스 9.8] Meta.cs

using System;
using System.Drawing;
namespace 원격제어기
{
    public class RecvKMEEventArgs : EventArgs
    {
        public Meta Meta{    get;    private set;    }
        public int Key
        {
            get
            {
                return Meta.Key;
            }
        }
        public Point Now
        {
            get
            {
                return Meta.Now;
            }
        }
        public MsgType MT
        {
            get
            {
                return Meta.Mt;
            }
        }
        internal RecvKMEEventArgs(Meta meta)
        {
            Meta = meta;
        }
    }
    public delegate void RecvKMEEventHandler(object sender, RecvKMEEventArgs e);
}

[소스 9.9] RecvKMEEventArgs.cs

using System.Net.Sockets;
using System.Threading;
using System.Net;

namespace 원격제어기
{
    public class RecvEventServer
    {
        Socket lis_sock;
        public event RecvKMEEventHandler RecvedKMEvent = null;
        Thread th;
        public RecvEventServer(string ip, int port)
        {            
            IPAddress ipaddr = IPAddress.Parse(ip);
            IPEndPoint ep = new IPEndPoint(ipaddr, port);
            lis_sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, 
                                 ProtocolType.Tcp);
            lis_sock.Bind(ep);
            lis_sock.Listen(5);
            ThreadStart ts = new ThreadStart(AcceptLoop);
            th = new Thread(ts);
            th.Start();
        }
        public delegate void ReceiveLoopDele(Socket dosock);
        void AcceptLoop()
        {
            Socket do_sock;
            ReceiveLoopDele rld = new ReceiveLoopDele(ReceiveLoop);
            try
            {
                while (true)
                {
                    do_sock = lis_sock.Accept();
                    rld.BeginInvoke(do_sock, null, null);
                }
            }
            catch{    Close();    }
        }
        void ReceiveLoop(Socket dosock)
        {
            byte[] buffer = new byte[9];
            int n = dosock.Receive(buffer);
            if (RecvedKMEvent != null)
            {
                RecvedKMEvent(this, new RecvKMEEventArgs(new Meta(buffer)));
            }
            dosock.Close();
        }

        public void Close()
        {
            if (th != null)
            {
                th = null;
            }
            if (lis_sock != null)
            {
                lis_sock.Close();
                lis_sock = null;
            } 
        }
    }
}

[소스 9.10] RecvEventServer.cs