做C#开发,很多场景需要传输文件,socket是一个不错选择,想想办法还能带上文件名。网上类似的文章也很多,但是好像有效的不多,完整的代码几乎没有。于是乎,我来贴一个,高手不要吐槽哦,因为这个简单而又简约的文章,也许、大概能帮助某些初学者嘛。服务端和客户端代码都在这里了。
服务端接收文件代码:
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
using System.IO;
using blank.Log;
using System.Threading;
using blank.Tools;
using System.Collections.Concurrent;
namespace FileSysServer
{
public class tcpServerSocket
{
private TcpListener listener;
public void Start()
{
listener = new TcpListener(IPAddress.Any, 8888);
listener.Start();//开始监听客户端的请求
while (true)
{
try
{
TcpClient tcpClient = listener.AcceptTcpClient();
if (tcpClient != null && tcpClient.Client != null)
{
Thread thread = new Thread(ReceiveMsg); //用一个线程单独处理这个连接
thread.Start(tcpClient);
}
}
catch (Exception e)
{
LogHelper.WriteLog("接到请求发生错误" + e.Message);
}
}
}
#region ReceiveMsg
private void ReceiveMsg(object o)
{
TcpClient c = (TcpClient)o;
while (true)
{
try
{
if (c == null || c.Client == null || !IsSocketConnected(c.Client))
{
Close(c);
break;
}
NetworkStream ns = c.GetStream();
if (ns != null)
{
StreamReader sr = new StreamReader(ns, Encoding.UTF8);
if (sr != null)
{
string result = sr.ReadToEnd();
if (string.IsNullOrEmpty(result) || result.Length < 10)
{
//伪心跳机制 发个0 回个1
c.Client.Send(Encoding.UTF8.GetBytes("1"));//
}
else
{
Task.Factory.StartNew(() => createFile(result));
}
}
if (sr != null)
{
sr.Close();
sr = null;
}
}
if (ns != null)
{
ns.Close();
ns = null;
}
}
catch (Exception ex)
{
LogHelper.WriteLog("异常2:" + ex.Message);
break;
}
}
}
#endregion
#region 创建文件
private void createFile(string reveiceName)
{
try
{
//@@@
int pos = reveiceName.IndexOf("@@@");
if (pos < 0)
{
LogHelper.WriteLog("文件内容错误");
return;
}
string fileName = reveiceName.Substring(0, pos);
if (string.IsNullOrEmpty(fileName) || fileName.Length < 10)
{
LogHelper.WriteLog("文件名错误");
return;
}
//解密文件名
//if (bool.Parse(MonitorPwd))
//{
// fileName = Encrypt.MD5Decrypt(fileName);
//}
string fileContent = reveiceName.Substring(pos + 3);
string fullPath = Path.Combine("D:\test", fileName);//获取存储路径及文件名
FileStream filesave = new FileStream(fullPath, FileMode.Create, FileAccess.Write);//创建文件流,用来写入数据
byte[] arrMsg = Encoding.UTF8.GetBytes(fileContent);
//filesave.WriteByte(arrMsg);
filesave.Write(arrMsg, 0, arrMsg.Length);//将数据写入到文件中
filesave.Close();
// fm.AddMsg(fullPath);
}
catch (Exception e)
{
LogHelper.WriteLog("创建文件发生错误" + e.Message);
}
}
#endregion
#region close
private void Close(TcpClient c)
{
if (c == null || c.Client == null)
{
return;
}
try
{
c.Client.Shutdown(SocketShutdown.Both);
c.Client.Close();
c.Client = null;
c.Close();
c = null;
}
catch (Exception e)
{
LogHelper.WriteLog("关闭TcpClient失败" + e.Message);
}
}
#endregion
#region IsSocketConnected
//这个方法网上找来的,如有雷同,纯属抄袭
private bool IsSocketConnected(Socket client)
{
bool blockingState = client.Blocking;
try
{
byte[] tmp = new byte[1];
client.Blocking = false;
client.Send(tmp, 0, 0);
return true;
}
catch (SocketException e)
{
// 产生 10035 == WSAEWOULDBLOCK 错误,说明被阻止了,但是还是连接的
if (e.NativeErrorCode.Equals(10035))
return false;
else
return true;
}
finally
{
client.Blocking = blockingState; // 恢复状态
}
}
#endregion
}
}
客户端发送文件代码
public static string SendFileData(string fileName)
{
TcpClient c = new TcpClient();
try
{
//Thread.Sleep(1000);
byte[] byteArray = Readbytes(fileName);
if (byteArray == null)
{
return string.Format("读取文件{0}失败", fileName);
}
c.Connect(IPAddress.Parse("127.0.0.1"), 8888); //部署的时候自己换iP
c.Client.Send(byteArray);//将读取成功的文件发送给SocketServer服务器
return "";
}
catch (Exception e)
{
return e.Message;
}
finally
{
Close(c); //这个关闭方法和服务端的那个是一样的,自己复制粘贴吧
}
}
这里有人可能会说,每一次都打开、关闭,不是长连接,效率不高啊,确实如此。开一个连接,然后一直传,不好吗?因为是传输文件,不是传文字内容,用一个socket发现有各种问题。可能有更好的写法,懒得研究了。这里暂时满足客户场景,也算稳定,不研究了。不过,如果有更好的代码,欢迎吐槽。
读取文件内容,这个代码自己随便修改,与Socket发送和接收没关系。这里也贴出来,方便大家测试,看看效果
#region 读文件
private static byte[] Readbytes(string fullName)
{
FileStream fs = null;
try
{
fs = new FileStream(fullName, FileMode.Open, FileAccess.Read);
//创建文件流,用来读取数据
string filename = Path.GetFileName(fullName);//提取文件名
//是否加密
//bool MonitorPwd = false;
//if (bool.Parse(MonitorPwd))
//{
// filename = Encrypt.MD5Encrypt(filename);
//}
byte[] arrFile = new byte[fs.Length]; //定义缓存控件,长度为文件长度
int length = fs.Read(arrFile, 0, arrFile.Length);//将文件读入缓存空间
string fn = string.Concat(filename, "@@@", Encoding.UTF8.GetString(arrFile, 0, length));
return Encoding.UTF8.GetBytes(fn);
}
catch (Exception e)
{
LogHelper.WriteLog("读文件失败" + e.Message);
return null;
}
finally
{
if (fs != null)
{
fs.Close();
fs = null;
}
}
}
#endregion
这个传输办法其实就是用几个特殊字符 @@@ 把文件名和文件内容连接在一起,如果文件名同样含有@@@特殊符号,就麻烦了。至于文件内容里面有这个特殊符号时不影响的。
文件名也可以做加密,解密处理,与传输没关系,所以注释了。
这个方法所传输的文件,没有那种超大文件,超大文件得想别的办法了。
写日志方法那个语句,如果没有,注释即可 // LogHelper.WriteLog("读文件失败" + e.Message);
一个服务端,可以带多个客户端,本人实际运行场景就是这样的。一个服务端大概有7个客户端
补充一下,客户端代码的 using
using System;
using System.Collections.Generic;
using blank.Tools;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.IO;