程序簡介
基於網友的提議,最近有點時間,便打算給之前的聊天程序增加一個功能-文件發送.
原理
文件發送跟字符串信息發送的原理其實是一樣的,都是通過將需要發送的數據轉換成計算機可以識別的字節數組來發送.當然,計算機本身並不知道你發送的是字符串信息還是文件,所以我們首先需要告訴計算機哪個發送的是文件,哪個是字符串信息;這里分別給它們的字節數組附加了一個類型標識符:字符串信息的字節數組標識符為0,文件的字節數組標識符為1.當一端將文件發送過去后,另一端則首先判斷發送過來的類型標識符(1或者0),然后再調用相應的方法將獲取的字節數組轉換成人可以看懂的字符串信息或文件.
界面設計 - 客戶端
這里新增了3個控件,用於實現文件發送功能.
Textbox: 文件名name: txtFileName
Button: 選擇文件name: btnSelectFile 發送文件name: btnSendFile
代碼實施 - 客戶端
首先,我們需要寫一個選擇發送文件的方法,這里使用了最常見OpenFileDialog方法,用於選取需要發送的文件.
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; //獲取包含文件名的全路徑 } }
選取文件之后,我們先發送文件的名稱和長度, 然后再發送文件.
/// <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"); }
代碼實施 - 服務端
服務端接受字符串信息,文件名稱和長度以及文件
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; } } }
運行程序
首先,啟動服務端並持續監聽客戶端對其的連接,當客戶端成功連接上服務端之后,兩端便可以開始通信了.
兩端建立連接之后,便可以開始互相通信了.
簡單的兩端對聊之后, 本人便打算發送個文件過去.
選取了一本張道真的語法書,后綴為.pdf(文件類型)
當點擊發送文件按鈕后,客戶端聊天內容中顯示"您發送了文件:張道真實用英語語法.pdf".
這時服務端收到文件后,程序彈出一個另存為對話框,用於保存接收到的文件.這里我們可以看到系統自動附加上了文件名和保存類型.
當服務端用戶接收並保存文件之后,聊天內容里顯示"您成功接收了文件張道真實用英語語法.pdf" 以及文件的保存路徑.
附上源代碼
服務端 ChatServer2.zip 客戶端 ChatClient2.zip