0%

基于C# Winform的简易聊天程序[第二篇-文件发送]

程序简介

基于网友的提议,最近有点时间,便打算给之前的聊天程序增加一个功能-文件发送.

原理

文件发送跟字符串信息发送的原理其实是一样的,都是通过将需要发送的数据转换成计算机可以识别的字节数组来发送.当然,计算机本身并不知道你发送的是字符串信息还是文件,所以我们首先需要告诉计算机哪个发送的是文件,哪个是字符串信息;这里分别给它们的字节数组附加了一个类型标识符:字符串信息的字节数组标识符为0,文件的字节数组标识符为1.当一端将文件发送过去后,另一端则首先判断发送过来的类型标识符(1或者0),然后再调用相应的方法将获取的字节数组转换成人可以看懂的字符串信息或文件.

界面设计 - 客户端

这里新增了3个控件,用于实现文件发送功能.

Textbox: 文件名name: txtFileName

Button: 选择文件name: btnSelectFile 发送文件name: btnSendFile

01-界面设计

代码实施 - 客户端

首先,我们需要写一个选择发送文件的方法,这里使用了最常见OpenFileDialog方法,用于选取需要发送的文件.

1
2
3
4
5
6
7
8
9
10
11
12
13
string filePath = null;   //文件的全路径
string fileName = null; //文件名称(不包含路径)
//选择要发送的文件
private void btnSelectFile_Click(object sender, EventArgs e)
{
OpenFileDialog ofDialog = new OpenFileDialog();
if (ofDialog.ShowDialog(this) == DialogResult.OK)
{
fileName = ofDialog.SafeFileName; //获取选取文件的文件名
txtFileName.Text = fileName; //将文件名显示在文本框上
filePath = ofDialog.FileName; //获取包含文件名的全路径
}
}

选取文件之后,我们先发送文件的名称和长度, 然后再发送文件.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/// <summary>
/// 发送文件的方法
/// </summary>
/// <param name="fileFullPath">文件全路径(包含文件名称)</param>
private void SendFile(string fileFullPath)
{
if (string.IsNullOrEmpty(fileFullPath))
{
MessageBox.Show(@"请选择需要发送的文件!");
return;
}

//发送文件之前 将文件名字和长度发送过去
long fileLength = new FileInfo(fileFullPath).Length;
string totalMsg = string.Format("{0}-{1}", fileName, fileLength);
ClientSendMsg(totalMsg, 2);


//发送文件
byte[] buffer = new byte[SendBufferSize];

using (FileStream fs = new FileStream(fileFullPath, FileMode.Open, FileAccess.Read))
{
int readLength = 0;
bool firstRead = true;
long sentFileLength = 0;
while ((readLength = fs.Read(buffer, 0, buffer.Length)) > 0 && sentFileLength < fileLength)
{
sentFileLength += readLength;
//在第一次发送的字节流上加个前缀1
if (firstRead)
{
byte[] firstBuffer = new byte[readLength + 1];
firstBuffer[0] = 1; //告诉机器该发送的字节数组为文件
Buffer.BlockCopy(buffer, 0, firstBuffer, 1, readLength);

socketClient.Send(firstBuffer, 0, readLength + 1, SocketFlags.None);

firstRead = false;
continue;
}
//之后发送的均为直接读取的字节流
socketClient.Send(buffer, 0, readLength, SocketFlags.None);
}
fs.Close();
}
txtMsg.AppendText("SoFlash:" + GetCurrentTime() + "\r\n您发送了文件:" + fileName + "\r\n");
}

代码实施 - 服务端

服务端接受字符串信息,文件名称和长度以及文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
string strSRecMsg = null;
/// <summary>
/// 接收客户端发来的信息
/// </summary>
private void ServerRecMsg(object socketClientPara)
{
Socket socketServer = socketClientPara as Socket;

long fileLength = 0;
while (true)
{
int firstReceived = 0;
byte[] buffer = new byte[ReceiveBufferSize];
try
{
//获取接收的数据,并存入内存缓冲区 返回一个字节数组的长度
if (socketServer != null) firstReceived = socketServer.Receive(buffer);

if (firstReceived > 0) //接受到的长度大于0 说明有信息或文件传来
{
if (buffer[0] == 0) //0为文字信息
{
strSRecMsg = Encoding.UTF8.GetString(buffer, 1, firstReceived - 1);//真实有用的文本信息要比接收到的少1(标识符)
txtMsg.AppendText("SoFlash:" + GetCurrentTime() + "\r\n" + strSRecMsg + "\r\n");
}
if (buffer[0] == 2)//2为文件名字和长度
{
string fileNameWithLength = Encoding.UTF8.GetString(buffer, 1, firstReceived - 1);
strSRecMsg = fileNameWithLength.Split('-').First(); //文件名
fileLength = Convert.ToInt64(fileNameWithLength.Split('-').Last());//文件长度
}
if (buffer[0] == 1)//1为文件
{
string fileNameSuffix = strSRecMsg.Substring(strSRecMsg.LastIndexOf('.')); //文件后缀
SaveFileDialog sfDialog = new SaveFileDialog()
{
Filter = "(*" + fileNameSuffix + ")|*" + fileNameSuffix + "", //文件类型
FileName = strSRecMsg
};

//如果点击了对话框中的保存文件按钮
if (sfDialog.ShowDialog(this) == DialogResult.OK)
{
string savePath = sfDialog.FileName; //获取文件的全路径
//保存文件
int received = 0;
long receivedTotalFilelength = 0;
bool firstWrite = true;
using (FileStream fs = new FileStream(savePath, FileMode.Create, FileAccess.Write))
{
while (receivedTotalFilelength < fileLength) //之后收到的文件字节数组
{
if (firstWrite)
{
fs.Write(buffer, 1, firstReceived - 1); //第一次收到的文件字节数组 需要移除标识符1 后写入文件
fs.Flush();

receivedTotalFilelength += firstReceived - 1;

firstWrite = false;
continue;
}
received = socketServer.Receive(buffer); //之后每次收到的文件字节数组 可以直接写入文件
fs.Write(buffer, 0, received);
fs.Flush();

receivedTotalFilelength += received;
}
fs.Close();
}

string fName = savePath.Substring(savePath.LastIndexOf("\\") + 1); //文件名 不带路径
string fPath = savePath.Substring(0, savePath.LastIndexOf("\\")); //文件路径 不带文件名
txtMsg.AppendText("天之涯:" + GetCurrentTime() + "\r\n您成功接收了文件" + fName + "\r\n保存路径为:" + fPath + "\r\n");
}
}
}
}
catch (Exception ex)
{
txtMsg.AppendText("系统异常消息:" + ex.Message);
break;
}
}
}

运行程序

首先,启动服务端并持续监听客户端对其的连接,当客户端成功连接上服务端之后,两端便可以开始通信了.

02-启动服务端

02-连接服务端

02-开始通信

两端建立连接之后,便可以开始互相通信了.

03-程序运行

03-程序运行02

简单的两端对聊之后, 本人便打算发送个文件过去.

03-程序运行03

选取了一本张道真的语法书,后缀为.pdf(文件类型)

03-程序运行04

当点击发送文件按钮后,客户端聊天内容中显示”您发送了文件:张道真实用英语语法.pdf”.

03-程序运行05

这时服务端收到文件后,程序弹出一个另存为对话框,用于保存接收到的文件.这里我们可以看到系统自动附加上了文件名和保存类型.

03-程序运行06

当服务端用户接收并保存文件之后,聊天内容里显示”您成功接收了文件张道真实用英语语法.pdf” 以及文件的保存路径.

03-程序运行08

附上源代码

服务端 ChatServer2.zip 客户端 ChatClient2.zip

转自:https://www.cnblogs.com/longwu/archive/2011/08/25/2153636.html