前言
文件上传有FTP、WebApi、WebService等等,这次我们来实现一个基于socket通信的本地客户端上传文件到服务器的例子。
运行效果
一、Socket(套接字)概念
说起socket通信,那就不得不介绍一下套接字(socket),套接字是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元,套接字Socket=(IP地址:端口号)。就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。从所处的地位来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议栈进行交互的接口,它包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。
二、Socket通信的建立
建立Socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket ,另一个运行于服务器端,称为ServerSocket 。
三、通信流程
根据连接启动的方式以及本地套接字要连接的目标,套接字之间的连接过程可以分为三个步骤:
(1)服务器监听。
(2)客户端请求。
(3)连接确认。
1.服务器监听
所谓服务器监听,是指服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态。
2.客户端请求
所谓客户端请求,是指由客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端接字提出连接请求 。
3.连接确认
所谓连接确认,是指当服务器端套接字监听到或者说接收到客户端套接字的连接请求,就会响应客户端套接字的请求,建立一个新的线程,并把服务器端套接字的描述发送给客户端。一旦客户端确认了此描述,连接就建立好了。而服务器端套接字继续处于监听状态,接收其他客户端套接字的连接请求。
四、实现
1.ClientSocket
本机IP地址在窗体加载的时候赋值。
private void FrmMain_Load(object sender, EventArgs e) { //获得本机的IP地址 this.textBox4.Text = Dns.GetHostByName(Dns.GetHostName()).AddressList[0].ToString(); }
接下来我们正式开始,点击浏览,选择需要上传的文件。此时我们获取当前文件的全路径、文件名称及合计字节。
/// <summary> /// 浏览 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void button1_Click(object sender, EventArgs e) { //选择要进行传输的文件 if (this.openFileDialog1.ShowDialog() == DialogResult.OK) { FileInfo EzoneFile = new FileInfo(this.openFileDialog1.FileName); this.textBox1.Text = EzoneFile.FullName; this.textBox2.Text = EzoneFile.Name; this.textBox3.Text = EzoneFile.Length.ToString(); } }
点击发送按钮,将文件以字节流的形式发送到服务器端,此时应该单独开一个子线程以保证监听运行,监听事件定义StartSend。
/// <summary> /// 开始发送 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void button2_Click(object sender, EventArgs e) { //开启文件传输子线程 Thread TempThread = new Thread(new ThreadStart(this.StartSend)); TempThread.Start(); }
StartSend事件中,根据文件路径创建一个文件对象,配置操作流、创建连接对象、IP\Port等等关键信息。首先我们来创建一个帮助类FileClientSocket.cs
using System; using System.Collections.Generic; using System.Linq; using System.Net.Sockets; using System.Text; namespace TouchSocketClient { public class FileClientSocket { public static int SendData(Socket s, byte[] data) { int total = 0; int size = data.Length; int dataleft = size; int sent; while (total < size) { sent = s.Send(data, total, dataleft, SocketFlags.None); total += sent; dataleft -= sent; } return total; } public static byte[] ReceiveData(Socket s, int size) { int total = 0; int dataleft = size; byte[] data = new byte[size]; int recv; while (total < size) { recv = s.Receive(data, total, dataleft, SocketFlags.None); if (recv == 0) { data = null; break; } total += recv; dataleft -= recv; } return data; } public static int SendVarData(Socket s, byte[] data) { int total = 0; int size = data.Length; int dataleft = size; int sent; byte[] datasize = new byte[4]; datasize = BitConverter.GetBytes(size); sent = s.Send(datasize); while (total < size) { sent = s.Send(data, total, dataleft, SocketFlags.None); total += sent; dataleft -= sent; } return total; } public static byte[] ReceiveVarData(Socket s) { int total = 0; int recv; byte[] datasize = new byte[4]; recv = s.Receive(datasize, 0, 4, SocketFlags.None); int size = BitConverter.ToInt32(datasize, 0); int dataleft = size; byte[] data = new byte[size]; while (total < size) { recv = s.Receive(data, total, dataleft, SocketFlags.None); if (recv == 0) { data = null; break; } total += recv; dataleft -= recv; } return data; } } }
StartSend方法实现
private void StartSend() { //创建一个文件对象 FileInfo EzoneFile = new FileInfo(this.textBox1.Text); //打开文件流 FileStream EzoneStream = EzoneFile.OpenRead(); //包的大小 int PacketSize = int.Parse(this.textBox6.Text); //包的数量 int PacketCount = (int)(EzoneStream.Length / ((long)PacketSize)); //最后一个包的大小 int LastDataPacket = (int)(EzoneStream.Length - ((long)(PacketSize * PacketCount))); //指向远程服务端节点 IPEndPoint ipep = new IPEndPoint(IPAddress.Parse(txtIP.Text.Trim()), int.Parse(this.textBox5.Text)); //创建套接字 Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //连接到发送端 client.Connect(ipep); //获得客户端节点对象 IPEndPoint clientep = (IPEndPoint)client.RemoteEndPoint; //获得客户端的IP地址 //this.textBox7.Text=clientep.Address.ToString(); //发送[文件名]到客户端 FileClientSocket.SendVarData(client,System.Text.Encoding.Unicode.GetBytes(EzoneFile.Name)); //发送[包的大小]到客户端 FileClientSocket.SendVarData(client,System.Text.Encoding.Unicode.GetBytes(PacketSize.ToString())); //发送[包的总数量]到客户端 FileClientSocket.SendVarData(client,System.Text.Encoding.Unicode.GetBytes(PacketCount.ToString())); //发送[最后一个包的大小]到客户端 FileClientSocket.SendVarData(client,System.Text.Encoding.Unicode.GetBytes(LastDataPacket.ToString())); //数据包 byte[] data = new byte[PacketSize]; //开始循环发送数据包 for (int i = 0; i < PacketCount; i++) { //从文件流读取数据并填充数据包 EzoneStream.Read(data, 0, data.Length); //发送数据包 FileClientSocket.SendVarData(client, data); } //如果还有多余的数据包,则应该发送完毕! if (LastDataPacket != 0) { data = new byte[LastDataPacket]; EzoneStream.Read(data, 0, data.Length); FileClientSocket.SendVarData(client, data); } //关闭套接字 client.Close(); //关闭文件流 EzoneStream.Close(); MessageBox.Show("文件传输完毕!"); }
至此客户端部分已经完成,下面开始实现服务端。
2.ServerSocket
我们新建一个控制台应用程序,TouchSocketServer。
在Main方法中 注册接收事件,并开始子线程进行处理。
static void Main(string[] args) { //开启接收线程 Thread TempThread = new Thread(new ThreadStart(StartReceive)); TempThread.Start(); }
同样我们服务端也需要一个Server类(FileSocketServer.cs)
namespace TouchSocketServer { public class FileSocketServer { public static int SendData(Socket s, byte[] data) { int total = 0; int size = data.Length; int dataleft = size; int sent; while (total < size) { sent = s.Send(data, total, dataleft, SocketFlags.None); total += sent; dataleft -= sent; } return total; } public static byte[] ReceiveData(Socket s, int size) { int total = 0; int dataleft = size; byte[] data = new byte[size]; int recv; while (total < size) { recv = s.Receive(data, total, dataleft, SocketFlags.None); if (recv == 0) { data = null; break; } total += recv; dataleft -= recv; } return data; } public static int SendVarData(Socket s, byte[] data) { int total = 0; int size = data.Length; int dataleft = size; int sent; byte[] datasize = new byte[4]; datasize = BitConverter.GetBytes(size); sent = s.Send(datasize); while (total < size) { sent = s.Send(data, total, dataleft, SocketFlags.None); total += sent; dataleft -= sent; } return total; } public static byte[] ReceiveVarData(Socket s) { int total = 0; int recv; byte[] datasize = new byte[4]; recv = s.Receive(datasize, 0, 4, SocketFlags.None); int size = BitConverter.ToInt32(datasize, 0); int dataleft = size; byte[] data = new byte[size]; while (total < size) { recv = s.Receive(data, total, dataleft, SocketFlags.None); if (recv == 0) { data = null; break; } total += recv; dataleft -= recv; } return data; } } }
接收函数
private static void StartReceive() { //创建一个网络端点 Console.ForegroundColor = ConsoleColor.DarkRed; Console.WriteLine($"{DateTime.Now} [初始化网络端点]"); IPEndPoint ipep = new IPEndPoint(IPAddress.Any, int.Parse("1005")); //MessageBox.Show(IPAddress.Any); //创建一个套接字 Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //绑定套接字到端口 server.Bind(ipep); //开始侦听(并堵塞该线程) Console.WriteLine($"{DateTime.Now} [开始侦听并堵塞该线程]"); server.Listen(10); while (true) { try { Socket client = server.Accept(); ClientSocket = client; Thread TempThread = new Thread(new ThreadStart(Create)); TempThread.Start(); } catch (Exception ex) { int k = 0; } } }
创建文件函数
public static void Create() { Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine($"{DateTime.Now} [创建对象]"); Socket client = ClientSocket; //确认连接 //Socket client = server.Accept(); //获得客户端节点对象 Console.ForegroundColor = ConsoleColor.Yellow; IPEndPoint clientep = (IPEndPoint)client.RemoteEndPoint; Console.WriteLine($"{DateTime.Now} 请求对象:[{clientep.Address}]"); //获得[文件名] string SendFileName = System.Text.Encoding.Unicode.GetString(FileSocketServer.ReceiveVarData(client)); Console.WriteLine($"{DateTime.Now} 文件名称:[{SendFileName}]"); //获得[包的大小] string bagSize = System.Text.Encoding.Unicode.GetString(FileSocketServer.ReceiveVarData(client)); //获得[包的总数量] int bagCount = int.Parse(System.Text.Encoding.Unicode.GetString(FileSocketServer.ReceiveVarData(client))); //获得[最后一个包的大小] string bagLast = System.Text.Encoding.Unicode.GetString(FileSocketServer.ReceiveVarData(client)); //自动创建文件夹 string path = $@"{System.AppDomain.CurrentDomain.BaseDirectory}\Log\{clientep.Address}({DateTime.Now.ToString("yyyyMMddhhmmssffffff")})"; Directory.CreateDirectory(path); //创建一个新文件 FileStream MyFileStream = new FileStream($@"{path}\" + SendFileName, FileMode.Create, FileAccess.Write); Console.WriteLine($"{DateTime.Now} 创建目标:[已创建]"); //已发送包的个数 int SendedCount = 0; while (true) { byte[] data = FileSocketServer.ReceiveVarData(client); if (data.Length == 0) { break; } else { SendedCount++; //将接收到的数据包写入到文件流对象 MyFileStream.Write(data, 0, data.Length); //显示已发送包的个数 } } //关闭文件流 MyFileStream.Close(); //关闭套接字 client.Close(); Console.WriteLine($"{DateTime.Now} 传输结果:[成功]"); }