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、服务器端代码如下:

复制代码
        <span style="color: #0000ff;">public</span><span style="color: #000000;"> ShakeHands()</span>

{

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类代码如下:

复制代码
<span style="color: #0000ff;">using</span><span style="color: #000000;"> System;</span>

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类代码如下:

 

复制代码
<span style="color: #0000ff;">using</span><span style="color: #000000;"> System;</span>

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、客户端发送文件代码

首先连接服务器代码:

复制代码
        <span style="color: #0000ff;">#region</span> 连接服务器

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

复制代码

连接服务器成功后,即可发送文件了,先选择文件:

复制代码
        <span style="color: #0000ff;">#region</span> 选择要发送的文件

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

复制代码

发送文件代码:

复制代码
        <span style="color: #008000;">//</span><span style="color: #008000;">使用socket向服务端发送文件</span>

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类的代码如下:

复制代码
<span style="color: #0000ff;">using</span><span style="color: #000000;"> System;</span>

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 服务器界面图

启动监听和接收文件的代码如下:

按 Ctrl+C 复制代码
按 Ctrl+C 复制代码

客户端选择文件后,即可直接发送文件:

客户端代码如下:

复制代码
        <span style="color: #008000;">//</span><span style="color: #008000;">使用TcpLister和TcpClient向服务端发送文件</span>

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 发送文件效果图

 

来源URL:http://www.cnblogs.com/bianlan/archive/2012/08/10/2632349.html