본문 바로가기
wpf/c#, wpf 채팅프로그램

c#,wpf 채팅프로그램 만들기 - 12 - 소켓통신(TcpListener, TcpClient)을 이용한 채팅프로그램을 만들어보자(WPF를 활용한 클라이언트, 5. 로직구현)

by devjh 2020. 10. 9.
반응형

채팅프로그램 만들기는 총 12개의 게시글로 구성되어있습니다.

 

첫번째 게시글 : 1:1단발성통신(동기서버 동기클라)

 

두번째 게시글 : 1:1지속성통신(동기서버 완성본 동기 클라이언트)

 

세번째 게시글 : 1:1통신(비동기서버)

 

네번째 게시글: 1:N통신(여기서부터는 여러명을 받아야하므로 당연히 비동기서버입니다.)

 

다섯번째 게시글 : 채팅프로그램 콘솔 서버

 

여섯번째 게시글 : 채팅프로그램 콘솔 클라이언트

 

일곱번째 게시글 : wpf를 통해 View를 구현한 서버

 

여덟번째 게시글 : wpf를 통해 View를 구현한 클라이언트(메인화면 만들기)

 

아홉번째 게시글 : wpf를 통해 View를 구현한 클라이언트(로그인화면 만들기)

 

열번째 게시글 : wpf를 통해 View를 구현한 클라이언트(채팅상대 선택화면 만들기)

 

열한번째 게시글 : wpf를 통해 View를 구현한 클라이언트(채팅화면 만들기)

 

열두번째 게시글 : wpf를 통해 View를 구현한 클라이언트(로직 구현)

 


이번게시글에서는 채팅프로그램 클라이언트편 로직을 구현하겠습니다.

 

이번 포스팅에 선행되어야 하는 개념의 예제입니다.

 

1. 외부스레드에서 UI 제어하는법

 

외부 Thread에서 UIThread 제어하기(DIspatcher의 이벤트 큐 사용하기)

이번게시글에서는 외부 스레드에서 UI스레드를 제어하는방법에 대해 포스팅하겠습니다. 예제코드입니다. 1. MainWindow.xaml 화면을 분할하여 Button과 TextBlock을 넣어주었습니다. 2. MainWindow.cs namespace

frozenpond.tistory.com

2. 외부스레드에서 UI 여는법

 

스레드에서 UI(Window) 열기

이번게시글에서는 내가 만든 스레드에서 UI를 여는방법에 대해 포스팅하겠습니다. 예제로 알아보겠습니다. 1. MainWindow.xaml 버튼 하나를 넣어주었습니다. 2. MainWindow.cs namespace STATest { /// /// MainWi..

frozenpond.tistory.com

 

 

1. MainWindow.xaml

<Window x:Class="ChattingClient.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:ChattingClient"
        mc:Ignorable="d"
        Title="ChattingServiceClient" Height="450" Width="600" Background="#BCBCBC">
    <Border BorderThickness="0" CornerRadius="10" Margin="10,30,10,10" Background="White">
        <StackPanel Orientation="Vertical" Margin="40,15,40,5">
            <TextBlock Foreground="Gray" FontSize="50" FontFamily="Ebrima" Text="Chatting Program" TextAlignment="Center" Margin="0,0,0,20"></TextBlock>
            <TextBlock x:Name="Info" Foreground="Gray" FontSize="20" Text="채팅프로그램을 이용려면 먼저 로그인해주세요" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,0,0,30"></TextBlock>
            <Button x:Name="Login_Btn" FontSize="20" Content ="로그인" Margin="0,0,0,25" Height="45" Click="Login_Btn_Click"/>
            <Button x:Name="OneOnOneChatting_Btn" FontSize="20" Content ="1:1채팅" Margin="0,0,0,25" Height="45" Click="OneOnOneChatting_Btn_Click"/>
            <Button x:Name="GroupChatting_Btn" FontSize="20" Content ="그룹채팅" Margin="0,0,0,25" Height="45" Click="GroupChatting_Btn_Click"/>
        </StackPanel>
    </Border>
</Window>

2. MainWindow.cs

namespace ChattingClient
{
    /// <summary>
    /// MainWindow.xaml에 대한 상호 작용 논리
    /// </summary>
    public partial class MainWindow : Window
    {
        public static string myName = null;
        TcpClient client = null;
        Thread ReceiveThread = null;
        ChattingWindow chattingWindow = null;
        Dictionary<string, ChattingThreadData> chattingThreadDic = new Dictionary<string, ChattingThreadData>();
        Dictionary<int, ChattingThreadData> groupChattingThreadDic = new Dictionary<int, ChattingThreadData>();

        public MainWindow()
        {
            InitializeComponent();
            // 하나의 피시에서 사용할때는 주석처리해주세요
            //if (System.Diagnostics.Process.GetProcessesByName("ChattingServiceClient").Length > 1)
            //{
            //    MessageBox.Show("채팅프로그램이 실행중입니다.", "Information", MessageBoxButton.OK, MessageBoxImage.Information);
            //    Environment.Exit(1);
            //}
        }

        private void RecieveMessage()
        {
            string receiveMessage = "";
            List<string> receiveMessageList = new List<string>();
            while (true)
            {
                try
                {
                    byte[] receiveByte = new byte[1024];
                    client.GetStream().Read(receiveByte, 0, receiveByte.Length);

                    receiveMessage = Encoding.Default.GetString(receiveByte);

                    string[] receiveMessageArray = receiveMessage.Split('>');
                    foreach (var item in receiveMessageArray)
                    {
                        if (!item.Contains('<'))
                            continue;
                        if (item.Contains("관리자<TEST"))
                            continue;

                        receiveMessageList.Add(item);
                    }

                    ParsingReceiveMessage(receiveMessageList);
                }
                catch (Exception e)
                {
                    MessageBox.Show("서버와의 연결이 끊어졌습니다.", "Server Error", MessageBoxButton.OK, MessageBoxImage.Error);
                    MessageBox.Show(e.Message);
                    MessageBox.Show(e.StackTrace);
                    Environment.Exit(1);
                }
                Thread.Sleep(500);
            }
        }

        private void ParsingReceiveMessage(List<string> messageList)
        {
            foreach (var item in messageList)
            {
                string chattingPartner = "";
                string message = "";

                if (item.Contains('<'))
                {
                    string[] splitedMsg = item.Split('<');

                    chattingPartner = splitedMsg[0];
                    message = splitedMsg[1];

                    // 하트비트 
                    if (chattingPartner == "관리자")
                    {
                        ObservableCollection<User> tempUserList = new ObservableCollection<User>();
                        string[] splitedUser = message.Split('$');
                        foreach (var el in splitedUser)
                        {
                            if (string.IsNullOrEmpty(el))
                                continue;
                            tempUserList.Add(new User(el));
                        }
                        UserListWindow.ChangeUserListView(tempUserList);
                        messageList.Clear();
                        return;
                    }

                    // 그룹채팅
                    else if (chattingPartner.Contains("#"))
                    {
                        string[] splitedChattingPartner = chattingPartner.Split('#');
                        List<string> chattingPartners = new List<string>();

                        foreach (var el in splitedChattingPartner)
                        {
                            if (string.IsNullOrEmpty(el))
                                continue;
                            chattingPartners.Add(el);
                        }

                        string sender = chattingPartners[0];

                        int chattingRoomNum = GetChattingRoomNum(chattingPartners);

                        if (chattingRoomNum < 0)
                        {
                            Thread groupChattingThread = new Thread(() => ThreadStartingPoint(chattingPartners));
                            groupChattingThread.SetApartmentState(ApartmentState.STA);
                            groupChattingThread.IsBackground = true;
                            groupChattingThread.Start();
                        }
                        else
                        {
                            if (groupChattingThreadDic[chattingRoomNum].chattingThread.IsAlive)
                            {
                                groupChattingThreadDic[chattingRoomNum].chattingWindow.ReceiveMessage(sender, message);
                            }
                        }
                        messageList.Clear();
                        return;
                    }

                    // 1:1채팅
                    else
                    {
                        if (!chattingThreadDic.ContainsKey(chattingPartner))
                        {
                            if(message == "ChattingStart")
                            {
                                Thread chattingThread = new Thread(() => ThreadStartingPoint(chattingPartner));
                                chattingThread.SetApartmentState(ApartmentState.STA);
                                chattingThread.IsBackground = true;
                                chattingThread.Start();
                            }
                        }
                        else
                        {
                            if (chattingThreadDic[chattingPartner].chattingThread.IsAlive)
                            {
                                chattingThreadDic[chattingPartner].chattingWindow.ReceiveMessage(chattingPartner, message);
                            }
                        }
                        messageList.Clear();
                        return;
                    }
                }
            }
            messageList.Clear();
        }

        private int GetChattingRoomNum(List<string> chattingPartners)
        {
            chattingPartners.Sort();
            string reqMember = "";
            foreach (var item in chattingPartners)
            {
                reqMember += item;
            }

            string originMember = "";
            foreach (var item in groupChattingThreadDic)
            {
                foreach (var el in item.Value.chattingWindow.chattingPartners)
                {
                    originMember += el;
                }
                if (originMember == reqMember)
                    return item.Value.chattingRoomNum;
                originMember = "";
            }
            return -1;
        }

        private void ThreadStartingPoint(string chattingPartner)
        {
            chattingWindow = new ChattingWindow(client, chattingPartner);
            chattingThreadDic.Add(chattingPartner, new ChattingThreadData(Thread.CurrentThread, chattingWindow));

            if (chattingWindow.ShowDialog() == true)
            {
                MessageBox.Show("채팅이 종료되었습니다.", "Information", MessageBoxButton.OK, MessageBoxImage.Information);
                chattingThreadDic.Remove(chattingPartner);
            }
        }

        private void ThreadStartingPoint(List<string> chattingPartners)
        {
            chattingPartners.Sort();
            chattingWindow = new ChattingWindow(client, chattingPartners);
            ChattingThreadData tempThreadData = new ChattingThreadData(Thread.CurrentThread, chattingWindow);
            groupChattingThreadDic.Add(tempThreadData.chattingRoomNum, tempThreadData);

            if (chattingWindow.ShowDialog() == true)
            {
                MessageBox.Show("채팅이 종료되었습니다.", "Information", MessageBoxButton.OK, MessageBoxImage.Information);
                groupChattingThreadDic.Remove(tempThreadData.chattingRoomNum);
            }
        }




        private void Login_Btn_Click(object sender, RoutedEventArgs e)
        {
            if (client != null)
            {
                MessageBox.Show("이미 로그인되었습니다.", "Information", MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }

            Login login = new Login();
            if (login.ShowDialog() == true)
            {
                try
                {
                    string ip = login.IpTextBox.Text;
                    string parsedName = "%^&";
                    parsedName += login.NameTextBox.Text;

                    client = new TcpClient();
                    client.Connect(ip, 9999);

                    byte[] byteData = new byte[parsedName.Length];
                    byteData = Encoding.Default.GetBytes(parsedName);
                    client.GetStream().Write(byteData, 0, byteData.Length);

                    Info.Text = string.Format("{0} 님 반갑습니다 ", login.NameTextBox.Text);
                    myName = login.NameTextBox.Text;

                    ReceiveThread = new Thread(RecieveMessage);
                    ReceiveThread.Start();
                }

                catch
                {
                    MessageBox.Show("서버연결에 실패하였습니다.", "Server Error", MessageBoxButton.OK, MessageBoxImage.Error);
                    client = null;
                }
            }
        }

        private void OneOnOneChatting_Btn_Click(object sender, RoutedEventArgs e)
        {

            if (client == null)
            {
                MessageBox.Show("먼저 로그인해주세요.", "Information", MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }


            string getUserProtocol = myName + "<GiveMeUserList>";
            byte[] byteData = new byte[getUserProtocol.Length];
            byteData = Encoding.Default.GetBytes(getUserProtocol);

            client.GetStream().Write(byteData, 0, byteData.Length);

            UserListWindow userListWindow = new UserListWindow(StaticDefine.ONE_ON_ONE_CHATTING);

            if (userListWindow.ShowDialog() == true)
            {
                if (chattingThreadDic.ContainsKey(userListWindow.OneOnOneReceiver))
                {
                    MessageBox.Show("해당유저와는 이미 채팅중입니다.", "Information", MessageBoxButton.OK, MessageBoxImage.Information);
                    return;
                }
                string chattingStartMessage = string.Format("{0}<ChattingStart>", userListWindow.OneOnOneReceiver);
                byte[] chattingStartByte = Encoding.Default.GetBytes(chattingStartMessage);

                client.GetStream().Write(chattingStartByte, 0, chattingStartByte.Length);
            }
        }

        private void GroupChatting_Btn_Click(object sender, RoutedEventArgs e)
        {
            if (client == null)
            {
                MessageBox.Show("먼저 로그인해주세요.", "Information", MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }

            string getUserProtocol = myName + "<GiveMeUserList>";
            byte[] byteData = new byte[getUserProtocol.Length];
            byteData = Encoding.Default.GetBytes(getUserProtocol);

            client.GetStream().Write(byteData, 0, byteData.Length);

            UserListWindow userListWindow = new UserListWindow(StaticDefine.GROUP_CHATTING);

            if (userListWindow.ShowDialog() == true)
            {
                string groupChattingUserStrData = MainWindow.myName;
                foreach (var item in userListWindow.GroupChattingReceivers)
                {
                    groupChattingUserStrData += "#";
                    groupChattingUserStrData += item.userName;
                }


                string chattingStartMessage = string.Format("{0}<GroupChattingStart>", groupChattingUserStrData);
                byte[] chattingStartByte = Encoding.Default.GetBytes(chattingStartMessage);

                client.GetStream().Write(chattingStartByte, 0, chattingStartByte.Length);
            }
        }

        protected override void OnClosing(CancelEventArgs e)
        {
            MessageBoxResult messageBoxResult = MessageBox.Show("채팅프로그램을 종료하시겠습니까?", "Question", MessageBoxButton.YesNo, MessageBoxImage.Question);
            if (messageBoxResult == MessageBoxResult.No)
            {
                e.Cancel = true;
                return;
            }

            Environment.Exit(1);
        }
    }
}

채팅요청을 하면 채팅창을 직접 여는게 아니라

 

서버측에 채팅상대와 ChattingStart or GroupChattingStart를 포함한 MSG를 보내주며

 

서버측에서 사용자를 연결해서 클라이언트의 채팅창을 열어주는게 핵심입니다.

 

ReceiveThread(UI스레드가 아닌 외부스레드)에서 새로운 window를 열어줄때는

STA 스레드를 만들어서 채팅창을 열어주도록 합니다.

 

외부스레드에서 UI스레드에 접근할때는 dispatcher.invoke를 사용하여 접근해 줍니다.

 

3. ChattingWindow.xaml

<Window x:Class="ChattingClient.ChattingWindow"
        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"
        PreviewKeyDown="Window_PreviewKeyDown"
        xmlns:local="clr-namespace:ChattingClient"
        mc:Ignorable="d"
        Title="ChattingWindow" Height="500" Width="500" Background="#BCBCBC">
    <Grid Margin="8">
        <Grid.RowDefinitions>
            <RowDefinition Height="6*"/>
            <RowDefinition Height="1*"/>
        </Grid.RowDefinitions>

        <ListView x:Name="messageListView" Grid.Row="0" Margin="0,0,0,10">
        </ListView>

        <Grid Grid.Row="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="4*"></ColumnDefinition>
                <ColumnDefinition Width="1*"></ColumnDefinition>
            </Grid.ColumnDefinitions>
            <TextBox Grid.Column="0" x:Name="Send_Text_Box"></TextBox>
            <Button Grid.Column="1" x:Name="Send_btn" Margin="10,0,0,0" Content="전송" Click="Send_btn_Click"></Button>
        </Grid>
    </Grid>
</Window>

 

 

4. ChattingWindow.cs

namespace ChattingClient
{
    /// <summary>
    /// ChattingWindow.xaml에 대한 상호 작용 논리
    /// </summary>
    public partial class ChattingWindow : Window
    {
        private string chattingPartner = null;
        private TcpClient client = null;
        private ObservableCollection<string> messageList = new ObservableCollection<string>();
        public List<string> chattingPartners = null;

        public ChattingWindow(TcpClient client, string chattingPartner)
        {
            this.chattingPartner = chattingPartner;
            this.client = client;
            InitializeComponent();
            messageListView.ItemsSource = messageList;
            messageList.Add(string.Format("{0}님이 입장하였습니다.", chattingPartner));
            this.Title = chattingPartner + "님과의 채팅방";
        }

        public ChattingWindow(TcpClient client, List<string> targetChattingPartners)
        {
            this.client = client;
            this.chattingPartners = targetChattingPartners;
            InitializeComponent();
            messageListView.ItemsSource = messageList;
            string enteredUser = "";
            foreach (var item in targetChattingPartners)
            {
                enteredUser += item;
                enteredUser += "님, ";
            }
            messageList.Add(string.Format("{0}이 입장하였습니다.", enteredUser));
            this.Title = enteredUser + "과의 채팅방";
        }


        private void Send_btn_Click(object sender, RoutedEventArgs e)
        {
            if (string.IsNullOrEmpty(Send_Text_Box.Text))
                return;
            string message = Send_Text_Box.Text;
            string parsedMessage = "";

            if (message.Contains('<') || message.Contains('>'))
            {
                MessageBox.Show("죄송합니다. >,< 기호는 사용하실수 없습니다.", "Information", MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }

            if (chattingPartner != null)
            {
                parsedMessage = string.Format("{0}<{1}>", chattingPartner, message);
                byte[] byteData = Encoding.Default.GetBytes(parsedMessage);
                client.GetStream().Write(byteData, 0, byteData.Length);
            }
            // 그룹채팅
            else
            {
                string partners = MainWindow.myName;
                foreach (var item in chattingPartners)
                {
                    if (item == MainWindow.myName)
                        continue;
                    partners += "#" + item;
                }

                parsedMessage = string.Format("{0}<{1}>", partners, message);
                byte[] byteData = Encoding.Default.GetBytes(parsedMessage);
                client.GetStream().Write(byteData, 0, byteData.Length);
            }
            messageList.Add("나: " + message);
            Send_Text_Box.Clear();

            ScrollToBot();
        }

        private void Window_PreviewKeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                if (string.IsNullOrEmpty(Send_Text_Box.Text))
                    return;
                string message = Send_Text_Box.Text;
                string parsedMessage = "";

                if (message.Contains('<') || message.Contains('>'))
                {
                    MessageBox.Show("죄송합니다. >,< 기호는 사용하실수 없습니다.", "Information", MessageBoxButton.OK, MessageBoxImage.Information);
                    return;
                }

                if (chattingPartner != null)
                {
                    parsedMessage = string.Format("{0}<{1}>", chattingPartner, message);
                    byte[] byteData = Encoding.Default.GetBytes(parsedMessage);
                    client.GetStream().Write(byteData, 0, byteData.Length);
                }
                // 그룹채팅
                else
                {
                    string partners = MainWindow.myName;
                    foreach (var item in chattingPartners)
                    {
                        if (item == MainWindow.myName)
                            continue;
                        partners += "#" + item;
                    }

                    parsedMessage = string.Format("{0}<{1}>", partners, message);
                    byte[] byteData = Encoding.Default.GetBytes(parsedMessage);
                    client.GetStream().Write(byteData, 0, byteData.Length);
                }

                messageList.Add("나: " + message);
                Send_Text_Box.Clear();

                ScrollToBot();
            }
        }


        public void ReceiveMessage(string sender, string message)
        {
            if (message == "ChattingStart")
            {
                return;
            }

            if (message == "상대방이 채팅방을 나갔습니다.")
            {
                Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() =>
                {
                    string parsedMessage = string.Format("{0}님이 채팅방을 나갔습니다.", sender);
                    messageList.Add(parsedMessage);

                    ScrollToBot();
                }));
                return;
            }

            Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() =>
            {
                messageList.Add(string.Format("{0}: {1}", sender, message));
                messageListView.ScrollIntoView(messageListView.Items[messageListView.Items.Count - 1]);

                ScrollToBot();
            }));
        }

        protected override void OnClosing(CancelEventArgs e)
        {
            string message = string.Format("{0}님과의 채팅을 종료하시겠습니까?", chattingPartner);

            MessageBoxResult messageBoxResult = MessageBox.Show(message, "Question", MessageBoxButton.YesNo, MessageBoxImage.Question);
            if (messageBoxResult == MessageBoxResult.No)
            {
                e.Cancel = true;
                return;
            }

            string exitMessage = "상대방이 채팅방을 나갔습니다.";
            string parsedMessage = string.Format("{0}<{1}>", chattingPartner, exitMessage);
            byte[] byteData = Encoding.Default.GetBytes(parsedMessage);
            client.GetStream().Write(byteData, 0, byteData.Length);

            this.DialogResult = true;
        }
    
        private void ScrollToBot()
        {
            if (VisualTreeHelper.GetChildrenCount(messageListView) > 0)
            {
                Border border = (Border)VisualTreeHelper.GetChild(messageListView, 0);
                ScrollViewer scrollViewer = (ScrollViewer)VisualTreeHelper.GetChild(border, 0);
                scrollViewer.ScrollToBottom();
            }
        }
    }
}

 

편의를 위해 서버에서 구분자로 >, <로 사용했으므로 

 

해당 기호를 클라이언트쪽에서 서버로 보내지 못하게 막아놨습니다.

 

사용자가 모든 기호를 입력하게 하고 싶다면 구분자를 유니코드로 사용하셔야 합니다.

 

5. Login.xaml

<Window x:Class="ChattingClient.Login"
        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:ChattingClient"
        mc:Ignorable="d"
        Title="Login" Height="350" Width="500" Background="#BCBCBC">
    <Border BorderThickness="0" CornerRadius="10" Margin="10,30,10,10" Background="White">
        <StackPanel Orientation="Vertical" Margin="40,15,40,5">
            <TextBlock Foreground="Gray" FontSize="35" FontFamily="Ebrima" Text="LOGIN"></TextBlock>
            <TextBlock Foreground="Gray" FontSize="13" Text="이름과 서버ip를 입력해주세요"></TextBlock>
            <TextBlock Text="이름" Foreground="Gray" FontWeight="Bold" Margin="0,10,0,0"></TextBlock>
            <Border BorderThickness="1.3" BorderBrush="Gray" Height="40" CornerRadius="13" Padding="5,10,5,5">
                <TextBox x:Name="NameTextBox" BorderThickness="0" TextAlignment="Center" FontSize="15"></TextBox>
            </Border>
            <TextBlock Text="서버ip" Foreground="Gray" FontWeight="Bold"></TextBlock>
            <Border BorderThickness="1.3" BorderBrush="Gray" Height="40" CornerRadius="13" Padding="5,10,5,5">
                <TextBox x:Name="IpTextBox" BorderThickness="0" TextAlignment="Center" FontSize="15"></TextBox>
            </Border>
            <Button x:Name="Login_Btn" Margin="0,25,0,0" FontSize="20" Content ="로그인" Click="Login_Btn_Click"/>
        </StackPanel>
    </Border>
</Window>

6. Login.cs

namespace ChattingClient
{
    /// <summary>
    /// Login.xaml에 대한 상호 작용 논리
    /// </summary>
    public partial class Login : Window
    {
        public Login()
        {
            InitializeComponent();
        }
        public string userName
        {
            get
            {
                return NameTextBox.Text;
            }
            private set
            {
                NameTextBox.Text = value;
            }
        }

        public string userIp
        {
            get
            {
                return IpTextBox.Text;
            }
            private set
            {
                IpTextBox.Text = value;
            }
        }

        private void Login_Btn_Click(object sender, RoutedEventArgs e)
        {
            if (string.IsNullOrEmpty(NameTextBox.Text) || string.IsNullOrEmpty(IpTextBox.Text))
            {
                MessageBox.Show("이름과 ip를 정확히 입력해주세요", "Login Error", MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }

            string nameCheck = string.Format("당신은 {0} 님이 맞습니까?", NameTextBox.Text);
            MessageBoxResult nameMessageBoxResult = MessageBox.Show(nameCheck, "Question", MessageBoxButton.YesNo, MessageBoxImage.Question);
            if (nameMessageBoxResult == MessageBoxResult.No)
            {
                return;
            }

            string ipCheck = string.Format("서버의 ip는 {0} 이 맞습니까?", IpTextBox.Text);
            MessageBoxResult ipmessageBoxResult = MessageBox.Show(ipCheck, "Question", MessageBoxButton.YesNo, MessageBoxImage.Question);
            if (ipmessageBoxResult == MessageBoxResult.No)
            {
                return;
            }

            this.DialogResult = true;
        }
    }
}

 

7. UserListWindow.xaml

<Window x:Class="ChattingClient.UserListWindow"
        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:ChattingClient"
        mc:Ignorable="d"
        Title="UserListWindow" Height="400" Width="250" Background="#BCBCBC">
    <Grid Margin="10,10,10,0">
        <Grid.RowDefinitions>
            <RowDefinition Height="10*"></RowDefinition>
            <RowDefinition Height="1*"></RowDefinition>
        </Grid.RowDefinitions>
        <ListView x:Name="UserListView" Grid.Row="0" BorderBrush="White">
            <ListView.Resources>
                <DataTemplate x:Key="UserCellTemplate">
                    <Grid Width="190">
                        <TextBlock  HorizontalAlignment="Center" Text="{Binding userName}"/>
                    </Grid>
                </DataTemplate>

                <DataTemplate x:Key="UserHeaderTemplate">
                    <Grid Width="190">
                        <TextBlock HorizontalAlignment="Center" Text="User"/>
                    </Grid>
                </DataTemplate>
            </ListView.Resources>

            <ListView.View>
                <GridView>
                    <GridViewColumn HeaderTemplate="{StaticResource UserHeaderTemplate}" CellTemplate="{StaticResource UserCellTemplate}"/>
                </GridView>
            </ListView.View>
        </ListView>
        <Grid Grid.Row="1" Margin="0,5,0,5">
            <Button x:Name="Chatting_btn" Click="Chatting_btn_Click"></Button>
        </Grid>
    </Grid>
</Window>

 

 

8. UserListWindow.cs

namespace ChattingClient
{
    /// <summary>
    /// UserListWindow.xaml에 대한 상호 작용 논리
    /// </summary>
    public partial class UserListWindow : Window
    {
        private static ObservableCollection<User> currentUserList = new ObservableCollection<User>();
        private int chattingType = -1;

        private string oneOnOneReceiver { get; set; }
        public string OneOnOneReceiver
        {
            get
            {
                return oneOnOneReceiver;
            }
            private set
            {
                oneOnOneReceiver = value;
            }
        }

        private List<User> groupChattingReceivers { get; set; }
        public List<User> GroupChattingReceivers
        {
            get
            {
                return groupChattingReceivers;
            }
            set
            {
                groupChattingReceivers = value;
            }
        }

        public UserListWindow(int chattingType)
        {
            InitializeComponent();
            UserListView.ItemsSource = currentUserList;

            if (chattingType == StaticDefine.ONE_ON_ONE_CHATTING)
            {
                this.chattingType = chattingType;
                Chatting_btn.Content = "1:1채팅";
                UserListView.SelectionMode = SelectionMode.Single;
            }

            else if (chattingType == StaticDefine.GROUP_CHATTING)
            {
                this.chattingType = chattingType;
                Chatting_btn.Content = "그룹채팅";
                UserListView.SelectionMode = SelectionMode.Extended;
            }
        }

        public static void ChangeUserListView(IEnumerable<User> tempUserList)
        {
            Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() =>
            {
                currentUserList.Clear();
                foreach (var item in tempUserList)
                {
                    currentUserList.Add(item);
                }
            }));
        }

        private void Chatting_btn_Click(object sender, RoutedEventArgs e)
        {
            if (UserListView.SelectedItem == null)
            {
                MessageBox.Show("채팅상대를 선택해주세요.", "Information", MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }

            if (chattingType == StaticDefine.ONE_ON_ONE_CHATTING)
            {
                List<User> dummyChattingUser = UserListView.SelectedItems.Cast<User>().ToList();

                User selectedUser = (User)UserListView.SelectedItem;
                if (MainWindow.myName == selectedUser.userName)
                {
                    MessageBox.Show("자기 자신과는 채팅할 수 없습니다.", "Information", MessageBoxButton.OK, MessageBoxImage.Information);
                    return;
                }
                string msg = string.Format("{0}님에게 {1}요청을 하시겠습니까?", selectedUser.userName, Chatting_btn.Content);
                MessageBoxResult messageBoxResult = MessageBox.Show(msg, "Question", MessageBoxButton.YesNo, MessageBoxImage.Question);
                if (messageBoxResult == MessageBoxResult.No)
                {
                    return;
                }
                this.OneOnOneReceiver = selectedUser.userName;

                this.DialogResult = true;

            }
            else if (chattingType == StaticDefine.GROUP_CHATTING)
            {
                List<User> groupChattingUser = UserListView.SelectedItems.Cast<User>().ToList();
                foreach (var item in groupChattingUser)
                {
                    if (item.userName == MainWindow.myName)
                    {
                        MessageBox.Show("자기 자신과는 채팅할 수 없습니다.", "information", MessageBoxButton.OK, MessageBoxImage.Information);
                        return;
                    }
                }

                if (groupChattingUser.Count < 2)
                {
                    MessageBox.Show("그룹채팅의 최소 인원은 2명입니다.", "information", MessageBoxButton.OK, MessageBoxImage.Information);
                    return;
                }

                string msg = string.Format("선택유저과 {0}을 하시겠습니까?", Chatting_btn.Content);
                MessageBoxResult messageBoxResult = MessageBox.Show(msg, "Question", MessageBoxButton.YesNo, MessageBoxImage.Question);
                if (messageBoxResult == MessageBoxResult.No)
                {
                    return;
                }

                this.GroupChattingReceivers = groupChattingUser;

                this.DialogResult = true;

            }
        }
    }
}

 

9. User.cs

namespace ChattingClient
{
    public class User
    {
        public string userName { get; set; }

        public User(string name)
        {
            this.userName = name;
        }
    }
}

 

10. StaticDefine.cs

namespace ChattingClient
{
    class StaticDefine
    {
        public const int ONE_ON_ONE_CHATTING = 1;
        public const int GROUP_CHATTING = 2;
    }
}

 

11. ChattingThreadData.cs

namespace ChattingClient
{
    public class ChattingThreadData
    {
        public static int chattingRoomCnt = 0;
        public Thread chattingThread;
        public ChattingWindow chattingWindow;
        public int chattingRoomNum;
        private static object obj = new object();

        public ChattingThreadData(Thread chattingThread, ChattingWindow chattingWindow)
        {
            lock (obj)
            {
                this.chattingThread = chattingThread;
                this.chattingWindow = chattingWindow;
                this.chattingRoomNum = ++chattingRoomCnt;
            }
        }
    }
}

 

12. 아이콘설정하기

 

찾아보기를 클릭하여 아이콘을 설정해줍니다.

 

13. 실행화면

메인화면
제우스로그인
하데스로그인
비너스로그인
서버에서 보내준 리스트를 선택하여 제우스가 하데스에게 채팅요청
제우스와 하데스의 채팅하는 모습
비너스가 그룹채팅을 개설
비너스가 그룹채팅을 열어 그룹채팅을 하는 모습
서버로그 좌측부터 현재접속자, 접근로그, 채팅로그(비너스는 채팅 종료후 클라이언트 종료)

 

테스트를 위해 하나의 피시에서 실행하였지만

 

실제로 사용하려면 서버에서 클라이언트ip를 LocalEndPoint대신 RemoteEndPoint로 받아오고

 

클라이언트는 접속할때 서버의 실제 ip를 입력하면 됩니다.

 

단, 동명이인은 없다는 가정하에 로직을 구현하였고, 의도적으로 너무 긴 메시지를 빠른속도로 보내면 

 

서버의 스트림버퍼가 버티질 못하고, 스레드풀도 버티지 못하므로

 

클라이언트의 채팅메시지 길이를 제한하거나, 타이머를 설정해 입력 인터벌을 주거나, 서버에서 버퍼의 크기가 너무 커지지 않게 따로 저장을 해놨다가 처리한다던가.. 등

 

서버 애플리케이션의 부하를 효율적으로 막을 방법을 고민해보시면 좋을 것 같습니다.

 

소스코드

https://github.com/jaeho310/windows_chatting_client

 

GitHub - jaeho310/windows_chatting_client: WIndows Chatting Services(Client)

WIndows Chatting Services(Client). Contribute to jaeho310/windows_chatting_client development by creating an account on GitHub.

github.com

https://github.com/jaeho310/windows_chatting_server

 

GitHub - jaeho310/windows_chatting_server

Contribute to jaeho310/windows_chatting_server development by creating an account on GitHub.

github.com

 

윈도우 채팅 앱 프로젝트 끝

반응형

댓글