C#基于Socket的简单聊天室实践

序:实现一个基于Socket的简易的聊天室,实现的思路如下:

程序的结构:多个客户端+一个服务端,客户端都是向服务端发送消息,然后服务端转发给所有的客户端,这样形成一个简单的聊天室功能。

实现的细节:服务端启动一个监听套接字。每一个客户端连接到服务端,都是开启了一个线程,线程函数是封装了通信套接字,来实现与客户端的通信。多个客户端连接时产生的通信套接字用一个静态的Dictionary保存。具体的实现可以参考代码及其注释。

术语理解:

套接字Socket:源于Unix,为了解决传输层网络编程的问题,Unix提供了类似于文件操作的方式来完成网络编程。要实现不同的主机,不同的程序之间进行通信,必须有相应的协议,这个协议便是TCP/IP协议。Socket是负责传输层的,基于TCP的。不同的主机之间可以通过IP地址识别,但是不同的主机上有众多的程序或者说是进程。一台主机上的进程要跟另一台主机上的进程通信,必须有双方能够唯一识别的标志。就像人的身份证号,手机号等。这里出现了EndPoint(端点)的概念。

EndPoint(端点):由IP地址和端口号构成,端口对应进程。这两个组合起来可以唯一的标识网络中某台主机上的某一个进程。这样就有一个唯一的身份标识,后面可以进行通信了。

 每一个Socket需要绑定到端点进行通信。

Socket的常见的通信数据类型有两种:数据报(SOCK_DGRAM)和数据流(SOCK_STREAM),使用的网络协议TCP或UDP等等。

关于TCP

TCP是一种面向连接的,可靠的,基于字节流的传输层通信协议。

TCP的工作过程包括三个方面:

(1)建立连接:这个过程称为三次握手。

       第一次:客户端发送SYN包(SEQ=x)到服务器,并进入SYN_SEND状态,等待服务器确认。

       第二次:服务器收到SYN包,必须确认客户端的SYN(ACK=x+1),同时自己发送一个SYN包(SEQ=y),即SYN+ACK包,此时服务器进入SYN_RECV状态

       第三次:客户端收到服务器发来的SYN+ACK包,向服务器发送确认包ACK(ACK=y+1),此包发送完毕,客户端和服务端进入Established状态。至此三次握手完成。

(2)传输数据:一旦通信双方建立了TCP连接,就可以相互发送数据。

(3)终止连接:关闭连接,需要四次握手,这个是由于TCP的半关闭造成的。

这个网上有很多资料,大家可以查阅下。

关于.NET里面的Socket

在.NET里面的System.Net.Sockets命名空间下提供了对Socket的操作。并且专门封装了TcpClient和TcpListener两个类来简化操作。我这里是直接用Socket实现的。这里分为这样几个步骤:

在服务端:

(1)声明一个套接字(称为监听套接字)Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

(2)声明一个端点(EndPoint)上面提到过Socket需要跟它绑定才能通信。IPEndPoint endPoint = new IPEndPoint(IPAddress.Loopback, 8080);

(3)设置监听队列serverSocket.Listen(100);

(4)通过Accept()方法来获取一个通信套接字(当有客户端连接时),这个方法会阻塞线程,避免界面卡死的现象,启动一个线程,把这个Accept()放在线程函数里面。

在客户端:

(1)声明一个套接字,通过connect()向服务器发起连接。

(2)通过Receive方法获取服务器发来的消息(这里同样启用一个线程,通过while循环来实时监听服务器端发送的消息)

注意:数据是以字节流(Byte[])的形式传递的,我会使用Encoding.UTF8.GetString()方法来获取为字符串。都是通过Send()来向彼此发送消息。

后台代码:

复制代码
namespace ChatWPFServer
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class ChatServer : Window
    {

        public ChatServer()
        {
            InitializeComponent();
        }
        //保存多个客户端的通信套接字
        public static Dictionary<String, Socket> clientList = null;
        //声明一个监听套接字
        Socket serverSocket = null;
        //设置一个监听标记
        Boolean isListen = true;
        private void btnStart_Click(object sender, RoutedEventArgs e)
        {
            if (serverSocket == null)
            {
                try
                {
                    clientList = new Dictionary<string, Socket>();
                    serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//实例监听套接字
                    IPEndPoint endPoint = new IPEndPoint(IPAddress.Loopback, 8080);//端点
                    serverSocket.Bind(endPoint);//绑定
                    serverSocket.Listen(100);//设置最大连接数
                    Thread th = new Thread(StartListen);
                    th.IsBackground = true;
                    th.Start();
                    txtMsg.Dispatcher.BeginInvoke(new Action(() =>
                    {
                        txtMsg.Text += "服务启动...\r\n";
                    }));
                }
                catch (SocketException ex)
                {
                    MessageBox.Show(ex.ToString());
                }

            }
        }

        //线程函数,封装一个建立连接的通信套接字
        private void StartListen()
        {
            isListen = true;
            Socket clientSocket = default(Socket);

            while (isListen)
            {
                try
                {
                    clientSocket = serverSocket.Accept();//这个方法返回一个通信套接字
                }
                catch (SocketException ex)
                {
                    File.AppendAllText("E:\\Exception.txt", ex.ToString() + "\r\nStartListen\r\n" + DateTime.Now.ToString() + "\r\n");
                }

                Byte[] bytesFrom = new Byte[4096];
                String dataFromClient = null;
                if (clientSocket != null && clientSocket.Connected)
                {
                    try
                    {
                        Int32 len = clientSocket.Receive(bytesFrom);//获取客户端发来的信息
                        if (len > -1)
                        {
                            String tmp = Encoding.UTF8.GetString(bytesFrom, 0, len);
                            try
                            {
                                dataFromClient = EncryptionAndDecryption.TripleDESDecrypting(tmp);
                            }
                            catch (Exception ex)
                            {

                            }

                            Int32 sublen = dataFromClient.LastIndexOf("$");
                            if (sublen > -1)
                            {
                                dataFromClient = dataFromClient.Substring(0, sublen);
                                if (!clientList.ContainsKey(dataFromClient))
                                {
                                    clientList.Add(dataFromClient, clientSocket);

                                    BroadCast.PushMessage(dataFromClient + " Joined ", dataFromClient, false, clientList);

                                    HandleClient client = new HandleClient();

                                    client.StartClient(clientSocket, dataFromClient, clientList);

                                }
                                else
                                {
                                    clientSocket.Send(Encoding.UTF8.GetBytes(EncryptionAndDecryption.TripleDESEncrypting("#" + dataFromClient + "#")));
                                }
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        File.AppendAllText("E:\\Exception.txt", ex.ToString() + "\r\n\t\t" + DateTime.Now.ToString() + "\r\n");
                    }

                }
            }


        }

        private void btnStop_Click(object sender, RoutedEventArgs e)
        {
            if (serverSocket != null)
            {

                foreach (var socket in clientList.Values)
                {
                    socket.Close();
                }
                clientList.Clear();

                serverSocket.Close();
                serverSocket = null;
                isListen = false;
                txtMsg.Text += "服务停止\r\n";
            }
        }

        private void Window_Closed(object sender, EventArgs e)
        {
            isListen = false;
            BroadCast.PushMessage("Server has closed", "", false, clientList);
            clientList.Clear();
            serverSocket.Close();
            serverSocket = null;
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            try
            {
                clientList = new Dictionary<string, Socket>();
                serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//实例监听套接字
                IPEndPoint endPoint = new IPEndPoint(IPAddress.Loopback, 8080);//端点
                serverSocket.Bind(endPoint);//绑定
                serverSocket.Listen(100);//设置最大连接数
                Thread th = new Thread(StartListen);
                th.IsBackground = true;
                th.Start();
                txtMsg.Dispatcher.BeginInvoke(new Action(() =>
                {
                    txtMsg.Text += "服务启动...\r\n";
                }));
            }
            catch (SocketException ex)
            {
                MessageBox.Show(ex.ToString());
            }
        }

    }

    //这里专门负责接收客户端发来的消息,并且转发给所有的客户端
    public class HandleClient
    {
        Socket clientSocket;
        String clNo;
        Dictionary<String, Socket> clientList = new Dictionary<string, Socket>();
        public void StartClient(Socket inClientSocket, String clientNo, Dictionary<String, Socket> cList)
        {
            clientSocket = inClientSocket;
            clNo = clientNo;
            clientList = cList;
            Thread th = new Thread(Chat);
            th.IsBackground = true;
            th.Start();
        }

        private void Chat()
        {
            Byte[] bytesFromClient = new Byte[4096];
            String dataFromClient;
            String msgTemp = null;
            Byte[] bytesSend = new Byte[4096];
            Boolean isListen = true;

            while (isListen)
            {
                try
                {
                    Int32 len = clientSocket.Receive(bytesFromClient);
                    if (len > -1)
                    {
                        dataFromClient = EncryptionAndDecryption.TripleDESDecrypting(Encoding.UTF8.GetString(bytesFromClient, 0, len));
                        if (!String.IsNullOrWhiteSpace(dataFromClient))
                        {
                            dataFromClient = dataFromClient.Substring(0, dataFromClient.LastIndexOf("$"));
                            if (!String.IsNullOrWhiteSpace(dataFromClient))
                            {
                                BroadCast.PushMessage(dataFromClient, clNo, true, clientList);
                                msgTemp = clNo + ": " + dataFromClient + "\t\t" + DateTime.Now.ToString();
                                String newMsg = msgTemp;
                                File.AppendAllText("E:\\MessageRecords.txt", newMsg + "\r\n", Encoding.UTF8);
                            }
                            else
                            {
                                isListen = false;
                                clientList.Remove(clNo);
                                clientSocket.Close();
                                clientSocket = null;
                            }

                        }
                    }
                }
                catch (Exception ex)
                {
                    isListen = false;
                    clientList.Remove(clNo);
                    clientSocket.Close();
                    clientSocket = null;
                    File.AppendAllText("E:\\Exception.txt", ex.ToString() + "\r\nChat\r\n" + DateTime.Now.ToString() + "\r\n");
                }
            }
        }
    }

    //向所有的客户端发送消息
    public class BroadCast
    {
        public static void PushMessage(String msg, String uName, Boolean flag, Dictionary<String, Socket> clientList)
        {
            foreach (var item in clientList)
            {
                Socket brdcastSocket = (Socket)item.Value;
                String msgTemp = null;
                Byte[] castBytes = new Byte[4096];
                if (flag == true)
                {
                    msgTemp = EncryptionAndDecryption.TripleDESEncrypting(uName + ": " + msg + "\t\t" + DateTime.Now.ToString());
                    castBytes = Encoding.UTF8.GetBytes(msgTemp);
                }
                else
                {
                    msgTemp = EncryptionAndDecryption.TripleDESEncrypting(msg + "\t\t" + DateTime.Now.ToString());
                    castBytes = Encoding.UTF8.GetBytes(msgTemp);
                }
                try
                {
                    brdcastSocket.Send(castBytes);
                }
                catch (Exception ex)
                {
                    brdcastSocket.Close();
                    brdcastSocket = null;
                    File.AppendAllText("E:\\Exception.txt", ex.ToString() + "\r\nPushMessage\r\n" + DateTime.Now.ToString() + "\r\n");
                    continue;
                }

            }
        }
    }
}
复制代码
客户端代码:

后台代码:

复制代码
namespace ChatClient
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class ChatRoom : Window
    {
        public ChatRoom()
        {
            InitializeComponent();
        }

        //窗口闪烁代码
        public const UInt32 FLASHW_STOP = 0;
        public const UInt32 FLASHW_CAPTION = 1;
        public const UInt32 FLASHW_TRAY = 2;
        public const UInt32 FLASHW_ALL = 3;
        public const UInt32 FLASHW_TIMER = 4;
        public const UInt32 FLASHW_TIMERNOFG = 12;
        [DllImport("user32.dll")]
        static extern bool FlashWindowEx(ref FLASHWINFO pwfi);

        [DllImport("user32.dll")]
        static extern bool FlashWindow(IntPtr handle, bool invert);

        [DllImport("user32.dll")]
        public static extern IntPtr GetForegroundWindow();

        Socket clientSocket = null;
        static Boolean isListen = true;
        private void btnSend_Click(object sender, RoutedEventArgs e)
        {
            SendMessage();
        }

        private void SendMessage()
        {
            if (String.IsNullOrWhiteSpace(txtSendMsg.Text.Trim()))
            {
                MessageBox.Show("发送内容不能为空哦~");
                return;
            }
            if (clientSocket != null && clientSocket.Connected)
            {
                String sendMsg = EncryptionAndDecryption.TripleDESEncrypting(txtSendMsg.Text + "$");
                Byte[] bytesSend = Encoding.UTF8.GetBytes(sendMsg);
                clientSocket.Send(bytesSend);
                txtSendMsg.Text = "";
            }
            else
            {
                MessageBox.Show("未连接服务器或者服务器已停止,请联系管理员~");
                return;
            }
        }


        /// <summary>
        /// 每一个连接的客户端必须设置一个唯一的用户名,在服务端是把用户名和通信套接字
        /// 保存在Dictionary<UserName,ClientSocket>.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnConnect_Click(object sender, RoutedEventArgs e)
        {
            if (String.IsNullOrWhiteSpace(txtName.Text.Trim()))
            {
                MessageBox.Show("还是设置一个用户名吧,这样别人才能认识你哦~");
                return;
            }

            if (clientSocket == null || !clientSocket.Connected)
            {
                try
                {
                    clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                    clientSocket.BeginConnect(IPAddress.Loopback, 8080, (args) =>
                    {
                        if (args.IsCompleted)
                        {
                            Byte[] bytesSend = new Byte[4096];
                            txtName.Dispatcher.BeginInvoke(new Action(() =>
                            {
                                String tmp = EncryptionAndDecryption.TripleDESEncrypting(txtName.Text.Trim() + "$");
                                bytesSend = Encoding.UTF8.GetBytes(tmp);
                                if (clientSocket != null && clientSocket.Connected)
                                {
                                    clientSocket.Send(bytesSend);
                                    txtName.IsEnabled = false;
                                    txtSendMsg.Focus();
                                    Thread th = new Thread(DataFromServer);
                                    th.IsBackground = true;
                                    th.Start();

                                }
                                else
                                {
                                    MessageBox.Show("服务器已经关闭");
                                }
                            }));


                        }
                    }, null);

                }
                catch (SocketException ex)
                {
                    MessageBox.Show(ex.ToString());
                }

            }
            else
            {
                MessageBox.Show("You has already connected with Server");
            }
        }

        private void ShowMsg(String msg)
        {
            txtReceiveMsg.Dispatcher.BeginInvoke(new Action(() =>
            {
                txtReceiveMsg.Text += Environment.NewLine + msg;
                txtReceiveMsg.ScrollToEnd();
                IntPtr handle = new System.Windows.Interop.WindowInteropHelper(this).Handle;
                if (this.WindowState == WindowState.Minimized || handle != GetForegroundWindow())
                {
                    FLASHWINFO fInfo = new FLASHWINFO();

                    fInfo.cbSize = Convert.ToUInt32(Marshal.SizeOf(fInfo));
                    fInfo.hwnd = handle;
                    fInfo.dwFlags = FLASHW_TRAY | FLASHW_TIMERNOFG;
                    fInfo.uCount = UInt32.MaxValue;
                    fInfo.dwTimeout = 0;

                    FlashWindowEx(ref fInfo);
                }
            }));

        }

        //获取服务端的消息
        private void DataFromServer()
        {
            ShowMsg("Connected to the Chat Server...");
            isListen = true;
            try
            {

                while (isListen)
                {
                    Byte[] bytesFrom = new Byte[4096];
                    Int32 len = clientSocket.Receive(bytesFrom);


                    String dataFromClientTmp = Encoding.UTF8.GetString(bytesFrom, 0, len);
                    if (!String.IsNullOrWhiteSpace(dataFromClientTmp))
                    {
                        String dataFromClient = EncryptionAndDecryption.TripleDESDecrypting(dataFromClientTmp);
                        if (dataFromClient.StartsWith("#") && dataFromClient.EndsWith("#"))
                        {
                            String userName = dataFromClient.Substring(1, dataFromClient.Length - 2);
                            this.Dispatcher.BeginInvoke(new Action(() =>
                            {

                                MessageBox.Show("用户名:[" + userName + "]已经存在,请尝试其它用户名");
                            }));
                            isListen = false;
                            txtName.Dispatcher.BeginInvoke(new Action(() =>
                            {
                                txtName.IsEnabled = true;
                                clientSocket = null;
                            }));

                        }
                        else
                        {
                            ShowMsg(dataFromClient);
                        }
                    }
                }
            }
            catch (SocketException ex)
            {
                isListen = false;
                if (clientSocket != null && clientSocket.Connected)
                {
                    //我没有在客户端关闭连接而是向服务端发送一个消息,在服务器端关闭,这样主要
                    //为了异常的处理放到服务端。客户端关闭会抛异常,服务端也会抛异常。
                    clientSocket.Send(Encoding.UTF8.GetBytes(EncryptionAndDecryption.TripleDESEncrypting("$")));
                    MessageBox.Show(ex.ToString());
                }

            }
        }

        //这是定义了一个发送的快捷键,WPF的知识
        private void CommandBinding_SendMessage_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            if (String.IsNullOrWhiteSpace(txtSendMsg.Text.Trim()))
            {
                e.CanExecute = false;
            }
            else
            {
                e.CanExecute = true;
            }

        }

        private void CommandBinding_SendMessage_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            SendMessage();
        }

        private void Window_Activated_1(object sender, EventArgs e)
        {

            txtSendMsg.Focus();

        }

        private void Window_Closed_1(object sender, EventArgs e)
        {
            if (clientSocket != null && clientSocket.Connected)
            {
                clientSocket.Send(Encoding.UTF8.GetBytes(EncryptionAndDecryption.TripleDESEncrypting("$")));
            }
        }
    }
    public struct FLASHWINFO
    {

        public UInt32 cbSize;
        public IntPtr hwnd;
        public UInt32 dwFlags;
        public UInt32 uCount;
        public UInt32 dwTimeout;
    }
}


  • 11
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值