Ocean Optics 사의 OmniDriver 제어코드 - C#

Software/C#|2023. 2. 17. 09:27
반응형

Ocean Optics 사의 스펙트로메타 사용을 위해 제공되는 OmniDriver 를 C#으로 제어하기 위한 코드 입니다. OmniDriver 의 경우 JAVA 로 개발되었고 타 언어에서는 이것을 사용하기 위해 Wrapper 용 DLL 을 별도로 제공합니다. 

 

1. OmniDriver 를 설치하시면 C:\Program Files\Ocean Optics\OmniDriverSPAM\OOI_HOME 폴더에 NETOmniDriver-NET40.DLL 파일이 있습니다. 이 파일을 참조로 추가하신 후 사용하면 됩니다. 

   * 참고로 OmniDriver 설치 후 제공되는 매뉴얼에는 NETOmniDriver.DLL 이라고 명시되어 있는데 이 파일이 아니고 위에 써 있는 파일입니다.

   * 또한 참조 추가시에 프로젝트 참조로 하시지 마시고 COM 참조에서 찾아 보기를 통해 해당 파일을 찾아서 등록하셔야 합니다. 

Omni Driver 참조 추가 방법
Omni Driver 참조 추가 방법

2. 위에 처럼 등록하시면 솔루션 탐색기에서는 아래와 같이 종속성의 어셈블리안에 등록이 됩니다. 이렇게 참조추가가 안돼고 다른 위치에 등록이 되면 사용이 안돼더라고요.

OmniDriver 참조 추가
OmniDriver 참조 추가

3. 아래는 OmniDriver 를 제어하기 위한 클래스 코드 입니다. 

   * 이 클래스의 경우 한개의 SpectroMeter 를 사용하는 전제로 코드를 만들었습니다.

   * 여러개의 SpectroMeter 를 사용할 경우 코드를 수정해서 사용하시든 아니면 아래 클래스를 사용하고 객체를

      SpectroMeter 갯수 만큼 만들어 사용하시면 되겠습니다. 

using OmniDriver;

namespace OmniDriver
{
    public struct CalData
    {
        public double IntegTime = 1000;     // unit = [usec]
        public double[] dark;
        public double[] Reference;

        public CalData(int PixelCount)
        {
            dark = new double[PixelCount];
            Reference = new double[PixelCount];
        }
    }
    //-------------------------------------------------------------------------------------------------------
    public struct SpectroMeterParams
    {
        public int IntegrationTime { get; set; } = 1000;    // unit = [usec]
        public int BoxcarWidth { get; set; } = 3;
        public int ScansToAverage { get; set; } = 2;
        public int CorrectForElectricalDark { get; set; } = 0;   // 전기적 노이즈 제거 여부 : 0 or 1 
        public SpectroMeterParams() { }
        public SpectroMeterParams(int IntegTime, int BoxcarWidth, int ScanToAverage, int CorrectForElectricalDark)
        {
            this.IntegrationTime = IntegTime;
            this.BoxcarWidth = BoxcarWidth;
            this.ScansToAverage = ScanToAverage;
            this.CorrectForElectricalDark = CorrectForElectricalDark;
        }
    }
    //-------------------------------------------------------------------------------------------------------
    public class CSpectroMeter
    {
        private readonly OmniDriver.NETWrapper Wrapper;
        private readonly int Index;
        private CalData CalibrationData;

        private int _PixelCount;
        public int PixelCount { get { return _PixelCount; } }
        public string Serial { get { return Wrapper.getSerialNumber(Index); } }
        public int IntegrationTime
        {
            set { Wrapper.setIntegrationTime(Index, value); }
            get { return Wrapper.getIntegrationTime(Index); }
        }
        public int BoxcarWidth
        {
            set { Wrapper.setBoxcarWidth(Index, value); }
            get { return Wrapper.getBoxcarWidth(Index); }
        }
        public int ScansToAverage
        {
            set { Wrapper.setScansToAverage(Index, value); }
            get { return Wrapper.getScansToAverage(Index); }
        }
        public int CorrectForElectricalDark
        {
            set { Wrapper.setCorrectForElectricalDark(Index, value); }
            get { return Wrapper.getCorrectForElectricalDark(Index); }
        }
        public CSpectroMeter() 
        {
            Wrapper = new OmniDriver.NETWrapper();
            Index = 0;
        }
        //---------------------------------------------------------------------------------------------------
        public CSpectroMeter(OmniDriver.NETWrapper wrapper, int index)
        {
            this.Wrapper = wrapper;
            this.Index = index;
        }
        //---------------------------------------------------------------------------------------------------
        public string getSerialNumber()
        {
            var serial = Wrapper.getSerialNumber(Index);
            return serial;
        }
        //---------------------------------------------------------------------------------------------------
        public bool connect()
        {
            bool result = false;

            var count = Wrapper.openAllSpectrometers();
            if (count >= 0)
            {
                result = true;
                _PixelCount = Wrapper.getNumberOfDarkPixels(Index);
                CalibrationData = new CalData(_PixelCount);
            }

            return result;
        }
        //---------------------------------------------------------------------------------------------------
        public void getSpectrum(ref double[] Wavelength, ref double[] Spectrum)
        {
            Spectrum = Wrapper.getSpectrum(Index);        // 스펙트로메타에서 측정된 RAW Intensity 값 (Y 축)
            Wavelength = Wrapper.getWavelengths(Index);   // 스펙트로메타의 파장 데이타 (X 축)
        }
        //---------------------------------------------------------------------------------------------------
        public void getIrradianceData(ref double[] Wavelength, ref double[] Irradiance)
        {
            getSpectrum(ref Wavelength, ref Irradiance);

            for(int loop=0; loop < Irradiance.Length; loop++) 
            {
                if (Params.CorrectForElectricalDark == 0) Irradiance[loop] = Irradiance[loop] - CalibrationData.dark[loop];

                Irradiance[loop] = (Irradiance[loop] * CalibrationData.Reference[loop]) * (Params.IntegrationTime / CalibrationData.IntegTime);
            }
        }
        //---------------------------------------------------------------------------------------------------
        public void setSpectrometerParams(SpectroMeterParams Params)
        {
            Wrapper.setIntegrationTime(Index, Params.IntegrationTime);
            Wrapper.setScansToAverage(Index, Params.ScansToAverage);
            Wrapper.setBoxcarWidth(Index, Params.BoxcarWidth);
            Wrapper.setCorrectForElectricalDark(Index, Params.CorrectForElectricalDark);
        }
        //---------------------------------------------------------------------------------------------------
        public void close()
        {
            Wrapper.closeSpectrometer(Index);
        }
        //---------------------------------------------------------------------------------------------------
        public virtual bool readCalibrationFile(string filename) 
        {
            bool result = true;

            if (!File.Exists(filename)) result = false;
            else
            {
                using (StreamReader sr = new StreamReader(filename))
                {
                    int index = 0;
                    string[] split;
                    string line;

                    line = sr.ReadLine();  // Integration Time of reference sample
                    if (line is not null)
                    {
                        split = line.Split(',');
                        CalibrationData.IntegTime = double.Parse(split[1]);
                    }

                    sr.ReadLine();  // Header

                    while ((line = sr.ReadLine()) != null)
                    {
                        split = line.Split(',');
                        CalibrationData.dark[index] = double.Parse(split[1]);
                        CalibrationData.Reference[index] = double.Parse(split[2]);

                        index++;
                    }
                }
            }

            return result;
        }
        //---------------------------------------------------------------------------------------------------
    }
}
반응형

댓글()

ScottPlot (Growing Data - #2)

Software/C#|2023. 2. 16. 13:23
반응형

지난 포스팅에서 ScottPlot 의 Growing Data 사용 방법에 대해 기술했습니다.  그러나 기본적으로 제공하는 사용방법이 불편하기도 할 뿐더러 코드도 길고 메모리 Overflow 의 위험성도 있어서 사용 방법을 변경해서 기술해 보았습니다. 

 

* 데이타 버퍼로 배열을 사용하지 않고 List 를 사용하여 메모리 누수를 예방했습니다. 

* 지난번의 문제점으로 지적된 X 축의 증가 Step을 조정할 수 없는 단점을 제거하였습니다.

* 코드도 훨씬 간결해 졌습니다. 

 

1. 데이타저장을 위한 클래스를 하나 구현해 놓습니다. 지난번과는 다르게 데이타 갯수를 알려주는 Index 가 없어졌고 X축의 값을 저장하기 위한 버퍼가 추가 되었습니다. 

public class MeasuredData
{
    public readonly List<double> XAxis;    // X축 데이타 저장 버퍼
    public readonly List<double> YAxis;     // Y축 데이타 저장 버퍼

    public MeasuredData()
    {
        XAxis = new List<double>();
        YAxis = new List<double>();
    }

    public void Reset()
    {
        XAxis.Clear();
        YAxis.Clear();
    }
}

 

2. 타이머를 이용하여 랜덤으로 데이타를 생성하게 하였습니다. 

  * 기본 기능과 다른 점은 X축의 간격을 임의로 넣을 수 있다는 점 입니다. 

  * 코드량도 작아지고 사용도 편리해 졌습니다. 

public partial class Form1 : Form
{
    private readonly  MeasuredData measuredData = new MeasuredData();
    private Random random = new Random();
    private double XValue = 0;      // X 축의 값을 임의로 만들기 위해 임시로 만든 변수 입니다. (실제로는 측정 상황에서의 값이 되겠네요)

    public Form1()     //  지난번과 다르게 생성자에서 특별히 구현해 줄게 없습니다. 
    {
        InitializeComponent();     
    }
    //---------------------------------------------------------------------------------------------------
    private void timer1_Tick(object sender, EventArgs e)
    {
        formsPlot1.Plot.Clear();    // graph 출력을 클리어 합니다. 

        measuredData.XAxis.Add(XValue);                              // X 축 데이타 입력
        measuredData.YAxis.Add(random.NextDouble());       // Y 축 데이타 입력  

        formsPlot1.Plot.AddScatterLines(measuredData.XAxis.ToArray(), measuredData.YAxis.ToArray()); // List 를 Array 로 변경하여 그래프 출력

        formsPlot1.Render();    // 그래프 출력 화면 갱신

        XValue += 0.3;    
    }

    private void button1_Click(object sender, EventArgs e)
    {
        measuredData.Reset();      // 그래프 데이타 초기화

        timer1.Enabled = true;    // 타이머 시작 
    }
    //---------------------------------------------------------------------------------------------------
}

 

3. 출력 결과 화면 입니다. 

Growing Data 출력결과 (X축 추가)
Growing Data 출력결과 (X축 추가)

그런데 매번 그래프를 갱신하면서 그리는 것이라 속도면에서는 어떨지 잘모르겠네요.. 그런데 ScottPlot 가 그리는 속도가 꽤 빠르긴 합니다. 크게 속도를 요하는 것이 아니라면 이 방법도 괜찮아 보입니다. 

반응형

댓글()

ScottPlot (Growing Data - #1)

Software/C#|2023. 2. 16. 11:40
반응형

C# 으로 개발툴을 변경하면서 고민중에 하나가 자주 사용하는 차트 컴포넌트를 어떻게 할 것인가 였는데 ScottPlot 컴포넌트를 발견하고는 유레카를 외쳤습니다.. 무료인데다 기능도 아주 뛰어나고 사용성에 있어서도 만족사용 하게 되었습니다.. 무엇보다도 Multi Axis 을 많이 사용하는 제 입장에서는 너무 반가운 컴포넌트 였습니다. 

하지만 딱 한가지 아쉬운 부분이 시간의 경과에 따라 데이타가 늘어나는 그래프를 그릴 경우가 많이 있는데 특히 번인장비의 경우 시간에 따른 샘플의 특성 변화를 그래프로 실시간 보여주게 되는데 이게 ScottPlot 에서는 좀 간단하지가 않습니다. 

이번 장에서는 기본적으로 ScottPlot 에서 Growing Data를 그리는 방법을 코딩합니다. ScottPlot 예제에서 나오는 방법을 조금 수정해서 코딩하였습니다. 

 

 

1. 먼제 아래와 같이 측정데이타를 저장할 Class를 하나 만들어 줍니다.

public class MeasuredData
{
    public readonly double[] YAxis;    // 실제 측정데이타가 저장될 배열
    public int index { get; set; } = 0;    // 측정된 데이타의 갯수에 대한 index

    public MeasuredData()
    {
        YAxis = new double[10000];  // 배열의 갯수는 측정될 데이타의 최대 갯수보다 크게 설정 합니다. ( 이부분이 좀 문제)
    }
}

 

2. 타이머를 이용하여 주기적으로 데이타가 랜덤으로 생성되도록 코드를 작성하여 테스트 했습니다.

public partial class Form1 : Form
{
    private readonly  MeasuredData measuredData = new MeasuredData();
    private Random random = new Random();
    private readonly ScottPlot.Plottable.SignalPlot SignalPlot;  // 출력 데이타의 시그날 위치를 제어하게 됩니다. 
    //---------------------------------------------------------------------------------------------------
    public Form1()
    {
        InitializeComponent();
        SignalPlot = formsPlot1.Plot.AddSignal(measuredData.YAxis);  // 실제 화면에 표시될 시그날 정보와 생성된 시스날을 맵핑합니다. 
        SignalPlot.MaxRenderIndex = 0;                                                  // 출력될 시그날의 초기 위치를 설정(이걸 안하면 10000개의 데이타가 다 보입니다.)
        formsPlot1.Plot.SetAxisLimits(0, 10, -2, 2);     // 그래프 출력 Limit 설정  

        formsPlot1.Refresh();   // 이 부분을 안하면 ScottPlot 그래프 화면에 오류메시지가 보입니다. 
    }
   //---------------------------------------------------------------------------------------------------
    private void timer1_Tick(object sender, EventArgs e)      // 타이머 이벤트( 1초에 한번씩 데이타 생성하도록 했습니다.)
    {
        double currentRightEdge = formsPlot1.Plot.GetAxisLimits().XMax;   // 현재 그래프의 최대 X축의 값을 가져 옵니다. 
        if (measuredData.index > currentRightEdge)           // X축의 최대값이 데이타 갯수보다 작으면
        {
            formsPlot1.Plot.SetAxisLimits(xMax: currentRightEdge + 10);    // X 축의 최대값을 측정데이타 보다 10 step 많게 설정합니다. 
        }

        measuredData.YAxis[measuredData.index] = random.NextDouble();    // 랜덤으로 만들어진 값을 측정 버퍼에 넣습니다. 
        SignalPlot.MaxRenderIndex = measuredData.index++ ;                        // 그래프 화면에서 측정된 데이타 갯수 만큼만 그려 줍니다.  

        formsPlot1.Render();                                                                               // 그래프 화면 갱신 
    }
   //---------------------------------------------------------------------------------------------------
    private void button1_Click(object sender, EventArgs e)
    {
        timer1.Enabled = true;    // 타이머 시작 
    }
}

 

3. 실제 화면 에서는 아래와 같이 출력이 됩니다. 

Growing Data 출력 결과
Growing Data 출력 결과

Growing Data 출력 결과

4. 이 Growing Data 츨력은 아쉬운 부분이 좀 있습니다.

 

  (1) 이것저것 사용이 좀 번거롭다

  (2)  X 축의 증감부분을 선택할 수 없다는 것입니다. (이 부분이  가장 아쉽네요.)  무조건 1씩 증가 입니다.

  (3) 버퍼의 크기를 미리 설정해 놓아야 하는데 이게 잘못해서 실제 데이타 보다 적게 설정해 놓으면 overflow 문제가 발생할 수 있습니다.        ScottPlot 매뉴얼에도 이 부분이 명시되어 있는데 overflow 문제에 대한 오류처리를 잘 설계하거나 또는 버퍼를 충분히 설정해야 합니다.

 

 

반응형

댓글()

C# TCP/IP 를 이용한 Client 클래스

Software/C#|2023. 2. 13. 10:53
반응형

지난 글에서 멀티 Thread 를 이용한 서버를 구현했고 이번에는 Client 모듈을 클래스로 구현해 보았습니다. 

서버와의 연결 및 해제에 대한 이벤트와 데이타 수신에 대한 이벤트를 발생하도록 구현되어 있습니다. Client 객체를 생성한 후에 데이타 수신 이벤트를 구현하여 수신된 데이타를 원하는 목적으로 사용할 수 있습니다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Client
{
    public class ServerEventArgs : EventArgs
    {
        public string text { get; }    // 파라메터로 넘겨 줄 데이타
        public ServerEventArgs(string text)   // 생성자에서 변경된 Text 정보를 넘겨받는다.
        {
            this.text = text;
        }
    }
    internal class Client
    {
        public delegate void ClientEventHandler(object sender, ServerEventArgs e);
        public delegate void ServerDisConnectHandler(object sender, EventArgs e);

        private NetworkStream? stream = null ;
        private StreamReader? reader = null;
        private StreamWriter? writer = null;         

        private readonly TcpClient? client = null;
        public string ServerIP { set; get; } = "127.0.0.1";
        public int ServerPort { set; get; }

        private bool _connected = false;
        public event ClientEventHandler? OnDataReceived = null;
        public event ClientEventHandler? OnServerConnected = null;
        public event ServerDisConnectHandler? OnServerDisconnected = null;
        //--------------------------------------------------------------------------------------------------------------
        public bool Connected 
        {
            get { return _connected; } 
        }
        //--------------------------------------------------------------------------------------------------------------
        public Client()
        {
            var clientIP = GetLocalIP();
            var port = 10001;

            if (clientIP != null)
            {
                IPEndPoint clientAddress = new IPEndPoint(IPAddress.Parse(clientIP.ToString()), port);
                client = new TcpClient(clientAddress);
            }
        }
        //--------------------------------------------------------------------------------------------------------------
        public Client(string serverIP, int serverPort)
        {
            ServerIP = serverIP;
            ServerPort = serverPort;

            var clientIP = GetLocalIP();
            var port = 10001;

            if (clientIP != null)
            {
                IPEndPoint clientAddress = new IPEndPoint(IPAddress.Parse(clientIP.ToString()), port);
                client = new TcpClient(clientAddress);
            }
        }
        //--------------------------------------------------------------------------------------------------------------
        private static string GetLocalIP()
        {
            IPHostEntry host = Dns.GetHostEntry(Dns.GetHostName());
            string ip = string.Empty;

            foreach (var address in host.AddressList)
            {
                if (address.AddressFamily == AddressFamily.InterNetwork)
                {
                    ip = address.ToString();
                    break;
                }
            }

            return ip;
        }
        //--------------------------------------------------------------------------------------------------------
        public void Connect()
        {
            IPEndPoint serverAddress = new IPEndPoint(IPAddress.Parse(ServerIP), ServerPort);

            client?.Connect(serverAddress);
            _connected = (bool)client?.Connected!;

            if(_connected) 
            { 
                stream = client.GetStream();
                reader = new StreamReader(stream, Encoding.GetEncoding("utf-8"));
                writer = new StreamWriter(stream, Encoding.GetEncoding("utf-8")) { AutoFlush = true };

                if(OnServerConnected is not null)
                {
                    OnServerConnected(this, new ServerEventArgs(serverAddress.ToString()));     // 서버 연결에 대한 이벤트 함수 실행 
                }

                Thread receive_thread = new Thread(ReceiveMessage);
                receive_thread.Start();
            }
        }
        //--------------------------------------------------------------------------------------------------------------
        public void Disconnect()
        {
            if(_connected)
            {
                _connected = false;

                reader?.Close();
                writer?.Close();
                stream?.Close();

                client?.Close();
            }
        }
        //--------------------------------------------------------------------------------------------------------------
        public void SendMessage(string message)
        {
            writer?.WriteLine(message);
        }
        //--------------------------------------------------------------------------------------------------------------
        private void ReceiveMessage()
        {
            string? message;

            while(_connected)
            {
                try
                {
                    message = reader?.ReadLine();

                    if (message == null) break;

                    if (OnDataReceived is not null)
                    {
                        OnDataReceived(this, new ServerEventArgs(message));     // 데이타 수신에 대한 이벤트 함수 실행 
                    }
                }
                catch (Exception ex)
                {
                    if (OnServerDisconnected is not null)
                    {
                        OnServerDisconnected(this, new EventArgs());     
                    }

                    break; 
                }    
            }
        }
        //--------------------------------------------------------------------------------------------------------------
    }
}

사용 방법은 아래와 같습니다. 아래 코드에서 서버와의 연결 / 해제에 대한 이벤트는 없지만 필요하면 client 클래스 코드를 보시고 추가 하시면 됩니다. 

public partial class Form1 : Form
{
    Client client = new Client("192.168.0.12", 5555);
    public Form1()
    {
        InitializeComponent();

        client.OnDataReceived += DataReceived;
    }

    private void button1_Click(object sender, EventArgs e)
    {
        client.Connect();
    }

    private void button2_Click(object sender, EventArgs e)
    {
        client.SendMessage(textBox1.Text);
    }

    private void button3_Click(object sender, EventArgs e)
    {
        client.Disconnect();
    }

    private void DataReceived(object sender, ServerEventArgs e)
    {
        // client 클래스에서 데이타 수신부가 thread로 구현되어 있기 때문에 
           메인 화면의 TextBox 에 수신된 데이타를 쓸경우 아래와 같이 Invoke를 
           사용했습니다. 
        textBox2.Invoke(() => { textBox2.Text = e.text; });
    }
}
반응형

댓글()

C# Multi Thread를 이용한 다중 Client 접속 Server

Software/C#|2023. 2. 11. 12:44
반응형

C#에서 TCP/IP 를 이용한 서버 동작을 위한 클래스를 구현했습니다.  이 서버 클래스는 다중 클라이언트 접속을 허용하도록 제작하였고 클라이언트가 접속을 하면 Thread를 생성하여 개별 클라이언트와 통신하도록 구현되어 있습니다.

이번 구현은 클라이언트와 ehco 기능으로 구현되어 있어서 클라이언트에서 서버에 접속 후 메시지를 보내면 클라이언트로 보낸 메시지가 반송되도록  코딩되어 있습니다. 

클라이언트 메시지에 대한 동작 구현은 아래 코드에서 ehco 기능의 함수 부분을 수정하시면 됩니다. 

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace Server
{
    public class ServerEventArgs : EventArgs
    {
        public string text { get; }    // 파라메터로 넘겨 줄 데이타

        public ServerEventArgs(string text)   // 생성자에서 변경된 Text 정보를 넘겨받는다.
        {
            this.text = text;
        }
    }
    //------------------------------------------------------------------------------------------------------------
    public delegate void ServerEventHandler(object sender, ServerEventArgs e);
    //------------------------------------------------------------------------------------------------------------
     public class ClientHandle
    {
        private Socket socket;
        private NetworkStream? networkstream = null;
        private StreamReader? streamReader = null;
        private StreamWriter? streamWriter = null;
        //--------------------------------------------------------------------------------------------------------
        public ClientHandle(Socket client)
        {
            this.socket = client;
            networkstream = new NetworkStream(client);

            streamReader = new StreamReader(networkstream!, Encoding.GetEncoding("utf-8"));
            streamWriter = new StreamWriter(networkstream!, Encoding.GetEncoding("utf-8")) { AutoFlush = true };
        }
        //--------------------------------------------------------------------------------------------------------
        public void Run()
        {
            string? receiveData;        // 데이타 수신이 null 이 될수 있으므로 null 참조 가능으로 선언한다. 

            while (true)
            {
                receiveData = streamReader!.ReadLine();     // NetworkStream 으로 들어오는 데이타의 끝은 CR, LF 가 필요하다. 
                                                            // CR LF 가 없으면 버퍼가 overflow 될때 까지 함수 내부에서 빠져 나오지 못한다. 
                if (receiveData is null) break;             // Client 연결이 끊어지면 null 이 리턴된다. 

                WriteClientData(receiveData);  // Echo 기능 입니다. 다은 특별한 동작이 필요하면 이코드를 삭제하고 구현하시면 됩니다. 
            }

            DisConnect();       // Client 연결과 관련된 부분을 모두 닫아 버린다. 
        }
        //--------------------------------------------------------------------------------------------------------
        public void DisConnect()
        {
            streamReader?.Close();
            streamWriter?.Close();
            networkstream?.Close();

            socket.Close();
        }
        //--------------------------------------------------------------------------------------------------------
        public void WriteClientData(string Message)
        {
            streamWriter?.WriteLine(Message);
        }
        //--------------------------------------------------------------------------------------------------------
    }
    //------------------------------------------------------------------------------------------------------------
    public class MultiClientServer
    {
        private TcpListener? server = null;
        public event ServerEventHandler? OnClientConnected = null;
        public event ServerEventHandler? OnServerClosed = null;

        public string? IpAddress { set; get; } = null;
        public int Port { set; get; } = 5000;
        //--------------------------------------------------------------------------------------------------------
        public MultiClientServer()
        {
            this.IpAddress = GetLocalIP();
            Enable();
        }
        //--------------------------------------------------------------------------------------------------------
        public MultiClientServer(int port)
        {
            this.IpAddress = GetLocalIP();
            this.Port = port;

            Enable();
        }
        //--------------------------------------------------------------------------------------------------------
        private static string GetLocalIP()
        {
            IPHostEntry host = Dns.GetHostEntry(Dns.GetHostName());
            string ip = string.Empty;

            foreach (var address in host.AddressList)
            {
                if (address.AddressFamily == AddressFamily.InterNetwork)
                {
                    ip = address.ToString();
                    break;
                }
            }

            return ip;
        }
        //--------------------------------------------------------------------------------------------------------
        public bool Enable()
        {
            if (this.IpAddress == null) { return false; }
            else
            {
                IPEndPoint local = new IPEndPoint(IPAddress.Parse(IpAddress), Port);
                server = new TcpListener(local);

                return true;
            }
        }
        //--------------------------------------------------------------------------------------------------------
        public void Stop()
        {
            server?.Stop();     // server 동작을 종료한다. 
        }
        //--------------------------------------------------------------------------------------------------------
        public void Start()
        {
            if (server is not null)
            {
                server.Start();              // Server 동작             

                Thread connect = new Thread(WaitClientConnect);  // Client 접속 대기, Data 수신
                connect.Start();         
            }
        }
        //--------------------------------------------------------------------------------------------------------
        private void WaitClientConnect()
        {
            while (true)
            {
                try
                {
                    Socket? client = server?.AcceptSocket();   // Client  접속을 기다린다. 

                    if (client is not null)
                    {
                        var ipClient = ((IPEndPoint)client.RemoteEndPoint!).ToString();  // 접속된 Client의 IP 확인 

                        if (OnClientConnected is not null) OnClientConnected(this, new ServerEventArgs(ipClient));   // Client 연결에 대한 이벤트 함수 실행 

                        Thread connectedClient = new(new ThreadStart(new ClientHandle(client).Run));  // 접속된 Client 의 객체를 생성 Thread 를 사용하여 동작 수행
                        connectedClient.Start();
                    }
                }
                catch(Exception ex)         // server 기능을 stop 하게되면 예외가 발생한다. 
                {
                    if(OnServerClosed is not null)
                    {
                        OnServerClosed(this, new ServerEventArgs(ex.ToString()));  // server 기능 멈충에 대한 이벤트 발생 
                    }

                    break;
                }
            }
        }
        //--------------------------------------------------------------------------------------------------------
    }
}

 

사용방법    

public partial class Form1 : Form
{
    private MultiClientServer server = new MultiClientServer(5000) ; //로컬 IP는 자동으로 찾고 Port 5000 으로 서버 생성

    public Form1()
    {
        InitializeComponent();
        server.OnClientConnected += Server_OnClientConnected;  // 클라이언트가 접속되었을 경우 발생하는 이벤트
    }

    private void Server_OnClientConnected(object sender, ServerEventArgs e)
    {
      // 클라이언트가 연결되면 클라이언트 IP 를 화면에 보여 줍니다.
        textClientIP.Invoke(() => { textClientIP.Text = e.text; });  
    }

    private void button1_Click(object sender, EventArgs e)
    {
        server.Start();  // 서버 동작 시작 
    }
    private void button3_Click(object sender, EventArgs e)
    {
        server.Stop();  // 서버 동작 종료
    }
}
반응형

댓글()

C# 델리게이트를 이용한 이벤트 생성 (Part-3)

Software/C#|2023. 2. 6. 14:03
반응형

이벤트 생성 3번째 입니다. 이번 예제는 이벤트 함수의 파라메타로 이벤트 발생시 데이타 정보를 전달하는 방법에 대해 설명을 합니다. 예제는 part-1 에서 사용했던 MyText 클래스를 변형하여 사용하였습니다. 

 

우선 아래와 같이 이벤트 발생시 데이타를 추가하여 파라메터로 넘겨줄 클래스를 하나 생성 합니다.
기본 클래스인 EventArgs를 상속 받아서 만드는데 EventArgs는 아무것도 넘겨줄게 없을때 사용하는 클래스 입니다. 

public class MyTextEventArgs : EventArgs
{
    public string Text { get; }    // 파라메터로 넘겨 줄 데이타

    public MyTextEventArgs(string text)   // 생성자에서 변경된 Text 정보를 넘겨받는다.
    {
        this.Text = text;
    }
}

 

델리케이트 선언에서 함수 파라메터로 위에서 만든 클래스를 사용합니다. 
그리고 이벤트 함수 실행부분에서 변경된 Text 정보를 생성자의 파라메터로 넘겨 줍니다.

public class MyText
{
    public delegate void EventHandler(object sender, MyTextEventArgs e);  // 이벤트 핸들러의 형식을 delegate로 등록한다.

    private string text = "";
    public string Text
    {
        set
        {
            text = value;
            if(OnTextChangedHandler != null)
            {
                OnTextChangedHandler(this, new MyTextEventArgs(text));  // 이벤트 함수 실행시 변경된 Text 정보를 전달합니다.
            }
        }
    }

    public event EventHandler? OnTextChangedHandler = null ;   
}

 

사용방법은 아래와 같습니다. 

public partial class Form1 : Form
{
    MyText myText = new MyText();
    public Form1()
    {
        InitializeComponent();
        myText.OnTextChangedHandler += text_Changed;
    }

    private void text_Changed(object sender, MyTextEventArgs e) 
    {
        textBox1.Text = e.Text;    // 파라메터를 통해 넘어온 변경 데이타를 이용합니다.
    }

    private void button1_Click(object sender, EventArgs e)
    {
        myText.Text = "Hello World.";        
    }
}
반응형

댓글()

C# 델리게이트를 이용한 이벤트 생성 (Part-2)

Software/C#|2023. 2. 5. 00:22
반응형

이벤트 생성 part-2 에서는 MyTimer 클래스를 만들게 됩니다. 

타이머에 시간(초단위)을 설정하고 시작을 누르면 해당시간이 지나면 이벤트를 발생시키는 예제 입니다. 타이머 클래스 내부에 Thread 를 이용하여 시간의 흐름을 감지하도록 코딩되어 있습니다.

 

public class MyTimer
{
    public delegate void EventHandler(object sender, EventArgs e);
    public event EventHandler? OnTimeoutHandler = null;
    public int time {set; get;}    // Unit is sec 

    public MyTimer(int time)
    {
        this.time = time;
    }

    public void start()
    {
        Thread myThread = new Thread(run);
        myThread.Start();  // Thread 동작
    }

    private void run()   // 타이머 동작 함수 
    {
        var timeout = time;
        while(true)
        {
            Thread.Sleep(1000);
            if (--timeout <= 0) break; 
        }

        if(OnTimeoutHandler is not null) OnTimeoutHandler(this, new EventArgs());
        // 타이머 동작 완료가 되면 이벤트 함수를 호출합니다.
    }
}

 

사용방법은 아래와 같습니다. 

    public partial class Form1 : Form
    {
        MyTimer myTimer = new MyTimer(10);   // 10초 타이머 객체 생성 
        public Form1()
        {
            InitializeComponent();
            myTimer.OnTimeoutHandler += timeout_event;  // 이벤트 실행 함수 등록 
        }

        private void timeout_event(object sender, EventArgs e)
        {
            MessageBox.Show("Timeout Message..");  // 타이머 종료시 메시지 출력
        }

        private void button1_Click(object sender, EventArgs e)
        {
            myTimer.start();   // 타이머 시작 
        }
    }

 

반응형

댓글()

C# 델리게이트를 이용한 이벤트 생성 (Part-1)

Software/C#|2023. 2. 4. 10:31
반응형

객체 내에서 발생하는 어떤 사건에 대해 이벤트를 발생시키는 코드를 만들기 위해 인터넷을 검색해 봤는데 하나같이 뭔가 2% 부족한 부분이 있는것 같아서 이참에 간단한 예제를 하나 만들어 보았습니다. 

 

part-1에서는 MyText 라는 객체를 생성하고 객체의 Text 가 변경되면 이벤트가 발생하는 형식으로 예제를 구성했습니다.

 

아래는 MyText 라는 클래스입니다.

public class MyText
{
    public delegate void EventHandler(object sender, EventArgs e);  // 이벤트 발생을 위한 함수 형식의 델리게이트 선언

    private string text = "";
    public string Text
    {
        get { return text; }
        set
        {
            text = value;
            if(OnTextChangedHandler != null)
            {
                OnTextChangedHandler(this, new EventArgs());  // Text 가 변경될 경우 이벤트 함수 실행
            }
        }
    }

    public event EventHandler? OnTextChangedHandler = null;  // 이벤트 함수 핸들러 선언
}


이제 위의 클래스를 이용해서 실제 이벤트를 발생시키는 코드를 만들어 봅니다. 

폼에 버튼 하나와 TextBox 하나를 올려 놓고 테스트를 해 보았습니다. 

    public partial class Form1 : Form
    {
        MyText myText = new MyText();   // MyText 의 객체를 생성한다. 
        public Form1()
        {
            InitializeComponent();

            myText.OnTextChangedHandler += text_Changed;  // 객체내의 Text가 변경되었을 때 수행할 이벤트 함수를 등록한다.
        }

        private void text_Changed(object sender, EventArgs e) // 이벤트 함수 구현 부분
        {
            textBox1.Text = myText.Text;   // MyText 객체의 Text를 TextBox 에 쓴다.
        }

        private void button1_Click(object sender, EventArgs e)
        {
            myText.Text = "test";  // 버튼을 클릭하면 MyText 객체의 Text를 변경한다.
        }
    }

 

 

반응형

댓글()