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{ }
}
轉載于:https://www.cnblogs.com/zzp0320/p/7909828.html