天天看點

C# Socket TCP 程式設計,用戶端與服務端連接配接,發送字元串,檔案

做了個winform的Socket服務端和用戶端,能實作發送消息&文字

以下是圖檔和一些關鍵點,最後是代碼,注釋不多

C# Socket TCP 程式設計,用戶端與服務端連接配接,發送字元串,檔案
C# Socket TCP 程式設計,用戶端與服務端連接配接,發送字元串,檔案

服務端

服務端建立一個socket對象

參數 :尋址方案,ip版本4 ;套接字類型,位元組流 ;協定,TCP

Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

這時候可以在外邊建立new一個空的socket對象比如serverSocket.來儲存上面建立的對象,這樣其他地方就能使用這個socket

例外,在外部建立一個List<Socket> list 來儲存連用戶端socket對象.後面要用

socket需要ip位址和port,注意參數轉換類型

socket.Bind(new IPEndPoint(IPAddress.Parse(txtIP.Text.ToString()), int.Parse(txtPort.Text)));

設定連接配接等待隊列的數量

socket.Listen(10);

開始服務,等待用戶端連接配接,這時候需要開另外一個線程 來做這些事情,AcceptClientConnect是自己寫的一個方法,将socket傳給它

ThreadPool.QueueUserWorkItem(new WaitCallback(this.AcceptClientConnect), socket);

下面是AcceptClientConnect的代碼, 當有用戶端連接配接時,建立一個Socket對象.變量名為proxSocket,并且儲存到list中,友善以後使用,另外,需要在建立一個線程來處理該用戶端socket發送過來的資料.

public void AcceptClientConnect(object socket)
        {
            var serverSocket = socket as Socket;
            this.AppendTextToTxtLog("伺服器端開始接受用戶端的連結");
            while (true)
            {
                try
                {
                    var proxSocket = serverSocket.Accept();
                    this.AppendTextToTxtLog(string.Format("用戶端{0}連接配接上了", proxSocket.RemoteEndPoint.ToString()));
                    ClientProxClentList.Add(proxSocket);


                    //接受消息
                    ThreadPool.QueueUserWorkItem(new WaitCallback(this.ReceiveData), proxSocket);
                }
                catch (Exception)
                {
                }
                
            }
        }
           

其中AppendTextToTxtLog方法是給主視窗中傳遞文本資訊,因為txtLog這個控件不是監聽服務端連接配接的線程建立的.是以要用異步來讓它把新消息複制到txtLog中,代碼中if來判斷是否是txtLog目前線程的控件

public void AppendTextToTxtLog(string txt)
        {
            if (txtLog.InvokeRequired)
            {
                txtLog.BeginInvoke(new Action<string>(s =>
                {
                    this.txtLog.Text = string.Format("{0}\r\n{1}", s, txtLog.Text);
                }), txt);
            }
            else
            {
                this.txtLog.Text = string.Format("{0}\r\n{1}", txt, txtLog.Text);
            }
        }
           

下面是處理用戶端發送過來的資料的代碼,new一個byte[]數組,變量名為data,代銷為1024*1024,用來儲存用戶端發來的資料. int  readlen是實際接收到的資料的長度(data中沒用的到部分會自動為null),服務端和用戶端通信的時候,資料長度是大于0的.當資料長度等于0 的時候,說明用戶端要斷開連接配接,代碼中有這個判斷,還有try,catch捕獲異常(這裡處理的不怎麼好),最後.當複合條件.發送過來的資料轉換成字元串,這裡用到了有效長度,截取,轉換為字元串,添加到消息文本框中.

public void ReceiveData(object obj)
        {
            Socket proxSocket = obj as Socket;
            byte[] data = new byte[1024 * 1024];
            while (true)
            {
                int readLen = 0;
                try
                {
                    readLen = proxSocket.Receive(data, 0, data.Length, SocketFlags.None);
                }
                catch (Exception ex)
                {
                    //異常退出時
                    AppendTextToTxtLog(string.Format("用戶端{0}非正常退出", proxSocket.RemoteEndPoint.ToString()));
                    ClientProxClentList.Remove(proxSocket);
                    StopConnetct(proxSocket);
                    return;
                }
                if (readLen<=0)
                {
                    //用戶端正常退出
                    AppendTextToTxtLog(string.Format("用戶端:{0}正常退出", proxSocket.RemoteEndPoint.ToString()));
                    ClientProxClentList.Remove(proxSocket);
                    StopConnetct(proxSocket);
                    return;//方法結束->終結目前接受用戶端資料的異步線程
                }
                string txt = Encoding.Default.GetString(data, 0, readLen);
                AppendTextToTxtLog(string.Format("接收到用戶端{0}的消息{1}",proxSocket.RemoteEndPoint.ToString(),txt));
            }
        }
           

一下是發送消息的代碼 ,這個用戶端能發送字元串,檔案和視窗晃動,在原本發送的byte[] data前面在增加一位,為1 的時候是發送字元串,2發送晃動.3發送檔案,data數組前面加一位,new用一個新數組result,長度是data.length+1,之後用到了Buffer.BlockCopy()方法,參數分别是(原數組,原數組的開始位置,目标數組,目标數組的開始位置,原數組中取多長資料)

發送資料,用戶端對象.Send(要發送的byte數組,開始位置,長度,發送接收行為),一般發送接收行為用SocketFlags.None就可以了.發送的時候周遊一下list中的用戶端對象.判斷一下是否連接配接.

private void btnSend_Click(object sender, EventArgs e)
        {
            foreach (var proxSocket in ClientProxClentList)
            {
                if (proxSocket.Connected)
                {
                    //原始的字元串轉換成的位元組數組
                    byte[] data = Encoding.Default.GetBytes(txtSendMsg.Text);
                    //在頭部加上标記位元組
                    byte[] result = new byte[data.Length + 1];
                    //頭部協定位元組 1:代表字元串
                    result[0] = 1;
                    Buffer.BlockCopy(data, 0, result, 1, data.Length);
                    proxSocket.Send(result, 0, result.Length,SocketFlags.None);
                }
            }
        }
           

發送檔案的方法,new一個新的byte數組,第一位設定為3,然後讀取檔案,轉換為byte[]data,還是用Buffer.BlockCopy(),整合到變量名為result的byte數組中,周遊list中的用戶端對象,如果是連接配接的,那麼發送

private void button2_Click(object sender, EventArgs e)
        {
            using (OpenFileDialog ofd =new OpenFileDialog())
            {
                if (ofd.ShowDialog()!=DialogResult.OK)
                {
                    return;
                }
                byte[] data = File.ReadAllBytes(ofd.FileName);
                byte[] result = new byte[data.Length + 1];
                result[0] = 3;
                Buffer.BlockCopy(data, 0, result, 1,data.Length);

                foreach (var proxSocket in ClientProxClentList)
                {
                    if (!proxSocket.Connected)
                    {
                        continue;
                    }
                    proxSocket.Send(result, SocketFlags.None);
                }
            }
           
        }
           

最後一個是晃動的,隻要發一個第一位是2的byte數組過去就好

private void button1_Click(object sender, EventArgs e)
        {
            foreach (var proxSocket in ClientProxClentList)
            {
                if (proxSocket.Connected)
                {
                    proxSocket.Send(new byte[] { 2 }, SocketFlags.None);
                }
            }
        }
           

下面是服務端的整體代碼

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 ChatDemo
{
    public partial class Mainfrm : Form
    {
        public Mainfrm()
        {
            InitializeComponent();
        }
        private Socket serverSocket;
        List<Socket> ClientProxClentList = new List<Socket>();
        private bool state = false;
        private void btnStart_Click(object sender, EventArgs e)
        {
            if (!state)
            {
                Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                serverSocket = socket;
                //ip port
                socket.Bind(new IPEndPoint(IPAddress.Parse(txtIP.Text.ToString()), int.Parse(txtPort.Text)));
                //listen
                socket.Listen(10);//連接配接等待隊列
                ThreadPool.QueueUserWorkItem(new WaitCallback(this.AcceptClientConnect), socket);
                state = true;
                btnStart.Text = "停止服務";
            }
            else
            {
                try
                {
                    serverSocket.Close();
                    serverSocket.Shutdown(SocketShutdown.Both);
                }
                catch (Exception)
                {
                    state = false;
                    btnStart.Text = "開始服務";
                }
                
                
                

            }
            //對象
           
        }

        //日志文本框追加資料
        public void AppendTextToTxtLog(string txt)
        {
            if (txtLog.InvokeRequired)
            {
                txtLog.BeginInvoke(new Action<string>(s =>
                {
                    this.txtLog.Text = string.Format("{0}\r\n{1}", s, txtLog.Text);
                }), txt);
            }
            else
            {
                this.txtLog.Text = string.Format("{0}\r\n{1}", txt, txtLog.Text);
            }
        }
        public void AcceptClientConnect(object socket)
        {
            var serverSocket = socket as Socket;
            this.AppendTextToTxtLog("伺服器端開始接受用戶端的連結");
            while (true)
            {
                try
                {
                    var proxSocket = serverSocket.Accept();
                    this.AppendTextToTxtLog(string.Format("用戶端{0}連接配接上了", proxSocket.RemoteEndPoint.ToString()));
                    ClientProxClentList.Add(proxSocket);

                    //接受消息
                    ThreadPool.QueueUserWorkItem(new WaitCallback(this.ReceiveData), proxSocket);
                }
                catch (Exception)
                {
                }
                
            }
        }
        public void ReceiveData(object obj)
        {
            Socket proxSocket = obj as Socket;
            byte[] data = new byte[1024 * 1024];
            while (true)
            {
                int readLen = 0;
                try
                {
                    readLen = proxSocket.Receive(data, 0, data.Length, SocketFlags.None);
                }
                catch (Exception ex)
                {
                    //異常退出時
                    AppendTextToTxtLog(string.Format("用戶端{0}非正常退出", proxSocket.RemoteEndPoint.ToString()));
                    ClientProxClentList.Remove(proxSocket);
                    StopConnetct(proxSocket);
                    return;
                }
                if (readLen<=0)
                {
                    //用戶端正常退出
                    AppendTextToTxtLog(string.Format("用戶端:{0}正常退出", proxSocket.RemoteEndPoint.ToString()));
                    ClientProxClentList.Remove(proxSocket);
                    StopConnetct(proxSocket);
                    return;//方法結束->終結目前接受用戶端資料的異步線程
                }
                string txt = Encoding.Default.GetString(data, 0, readLen);
                AppendTextToTxtLog(string.Format("接收到用戶端{0}的消息{1}",proxSocket.RemoteEndPoint.ToString(),txt));
            }
        }

        private void btnSend_Click(object sender, EventArgs e)
        {
            foreach (var proxSocket in ClientProxClentList)
            {
                if (proxSocket.Connected)
                {
                    //原始的字元串轉換成的位元組數組
                    byte[] data = Encoding.Default.GetBytes(txtSendMsg.Text);
                    //在頭部加上标記位元組
                    byte[] result = new byte[data.Length + 1];
                    //頭部協定位元組 1:代表字元串
                    result[0] = 1;
                    Buffer.BlockCopy(data, 0, result, 1, data.Length);
                    proxSocket.Send(result, 0, result.Length,SocketFlags.None);
                }
            }
        }

        private void StopConnetct(Socket proxSocket)
        {
            try
            {
                if (proxSocket.Connected)
                {
                    proxSocket.Shutdown(SocketShutdown.Both);
                    proxSocket.Close(100);
                }
            }
            catch (Exception ex)
            {
            }
        }

        private void button2_Click(object sender, EventArgs e)
        {
            using (OpenFileDialog ofd =new OpenFileDialog())
            {
                if (ofd.ShowDialog()!=DialogResult.OK)
                {
                    return;
                }
                byte[] data = File.ReadAllBytes(ofd.FileName);
                byte[] result = new byte[data.Length + 1];
                result[0] = 3;
                Buffer.BlockCopy(data, 0, result, 1,data.Length);

                foreach (var proxSocket in ClientProxClentList)
                {
                    if (!proxSocket.Connected)
                    {
                        continue;
                    }
                    proxSocket.Send(result, SocketFlags.None);
                }
            }
           
        }

        private void button1_Click(object sender, EventArgs e)
        {
            foreach (var proxSocket in ClientProxClentList)
            {
                if (proxSocket.Connected)
                {
                    proxSocket.Send(new byte[] { 2 }, SocketFlags.None);
                }
            }
        }
    }
}
           

用戶端

點選按鈕連接配接Server,在外面建立一個socket對象名稱為ClientSocket.友善其他地方調用,連接配接成功後new一個線程名稱thread來處理接受到的消息,注意.thread設定為背景線程thread.IsBackground = true,線程開始的時候把目前的socket對象作為參數給它,thread.Start(ClientSocket);

private void btnConnect_Click(object sender, EventArgs e)
        {
            Socket socket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
            ClientSocket = socket;
            try
            {
                socket.Connect(IPAddress.Parse(txtIP.Text), int.Parse(txtPort.Text));
            }
            catch (Exception ex)
            {                
                MessageBox.Show("Bad");
                return;
            }            
            AppendTextToTxtLog(string.Format("服務端{0}已連接配接",ClientSocket.RemoteEndPoint.ToString()));
            Thread thread = new Thread(new ParameterizedThreadStart(ReceiveData));
            thread.IsBackground = true;
            thread.Start(ClientSocket);
        }
           

消息接受方法ReceiveData(), socket對象.Receive(data, 0, data.Length, SocketFlags.None);傳回值是實際接受的位元組流長度,變量名readLen,後面要用.

public void ReceiveData(object obj)
        {
            Socket proxSocket = obj as Socket;
            byte[] data = new byte[1024 * 1024];
            while (true)
            {
                int readLen = 0;
                try
                {
                    readLen = proxSocket.Receive(data, 0, data.Length, SocketFlags.None);
                }
                catch (Exception)
                {
                    //異常退出時
                    AppendTextToTxtLog(string.Format("服務端{0}非正常退出", proxSocket.RemoteEndPoint.ToString()));
                    StopConnetct();
                    return;
                }
                if (readLen <= 0)
                {
                    //用戶端正常退出
                    AppendTextToTxtLog(string.Format("服務端:{0}正常退出", proxSocket.RemoteEndPoint.ToString()));                    
                    StopConnetct();
                    return;//方法結束->終結目前接受用戶端資料的異步線程
                }
                //接受的資料中第一個位元組如果是1,那麼是字元串.2是閃屏.3是檔案
                if (data[0]==1)
                {
                    string strMsg = ProcessRecieveString(data, readLen);
                    AppendTextToTxtLog(string.Format("接收到服務端{0}的消息{1}", proxSocket.RemoteEndPoint.ToString(), strMsg));
                }
                else if (data[0] == 2)
                {
                    ProcessRecieveShake();
                }
                else if (data[0]==3)
                {
                    ProcessRecieveFile(data, readLen);
                }
            }
        }
           

下面是對位元組流首位分别是1,2,3的處理方法

public string ProcessRecieveString(byte[] data,int readLen)
        {
            string str = Encoding.Default.GetString(data, 1, readLen);
            return str;
        }
        public void ProcessRecieveShake()
        {
            Point oldLocation = this.Location;
            Random r = new Random();
            
            if (this.InvokeRequired)
            {
                txtLog.BeginInvoke(new Action<Point,Random>(Shake),oldLocation,r);
            }
            else
            {
                Shake(oldLocation, r);
            }         
        }

        private void Shake(Point oldLocation, Random r)
        {
            for (int i = 0; i < 50; i++)
            {
                this.Location = new Point(r.Next(oldLocation.X - 5, oldLocation.X + 5), r.Next(oldLocation.Y - 5, oldLocation.Y + 5));
                Thread.Sleep(50);
                this.Location = oldLocation;
            }
        }

        public void ProcessRecieveFile(byte[] data,int readLen)
        {
            using (SaveFileDialog sfd = new SaveFileDialog())
            {
                sfd.DefaultExt = "txt";
                sfd.Filter = "文本檔案(*.txt)|*.txt|所有檔案(*.*)|*.*";
                if (sfd.ShowDialog(this) != DialogResult.OK)
                {
                    return;
                }
                byte[] fileData = new byte[readLen - 1];
                Buffer.BlockCopy(data, 1, fileData, 0, readLen - 1);
                File.WriteAllBytes(sfd.FileName, fileData);
                AppendTextToTxtLog(string.Format("接收到檔案,已儲存到{0}", sfd.FileName));
            }
        }
           

下面是發送消息的代碼(和服務端的基本差不多)

private void btnSend_Click(object sender, EventArgs e)
        {
            // AppendTextToTxtLog("123");
            if (ClientSocket==null)
            {
                return;
            }
            if (ClientSocket.Connected)
            {
                byte[] data = Encoding.Default.GetBytes(txtSendMsg.Text);
                ClientSocket.Send(data, 0, data.Length, SocketFlags.None);
            }
            
        }
           

下面是用戶端的整體代碼

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 SocketClient
{
    public partial class Mianfrm : Form
    {
        public Mianfrm()
        {
            InitializeComponent();
            //Control.CheckForIllegalCrossThreadCalls = false;
        }
        public Socket ClientSocket { get; set;   }
        private void btnConnect_Click(object sender, EventArgs e)
        {
            Socket socket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
            ClientSocket = socket;
            try
            {
                socket.Connect(IPAddress.Parse(txtIP.Text), int.Parse(txtPort.Text));
            }
            catch (Exception ex)
            {                
                MessageBox.Show("Bad");
                return;
            }            
            AppendTextToTxtLog(string.Format("服務端{0}已連接配接",ClientSocket.RemoteEndPoint.ToString()));
            Thread thread = new Thread(new ParameterizedThreadStart(ReceiveData));
            thread.IsBackground = true;
            thread.Start(ClientSocket);
        }
        public void ReceiveData(object obj)
        {
            Socket proxSocket = obj as Socket;
            byte[] data = new byte[1024 * 1024];
            while (true)
            {
                int readLen = 0;
                try
                {
                    readLen = proxSocket.Receive(data, 0, data.Length, SocketFlags.None);
                }
                catch (Exception)
                {
                    //異常退出時
                    AppendTextToTxtLog(string.Format("服務端{0}非正常退出", proxSocket.RemoteEndPoint.ToString()));
                    StopConnetct();
                    return;
                }
                if (readLen <= 0)
                {
                    //用戶端正常退出
                    AppendTextToTxtLog(string.Format("服務端:{0}正常退出", proxSocket.RemoteEndPoint.ToString()));                    
                    StopConnetct();
                    return;//方法結束->終結目前接受用戶端資料的異步線程
                }
                //接受的資料中第一個位元組如果是1,那麼是字元串.2是閃屏.3是檔案
                if (data[0]==1)
                {
                    string strMsg = ProcessRecieveString(data, readLen);
                    AppendTextToTxtLog(string.Format("接收到服務端{0}的消息{1}", proxSocket.RemoteEndPoint.ToString(), strMsg));
                }
                else if (data[0] == 2)
                {
                    ProcessRecieveShake();
                }
                else if (data[0]==3)
                {
                    ProcessRecieveFile(data, readLen);
                }
            }
        }
        public string ProcessRecieveString(byte[] data,int readLen)
        {
            string str = Encoding.Default.GetString(data, 1, readLen);
            return str;
        }
        public void ProcessRecieveShake()
        {
            Point oldLocation = this.Location;
            Random r = new Random();
            
            if (this.InvokeRequired)
            {
                txtLog.BeginInvoke(new Action<Point,Random>(Shake),oldLocation,r);
            }
            else
            {
                Shake(oldLocation, r);
            }         
        }

        private void Shake(Point oldLocation, Random r)
        {
            for (int i = 0; i < 50; i++)
            {
                this.Location = new Point(r.Next(oldLocation.X - 5, oldLocation.X + 5), r.Next(oldLocation.Y - 5, oldLocation.Y + 5));
                Thread.Sleep(50);
                this.Location = oldLocation;
            }
        }

        public void ProcessRecieveFile(byte[] data,int readLen)
        {
            using (SaveFileDialog sfd = new SaveFileDialog())
            {
                sfd.DefaultExt = "txt";
                sfd.Filter = "文本檔案(*.txt)|*.txt|所有檔案(*.*)|*.*";
                if (sfd.ShowDialog(this) != DialogResult.OK)
                {
                    return;
                }
                byte[] fileData = new byte[readLen - 1];
                Buffer.BlockCopy(data, 1, fileData, 0, readLen - 1);
                File.WriteAllBytes(sfd.FileName, fileData);
                AppendTextToTxtLog(string.Format("接收到檔案,已儲存到{0}", sfd.FileName));
            }
        }
        public void AppendTextToTxtLog(string txt)
        {
            if (txtLog.InvokeRequired)
            {
                txtLog.BeginInvoke(new Action<string>(s =>{this.txtLog.Text = String.Format("{0}\r\n{1}", s, txtLog.Text);}), txt);
            }
            else
            {
                this.txtLog.Text = string.Format("{0}\r\n{1}", txt, txtLog.Text);
            }
        }
        private void btnSend_Click(object sender, EventArgs e)
        {
            // AppendTextToTxtLog("123");
            if (ClientSocket==null)
            {
                return;
            }
            if (ClientSocket.Connected)
            {
                byte[] data = Encoding.Default.GetBytes(txtSendMsg.Text);
                ClientSocket.Send(data, 0, data.Length, SocketFlags.None);
            }
            
        }
        private void StopConnetct()
        {
            try
            {
                if (ClientSocket.Connected)
                {
                    ClientSocket.Shutdown(SocketShutdown.Both);
                    ClientSocket.Close(100);
                }
            }
            catch (Exception ex)
            {
            }
        }

        private void Mianfrm_FormClosing(object sender, FormClosingEventArgs e)
        {
            StopConnetct();

        }

        private void Mianfrm_Load(object sender, EventArgs e)
        {
            AppendTextToTxtLog("請單擊連接配接到伺服器");
        }
    }
}
           

最後是軟體的界面,

C# Socket TCP 程式設計,用戶端與服務端連接配接,發送字元串,檔案
C# Socket TCP 程式設計,用戶端與服務端連接配接,發送字元串,檔案
C# Socket TCP 程式設計,用戶端與服務端連接配接,發送字元串,檔案
C# Socket TCP 程式設計,用戶端與服務端連接配接,發送字元串,檔案