基於Socket編程實現的聊天實例,想了解或學習的小夥伴們可以看看,簡單明瞭

最近工作不怎麼忙,學習了一下複習了一下Socket相關的知識,並寫了兩個簡單的Demo,一個客戶端,一個服務端,代碼如下:

服務端:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

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

        //定義一個鍵值對集合,將客戶端的IP地址和Socket存入集合中
        Dictionary<string, Socket> dicSocket = new Dictionary<string, Socket>();

        private void BtnStartListing_Click(object sender, EventArgs e)
        {
            //創建負責監聽的Socket
            Socket socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            //獲取要監聽的IP地址,把IP字符串轉爲IP
            IPAddress ip = IPAddress.Parse(txtIP.Text);
            //獲取要監聽的IP和端口,合併爲一個變量(創建要監聽的IP和端口)
            IPEndPoint point = new IPEndPoint(ip,Convert.ToInt32(txtPort.Text));
            //綁定要監聽的ip和端口號
            socketWatch.Bind(point);
            //給出監聽提示
            ShowMsg("監聽成功");
            //開始監聽。設置服務器在一個時間點內最多能夠監聽的隊列數量。
            socketWatch.Listen(10);

            Thread thread = new Thread(Listen);
            thread.IsBackground = true;
            thread.Start(socketWatch);

        }

        void ShowMsg(string str) {
            //如果不是創建這個控件的線程來調用它
            if (txtLog.InvokeRequired)
            {
                if (txtLog.InvokeRequired) {
                    //跨線程訪問
                    txtLog.Invoke(new Action<string>(s =>
                    {
                        txtLog.AppendText(s + "\r\n");
                    }), str);
                }
            }
            else {
                txtLog.AppendText(str+"\r\n");
            }
        }

        /// <summary>
        /// 等待客戶端的連接,同時創建一個與之通信的socket
        /// </summary>
        Socket socketCommunication;
        void Listen(object obj) {
            //下面開始等待客戶端來連接,同時會創建一個負責通訊的socket
            Socket socketWatch = obj as Socket;
            while (true)
            {
                //創建一個通訊連接
                socketCommunication = socketWatch.Accept();
                //把上面創建好的負責通訊的socket存入集合中
                dicSocket.Add(socketCommunication.RemoteEndPoint.ToString(),socketCommunication);
                //這裏cboUssers屬於跨線程訪問
                if (cboUsers.InvokeRequired)
                {
                    //跨線程訪問
                    cboUsers.Invoke(new Action(() =>
                    {
                        cboUsers.Items.Add(socketCommunication.RemoteEndPoint.ToString());
                    }), null);
                }
                else {
                    //將遠程連接的客戶端的IP地址和端口號存儲到下拉列表框中
                    cboUsers.Items.Add(socketCommunication.RemoteEndPoint.ToString());
                }
               
                //給出連接成功提示信息。
                ShowMsg(socketCommunication.RemoteEndPoint.ToString() + ":連接成功");
                //開啓一個新的線程不停的接收客戶端發來的消息
                Thread thread = new Thread(Receive);
                thread.IsBackground = true;//最好設置爲後臺線程,爲什麼?
                thread.Start(socketCommunication);
            }
        }
        /// <summary>
        /// 服務端接收客戶端發來的信息
        /// </summary>
        /// <param name="obj"></param>
        void Receive(object obj) {
            Socket socketCommunication = obj as Socket;
            //定義一個2M大小的字節數組
            byte[] buffer = new byte[1024*1024*2];
            while (true)
            {
                try
                {
                    //Receive方法的參數表示接收到的數據存儲的位置,需要的是一個字節數組,返回值爲int
                    int size = socketCommunication.Receive(buffer);
                    if (size == 0)
                    {
                        //關閉並退出Socket
                        socketCommunication.Shutdown(SocketShutdown.Both);
                        socketCommunication.Close();
                        return;
                    }
                    //string str1 = Encoding.Default.GetString(buffer, 0, r);
                    //通過Encodeing從buffer字節數組中的0個位置開始,把r個字節轉成字符串;使用Default編寫也可以
                    string str = Encoding.UTF8.GetString(buffer, 0, size);
                    //把接收到的數據放到文本框中
                    ShowMsg(socketCommunication.RemoteEndPoint.ToString() + ":" + str);
                }
                catch (Exception)
                {

      
                }
               
            }
        }

        /// <summary>
        /// 服務端向客戶端發送信息
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnSend_Click(object sender, EventArgs e)
        {
            //獲取要發送的文本
            string str = txtMsg.Text;
            //把輸入的字符串消息的轉爲字節數組
            byte[] buffer = Encoding.UTF8.GetBytes(str);
            byte[] newBuffer = new byte[buffer.Length + 1];
            newBuffer[0] = 0;
            //BlockCopy的用法是:將buffer字節從第0個下標往後複製buffer.Length個長度追加到newBuffer字節第1個下標2後面
            Buffer.BlockCopy(buffer, 0, newBuffer, 1, buffer.Length);
            //send方法需要的就是字節數組
            //socketCommunication.Send(buffer);
            string ip = cboUsers.SelectedItem.ToString();
            dicSocket[ip].Send(newBuffer);
        }

        private void btnGroupSend_Click(object sender, EventArgs e)
        {
            //獲取要發送的文本
            string str = txtMsg.Text;
            //把輸入的字符串消息的轉爲字節數組
            byte[] buffer = Encoding.UTF8.GetBytes(str);
            byte[] newBuffer = new byte[buffer.Length + 1];
            newBuffer[0] = 0;
            //BlockCopy的用法是:將buffer字節從第0個下標往後複製buffer.Length個長度追加到newBuffer字節第1個下標2後面
            Buffer.BlockCopy(buffer, 0, newBuffer, 1, buffer.Length);
            for (int i = 0; i < cboUsers.Items.Count; i++)
            {
                dicSocket[cboUsers.Items[i].ToString()].Send(newBuffer);
            }
          
        }
        /// <summary>
        /// 選擇要發送的文件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnSelect_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.InitialDirectory = @"D:\";
            ofd.Title = "請選擇要發送的文件";
            ofd.Filter = "所有文件|*.*";
            ofd.ShowDialog();
            txtPath.Text = ofd.FileName;
        }

        private void btnSendFile_Click(object sender, EventArgs e)
        {
            //#region 第一種實現方式,使用文件流讀寫
            ////獲取要發送文件的路徑
            //string path = txtPath.Text;
            //using (FileStream fsRead=new FileStream(path,FileMode.Open,FileAccess.Read)) {
            //    byte[] buffer = new byte[1024 * 1024 * 5];
            //    int size = fsRead.Read(buffer,0,buffer.Length);
            //    List<byte> list = new List<byte>();
            //    list.Add(1);
            //    list.AddRange(buffer);
            //    byte[] newBuffer = list.ToArray();
            //    //第四個參數一般設置爲none,除非外網與內網通訊有特殊的要求
            //    dicSocket[cboUsers.SelectedItem.ToString()].Send(newBuffer,0,size+1,SocketFlags.None);
            //}
            //#endregion

            #region 第二種實現方式,使用File.ReadAllBytes讀取
            using (OpenFileDialog ofd = new OpenFileDialog())
            {
                if (ofd.ShowDialog() != DialogResult.OK)
                {
                    //如果沒有選擇文件
                    return;
                }
                else
                {
                    byte[] buffer = File.ReadAllBytes(ofd.FileName);
                    byte[] newBuffer = new byte[buffer.Length + 1];
                    newBuffer[0] = 1;
                    Buffer.BlockCopy(buffer, 0, newBuffer, 1, buffer.Length);
                    //這裏第二個參數SocketFlags.None可以不需要,因爲這裏不存在多發送內容,讀取的是文件中的所有字節數
                    dicSocket[cboUsers.SelectedItem.ToString()].Send(newBuffer, SocketFlags.None);
                }
            }
            #endregion
        }

        private void btnZD_Click(object sender, EventArgs e)
        {
            byte[] buffer = new byte[1];
            buffer[0] = 2;
            dicSocket[cboUsers.SelectedItem.ToString()].Send(buffer);
        }
    }
}

運行效果:

客戶端:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace SocketChatClient
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            //取消跨線程之間的檢查
            Control.CheckForIllegalCrossThreadCalls = false;
        }
        Socket socketCommunication;
        private void btnStart_Click(object sender, EventArgs e)
        {
            //創建一個通訊的Socket
            socketCommunication = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            //獲得要連接的遠程服務器的IP和端口號
            IPAddress ip = IPAddress.Parse(txtServer.Text);
            IPEndPoint point = new IPEndPoint(ip,Convert.ToInt32(txtPort.Text));
            //調用Connection方法去連接遠程服務器
            socketCommunication.Connect(point);
            //提示連接成功
            ShowMsg("連接成功");
            //開啓一個新的線程不斷的去接收服務端發來的消息
            Thread thread = new Thread(Receive);
            //設置爲後臺線程
            thread.IsBackground = true;
            thread.Start();
        }

        void ShowMsg(string str)
        {
            //如果不是創建這個控件的線程來調用它
            if (txtLog.InvokeRequired)
            {
                if (txtLog.InvokeRequired)
                {
                    //跨線程訪問
                    txtLog.Invoke(new Action<string>(s =>
                    {
                        txtLog.AppendText(s + "\r\n");
                    }), str);
                }
            }
            else
            {
                txtLog.AppendText(str + "\r\n");
            }
        }

        /// <summary>
        /// 客戶端向服務端發送消息
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnSend_Click(object sender, EventArgs e)
        {
            //獲取輸入的信息
            string str = txtMsg.Text.Trim();
            //把輸入的消息轉爲字節數組,所用的編碼要和服務端保持一致
            byte[] buffer = Encoding.UTF8.GetBytes(str);
            //向服務端發送消息
            socketCommunication.Send(buffer);
        }

        void Receive() {
            try
            {
                //因爲要不停的去接收,所以寫一個while循環裏
                byte[] buffer = new byte[1024 * 1024 * 3];
                while (true)
                {
                    //不管是發送還是接收消息,都要拿到負責通訊的socket
                    //size表示實際接收到的有效字節數
                    int size = socketCommunication.Receive(buffer);
                    //爲防止服務器端關掉,客戶端還在不斷的接收消息
                    int type = buffer[0];
                    if (size == 0)
                    {
                        break;
                    }
                    if (type == 0)
                    {
                        //接收文本

                        string str = Encoding.UTF8.GetString(buffer, 1, size - 1);
                        //在文本框中顯示接收到的消息
                        ShowMsg(socketCommunication.RemoteEndPoint + ":" + str);
                    }
                    else if (type == 1)
                    {
                        //接收文件

                        //#region 第一種實現思路,使用文件流讀寫
                        //SaveFileDialog sfd = new SaveFileDialog();
                        //sfd.InitialDirectory = @"F:\123";
                        //sfd.Title = "請選擇要保存的文件位置";
                        //sfd.Filter = "所有文件|*.*|";
                        //sfd.ShowDialog(this);
                        //string path = sfd.FileName;
                        //using (FileStream fsWrite=new FileStream(path,FileMode.OpenOrCreate,FileAccess.Write)) {
                        //    //把接收到的數據從第一個字節位置,size-1個字節的數據寫入到流指向的文件中
                        //    fsWrite.Write(buffer,1,size-1);
                        //}
                        //MessageBox.Show("保存成功");
                        //#endregion

                        #region 第二種實現思路,不用使用文件流寫入使用File.WriteAllBytes
                        using (SaveFileDialog sfd = new SaveFileDialog())
                        {
                            sfd.DefaultExt = "doc";
                            sfd.Filter = "所有文件|*.*|文本文件(*.txt)|*.txt|word文檔(*.doc)|*.doc";
                            if (sfd.ShowDialog(this) != DialogResult.OK)
                            {
                                return;
                            }
                            else
                            {
                                byte[] newBuffer = new byte[size - 1];
                                Buffer.BlockCopy(buffer, 1, newBuffer, 0, size - 1);
                                File.WriteAllBytes(sfd.FileName, newBuffer);
                                MessageBox.Show("保存成功");
                            }
                        }
                        #endregion
                    }
                    else if (type == 2) {
                        //接收震動
                        ZD();
                    }
                 
                  
                }
            }
            catch (Exception e)
            {
                MessageBox.Show(e.Message);
            }
        }
        /// <summary>
        /// 震動,改變窗體作爲位置
        /// </summary>
        void ZD()
        {
            for (int i = 0; i < 600; i++)
            {
                this.Location = new Point(200, 200);
                this.Location = new Point(260, 260);
            }
        }
    }
}

運行效果:

 

想學習Socket知識的小夥伴們可以根據這個簡單的Demo學習一下,幾乎每行代碼都有註釋,簡單明瞭。如果哪有不明白的或不正確的地方可以私信我。

 

Demo鏈接:https://github.com/wangongshen/Wgs.CSDN.Demo2019

發佈了61 篇原創文章 · 獲贊 4 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章