這篇文章將完成 Part.4 中剩餘的部分,它們原本是一篇完整的文章,可是由於上一篇比較長,合併起來頁數太多,瀏覽起來可能會比較不方便,我就將它拆爲兩篇了,本文即是它的後半部分。咱們繼續進行上一篇沒有完成的步驟:客戶端接收來自服務端的文件。html
對於服務端,咱們只須要實現上一章遺留的sendFile()方法就能夠了,它起初在handleProtocol中是註釋掉的。另外,因爲建立鏈接、獲取流等操做與receiveFile()是沒有區別的,因此咱們將它提出來做爲一個公共方法getStreamToClient()。下面是服務端的代碼,只包含新增改過的代碼,對於原有方法我只給出了簽名:緩存
class Server {
static void Main(string[] args) {
Console.WriteLine("Server is running ... ");
IPAddress ip = IPAddress.Parse("127.0.0.1");
TcpListener listener = new TcpListener(ip, 8500);
listener.Start(); // 開啓對控制端口 8500 的偵聽
Console.WriteLine("Start Listening ...");
while (true) {
// 獲取一個鏈接,同步方法,在此處中斷
TcpClient client = listener.AcceptTcpClient();
RemoteClient wapper = new RemoteClient(client);
wapper.BeginRead();
}
}
}
public class RemoteClient {
// 字段 略
public RemoteClient(TcpClient client) {}
// 開始進行讀取
public void BeginRead() { }
// 再讀取完成時進行回調
private void OnReadComplete(IAsyncResult ar) { }
// 處理protocol
private void handleProtocol(object obj) {
string pro = obj as string;
ProtocolHelper helper = new ProtocolHelper(pro);
FileProtocol protocol = helper.GetProtocol();
if (protocol.Mode == FileRequestMode.Send) {
// 客戶端發送文件,對服務端來講則是接收文件
receiveFile(protocol);
} else if (protocol.Mode == FileRequestMode.Receive) {
// 客戶端接收文件,對服務端來講則是發送文件
sendFile(protocol);
}
}
// 發送文件
private void sendFile(FileProtocol protocol) {
TcpClient localClient;
NetworkStream streamToClient = getStreamToClient(protocol, out localClient);
// 得到文件的路徑
string filePath = Environment.CurrentDirectory + "/" + protocol.FileName;
// 建立文件流
FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
byte[] fileBuffer = new byte[1024]; // 每次傳1KB
int bytesRead;
int totalBytes = 0;
// 建立獲取文件發送狀態的類
SendStatus status = new SendStatus(filePath);
// 將文件流轉寫入網絡流
try {
do {
Thread.Sleep(10); // 爲了更好的視覺效果,暫停10毫秒:-)
bytesRead = fs.Read(fileBuffer, 0, fileBuffer.Length);
streamToClient.Write(fileBuffer, 0, bytesRead);
totalBytes += bytesRead; // 發送了的字節數
status.PrintStatus(totalBytes); // 打印發送狀態
} while (bytesRead > 0);
Console.WriteLine("Total {0} bytes sent, Done!", totalBytes);
} catch {
Console.WriteLine("Server has lost...");
}
streamToClient.Dispose();
fs.Dispose();
localClient.Close();
}
// 接收文件
private void receiveFile(FileProtocol protocol) { }
// 獲取鏈接到遠程的流 -- 公共方法
private NetworkStream getStreamToClient(FileProtocol protocol, out TcpClient localClient) {
// 獲取遠程客戶端的位置
IPEndPoint endpoint = client.Client.RemoteEndPoint as IPEndPoint;
IPAddress ip = endpoint.Address;
// 使用新端口號,得到遠程用於接收文件的端口
endpoint = new IPEndPoint(ip, protocol.Port);
// 鏈接到遠程客戶端
try {
localClient = new TcpClient();
localClient.Connect(endpoint);
} catch {
Console.WriteLine("沒法鏈接到客戶端 --> {0}", endpoint);
localClient = null;
return null;
}
// 獲取發送文件的流
NetworkStream streamToClient = localClient.GetStream();
return streamToClient;
}
// 隨機獲取一個圖片名稱
private string generateFileName(string fileName) {}
}網絡
服務端的sendFile方法和客戶端的SendFile()方法徹底相似,上面的代碼幾乎是一次編寫成功的。另外注意我將客戶端使用的SendStatus類也拷貝到了服務端。接下來咱們看下客戶端。app
首先要注意的是客戶端的SendFile()接收的參數是文件全路徑,可是在寫入到協議時只獲取了路徑中的文件名稱。這是由於服務端不須要知道文件在客戶端的路徑,因此協議中只寫文件名;而爲了使客戶端的SendFile()方法更通用,因此它接收本地文件的全路徑。異步
客戶端的ReceiveFile()的實現也和服務端的receiveFile()方法相似,一樣,因爲要保存到本地,爲了不文件名重複,我將服務端的generateFileName()方法複製了過來。學習
public class ServerClient :IDisposable {
// 字段略
public ServerClient() {}
// 發送消息到服務端
public void SendMessage(string msg) {}
// 發送文件 - 異步方法
public void BeginSendFile(string filePath) { }
private void SendFile(object obj) { }
// 發送文件 -- 同步方法
public void SendFile(string filePath) {}
// 接收文件 -- 異步方法
public void BeginReceiveFile(string fileName) {
ParameterizedThreadStart start =
new ParameterizedThreadStart(ReceiveFile);
start.BeginInvoke(fileName, null, null);
}
public void ReceiveFile(object obj) {
string fileName = obj as string;
ReceiveFile(fileName);
}
// 接收文件 -- 同步方法
public void ReceiveFile(string fileName) {
IPAddress ip = IPAddress.Parse("127.0.0.1");
TcpListener listener = new TcpListener(ip, 0);
listener.Start();
// 獲取本地偵聽的端口號
IPEndPoint endPoint = listener.LocalEndpoint as IPEndPoint;
int listeningPort = endPoint.Port;
// 獲取發送的協議字符串
FileProtocol protocol =
new FileProtocol(FileRequestMode.Receive, listeningPort, fileName);
string pro = protocol.ToString();
SendMessage(pro); // 發送協議到服務端
// 中斷,等待遠程鏈接
TcpClient localClient = listener.AcceptTcpClient();
Console.WriteLine("Start sending file...");
NetworkStream stream = localClient.GetStream();
// 獲取文件保存的路勁
string filePath =
Environment.CurrentDirectory + "/" + generateFileName(fileName);
// 建立文件流
FileStream fs = new FileStream(filePath, FileMode.CreateNew, FileAccess.Write);
byte[] fileBuffer = new byte[1024]; // 每次傳1KB
int bytesRead;
int totalBytes = 0;
// 從緩存buffer中讀入到文件流中
do {
bytesRead = stream.Read(buffer, 0, BufferSize);
fs.Write(buffer, 0, bytesRead);
totalBytes += bytesRead;
Console.WriteLine("Receiving {0} bytes ...", totalBytes);
} while (bytesRead > 0);
Console.WriteLine("Total {0} bytes received, Done!", totalBytes);
fs.Dispose();
stream.Dispose();
localClient.Close();
listener.Stop();
}
// 隨機獲取一個圖片名稱
private string generateFileName(string fileName) {}
public void Dispose() {
if (streamToServer != null)
streamToServer.Dispose();
if (client != null)
client.Close();
}
}測試
上面關鍵的一句就是建立協議那句,注意到將mode由Send改成了Receive,同時傳去了想要接收的服務端的文件名稱。htm
如今咱們已經完成了全部收發文件的步驟,能夠看到服務端的全部操做都是被動的,接下來咱們修改客戶端的Main()程序,建立一個菜單,而後根據用戶輸入發送或者接收文件。blog
class Program {
static void Main(string[] args) {
ServerClient client = new ServerClient();
string input;
string path = Environment.CurrentDirectory + "/";
do {
Console.WriteLine("Send File: S1 - Client01.jpg, S2 - Client02.jpg, S3 - Client03.jpg");
Console.WriteLine("Receive File: R1 - Server01.jpg, R1 - Server02.jpg, R3- Server03.jpg");
Console.WriteLine("Press 'Q' to exit. \n");
Console.Write("Enter your choice: ");
input = Console.ReadLine();
switch(input.ToUpper()){
case "S1":
client.BeginSendFile(path + "Client01.jpg");
break;
case "S2":
client.BeginSendFile(path + "Client02.jpg");
break;
case "S3":
client.BeginSendFile(path + "Client02.jpg");
break;
case "R1":
client.BeginReceiveFile("Server01.jpg");
break;
case "R2":
client.BeginReceiveFile("Server01.jpg");
break;
case "R3":
client.BeginReceiveFile("Server01.jpg");
break;
}
} while (input.ToUpper() != "Q");
client.Dispose();
}
}圖片
因爲這是一個控制檯應用程序,而且採用了異步操做,因此這個菜單的出現順序有點混亂。我這裏描述起來比較困難,你將代碼下載下來後運行一下就知道了:-)
程序的運行結果和上一節相似,這裏我就再也不貼圖了。接下來是本系列的最後一篇,將發送字符串與傳輸文件的功能結合起來,建立一個能夠發送消息並能收發文件的聊天程序,至於語音聊天嘛...等我學習了再告訴你 >_<
出處:http://www.cnblogs.com/JimmyZhang/archive/2008/09/16/1291858.html