C#_TCP_Server (MVVM)

바위게·2024년 1월 17일

WPF

목록 보기
2/8

TCP 통신

Server와 Client간의 TCP 패킷 통신

Server는 IP와 Port를 이용해 서버를 개설하고 클라이언트의 접속을 대기한다.

패킷은 Header,Body,Footer로 구성

  • Header : SyncPattern + Type + Content Size
  • Body : Content
  • Footer : Header 역순

클라이언트와 접속이 성공하면 클라이언트가 보낸 패킷의 header를 체크한다.

  • syncPattern
  1. 서버쪽에서 미리 지정한 상수와 같은지 비교한다
  2. SyncPattern이 서로 일치할경우에만 Body,Footer를 받는다.
  3. Type으로 정상,비정상을 체크한다 (사실 그냥 별거없음)
  4. ContentSize를 확인후 ContentSize만큼의 바이트를 Content Byte배열에 저장한다.
  5. Content 까지 모두다 읽어 들였으면 남은건 자동적으로 Footer

Model

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

namespace TCP_Transmission.Model
{
    public class MessageModel
    {
        private byte[] packet;

        private byte[] syncPatter;
        private byte[] type;
        private byte[] length;

        private byte[] footer;

        public byte[] Packet { get => packet; set => packet = value; }
        //Header
        public byte[] SyncPattern { get => syncPatter; set => syncPatter = value; }
        public byte[] Type { get => type; set => type = value; }
        public byte[] Length { get => length; set => length = value; }

        //Body
        public string Content { get; set; }
        public string Time { get; set; }
        public string IP { get; set; }

        //Footer
        public byte[] Footer { get => footer; set => footer = value; }



        public MessageModel()
        {
            // Byte Header 생성
            packet = new byte[8];
            syncPatter = new byte[4];
            type = new byte[2];
            length = new byte[2];

        }

        /// <summary>
        /// 패킷화
        /// </summary>
        /// <returns></returns>
        public byte[] GeneratePacket()
        {
            byte[] header = syncPatter.Concat(Type).Concat(Length).ToArray();
            byte[] body = Encoding.UTF8.GetBytes(Content);

            footer = new byte[header.Length];
            footer = Enumerable.Reverse(header).ToArray();
            Packet = header.Concat(body).Concat(footer).ToArray();

            return Packet;
        }

        public bool CompareHeader(NetworkStream network, byte[] header, uint syncNum)
        {            
            uint sync;
            byte[] syncArr = new byte[4];
            syncArr = header.Take(4).ToArray();
            sync = BitConverter.ToUInt32(syncArr, 0);
            if (sync == syncNum)
            {
                //Type
                if (header[5] == 0)
                    Content = "정상: ";
                else
                    Content = "오류: ";

                //Length                
                int length = header[7];

                //Content                
                byte[] contentArr = new byte[length];
                network.Read(contentArr, 0, length);

                //Footer
                byte[] footerArr = new byte[8];
                network.Read(footerArr, 0, 8);

                Content += Encoding.UTF8.GetString(contentArr);                
                return true;
            }
            else
            {
                MessageBox.Show("SyncPattern이 일치하지않음..");
                return false;

            }
        }
    }
}

ViewModel

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using TCP_Transmission.Model;
using WPF_Default;
//추가 
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.IO;
using System.Windows.Controls;
using System.ComponentModel;
using System.Windows.Forms;

//다른이름저장처럼 경로 지정 -> 완료
//인코딩 방식 바꾸기 -> 완료
//Config -> 완료
//ByteConverter


namespace TCP_Transmission.ViewModel
{
    class ServerViewModel : Notifier
    {
        NetworkStream networkStream;

        private string content;        
        private string ip;
        private string port;
        private string startText;
        private MessageModel selectedItem;
        private int exportFileNum = 0;
        private uint syncNum = 1234567890;
        private byte[] originSync;
        private bool isCheck = false;

        private ObservableCollection<MessageModel> messageCollection;

        public ICommand startCommand { get; set; }
        public ICommand sendCommand { get; set; }
        public ICommand deleteContextCommand { get; set; }
        public ICommand exportContextCommand { get; set; }
        public ICommand systemExitCommand { get; set; }
        public ICommand openMessageCommand { get; set; }
        public ServerViewModel()
        {
            //Command
            startCommand = new Command(StartMethod, canexecuteMethod);
            sendCommand = new Command(SendMethod, canexecuteMethod);
            deleteContextCommand = new Command(DeleteContextMenu, canexecuteMethod);
            exportContextCommand = new Command(ExportContextMenu, canexecuteMethod);
            openMessageCommand = new Command(OpenMessageCommand, canexecuteMethod);
            systemExitCommand = new Command(SystemExit, canexecuteMethod);

            messageCollection = new ObservableCollection<MessageModel>();
            Port = "3000";
            StartText = "Start";
            //originSync = BitConverter.GetBytes(syncNum);




            IPHostEntry host = Dns.GetHostEntry(Dns.GetHostName());
            foreach (IPAddress a in host.AddressList)
            {
                if (a.AddressFamily == AddressFamily.InterNetwork)
                {
                    ip = a.ToString();
                    GenerateMessage("Local IP주소 획득..." + ip);
                }
            }
        }

        private void SystemExit(object obj)
        {
            Environment.Exit(0);

        }

        /// <summary>
        /// 이전대화 불러오기 CSV
        /// </summary>
        /// <param name="obj"></param>
        private void OpenMessageCommand(object obj)
        {
            string path;
            string saveLine = "";
            OpenFileDialog openBrowserDialog = new OpenFileDialog();
            openBrowserDialog.Filter = "대화 (*.csv)|*.csv";
            if (openBrowserDialog.ShowDialog() == DialogResult.OK)
            {
                path = openBrowserDialog.FileName;
                StreamReader streamReader = new StreamReader(path, Encoding.GetEncoding("ks_c_5601-1987"));


                while (saveLine != null)
                {
                    saveLine = streamReader.ReadLine();
                    if (saveLine == null)
                        break;
                    GenerateMessage(saveLine, streamReader.ReadLine(), streamReader.ReadLine());
                    Console.WriteLine("데이터 읽어오는중...");
                }
            }
        }

        private bool canexecuteMethod(object arg)
        {
            return true;
        }

        #region Getter & Setter
        public string Content
        {
            get { return content; }
            set { content = value; OnPropertyChanged("Content"); }
        }
        public ObservableCollection<MessageModel> MessageCollection
        {
            get { return messageCollection; }
            set { messageCollection = value; }
        }
        public string IP
        {
            get { return ip; }
            set { ip = value; OnPropertyChanged("IP"); }
        }
        public string Port
        {
            get { return port; }
            set { port = value; OnPropertyChanged("Port"); }
        }

        public string StartText
        {
            get { return startText; }
            set { startText = value; OnPropertyChanged("StartText"); }
        }

        public MessageModel SelectedItem
        {
            get { return selectedItem; }
            set { selectedItem = value; OnPropertyChanged("SelectedItemNum"); Console.WriteLine(selectedItem); }
        }
        public bool IsCheck
        {
            get { return isCheck; }
            set { isCheck = value; OnPropertyChanged("IsCheck"); }
        }

        #endregion

        #region ButtonMethode
        public void StartMethod(object obj)
        {
            Thread thread = new Thread(Connect);
            thread.IsBackground = true;
            thread.Start();
            //MessageCollection.Add(new Message() { Content = "서버 실행...", IP = this.IP, Time = DateTime.Now.ToString("dddd hh:mm") });
            GenerateMessage("서버 실행...");

            if (StartText == "Start")
                StartText = "Stop";
            else
                StartText = "Start";

        }

        public void SendMethod(object obj)
        {
            byte[] header = new byte[8];
            byte[] sync = new byte[4];
            byte[] type = new byte[2];
            byte[] length = new byte[2];

            sync = BitConverter.GetBytes((uint)syncNum);


            //Type
            if (IsCheck)
                type[1] = 1;
            else
                type[1] = 0;

            //Length
            length[1] = (byte)Content.Length;

            MessageModel messageModel = new MessageModel() { SyncPattern = sync, Type = type, Length = length, Content = this.Content, IP = this.IP, Time = DateTime.Now.ToString("F") };
            MessageCollection.Add(messageModel);

            messageModel.GeneratePacket();

            //패킷 전송...

            //if (BitConverter.IsLittleEndian)
            //    Array.Reverse(messageModel.Packet);


            //binaryWriter.Write(messageModel.Packet);
            networkStream.Write(messageModel.Packet, 0, messageModel.Packet.Length);
            Console.WriteLine(messageModel.Packet);
        }


        public void DeleteContextMenu(object obj)
        {
            Console.WriteLine("DeleteContextMenu Clicked");
            messageCollection.Remove(selectedItem);
        }

        /// <summary>
        /// Message 내보내기 CSV
        /// </summary>
        /// <param name="obj"></param>
        public void ExportContextMenu(object obj)
        {
            string path = "";
            FolderBrowserDialog folderBrowserDialog = new FolderBrowserDialog();
            if (folderBrowserDialog.ShowDialog() == DialogResult.OK)
            {
                path = folderBrowserDialog.SelectedPath;
                StreamWriter file = new StreamWriter(path + "/ExportData" + exportFileNum + ".csv", true, Encoding.GetEncoding("euc-kr"));
                Console.WriteLine(path);
                file.WriteLine(selectedItem.Time);
                file.WriteLine(selectedItem.IP);
                file.WriteLine(selectedItem.Content);
                file.Flush();
                file.Close();
            }
        }

        #endregion


        /// <summary>
        /// TCP 연결
        /// </summary>
        private void Connect()
        {
            TcpListener listener = new TcpListener(IPAddress.Parse(ip), int.Parse(port));
            listener.Start();

            //요청 수락
            TcpClient client = listener.AcceptTcpClient();            
            App.Current.Dispatcher.Invoke(() =>
            {
                GenerateMessage("연결성공");
            });

          
            networkStream = client.GetStream();

            while (client.Connected)
            {
                Console.WriteLine("메시지");
                byte[] headerArr = new byte[8];
                networkStream.Read(headerArr, 0, 8);
                MessageModel messageModel = new MessageModel();
                //head
                if (messageModel.CompareHeader(networkStream, headerArr, syncNum))
                    GenerateMessage(messageModel.Content);
            }
            client.Close();
            networkStream.Close();
        }


        /// <summary>
        /// 메시지 생성
        /// </summary>
        /// <param name="text"></param>
        public void GenerateMessage(string text)
        {
            Console.WriteLine(text);
            App.Current.Dispatcher.Invoke(() =>
            {
                MessageCollection.Add(new MessageModel() { Content = text, IP = this.IP, Time = DateTime.Now.ToString("F") });
            });
        }

        public void GenerateMessage(string date, string ip, string text)
        {
            MessageCollection.Add(new MessageModel() { Content = text, IP = ip, Time = date });
        }
    }
}

View

<Window x:Class="TCP_Transmission.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:TCP_Transmission.ViewModel"        
        WindowStyle="None"
        MouseDown="Window_MouseDown"
        
        mc:Ignorable="d"
        Title="Server" Height="500" Width="550"
        Background="#FF002970">
    <Window.DataContext>
        <local:ServerViewModel/>
    </Window.DataContext>


    <Grid>

        <Grid.RowDefinitions>
            <RowDefinition Height="30"/>
            <RowDefinition Height="42"/>
            <RowDefinition Height="380*"/>
            <RowDefinition Height="47*"/>
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>

        <!--ToolBar-->
        <Grid Grid.Column="0"  Background="#FF06003E">
            <TextBlock Text="Server" FontSize="20" VerticalAlignment="Center" HorizontalAlignment="Center" FontFamily="Lucida Bright" FontWeight="Bold" Foreground="Gold" />
        </Grid>
        <Grid Grid.Column="1" Grid.ColumnSpan="4" Background="#FF06003E">
            <StackPanel  Orientation="Horizontal" HorizontalAlignment="Right" >
                <Button Content="-" Width="20" Height="20" Click="Button_Click" Background="Gold" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="10,0,10,0" />

                <Button Content="X" Width="20" Height="20" Click="Button_Click" Background="Gold" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="10,0,10,0" Command="{Binding systemExitCommand}"  />

            </StackPanel>
        </Grid>

        <!--TOP-->
        <Grid  Grid.Column="0" Grid.Row="1" VerticalAlignment="Center" >
            <TextBlock Foreground="Gold" FontSize="20" HorizontalAlignment="Center" VerticalAlignment="Center"  ><Run FontFamily="Lucida Bright" Text="IP"/></TextBlock>
        </Grid>
        <Grid Grid.Column="1" Grid.Row="1">
            <TextBox Text="{Binding IP}" IsReadOnly="True" Height="20" VerticalAlignment="Center" Background="#FF4F6B9B"/>
        </Grid>
        <Grid Grid.Column="2" Grid.Row="1">
            <TextBlock Foreground="Gold" FontSize="20" TextAlignment="Center" HorizontalAlignment="Center" VerticalAlignment="Center" ><Run FontFamily="Lucida Bright" Text="Port"/></TextBlock>
        </Grid>
        <Grid Grid.Column="3" Grid.Row="1">
            <TextBox  Text="{Binding Port}" Height="20" Background="#FF4F6B9B"/>
        </Grid>
        <Grid Grid.Column="4" Grid.Row="1">
            <Button Command="{Binding startCommand}" Background="AliceBlue" Content="{Binding StartText}" HorizontalAlignment="Center" Height="20"  Width="40"/>
        </Grid>

        <!--Content-->
        <Grid Grid.Row=" 2" Grid.ColumnSpan="5">
            <DataGrid  SelectedItem="{Binding SelectedItem}"  ItemsSource="{Binding MessageCollection}" AutoGenerateColumns="False" CanUserAddRows="False" Margin="10" Background="#FFA19FAE" >
                <DataGrid.Columns>
                    <DataGridTextColumn Header="Time" Binding="{Binding Time}" IsReadOnly="True"/>
                    <DataGridTextColumn Header="Client" Binding="{Binding IP}" IsReadOnly="True"/>
                    <DataGridTextColumn Header="Message" Binding="{Binding Content}" IsReadOnly="True" Width="*" />
                </DataGrid.Columns>
                <DataGrid.ContextMenu >
                    <ContextMenu Name="GridContext" >
                        <MenuItem Header="Delete" Command="{Binding deleteContextCommand}"/>
                        <MenuItem Header="Export" Command="{Binding exportContextCommand}"/>
                        <MenuItem Header="Import" Command="{Binding openMessageCommand}"/>
                    </ContextMenu>
                </DataGrid.ContextMenu>
            </DataGrid>
        </Grid>
        <!--<Grid Grid.Row="2" Grid.Column="5" VerticalAlignment="Bottom" >
            <Button Command="{Binding openMessageCommand}" Content="이전 대화 불러오기"  FontSize="10" Height="20" Margin="-22,0,0,0" />
        </Grid>-->
        <!--Bottom-->
        <Grid Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="4">
            <TextBox Text="{Binding Content}"  HorizontalAlignment="Stretch" Height="30" Margin="10" Background="#FF4F6B9B"/>
        </Grid>
        <Grid Grid.Row="3" Grid.Column="4">
            <StackPanel Orientation="Horizontal" Margin="-10,0,0,0">
                <CheckBox Foreground="Red" IsChecked="{Binding IsCheck}" Content="Error" HorizontalAlignment="Center" VerticalAlignment="Center" FontFamily="Lucida Bright" Margin="0,0,5,0" />
                <Button Command="{Binding sendCommand}" Content="Send" Height="30" />
            </StackPanel>
        </Grid>


    </Grid>
</Window>
profile
게임하는만큼만 개발공부하자

0개의 댓글