C# Socket服务端与客户端通信(包含大文件的断点传输)
C# Socket服务端与客户端通信(包含大文件的断点传输)
步骤:
一、服务端的建立
1.服务端的项目建立以及页面布局
2.各功能按键的事件代码
1)传输类型说明以及全局变量
2)Socket通信服务端具体步骤:
(1)建立一个Socket
(2)接收信息
(3)发送数据(这里分发送字符串、文件(包含大文件)、震动)
二、客户端的建立
1.服务端的项目建立以及页面布局
2.各功能按键的事件代码
1)传输类型说明以及全局变量
2)Socket通信服务端具体步骤:
(1)建立一个Socket
(2)接收信息
(3)发送数据(这里分发送字符串、文件(包含大文件)、震动)
注意:此图是Socket通信的精华,在使用Socket通信时,有什么迷惑的可以看看此图,下面我们讲解的时候也是参照此图
Socket大家肯定很熟悉,对已内部的通信逻辑,肯定也有一定得了解---
对于Socket研究了两天写了一个小程序,通过Socket服务端与客户端的通信,以及大文件之间断点的传输(这里只做了服务端给客户端传送大文件,如果想把客户端的大文件传送给服务端也是一样的道理,看了文章,大家肯定可以自己实现)······
(自己才疏学浅,如有bug请谅解,但功能还是能实现的)
下面根据步骤进入正题:
一、服务端的建立
1.服务端的项目建立以及页面布局
新建解决方案“Socket通信”以及两个Winform项目(1)SockeClient——客户端 (2)SocketServer——服务器
给服务端界面布局——参照上图(这个大家肯定都是手到擒来就不累赘了······)
2.各功能按键的事件代码
先把整个服务端的代码贴出来,然后我们在一一讲解
namespace SocketServer { public partial class Form1 : Form { //说明:在传递信息的时候,会在需要传递的信息前面加一个字符来标识传递的是不同的信息 // 0:表示传递的是字符串信息 // 1:表示传递的是文件信息 // 2:表示的是震动 /// <summary> /// 用来存放连接服务的客户端的IP地址和端口号,对应的Socket /// </summary> Dictionary<string, Socket> dicSocket = new Dictionary<string, Socket>(); public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { //不检测跨线程之间的空间调用 Control.CheckForIllegalCrossThreadCalls = false; } /// <summary> /// 开启监听 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnStart_Click(object sender, EventArgs e) { try { //当点击开始监听的时候 在服务器端创建一个负责监IP地址跟端口号的Socket Socket socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //获取IP IPAddress ip = IPAddress.Any; //创建端口号 IPEndPoint port = new IPEndPoint(ip, Convert.ToInt32(txtPort.Text)); //监听 socketWatch.Bind(port); ShowMsg("监听成功"); socketWatch.Listen(10); //新建线程,去接收客户端发来的信息 Thread td = new Thread(AcceptMgs); td.IsBackground = true; td.Start(socketWatch); } catch { } } /// <summary> /// 接收客户端发送的信息 /// </summary> /// <param name="o"></param> private void AcceptMgs(object o) { try { Socket socketWatc = (Socket)o; while (true) { ////负责跟客户端通信的Socket Socket socketSend = socketWatc.Accept(); //将远程连接的客户端的IP地址和Socket存入集合中 dicSocket.Add(socketSend.RemoteEndPoint.ToString(), socketSend); //将远程连接的客户端的IP地址和端口号存储下拉框中 cboUsers.Items.Add(socketSend.RemoteEndPoint.ToString()); ShowMsg(socketSend.RemoteEndPoint.ToString() + ": 连接成功"); //新建线程循环接收客户端发来的信息 Thread td = new Thread(Recive); td.IsBackground = true; td.Start(socketSend); } } catch { } } /// <summary> /// 接收客户端发来的数据,并显示出来 /// </summary> private void Recive(object o) { Socket socketSend = (Socket)o; try { while (true) { //客户端连接成功后,服务器应该接受客户端发来的消息 if (socketSend == null) { MessageBox.Show("请选择要发送的客户端"); continue; } byte[] buffer = new byte[1024 * 1024 * 2]; //实际接受到的有效字节数 int r = socketSend.Receive(buffer); //如果客户端关闭,发送的数据就为空,然后就跳出循环 if (r == 0) { break; } if (buffer[0] == 0) //如果接收的字节数组的第一个字节是0,说明接收的字符串信息 { string strMsg = Encoding.UTF8.GetString(buffer, 1, r - 1); ShowMsg(socketSend.RemoteEndPoint.ToString() + ": " + strMsg); } else if (buffer[0] == 1) //如果接收的字节数组的第一个字节是1,说明接收的是文件 { string filePath = ""; SaveFileDialog sfd = new SaveFileDialog(); sfd.Title = "保存文件"; sfd.InitialDirectory = @"C:\Users\Administrator\Desktop"; sfd.Filter = "文本文件|*.txt|图片文件|*.jpg|视频文件|*.avi|所有文件|*.*"; //如果没有选择保存文件路径就一直打开保存框 while (true) { sfd.ShowDialog(this); filePath = sfd.FileName; if (string.IsNullOrEmpty(filePath)) { continue; } else { break; } } //保存接收的文件 using (FileStream fsWrite = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write)) { fsWrite.Write(buffer, 1, r - 1); } ShowMsg(socketSend.RemoteEndPoint + ": 接收文件成功"); } else if (buffer[0] == 2) //如果接收的字节数组的第一个字节是2,说明接收的是震动 { ZD(); } } } catch{} } /// <summary> /// 显示信息 /// </summary> /// <param name="message"></param> private void ShowMsg(string message) { txtLog.AppendText(message + "\r\n"); } /// <summary> /// 发送信息 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnSend_Click(object sender, EventArgs e) { //获得选中客户端ip对应的通信Socket if (cboUsers.SelectedItem == null) { MessageBox.Show("请选择要发送的客户端"); return; } Socket socketSend = dicSocket[cboUsers.SelectedItem.ToString()]; if (socketSend == null) { MessageBox.Show("请选择要发送的客户端"); return; } string strSend=txtMsg.Text; try { byte[] buffer = Encoding.UTF8.GetBytes(strSend); //获得发送的信息时候,在数组前面加上一个字节 0 List<byte> list = new List<byte>(); list.Add(0); list.AddRange(buffer); //将泛型集合转换为数组 byte[] newBuffer = list.ToArray(); //将了标识字符的字节数组传递给客户端 socketSend.Send(newBuffer); txtMsg.Text = ""; } catch { } } /// <summary> /// 选择文件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnSelect_Click(object sender, EventArgs e) { //打开文件 OpenFileDialog ofd = new OpenFileDialog(); ofd.Title = "选择要传的文件"; ofd.InitialDirectory = @"C:\Users\Administrator\Desktop"; ofd.Filter = "文本文件|*.txt|图片文件|*.jpg|视频文件|*.avi|所有文件|*.*"; ofd.ShowDialog(); //得到选择文件的路径 txtPath.Text = ofd.FileName; } /// <summary> /// 发送文件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnSendFile_Click(object sender, EventArgs e) { //判断是否选择了要发送的客户端 if (cboUsers.SelectedItem == null) { MessageBox.Show("请选择要发送的客户端"); return; } Socket socketSend = dicSocket[cboUsers.SelectedItem.ToString()]; if (socketSend == null) { MessageBox.Show("请选择要发送的客户端"); return; } string filePath = txtPath.Text; if (string.IsNullOrEmpty(filePath)) { MessageBox.Show("请选择文件"); return; } Thread td = new Thread(SendBigFile); td.IsBackground = true; td.Start(); } /// <summary> /// 大文件断点传送 /// </summary> private void SendBigFile() { string filePath = txtPath.Text; Socket socketSend = dicSocket[cboUsers.SelectedItem.ToString()]; try { //读取选择的文件 using (FileStream fsRead = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Read)) { //1. 第一步:发送一个包,表示文件的长度,让客户端知道后续要接收几个包来重新组织成一个文件 long length = fsRead.Length; byte[] byteLength = Encoding.UTF8.GetBytes(length.ToString()); //获得发送的信息时候,在数组前面加上一个字节 1 List<byte> list = new List<byte>(); list.Add(1); list.AddRange(byteLength); socketSend.Send(list.ToArray()); // //2. 第二步:每次发送一个1MB的包,如果文件较大,则会拆分为多个包 byte[] buffer = new byte[1024 * 1024]; long send = 0; //发送的字节数 while (true) //大文件断点多次传输 { int r = fsRead.Read(buffer, 0, buffer.Length); if (r == 0) { break; } socketSend.Send(buffer, 0, r, SocketFlags.None); send += r; ShowMsg(string.Format("{0}: 已发送:{1}/{2}", socketSend.RemoteEndPoint, send, length)); } ShowMsg("发送完成"); txtPath.Text = ""; } } catch { } } /// <summary> /// 震动 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnZD_Click(object sender, EventArgs e) { //判断是否选择了要发送的客户端 if (cboUsers.SelectedItem == null) { MessageBox.Show("请选择要发送的客户端"); return; } Socket socketSend = dicSocket[cboUsers.SelectedItem.ToString()]; if (socketSend == null) { MessageBox.Show("请选择要发送的客户端"); return; } try { // 首字节是2说明是震动 byte[] buffer = new byte[1]; buffer[0] = 2; socketSend.Send(buffer); } catch { } } /// <summary> /// 震动 /// </summary> private void ZD() { //获取当前窗体的坐标 Point point = this.Location; //反复给窗体坐标复制一百次,达到震动的效果 for (int i = 0; i < 100; i++) { this.Location = new Point(point.X - 5, point.Y - 5); this.Location = new Point(point.X + 5, point.Y + 5); } this.Location = point; } } }
1)传输类型说明以及全局变量
这些说明以及全局变量,说的也比较清楚,也不累赘了。
2)Socket通信服务端具体步骤:
(这些步骤都是根据第一个图来的)
(1)建立一个Socket
/// <summary> /// 开启监听 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnStart_Click(object sender, EventArgs e) { try { //当点击开始监听的时候 在服务器端创建一个负责监IP地址跟端口号的Socket Socket socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //获取IP IPAddress ip = IPAddress.Any; //创建端口号 IPEndPoint port = new IPEndPoint(ip, Convert.ToInt32(txtPort.Text)); //监听 socketWatch.Bind(port); ShowMsg("监听成功"); socketWatch.Listen(10); //新建线程,去接收客户端发来的信息 Thread td = new Thread(AcceptMgs); td.IsBackground = true; td.Start(socketWatch); } catch { } }
在开启监听按钮里,我们建立了Socket,以及监听的最大客户端数 socketWatch.Listen(10)
由于服务端会不停的去监视接收客户端发来的信息,如果把这个工作放到主线程里,程序会出现假死的现象,所以这里给他放到一个新的线程里。
(2)接收信息
/// <summary> /// 接收客户端发送的信息 /// </summary> /// <param name="o"></param> private void AcceptMgs(object o) { try { Socket socketWatc = (Socket)o; while (true) { ////负责跟客户端通信的Socket Socket socketSend = socketWatc.Accept(); //将远程连接的客户端的IP地址和Socket存入集合中 dicSocket.Add(socketSend.RemoteEndPoint.ToString(), socketSend); //将远程连接的客户端的IP地址和端口号存储下拉框中 cboUsers.Items.Add(socketSend.RemoteEndPoint.ToString()); ShowMsg(socketSend.RemoteEndPoint.ToString() + ": 连接成功"); //新建线程循环接收客户端发来的信息 Thread td = new Thread(Recive); td.IsBackground = true; td.Start(socketSend); } } catch { } }
接收信息是会根据接收到字节数字的第一个字节来判断接收到的是什么
这个在方法Recive里进行判断
/// <summary>
namespace SocketClient { public partial class Form1 : Form { //说明:在传递信息的时候,会在需要传递的信息前面加一个字符来标识传递的是不同的信息 // 0:表示传递的是字符串信息 // 1:表示传递的是文件信息 // 2:表示的是震动 /// <summary> /// 用来存放连接服务的IP地址和端口号,对应的Socket (这个为了以后的扩展用,现在暂时没用) /// </summary> Dictionary<string, Socket> dicSocket = new Dictionary<string, Socket>(); /// <summary> /// 存储保存文件的路径 /// </summary> string filePath = ""; /// <summary> /// 负责通信的Socket /// </summary> Socket socketSend; public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { //不检测跨线程之间的空间调用 Control.CheckForIllegalCrossThreadCalls = false; } /// <summary> /// 建立连接 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnStart_Click(object sender, EventArgs e) { try { //创建负责通信的Socket socketSend = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //获取服务端的IP IPAddress ip = IPAddress.Parse(txtServer.Text.Trim()); //获取服务端的端口号 IPEndPoint port = new IPEndPoint(ip, Convert.ToInt32(txtPort.Text)); //获得要连接的远程服务器应用程序的IP地址和端口号 socketSend.Connect(port); ShowMsg("连接成功"); //新建线程,去接收客户端发来的信息 Thread td = new Thread(AcceptMgs); td.IsBackground = true; td.Start(); } catch { } } /// <summary> /// 接收数据 /// </summary> private void AcceptMgs() { try { /// <summary> /// 存储大文件的大小 /// </summary> long length = 0; long recive = 0; //接收的大文件总的字节数 while (true) { byte[] buffer = new byte[1024 * 1024]; int r = socketSend.Receive(buffer); if (r == 0) { break; } if (length > 0) //判断大文件是否已经保存完 { //保存接收的文件 using (FileStream fsWrite = new FileStream(filePath, FileMode.Append, FileAccess.Write)) { fsWrite.Write(buffer, 0, r); length -= r; //减去每次保存的字节数 ShowMsg(string.Format("{0}: 已接收:{1}/{2}", socketSend.RemoteEndPoint, recive-length, recive)); if (length <= 0) { ShowMsg(socketSend.RemoteEndPoint + ": 接收文件成功"); } continue; } } if (buffer[0] == 0) //如果接收的字节数组的第一个字节是0,说明接收的字符串信息 { string strMsg = Encoding.UTF8.GetString(buffer, 1, r - 1); ShowMsg(socketSend.RemoteEndPoint.ToString() + ": " + strMsg); } else if (buffer[0] == 1) //如果接收的字节数组的第一个字节是1,说明接收的是文件 { length = int.Parse(Encoding.UTF8.GetString(buffer,1,r-1)); recive = length; filePath = ""; SaveFileDialog sfd = new SaveFileDialog(); sfd.Title = "保存文件"; sfd.InitialDirectory = @"C:\Users\Administrator\Desktop"; sfd.Filter = "文本文件|*.txt|图片文件|*.jpg|视频文件|*.avi|所有文件|*.*"; //如果没有选择保存文件路径就一直打开保存框 while (true) { sfd.ShowDialog(this); filePath = sfd.FileName; if (string.IsNullOrEmpty(filePath)) { continue; } else { break; } } } else if (buffer[0] == 2) //如果接收的字节数组的第一个字节是2,说明接收的是震动 { ZD(); } } } catch { } } /// <summary> /// 显示信息 /// </summary> /// <param name="message"></param> private void ShowMsg(string message) { txtLog.AppendText(message + "\r\n"); } /// <summary> /// 发送数据 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnSend_Click(object sender, EventArgs e) { try { byte[] buffer = Encoding.UTF8.GetBytes(txtMsg.Text); //获得发送的信息时候,在数组前面加上一个字节 0 List<byte> list = new List<byte>(); list.Add(0); list.AddRange(buffer); //将泛型集合转换为数组 byte[] newBuffer = list.ToArray(); //将了标识字符的字节数组传递给客户端 socketSend.Send(newBuffer); txtMsg.Text = ""; } catch{} } /// <summary> /// 选择文件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnSelect_Click(object sender, EventArgs e) { //打开文件 OpenFileDialog ofd = new OpenFileDialog(); ofd.Title = "选择要传的文件"; ofd.InitialDirectory = @"C:\Users\Administrator\Desktop"; ofd.Filter = "文本文件|*.txt|图片文件|*.jpg|视频文件|*.avi|所有文件|*.*"; ofd.ShowDialog(); //得到选择文件的路径 txtPath.Text = ofd.FileName; } /// <summary> /// 发送文件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnSendFile_Click(object sender, EventArgs e) { try { string filePath = txtPath.Text; if (string.IsNullOrEmpty(filePath)) { MessageBox.Show("请选择文件"); return; } //读取选择的文件 using (FileStream fsRead = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Read)) { byte[] buffer = new byte[1024 * 1024 * 2]; int r = fsRead.Read(buffer, 0, buffer.Length); //获得发送的信息时候,在数组前面加上一个字节 1 List<byte> list = new List<byte>(); list.Add(1); list.AddRange(buffer); byte[] newBuffer = list.ToArray(); //将了标识字符的字节数组传递给客户端 socketSend.Send(newBuffer, 0, r + 1, SocketFlags.None); txtPath.Text = ""; } } catch{ } } /// <summary> /// 震动 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnZD_Click(object sender, EventArgs e) { try { // 首字节是2说明是震动 byte[] buffer = new byte[1]; buffer[0] = 2; socketSend.Send(buffer); } catch{ } } /// <summary> /// 震动 /// </summary> private void ZD() { //获取当前窗体的坐标 Point point = this.Location; //反复给窗体坐标复制一百次,达到震动的效果 for (int i = 0; i < 100; i++) { this.Location = new Point(point.X - 5, point.Y - 5); this.Location = new Point(point.X + 5, point.Y + 5); } this.Location = point; } } }
/// 接收客户端发来的数据,并显示出来 /// </summary> private void Recive(object o) { Socket socketSend = (Socket)o; try { while (true) { //客户端连接成功后,服务器应该接受客户端发来的消息 if (socketSend == null) { MessageBox.Show("请选择要发送的客户端"); continue; } byte[] buffer = new byte[1024 * 1024 * 2]; //实际接受到的有效字节数 int r = socketSend.Receive(buffer); //如果客户端关闭,发送的数据就为空,然后就跳出循环 if (r == 0) { break; } if (buffer[0] == 0) //如果接收的字节数组的第一个字节是0,说明接收的字符串信息 { string strMsg = Encoding.UTF8.GetString(buffer, 1, r - 1); ShowMsg(socketSend.RemoteEndPoint.ToString() + ": " + strMsg); } else if (buffer[0] == 1) //如果接收的字节数组的第一个字节是1,说明接收的是文件 { string filePath = ""; SaveFileDialog sfd = new SaveFileDialog(); sfd.Title = "保存文件"; sfd.InitialDirectory = @"C:\Users\Administrator\Desktop"; sfd.Filter = "文本文件|*.txt|图片文件|*.jpg|视频文件|*.avi|所有文件|*.*"; //如果没有选择保存文件路径就一直打开保存框 while (true) { sfd.ShowDialog(this); filePath = sfd.FileName; if (string.IsNullOrEmpty(filePath)) { continue; } else { break; } } //保存接收的文件 using (FileStream fsWrite = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write)) { fsWrite.Write(buffer, 1, r - 1); } ShowMsg(socketSend.RemoteEndPoint + ": 接收文件成功"); } else if (buffer[0] == 2) //如果接收的字节数组的第一个字节是2,说明接收的是震动 { ZD(); } } } catch{} }
(3)发送数据(这里分发送字符串、文件(包含大文件)、震动)
发送字符串信息
/// <summary> /// 发送信息 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnSend_Click(object sender, EventArgs e) { //获得选中客户端ip对应的通信Socket if (cboUsers.SelectedItem == null) { MessageBox.Show("请选择要发送的客户端"); return; } Socket socketSend = dicSocket[cboUsers.SelectedItem.ToString()]; if (socketSend == null) { MessageBox.Show("请选择要发送的客户端"); return; } string strSend=txtMsg.Text; try { byte[] buffer = Encoding.UTF8.GetBytes(strSend); //获得发送的信息时候,在数组前面加上一个字节 0 List<byte> list = new List<byte>(); list.Add(0); list.AddRange(buffer); //将泛型集合转换为数组 byte[] newBuffer = list.ToArray(); //将了标识字符的字节数组传递给客户端 socketSend.Send(newBuffer); txtMsg.Text = ""; } catch { } }
发送震动
/// <summary> /// 震动 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnZD_Click(object sender, EventArgs e) { //判断是否选择了要发送的客户端 if (cboUsers.SelectedItem == null) { MessageBox.Show("请选择要发送的客户端"); return; } Socket socketSend = dicSocket[cboUsers.SelectedItem.ToString()]; if (socketSend == null) { MessageBox.Show("请选择要发送的客户端"); return; } try { // 首字节是2说明是震动 byte[] buffer = new byte[1]; buffer[0] = 2; socketSend.Send(buffer); } catch { } } /// <summary> /// 震动 /// </summary> private void ZD() { //获取当前窗体的坐标 Point point = this.Location; //反复给窗体坐标复制一百次,达到震动的效果 for (int i = 0; i < 100; i++) { this.Location = new Point(point.X - 5, point.Y - 5); this.Location = new Point(point.X + 5, point.Y + 5); } this.Location = point; }
发送文件(包含大文件)
首先要选择文件
/// <summary> /// 选择文件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnSelect_Click(object sender, EventArgs e) { //打开文件 OpenFileDialog ofd = new OpenFileDialog(); ofd.Title = "选择要传的文件"; ofd.InitialDirectory = @"C:\Users\Administrator\Desktop"; ofd.Filter = "文本文件|*.txt|图片文件|*.jpg|视频文件|*.avi|所有文件|*.*"; ofd.ShowDialog(); //得到选择文件的路径 txtPath.Text = ofd.FileName; }
然后在发送文件
/// <summary> /// 发送文件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnSendFile_Click(object sender, EventArgs e) { //判断是否选择了要发送的客户端 if (cboUsers.SelectedItem == null) { MessageBox.Show("请选择要发送的客户端"); return; } Socket socketSend = dicSocket[cboUsers.SelectedItem.ToString()]; if (socketSend == null) { MessageBox.Show("请选择要发送的客户端"); return; } string filePath = txtPath.Text; if (string.IsNullOrEmpty(filePath)) { MessageBox.Show("请选择文件"); return; } Thread td = new Thread(SendBigFile); td.IsBackground = true; td.Start(); } /// <summary> /// 大文件断点传送 /// </summary> private void SendBigFile() { string filePath = txtPath.Text; Socket socketSend = dicSocket[cboUsers.SelectedItem.ToString()]; try { //读取选择的文件 using (FileStream fsRead = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Read)) { //1. 第一步:发送一个包,表示文件的长度,让客户端知道后续要接收几个包来重新组织成一个文件 long length = fsRead.Length; byte[] byteLength = Encoding.UTF8.GetBytes(length.ToString()); //获得发送的信息时候,在数组前面加上一个字节 1 List<byte> list = new List<byte>(); list.Add(1); list.AddRange(byteLength); socketSend.Send(list.ToArray()); // //2. 第二步:每次发送一个4KB的包,如果文件较大,则会拆分为多个包 byte[] buffer = new byte[1024 * 1024]; long send = 0; //发送的字节数 while (true) //大文件断点多次传输 { int r = fsRead.Read(buffer, 0, buffer.Length); if (r == 0) { break; } socketSend.Send(buffer, 0, r, SocketFlags.None); send += r; ShowMsg(string.Format("{0}: 已发送:{1}/{2}", socketSend.RemoteEndPoint, send, length)); } ShowMsg("发送完成"); txtPath.Text = ""; } } catch { } }
注意:(1)发送文件的时候会分两步发送 :第一步:发送一个包,表示文件的长度,让客户端知道后续要接收几个包来重新组织成一个文件
第二步:每次发送一个1MB的包,如果文件较大,则会拆分为多个包
(2)每个客户端连接服务端的啥时候,都会把客户端的ip以及端口号,放到下拉框里,想给那个客户端发信息,就选择对应的客户端
二、客户端的建立
1.客户端的项目建立以及页面布局
客户端的界面布局与服务端很像,就是把对应的开始监听换成连接,当然代码也会有所改变,后面会讲到·····
2.各功能按键的事件代码
先把整个服客户端的代码贴出来,然后我们在一一讲解
namespace SocketClient { public partial class Form1 : Form { //说明:在传递信息的时候,会在需要传递的信息前面加一个字符来标识传递的是不同的信息 // 0:表示传递的是字符串信息 // 1:表示传递的是文件信息 // 2:表示的是震动 /// <summary> /// 用来存放连接服务的IP地址和端口号,对应的Socket (这个为了以后的扩展用,现在暂时没用) /// </summary> Dictionary<string, Socket> dicSocket = new Dictionary<string, Socket>(); /// <summary> /// 存储保存文件的路径 /// </summary> string filePath = ""; /// <summary> /// 负责通信的Socket /// </summary> Socket socketSend; public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { //不检测跨线程之间的空间调用 Control.CheckForIllegalCrossThreadCalls = false; } /// <summary> /// 建立连接 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnStart_Click(object sender, EventArgs e) { try { //创建负责通信的Socket socketSend = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //获取服务端的IP IPAddress ip = IPAddress.Parse(txtServer.Text.Trim()); //获取服务端的端口号 IPEndPoint port = new IPEndPoint(ip, Convert.ToInt32(txtPort.Text)); //获得要连接的远程服务器应用程序的IP地址和端口号 socketSend.Connect(port); ShowMsg("连接成功"); //新建线程,去接收客户端发来的信息 Thread td = new Thread(AcceptMgs); td.IsBackground = true; td.Start(); } catch { } } /// <summary> /// 接收数据 /// </summary> private void AcceptMgs() { try { /// <summary> /// 存储大文件的大小 /// </summary> long length = 0; long recive = 0; //接收的大文件总的字节数 while (true) { byte[] buffer = new byte[1024 * 1024]; int r = socketSend.Receive(buffer); if (r == 0) { break; } if (length > 0) //判断大文件是否已经保存完 { //保存接收的文件 using (FileStream fsWrite = new FileStream(filePath, FileMode.Append, FileAccess.Write)) { fsWrite.Write(buffer, 0, r); length -= r; //减去每次保存的字节数 ShowMsg(string.Format("{0}: 已接收:{1}/{2}", socketSend.RemoteEndPoint, recive-length, recive)); if (length <= 0) { ShowMsg(socketSend.RemoteEndPoint + ": 接收文件成功"); } continue; } } if (buffer[0] == 0) //如果接收的字节数组的第一个字节是0,说明接收的字符串信息 { string strMsg = Encoding.UTF8.GetString(buffer, 1, r - 1); ShowMsg(socketSend.RemoteEndPoint.ToString() + ": " + strMsg); } else if (buffer[0] == 1) //如果接收的字节数组的第一个字节是1,说明接收的是文件 { length = int.Parse(Encoding.UTF8.GetString(buffer,1,r-1)); recive = length; filePath = ""; SaveFileDialog sfd = new SaveFileDialog(); sfd.Title = "保存文件"; sfd.InitialDirectory = @"C:\Users\Administrator\Desktop"; sfd.Filter = "文本文件|*.txt|图片文件|*.jpg|视频文件|*.avi|所有文件|*.*"; //如果没有选择保存文件路径就一直打开保存框 while (true) { sfd.ShowDialog(this); filePath = sfd.FileName; if (string.IsNullOrEmpty(filePath)) { continue; } else { break; } } } else if (buffer[0] == 2) //如果接收的字节数组的第一个字节是2,说明接收的是震动 { ZD(); } } } catch { } } /// <summary> /// 显示信息 /// </summary> /// <param name="message"></param> private void ShowMsg(string message) { txtLog.AppendText(message + "\r\n"); } /// <summary> /// 发送数据 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnSend_Click(object sender, EventArgs e) { try { byte[] buffer = Encoding.UTF8.GetBytes(txtMsg.Text); //获得发送的信息时候,在数组前面加上一个字节 0 List<byte> list = new List<byte>(); list.Add(0); list.AddRange(buffer); //将泛型集合转换为数组 byte[] newBuffer = list.ToArray(); //将了标识字符的字节数组传递给客户端 socketSend.Send(newBuffer); txtMsg.Text = ""; } catch{} } /// <summary> /// 选择文件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnSelect_Click(object sender, EventArgs e) { //打开文件 OpenFileDialog ofd = new OpenFileDialog(); ofd.Title = "选择要传的文件"; ofd.InitialDirectory = @"C:\Users\Administrator\Desktop"; ofd.Filter = "文本文件|*.txt|图片文件|*.jpg|视频文件|*.avi|所有文件|*.*"; ofd.ShowDialog(); //得到选择文件的路径 txtPath.Text = ofd.FileName; } /// <summary> /// 发送文件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnSendFile_Click(object sender, EventArgs e) { try { string filePath = txtPath.Text; if (string.IsNullOrEmpty(filePath)) { MessageBox.Show("请选择文件"); return; } //读取选择的文件 using (FileStream fsRead = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Read)) { byte[] buffer = new byte[1024 * 1024 * 2]; int r = fsRead.Read(buffer, 0, buffer.Length); //获得发送的信息时候,在数组前面加上一个字节 1 List<byte> list = new List<byte>(); list.Add(1); list.AddRange(buffer); byte[] newBuffer = list.ToArray(); //将了标识字符的字节数组传递给客户端 socketSend.Send(newBuffer, 0, r + 1, SocketFlags.None); txtPath.Text = ""; } } catch{ } } /// <summary> /// 震动 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnZD_Click(object sender, EventArgs e) { try { // 首字节是2说明是震动 byte[] buffer = new byte[1]; buffer[0] = 2; socketSend.Send(buffer); } catch{ } } /// <summary> /// 震动 /// </summary> private void ZD() { //获取当前窗体的坐标 Point point = this.Location; //反复给窗体坐标复制一百次,达到震动的效果 for (int i = 0; i < 100; i++) { this.Location = new Point(point.X - 5, point.Y - 5); this.Location = new Point(point.X + 5, point.Y + 5); } this.Location = point; } } }
1)传输类型说明以及全局变量
这些说明以及全局变量,说的也比较清楚,也不累赘了。
2)Socket通信服务端具体步骤:
(这些步骤都是根据第一个图来的)
(1)建立一个通信的Socket
/// <summary> /// 建立连接 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnStart_Click(object sender, EventArgs e) { try { //创建负责通信的Socket socketSend = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //获取服务端的IP IPAddress ip = IPAddress.Parse(txtServer.Text.Trim()); //获取服务端的端口号 IPEndPoint port = new IPEndPoint(ip, Convert.ToInt32(txtPort.Text)); //获得要连接的远程服务器应用程序的IP地址和端口号 socketSend.Connect(port); ShowMsg("连接成功"); //新建线程,去接收客户端发来的信息 Thread td = new Thread(AcceptMgs); td.IsBackground = true; td.Start(); } catch { } }
在连接按钮里,我们建立了Socket
由于客户端会不停的去监视接收服务端发来的信息,如果把这个工作放到主线程里,程序会出现假死的现象,所以这里给他放到一个新的线程里。
(2)接收信息
/// <summary> /// 接收数据 /// </summary> private void AcceptMgs() { try { /// <summary> /// 存储大文件的大小 /// </summary> long length = 0; long recive = 0; //接收的大文件总的字节数 while (true) { byte[] buffer = new byte[1024 * 1024]; int r = socketSend.Receive(buffer); if (r == 0) { break; } if (length > 0) //判断大文件是否已经保存完 { //保存接收的文件 using (FileStream fsWrite = new FileStream(filePath, FileMode.Append, FileAccess.Write)) { fsWrite.Write(buffer, 0, r); length -= r; //减去每次保存的字节数 ShowMsg(string.Format("{0}: 已接收:{1}/{2}", socketSend.RemoteEndPoint, recive-length, recive)); if (length <= 0) { ShowMsg(socketSend.RemoteEndPoint + ": 接收文件成功"); } continue; } } if (buffer[0] == 0) //如果接收的字节数组的第一个字节是0,说明接收的字符串信息 { string strMsg = Encoding.UTF8.GetString(buffer, 1, r - 1); ShowMsg(socketSend.RemoteEndPoint.ToString() + ": " + strMsg); } else if (buffer[0] == 1) //如果接收的字节数组的第一个字节是1,说明接收的是文件 { length = int.Parse(Encoding.UTF8.GetString(buffer,1,r-1)); recive = length; filePath = ""; SaveFileDialog sfd = new SaveFileDialog(); sfd.Title = "保存文件"; sfd.InitialDirectory = @"C:\Users\Administrator\Desktop"; sfd.Filter = "文本文件|*.txt|图片文件|*.jpg|视频文件|*.avi|所有文件|*.*"; //如果没有选择保存文件路径就一直打开保存框 while (true) { sfd.ShowDialog(this); filePath = sfd.FileName; if (string.IsNullOrEmpty(filePath)) { continue; } else { break; } } } else if (buffer[0] == 2) //如果接收的字节数组的第一个字节是2,说明接收的是震动 { ZD(); } } } catch { } }
接收信息是会根据接收到字节数字的第一个字节来判断接收到的是什么,如果接收的是个大文件,首先会接收大文件的大小,然后根据大小接收相同大小的字节数组追加保存到一个文件里去。
(3)发送数据(这里分发送字符串、文件(包含大文件)、震动)
发送字符串信息
/// <summary> /// 发送数据 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnSend_Click(object sender, EventArgs e) { try { byte[] buffer = Encoding.UTF8.GetBytes(txtMsg.Text); //获得发送的信息时候,在数组前面加上一个字节 0 List<byte> list = new List<byte>(); list.Add(0); list.AddRange(buffer); //将泛型集合转换为数组 byte[] newBuffer = list.ToArray(); //将了标识字符的字节数组传递给客户端 socketSend.Send(newBuffer); txtMsg.Text = ""; } catch{} }
发送震动
/// <summary> /// 震动 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnZD_Click(object sender, EventArgs e) { try { // 首字节是2说明是震动 byte[] buffer = new byte[1]; buffer[0] = 2; socketSend.Send(buffer); } catch{ } } /// <summary> /// 震动 /// </summary> private void ZD() { //获取当前窗体的坐标 Point point = this.Location; //反复给窗体坐标复制一百次,达到震动的效果 for (int i = 0; i < 100; i++) { this.Location = new Point(point.X - 5, point.Y - 5); this.Location = new Point(point.X + 5, point.Y + 5); } this.Location = point; }
发送文件(不包含大文件)
首先要选择文件
/// <summary> /// 选择文件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnSelect_Click(object sender, EventArgs e) { //打开文件 OpenFileDialog ofd = new OpenFileDialog(); ofd.Title = "选择要传的文件"; ofd.InitialDirectory = @"C:\Users\Administrator\Desktop"; ofd.Filter = "文本文件|*.txt|图片文件|*.jpg|视频文件|*.avi|所有文件|*.*"; ofd.ShowDialog(); //得到选择文件的路径 txtPath.Text = ofd.FileName; }
然后在发送文件
/// <summary> /// 发送文件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnSendFile_Click(object sender, EventArgs e) { try { string filePath = txtPath.Text; if (string.IsNullOrEmpty(filePath)) { MessageBox.Show("请选择文件"); return; } //读取选择的文件 using (FileStream fsRead = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Read)) { byte[] buffer = new byte[1024 * 1024 * 2]; int r = fsRead.Read(buffer, 0, buffer.Length); //获得发送的信息时候,在数组前面加上一个字节 1 List<byte> list = new List<byte>(); list.Add(1); list.AddRange(buffer); byte[] newBuffer = list.ToArray(); //将了标识字符的字节数组传递给客户端 socketSend.Send(newBuffer, 0, r + 1, SocketFlags.None); txtPath.Text = ""; } } catch{ } }