如果设计一个服务器程序,每当处理用户请求时,都开始一个线程,将会在一定程序上消耗服务器的资源。为此,一个最好的解决方法就是在服务器启动之前,事先创建一些线程对象,然后,当处理客户端请求时,就从这些建好的线程中获得线程对象,并处理请求。保存这些线程对象的结构就叫做线程池。
在C#中可以通过System.Threading.ThreadPool类来实现,在默认情况下,ThreadPool最大可建立500个工作线程和1000个I/O线程(根据机器CPU个数和.net framework版本的不同,这些数据可能会有变化)。下面是一个用C#从线程池获得线程的例子:
private
static
void
execute(
object
state)
{
Console.WriteLine(state);
}
static
void
Main(
string
[] args)
{
int
workerThreads;
int
completionPortThreads;
ThreadPool.GetMaxThreads(
out
workerThreads,
out
completionPortThreads);
Console.WriteLine(workerThreads);
Console.WriteLine(completionPortThreads);
ThreadPool.QueueUserWorkItem(execute,
"
线程1
"
);
//
从线程池中得到一个线程,并运行execute
ThreadPool.QueueUserWorkItem(execute,
"
线程2
"
);
ThreadPool.QueueUserWorkItem(execute,
"
线程3
"
);
Console.ReadLine();
}
下图为上面代码的运行结果。
要注意的是,使用ThreadPool获得的线程都是后台线程。
下面的程序是我设计的一个下载文件服务器的例子。这个例子从ThreadPool获得线程,并处理相应的客户端请求。
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
using
System.Threading;
using
System.Net.Sockets;
using
System.IO;
namespace
MyThread
{
class
FileServer
{
private
String root;
private
Thread listenerThread;
private
void
worker(
object
state)
{
TcpClient client
=
state
as
TcpClient;
try
{
client.ReceiveTimeout
=
2000
;
Stream stream
=
client.GetStream();
System.IO.StreamReader sr
=
new
StreamReader(stream);
String line
=
sr.ReadLine();
String[] array
=
line.Split(
'
'
);
String path
=
array[
1
].Replace(
'
/
'
,
'
\\
'
);
String filename
=
root
+
path;
if
(File.Exists(filename))
//
如果下载文件存在,开始下载这个文件
{
FileStream fileStream
=
new
FileStream(filename, FileMode.Open, FileAccess.Read,
FileShare.Read);
byte
[] buffer
=
new
byte
[
8192
];
//
每次下载8K
int
count
=
0
;
String responseHeader
=
"
HTTP/1.1 200 OK\r\n
"
+
"
Content-Type:application/octet-stream\r\n
"
+
"
Content-Disposition:attachment;filename=
"
+
filename.Substring(filename.LastIndexOf(
"
\\
"
)
+
1
)
+
"
\r\n\r\n
"
;
byte
[] header
=
ASCIIEncoding.ASCII.GetBytes(responseHeader);
stream.Write(header,
0
, header.Length);
while
((count
=
fileStream.Read(buffer,
0
, buffer.Count()))
>
0
)
{
stream.Write(buffer,
0
, count);
}
Console.WriteLine(filename
+
"
下载完成
"
);
}
else
//
文件不存在,输出提示信息
{
String response
=
"
HTTP/1.1 200 OK\r\nContent-Type:text/plain;charset=utf-8\r\n\r\n文件不存在
"
;
byte
[] buffer
=
ASCIIEncoding.UTF8.GetBytes(response);
stream.Write(buffer,
0
, buffer.Length);
}
}
catch
(Exception e)
{
Console.WriteLine(e.Message);
}
finally
{
if
(client
!=
null
)
{
client.Close();
}
}
}
private
void
listener()
{
TcpListener listener
=
new
TcpListener(
1234
);
listener.Start();
//
开始监听客户端请求
TcpClient client
=
null
;
while
(
true
)
{
client
=
listener.AcceptTcpClient();
client.ReceiveTimeout
=
2000
;
ThreadPool.QueueUserWorkItem(worker, client);
//
从线程池中获得一个线程来处理客户端请求
}
}
public
FileServer(String root)
{
this
.root
=
root;
}
public
void
start()
{
listenerThread
=
new
Thread(listener);
listenerThread.Start();
//
开始运行监听线程
}
}
}
FileServer类的使用方法:
FileServer fs = new FileServer(“d:\\download”);
fs.start(); // 端口为1234
如果d:"download目录中有一个叫aa.exe的文件,在浏览器中输入如下的地址可下载:
http://localhost:1234/aa.exe
下图为下载对话框:
要注意的是,本程序并没有处理含有中文和其他特殊字符(如空格)的url,因为,文件名要为英文名(不能有空格等特殊字符)。
下一篇:C#线程系列讲座(4):同步与死锁