c#TCP傳輸文件


TCP是一種面向連接的,可靠的,基於字節流的傳輸層通信協議。TCP建立一個連接需要三次握手,而終止一個連接要經過四次握手。一旦通信雙方建立了TCP連接,連接中的任何一方都能向對方發送數據和接受對方發來的數據。TCP協議負責把用戶數據(字節流)按一定的格式和長度組成多個數據報進行發送,並在接收到數據報之后按分解順序重新組裝和恢復傳輸的數據。

使用TCP傳輸文件,可以直接使用socket進行傳輸,也可以使用TcpLister類和TcpClient類進行傳輸。其實TcpLister和TcpClient就是Socket封裝后的類,是.NET為了簡化編程復雜度而對套接字又進行了封裝。但是,TcpLister和TcpClient只支持標准協議編程。如果希望編寫非標准協議的程序,只能使用套接字socket來實現。

下面分別講解兩種方法進行文件傳輸:

因為和一些終端進行文件傳輸時,受發送緩沖區最大發送字節的影響,我這里每次發送512字節,循環發送,直到把文件傳輸完,然后關閉連接;接收文件時,同樣是每次接收512字節,然后寫入文件,當所有的數據都接收完時,最后關閉連接。

當然,最后一次發送和接收的數據,以實際計算的數據大小來發送或者接收,不會是512字節,以免造成數據空白。

一、直接使用socket進行文件傳輸

服務端和發送端Demo界面分別如圖1、2所示:

圖1 服務端界面圖

圖2 客戶端界面圖

1、服務器端代碼如下:

        public ShakeHands()
        {
            InitializeComponent();
            IPAddress[] ips = Dns.GetHostAddresses(Dns.GetHostName());
            txtIp.Text = ips[1].ToString();
            int port = 50001;
            txtPort.Text = port.ToString();
            ListBox.CheckForIllegalCrossThreadCalls = false;//關閉跨線程對ListBox的檢查
            
        }

        

        #region 啟動TCP監聽服務,開始接收連接和文件
        private void btnBegin_Click(object sender, EventArgs e)
        {
            try
            {
                ReceiveFiles.BeginListening(txtIp.Text, txtPort.Text, lstbxMsgView, listbOnline);
                btnBegin.Enabled = false;
                btnCancel.Enabled = true;
            }
            catch (Exception ex)
            {
                ShwMsgForView.ShwMsgforView(lstbxMsgView, "監聽服務器出現了錯誤:"+ex.Message);
            }
        }
        #endregion

其中,啟動監聽,接收文件ReceiveFiles類代碼如下:

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


namespace BusinessLogicLayer
{
    public class ReceiveFiles
    {
        private static Thread threadWatch = null;
        private static Socket socketWatch = null;
        private static ListBox lstbxMsgView;//顯示接受的文件等信息
        private static ListBox listbOnline;//顯示用戶連接列表

        private static Dictionary<string, Socket> dict = new Dictionary<string, Socket>();
        /// <summary>
        /// 開始監聽
        /// </summary>
        /// <param name="localIp"></param>
        /// <param name="localPort"></param>
        public static void BeginListening(string localIp, string localPort, ListBox listbox, ListBox listboxOnline)
        {
            //基本參數初始化
            lstbxMsgView = listbox;
            listbOnline = listboxOnline;

            //創建服務端負責監聽的套接字,參數(使用IPV4協議,使用流式連接,使用Tcp協議傳輸數據)
            socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            //獲取Ip地址對象
            IPAddress address = IPAddress.Parse(localIp);
            //創建包含Ip和port的網絡節點對象
            IPEndPoint endpoint = new IPEndPoint(address, int.Parse(localPort));
            //將負責監聽的套接字綁定到唯一的Ip和端口上
            socketWatch.Bind(endpoint);
            //設置監聽隊列的長度
            socketWatch.Listen(10);
            //創建負責監聽的線程,並傳入監聽方法
            threadWatch = new Thread(WatchConnecting);
            threadWatch.IsBackground = true;//設置為后台線程
            threadWatch.Start();//開始線程
            //ShowMgs("服務器啟動監聽成功");
            ShwMsgForView.ShwMsgforView(lstbxMsgView, "服務器啟動監聽成功");
        }

        /// <summary>
        /// 連接客戶端
        /// </summary>
        private static void WatchConnecting()
        {
            while (true)//持續不斷的監聽客戶端的請求
            {
                //開始監聽 客戶端連接請求,注意:Accept方法,會阻斷當前的線程
                Socket connection = socketWatch.Accept();
                if (connection.Connected)
                {
                    //向列表控件中添加一個客戶端的Ip和端口,作為發送時客戶的唯一標識
                    listbOnline.Items.Add(connection.RemoteEndPoint.ToString());
                    //將與客戶端通信的套接字對象connection添加到鍵值對集合中,並以客戶端Ip做為健
                    dict.Add(connection.RemoteEndPoint.ToString(), connection);

                    //創建通信線程
                    ParameterizedThreadStart pts = new ParameterizedThreadStart(RecMsg);
                    Thread thradRecMsg = new Thread(pts);
                    thradRecMsg.IsBackground = true;
                    thradRecMsg.Start(connection);
                    ShwMsgForView.ShwMsgforView(lstbxMsgView, "客戶端連接成功" + connection.RemoteEndPoint.ToString());
                }
            }
        }

        /// <summary>
        /// 接收消息
        /// </summary>
        /// <param name="socketClientPara"></param>
        private static void RecMsg(object socketClientPara)
        {
            Socket socketClient = socketClientPara as Socket;

            while (true)
            {
                //定義一個接受用的緩存區(100M字節數組)
                //byte[] arrMsgRec = new byte[1024 * 1024 * 100];
                //將接收到的數據存入arrMsgRec數組,並返回真正接受到的數據的長度   
                if (socketClient.Connected)
                {
                    try
                    {
                        //因為終端每次發送文件的最大緩沖區是512字節,所以每次接收也是定義為512字節
                        byte[] buffer = new byte[512];
                        int size = 0;
                        long len = 0;
                        string fileSavePath = @"..\..\files";//獲得用戶保存文件的路徑
                        if (!Directory.Exists(fileSavePath))
                        {
                            Directory.CreateDirectory(fileSavePath);
                        }
                        string fileName = fileSavePath + "\\" + DateTime.Now.ToString("yyyyMMddHHmmssffff") + ".doc";
                        //創建文件流,然后讓文件流來根據路徑創建一個文件
                        FileStream fs = new FileStream(fileName, FileMode.Create);
                        //從終端不停的接受數據,然后寫入文件里面,只到接受到的數據為0為止,則中斷連接

                        DateTime oTimeBegin = DateTime.Now;

                        while ((size = socketClient.Receive(buffer, 0, buffer.Length, SocketFlags.None)) > 0)
                        {
                            fs.Write(buffer, 0, size);
                            len += size;
                        }
                        DateTime oTimeEnd = DateTime.Now;
                        TimeSpan oTime = oTimeEnd.Subtract(oTimeBegin);
                        fs.Flush();
                        ShwMsgForView.ShwMsgforView(lstbxMsgView,socketClient.RemoteEndPoint + "斷開連接");
                        dict.Remove(socketClient.RemoteEndPoint.ToString());
                        listbOnline.Items.Remove(socketClient.RemoteEndPoint.ToString());
                        socketClient.Close();
                        ShwMsgForView.ShwMsgforView(lstbxMsgView, "文件保存成功:" + fileName);
                        ShwMsgForView.ShwMsgforView(lstbxMsgView, "接收文件用時:" + oTime.ToString()+",文件大小:"+len/1024+"kb");
                    }
                    catch
                    {
                        ShwMsgForView.ShwMsgforView(lstbxMsgView, socketClient.RemoteEndPoint + "下線了");
                        dict.Remove(socketClient.RemoteEndPoint.ToString());
                        listbOnline.Items.Remove(socketClient.RemoteEndPoint.ToString());
                        break;
                    }
                }
                else
                {

                }
            }
        }

        /// <summary>
        /// 關閉連接
        /// </summary>
        public static void CloseTcpSocket()
        {
            dict.Clear();
            listbOnline.Items.Clear();
            threadWatch.Abort();
            socketWatch.Close();
            ShwMsgForView.ShwMsgforView(lstbxMsgView, "服務器關閉監聽");
        }
    }


}

 

顯示時時動態信息ShwMsgForView類代碼如下:

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;


namespace BusinessLogicLayer
{
    public class ShwMsgForView
    {
        delegate void ShwMsgforViewCallBack(ListBox listbox, string text);
        public static void ShwMsgforView(ListBox listbox, string text)
        {
            if (listbox.InvokeRequired)
            {
                ShwMsgforViewCallBack shwMsgforViewCallBack = ShwMsgforView;
                listbox.Invoke(shwMsgforViewCallBack, new object[] { listbox, text });
            }
            else
            {
                listbox.Items.Add(text);
                listbox.SelectedIndex = listbox.Items.Count - 1;
                listbox.ClearSelected();
            }
        }
    }
}

 

2、客戶端發送文件代碼

首先連接服務器代碼:

        #region 連接服務器
        private void btnBegin_Click(object sender, EventArgs e)
        {
            IPAddress address = IPAddress.Parse(txtIp.Text.Trim());
            IPEndPoint endpoint = new IPEndPoint(address, int.Parse(txtPort.Text.Trim()));
            //創建服務端負責監聽的套接字,參數(使用IPV4協議,使用流式連接,使用TCO協議傳輸數據)
            socketClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            socketClient.Connect(endpoint);
            if (socketClient.Connected)
            {
                ShowMgs(socketClient.RemoteEndPoint +"連接成功");
            }
        }
        #endregion

連接服務器成功后,即可發送文件了,先選擇文件:

        #region 選擇要發送的文件
        private void btnSelectFile_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                txtFileName.Text = ofd.FileName;
            }
        }
        #endregion

發送文件代碼:

        //使用socket向服務端發送文件
        private void btnSendFile_Click(object sender, EventArgs e)
        {
            int i = Net.SendFile(socketClient, txtFileName.Text,512,1);
            if (i == 0)
            {
                ShowMgs(txtFileName.Text + "文件發送成功");
                socketClient.Close();
                ShowMgs("連接關閉");
            }
            else
            {
                ShowMgs(txtFileName.Text + "文件發送失敗,i="+i);
            }
            
        }

其中,發送文件Net類的代碼如下:

using System;
using System.Net;
using System.Net.Sockets;
using System.IO;

namespace MyCharRoomClient
{
    /// <summary>
    /// Net : 提供靜態方法,對常用的網絡操作進行封裝
    /// </summary>
    public sealed class Net
    {
        private Net()
        {
        }

        /// <summary>
        /// 向遠程主機發送數據
        /// </summary>
        /// <param name="socket">要發送數據且已經連接到遠程主機的 Socket</param>
        /// <param name="buffer">待發送的數據</param>
        /// <param name="outTime">發送數據的超時時間,以秒為單位,可以精確到微秒</param>
        /// <returns>0:發送數據成功;-1:超時;-2:發送數據出現錯誤;-3:發送數據時出現異常</returns>
        /// <remarks >
        /// 當 outTime 指定為-1時,將一直等待直到有數據需要發送
        /// </remarks>
        public static int SendData(Socket socket, byte[] buffer, int outTime)
        {
            if (socket == null || socket.Connected == false)
            {
                throw new ArgumentException("參數socket 為null,或者未連接到遠程計算機");
            }
            if (buffer == null || buffer.Length == 0)
            {
                throw new ArgumentException("參數buffer 為null ,或者長度為 0");
            }

            int flag = 0;
            try
            {
                int left = buffer.Length;
                int sndLen = 0;

                while (true)
                {
                    if ((socket.Poll(outTime * 100, SelectMode.SelectWrite) == true))
                    {        // 收集了足夠多的傳出數據后開始發送
                        sndLen = socket.Send(buffer, sndLen, left, SocketFlags.None);
                        left -= sndLen;
                        if (left == 0)
                        {                                        // 數據已經全部發送
                            flag = 0;
                            break;
                        }
                        else
                        {
                            if (sndLen > 0)
                            {                                    // 數據部分已經被發送
                                continue;
                            }
                            else
                            {                                                // 發送數據發生錯誤
                                flag = -2;
                                break;
                            }
                        }
                    }
                    else
                    {                                                        // 超時退出
                        flag = -1;
                        break;
                    }
                }
            }
            catch (SocketException e)
            {

                flag = -3;
            }
            return flag;
        }


        /// <summary>
        /// 向遠程主機發送文件
        /// </summary>
        /// <param name="socket" >要發送數據且已經連接到遠程主機的 socket</param>
        /// <param name="fileName">待發送的文件名稱</param>
        /// <param name="maxBufferLength">文件發送時的緩沖區大小</param>
        /// <param name="outTime">發送緩沖區中的數據的超時時間</param>
        /// <returns>0:發送文件成功;-1:超時;-2:發送文件出現錯誤;-3:發送文件出現異常;-4:讀取待發送文件發生錯誤</returns>
        /// <remarks >
        /// 當 outTime 指定為-1時,將一直等待直到有數據需要發送
        /// </remarks>
        public static int SendFile(Socket socket, string fileName, int maxBufferLength, int outTime)
        {
            if (fileName == null || maxBufferLength <= 0)
            {
                throw new ArgumentException("待發送的文件名稱為空或發送緩沖區的大小設置不正確.");
            }
            int flag = 0;
            try
            {
                FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
                long fileLen = fs.Length;                        // 文件長度
                long leftLen = fileLen;                            // 未讀取部分
                int readLen = 0;                                // 已讀取部分
                byte[] buffer = null;

                if (fileLen <= maxBufferLength)
                {            /* 文件可以一次讀取*/
                    buffer = new byte[fileLen];
                    readLen = fs.Read(buffer, 0, (int)fileLen);
                    flag = SendData(socket, buffer, outTime);
                }
                else
                {
                    /* 循環讀取文件,並發送 */

                    while (leftLen != 0)
                    {
                        if (leftLen < maxBufferLength)
                        {
                            buffer = new byte[leftLen];
                            readLen = fs.Read(buffer, 0, Convert.ToInt32(leftLen));
                        }
                        else
                        {
                            buffer = new byte[maxBufferLength];
                            readLen = fs.Read(buffer, 0, maxBufferLength);
                        }
                        if ((flag = SendData(socket, buffer, outTime)) < 0)
                        {
                            break;
                        }
                        leftLen -= readLen;
                    }
                }
                fs.Flush();
                fs.Close();
            }
            catch (IOException e)
            {

                flag = -4;
            }
            return flag;
        }

    }
}

 

這樣,就可以進行文件的傳輸了,效果圖如圖3所示

圖3 文件傳輸效果圖

 

二、使用TcpLister和TcpClient進行文件傳輸

TcpLister和TcpClient進行文件傳輸相對來說就要簡單些,服務器Demo界面如圖4所示:

圖4 服務器界面圖

啟動監聽和接收文件的代碼如下:

 
         
TcpListener listener;

#region
服務器啟動監聽服務,並開始接收文件 private void btnBegin_Click(object sender, EventArgs e) { btnBegin.Enabled = false; listener = new TcpListener(IPAddress.Parse(txtIp.Text), int.Parse(txtPort.Text)); listener.Start(); ShwMsgForView.ShwMsgforView(lstbxMsgView, "服務器開始監聽"); Thread th = new Thread(ReceiveMsg); th.Start(); th.IsBackground = true; } public void ReceiveMsg() { while (true) { try { int size = 0; int len = 0; TcpClient client = listener.AcceptTcpClient(); if (client.Connected) { //向列表控件中添加一個客戶端的Ip和端口,作為發送時客戶的唯一標識 listbOnline.Items.Add(client.Client.RemoteEndPoint); ShwMsgForView.ShwMsgforView(lstbxMsgView, "客戶端連接成功" + client.Client.RemoteEndPoint.ToString()); } NetworkStream stream = client.GetStream(); if (stream != null) { SaveFileDialog sfd = new SaveFileDialog(); if (sfd.ShowDialog(this) == System.Windows.Forms.DialogResult.OK) { string fileSavePath = sfd.FileName;//獲得用戶保存文件的路徑 FileStream fs = new FileStream(fileSavePath, FileMode.Create, FileAccess.Write); byte[] buffer = new byte[512]; while ((size = stream.Read(buffer, 0, buffer.Length)) > 0) { fs.Write(buffer, 0, size); len += size; } fs.Flush(); stream.Flush(); stream.Close(); client.Close(); ShwMsgForView.ShwMsgforView(lstbxMsgView, "文件接受成功" + fileSavePath); } } } catch(Exception ex) { ShwMsgForView.ShwMsgforView(lstbxMsgView, "出現異常:" + ex.Message); } } } #endregion

客戶端選擇文件后,即可直接發送文件:

客戶端代碼如下:

        //使用TcpLister和TcpClient向服務端發送文件
        private void button1_Click(object sender, EventArgs e)
        {
            TcpClient client = new TcpClient();
           
            client.Connect(IPAddress.Parse(txtIp.Text), int.Parse(txtPort.Text));
            NetworkStream ns = client.GetStream();
            FileStream fs = new FileStream(txtFileName.Text, FileMode.Open);
            int size = 0;//初始化讀取的流量為0   
            long len = 0;//初始化已經讀取的流量   
            while (len < fs.Length)
            {
                byte[] buffer = new byte[512];
                size = fs.Read(buffer, 0, buffer.Length);
                ns.Write(buffer, 0, size);
                len += size;
                //Pro((long)len);   
            }
            fs.Flush();
            ns.Flush();
            fs.Close();
            ns.Close();
            ShowMgs(txtFileName.Text + "文件發送成功");
        }

其中發送文件效果圖如圖5所示:

圖5 發送文件效果圖

 


 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM