언어 자료구조 알고리즘/프로그래밍 실습

[C#, Windows Forms, 소켓 통신] 부재 입출고 자동 관리 창고 시스템 시뮬레이션 - Smart Factory 실습

언제나휴일 2020. 11. 27. 09:39
반응형

1. 부재 입출고 자동 관리 창고 시스템 시뮬레이션

다음 시나리오처럼 동작하는 시뮬레이션 솔루션을 구현하시오.
- 자동 관리 창고 시스템 : GUI 프로그램(Windows Forms 앱)+서버
- 중앙 관재 : GUI 프로그램(Windows Forms 앱) + 클라이언트

- 공통으로 사용할 라이브러리: 클래스 라이브러리

2. 시나리오

부재를 입고 및 출고하면 RFID에 의해 자동으로 관리해 주는 창고 시스템이다.
부재 입고 시에 RFID를 통해 부재명과 개수를 파악할 수 있다.
처음으로 입력하는 부재일 때는 부재 ID(1부터 순차 주여)를 부여한다.
창고 제어창에는 전체 부재 목록을 확인할 수 있다.
원하는 부재와 출고 개수를 지정하면 자동 출고한다.

중앙 관재를 통해 부재 입고, 부재 출고, 현황 파악이 가능하다.

3. 공통으로 사용할 라이브러리

3.1 Material 클래스 정의

부재 ID, 부재 이름, 보유 개수를 멤버로 갖는 Material 클래스 정의

namespace SF_CoreLib
{
    /// <summary>
    /// 재료 클래스
    /// </summary>
    public class Material
    {
        /// <summary>
        /// 부재 ID - 가져오기
        /// </summary>
        public int ID { get; }
        /// <summary>
        /// 부재 이름 - 가져오기
        /// </summary>
        public string Name { get; }
        /// <summary>
        /// 보유 개수 - 가져오기 및 설정하기
        /// </summary>
        public int Count { get; set; }
        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="id">부재 ID</param>
        /// <param name="name">부재 이름</param>
        /// <param name="count">보유 개수</param>
        public Material(int id, string name,int count)
        {
            ID = id;
            Name = name;
            Count = count;
        }
        /// <summary>
        /// ToString 재정의
        /// </summary>
        /// <returns>부재 이름</returns>
        public override string ToString()
        {
            return Name;
        }
    }
}

4. 자동 관리 창고 시스템 구현

3.에서 만든 라이브러리를 참조 추가합니다.

4.1 컨트롤 배치

창고 자식 컨트롤 배치

4.2 이벤트 핸들러 등록

다음 이벤트 핸들러를 등록한다.

Form1의 Load 이벤트 핸들러

tbar_count의 Scroll 이벤트 핸들러

btn_in과 btn_out의 Click 이벤트 핸들러

lv_material의 SelectedIndexChanged 이벤트 핸들러

4.3 Form1.cs 구현

using SF_CoreLib;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace 창고
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void lv_material_SelectedIndexChanged(object sender, EventArgs e)
        {
            if(lv_material.SelectedItems.Count==0)
            {
                btn_out.Enabled = false;
                nud_count.Maximum = 0;
                return;
            }
            ListViewItem lvi = lv_material.SelectedItems[0];
            string name = lvi.SubItems[1].Text;
            Material material = mdic[name];
            nud_count.Maximum = material.Count;
            btn_out.Enabled = true;
        }

        private void tbar_count_Scroll(object sender, EventArgs e)
        {
            int count = tbar_count.Value;
            lb_count.Text = count.ToString();
        }

        private void btn_in_Click(object sender, EventArgs e)
        {
            string name = tbox_name.Text;
            if(name == string.Empty)
            {
                MessageBox.Show("부재 이름을 먼저 입력하세요.");
                return;
            }
            int cnt = tbar_count.Value;
            if(mdic.ContainsKey(name) == false)
            {
                sn++;
                Material material = new Material(sn, name, cnt);
                mdic[name] = material;
                string[] sitems = new string[] { sn.ToString(), name, cnt.ToString() };
                ListViewItem lvi = new ListViewItem(sitems);
                lv_material.Items.Add(lvi);
            }
            else
            {
                Material material = mdic[name];
                material.Count += cnt;
                foreach(ListViewItem lvi in lv_material.Items)
                {
                    if(lvi.SubItems[0].Text == material.ID.ToString())
                    {
                        lvi.SubItems[2].Text = material.Count.ToString();
                        break;
                    }
                }
            }
        }

        private void btn_out_Click(object sender, EventArgs e)
        {
            ListViewItem lvi = lv_material.SelectedItems[0];
            string name = lvi.SubItems[1].Text;
            Material material = mdic[name];
            int count = (int)nud_count.Value;
            material.Count -= count;
            lvi.SubItems[2].Text = material.Count.ToString();
            nud_count.Maximum = material.Count;
        }

        Dictionary<string, Material> mdic;
        int sn;
        SFServer sfs = new SFServer("192.168.10.102", 19202);
        private void Form1_Load(object sender, EventArgs e)
        {
            mdic = new Dictionary<string, Material>();
            sfs.Start();
            sfs.OnOut += Sfs_OnOut;
            sfs.OnIn += Sfs_OnIn;
        }

        private void Sfs_OnIn(object sender, InEventArgs e)
        {
            tbox_name.Text = e.Name;
            tbar_count.Value = e.Count;
            lb_count.Text = e.Count.ToString();
            btn_in_Click(null, null);
        }

        private void Sfs_OnOut(object sender, OutEventArgs e)
        {
            string name = e.Name;
            foreach(ListViewItem lvi in lv_material.Items)
            {
                if(lvi.SubItems[1].Text == name)
                {
                    lvi.Selected = true;
                    decimal cnt = e.Count;
                    if(nud_count.Maximum<cnt)
                    {
                        cnt = nud_count.Maximum;
                    }
                    nud_count.Value = cnt;
                    btn_out_Click(null, null);
                    break;
                }
            }
            
        }
    }
}

4.4 SFServer.cs

SFServer(스마트 팩토리 클라이언트) 클래스를 추가하고 구현합니다.

using System.IO;
using System.Net;
using System.Net.Sockets;

namespace 창고
{
    public class SFServer
    {
        public event OutEventHandler OnOut;
        public event InEventHandler OnIn;
        public string IP{get;}
        public int Port { get; }

        public SFServer(string ip, int port)
        {
            IP= ip;
            Port = port;
        }

        Socket sock;
        public void Start()
        {
            //소켓 생성
            sock = new Socket(AddressFamily.InterNetwork,
                SocketType.Stream,
                ProtocolType.Tcp);
            //소켓과 네트워크 인터페이스 결합
            IPAddress addr = IPAddress.Parse(IP);
            IPEndPoint end = new IPEndPoint(addr, Port);
            sock.Bind(end);
            //백 로그 큐 크기 결정
            sock.Listen(5);
            //클라이언트 연결 요청 대기 및 수락 Loop
            AcceptAsync();
        }

        delegate void AcceptDele();
        private void AcceptAsync()
        {
            AcceptDele dele = AcceptLoop;
            dele.BeginInvoke(null, null);
        }
        void AcceptLoop()
        {
            while(true)
            {
                Socket dosock = sock.Accept();
                DoIt(dosock);
            }

        }
        enum MsgType
        {
            MT_IN, MT_OUT
        }
        private void DoIt(Socket dosock)
        {
            byte[] packet = new byte[1024];
            dosock.Receive(packet);
            MemoryStream ms = new MemoryStream(packet);
            BinaryReader br = new BinaryReader(ms);
            MsgType mt = (MsgType)br.ReadInt32();
            string name = br.ReadString();
            int count = br.ReadInt32();
            br.Close();
            ms.Close();
            if(mt== MsgType.MT_IN)
            {
                if(OnIn!=null)
                {
                    OnIn(this, new InEventArgs(name, count));
                }
            }
            if(mt == MsgType.MT_OUT)
            {
                if(OnOut != null)
                {
                    OnOut(this, new OutEventArgs(name, count));
                }
            }
            dosock.Close();
        }
    }
}

4.5 InEventArgs.cs - 입고 패킷 수신 이벤트 인자 및 대리자

using System;

namespace 창고
{
    public delegate void InEventHandler(object sender, InEventArgs e);
    public class InEventArgs:EventArgs
    {
        public string Name { get;  }
        public int Count { get; }
        public InEventArgs(string name,int count)
        {
            Name = name;
            Count = count;
        }
    }
}

4.6 OutEventArgs.cs  - 출고 패킷 수신 이벤트 인자 및 대리자

using System;

namespace 창고
{
    public delegate void OutEventHandler(object sender, OutEventArgs e);
    public class OutEventArgs:EventArgs
    {
        public string Name { get; }
        public int Count { get; }
        public OutEventArgs(string name,int count)
        {
            Name = name;
            Count = count;
        }
    }
}

5. 중앙 관재 구현

5.1 자식 컨트롤 배치

중앙 관재 자식 컨트롤 구현

5.2 이벤트 핸들러 등록

세 개의 버튼의 Click 이벤트 핸들러를 등록합니다.

5.3 구현

5.3.1 Form1.cs

using System;
using System.Windows.Forms;

namespace 창고_제어
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        
        string ip;
        int port;
        private void btn_set_Click(object sender, EventArgs e)
        {
            ip = tbox_ip.Text;
            port = int.Parse(tbox_port.Text);
            btn_in.Enabled = true;
            btn_out.Enabled = true;
        }

        private void btn_in_Click(object sender, EventArgs e)
        {
            string pname = tbox_name.Text;
            int cnt = tbar_count.Value;
            SFClient.SendIn(ip,port,pname, cnt);
        }

        private void btn_out_Click(object sender, EventArgs e)
        {
            string pname = tbox_name.Text;
            int cnt = (int)nud_count.Value;
            cnt = SFClient.SendOut(ip, port, pname, cnt);
        }

        private void tbar_count_Scroll(object sender, EventArgs e)
        {
            lb_count.Text = tbar_count.Value.ToString();
        }
    }
}

5.3.2 SFCient 구현

SFClient 클래스를 추가하고 다음처럼 구현합니다.

using System.IO;
using System.Net;
using System.Net.Sockets;

namespace 창고_제어
{
    internal class SFClient
    {
        enum MsgType
        {
            MT_IN, MT_OUT
        }
        internal static void SendIn(string ip, int port, string pname, int cnt)
        {
            Socket sock = Connect(ip, port);
            byte[] packet = MakePacket(MsgType.MT_IN,pname, cnt);
            sock.Send(packet);
            sock.Close();
        }
        internal static int SendOut(string ip, int port, string pname, int cnt)
        {
            Socket sock = Connect(ip, port);
            byte[] packet = MakePacket(MsgType.MT_OUT, pname, cnt);
            sock.Send(packet);
            byte[] p2 = new byte[1024];
            sock.Close();
            return 0;
        }
        private static byte[] MakePacket(MsgType mt,string pname, int cnt)
        {
            //전송 메시지 ID, 전송메시지 
            byte[] packet = new byte[1024];
            MemoryStream ms = new MemoryStream(packet);
            BinaryWriter bw = new BinaryWriter(ms);
            bw.Write((int)mt);
            bw.Write(pname);
            bw.Write(cnt);
            bw.Close();
            ms.Close();
            return packet;
        }

        private static Socket Connect(string ip, int port)
        {
            Socket sock = new Socket(AddressFamily.InterNetwork,
                SocketType.Stream,
                ProtocolType.Tcp);
            IPAddress addr = IPAddress.Parse(ip);
            IPEndPoint end = new IPEndPoint(addr, port);
            sock.Connect(end);
            return sock;
        }



        private static int UnPackInt(byte[] p2)
        {
            MemoryStream ms = new MemoryStream(p2);
            BinaryReader br = new BinaryReader(ms);
            int value = br.ReadInt32();
            br.Close();
            ms.Close();
            return value;
        }
    }
}
반응형