上期回顾:Sync Music Player项目 (1) ——功能简述及技术设想
三、技术测试
我做技术测试主要是因为以前没做过这类软件,这样可以熟悉一下网络通讯的编写。另一个方面,是要试试如何实现播放的功能。
好的,开始我的测试。
为了简化测试环境,我分别做了一个服务端和一个客户端。服务端的功能很简单,加入两个按钮,一个用于打开文件,另一个用于启动TcpListener。为了了解服务端的运行情况,我还加入了一个文本框。由于服务端要对文件和网络进行操作,因而加入System.IO,System.Net,System.Net.Sockets三个命名空间。两个按钮的单击事件代码如下所示:
![](/Images/OutliningIndicators/ContractedBlock.gif)
Code
private string filename=null;
private void btnOpen_Click(object sender, EventArgs e)
{
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
filename = openFileDialog1.FileName;
btnStart.Enabled = true;
tbStatus.Text += "File:" + Path.GetFileName(filename) + " Opened\n";
}
}
private void btnStart_Click(object sender, EventArgs e)
{
TcpListener tcplistener = new TcpListener(IPAddress.Any, int.Parse(tbPort.Text));
tcplistener.Start();
SetText("Connecting");
while (true)
{
int bufferSize = 256;
TcpClient client = tcplistener.AcceptTcpClient();
NetworkStream stream = client.GetStream();
byte[] buffer = new byte[bufferSize];
int readBytes = 0;
readBytes = stream.Read(buffer, 0, bufferSize);
string request = Encoding.ASCII.GetString(buffer).Substring(0, readBytes);
if (request.StartsWith("Headers"))
{
byte[] responseBuffer=new byte[bufferSize];
string response=Path.GetFileName(filename)+":";
FileInfo file=new FileInfo(filename);
response+=file.Length.ToString();
responseBuffer=Encoding.ASCII.GetBytes(response);
stream.Write(responseBuffer,0,responseBuffer.Length);
}
else if (request.StartsWith("Download"))
{
byte[] data = File.ReadAllBytes(filename);
stream.Write(data, 0, data.Length);
}
stream.Close();
client.Close();
SetText("Done");
}
}
这是一个典型的TCP连接的服务端代码,是从《C#入门经典》中摘来,再改改就行了。这样就能够响应客户端的请求了。
客户端其实就更简单了,仅仅需要一个按钮连接到服务端请求文件即可,代码如下:
![](/Images/OutliningIndicators/ContractedBlock.gif)
Code
private void downloadFile()
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
//声明
int bufferSize = 40960;
string filename = null;
int fileLength = 0;
![](/Images/OutliningIndicators/InBlock.gif)
//初始化连接
TcpClient client = new TcpClient();
IPAddress ip = new IPAddress(0x100007F);
try
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
client.Connect(ip, 8888);
}
catch (Exception ex)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
MessageBox.Show("Error:", ex.Message);
return;
}
NetworkStream stream = client.GetStream();
![](/Images/OutliningIndicators/InBlock.gif)
//发送文件信息请求
string request = "Headers";
byte[] requestBuffer = Encoding.ASCII.GetBytes(request);
stream.Write(requestBuffer,0,requestBuffer.Length);
![](/Images/OutliningIndicators/InBlock.gif)
//接收文件信息
byte[] responseBuffer = new byte[bufferSize];
int bytesRead = 0;
string responseString=null;
bytesRead = stream.Read(responseBuffer, 0, bufferSize);
responseString = Encoding.ASCII.GetString(responseBuffer, 0, bytesRead);
string[] fileInfo = responseString.Split(':');
filename = fileInfo[0];
fileLength = Convert.ToInt32(fileInfo[1]);
stream.Close();
client.Close();
![](/Images/OutliningIndicators/InBlock.gif)
//发送下载请求
client=new TcpClient();
try
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
client.Connect(ip, 8888);
}
catch (Exception ex)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
MessageBox.Show("Error:", ex.Message);
return;
}
stream = client.GetStream();
request = "Download";
requestBuffer = Encoding.ASCII.GetBytes(request);
stream.Write(requestBuffer, 0, requestBuffer.Length);
![](/Images/OutliningIndicators/InBlock.gif)
//接收请求
FileStream filestream = new FileStream("1.mp3", FileMode.Create);
label1.Text = filename + fileLength.ToString();
bytesRead = 0;
int readCount = 0;
DateTime starttime = DateTime.Now;
do
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
bytesRead = stream.Read(responseBuffer, 0, bufferSize);
filestream.Write(responseBuffer, 0, bytesRead);
readCount += bytesRead;
progressBar1.Value =Convert.ToInt32(((double)readCount / fileLength)*100);
}
while (bytesRead > 0);
DateTime endtime = DateTime.Now;
label1.Text ="average speed:"+Math.Floor(readCount / (endtime - starttime).TotalMilliseconds).ToString()+"KB/S";
![](/Images/OutliningIndicators/InBlock.gif)
stream.Close();
filestream.Close();
client.Close();
}
这边需要说明一下,其实这个不是最初的测试版本,不过也挺像了,相较原始版本,我只是加了一个进度条,以及请求文件信息的功能。一开始,我是直接向服务端发送下载请求,然后服务端返回文件。后来我想,这样下载过程什么都看不见,因而又加了个进度条,为了设置进度条,我就在正式下载前请求文件信息。另外,无论是客户端还是服务端,每次信息的传递都要进行一次连接。这样就基本实现下载功能了。不过测试中我发现,每次下载的时候服务端都会出现"Not Responding"的提示,后来才知道,这是因为服务端单击"start“按钮启动TcpListener后,由于里面是个无限循环语句,因而用户界面无法响应用户的事件,从而使系统认为该应用程序已经处于停止响应状态。由此,我第一次接触多线程。
基本的多线程使用其实是很简单的。只要使用System.Threading命名空间下的Thread类即可。我把原来start按钮的事件代码全部放到一个void Wait()函数里面,并且把该按钮事件代码修改为:
![](/Images/OutliningIndicators/ContractedBlock.gif)
Code
private void btnStart_Click(object sender, EventArgs e)
{
Thread threadWait = new Thread(Wait);
threadWait.Start();
tbStatus.Text += "Service Started.";
}
仅仅需要几条语句就实现了简单的多线程功能。不过多线程的应用岂会仅限于此。在使用多线程的时候我们得考虑线程间通讯问题。比如,用户界面上的控件是由主线程生成的,那么,另一个线程如果试图修改这个控件的值就会出现如下错误:
![1.png](/images/cnblogs_com/acen/1.png)
那么如何解决这个问题呢,其实也很简单,仅仅需要使用委托即可。相关代码如下:
![](/Images/OutliningIndicators/ContractedBlock.gif)
Code
delegate void SetTextCallback(string text);
private void SetText(string text)
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
if (this.tbStatus.InvokeRequired)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
SetTextCallback d = new SetTextCallback(SetText);
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
this.Invoke(d, new object[]
{ text });
}
else
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
this.tbStatus.Text += '\n';
this.tbStatus.Text += text;
}
}
private void Wait()
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
//other codes
SetText("test");
//other codes
}
![](/Images/OutliningIndicators/None.gif)
不仅是文本框,其它控件也都可以通过这种方式实现跨线程调用。
需要特别说明的是,对于状态栏有点不同。比如有一个状态栏statusStrip1,其中有个lbStatus,那么,判断语句应为if (this.statusStrip1.InvokeRequired)而不是if (this.lbStatus.InvokeRequired),如下:
![](/Images/OutliningIndicators/ContractedBlock.gif)
Code
private void SetStatus(string text)
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
if (this.statusStrip1.InvokeRequired)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
SetStatusCallBack d = new SetStatusCallBack(SetStatus);
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
this.Invoke(d, new object[]
{ text });
}
else
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
lbStatus.Text = text;
}
}
对新线程传入参数倒是比较简单,但要注意,参数必须声明为object。如下所示:
![](/Images/OutliningIndicators/ContractedBlock.gif)
Code
private void SendPlayCommand(object songName)
{
// …
}
private void btnPlay_Click(object sender, EventArgs e)
{
Thread threadPlay = new Thread(SendPlayCommand);
threadPlay.Start(NowSharing);
}
未完待续。