C#线程系列讲座(3):线程池和文件下载服务器
如果设计一个服务器程序,每当处理用户请求时,都开始一个线程,将会在一定程度上消耗服务器的资源。因此,一个最好的解决方法就是在服务器启动之前,事先创建一些线程对象,然后,当处理客户请求时,就从这些建好的线程中获得线程对象,并处理请求。保存这些线程对象的结构就叫做线程池。
在C#中可以通过System.Threading.ThreadPool类来实现,在默认情况下,ThreadPool最大可建立500个工作线程和1000个I/O线程(根据机器CPU个数和.net framework版本的不同,这些数据可能会有变化)。下面是一个用C#从线程池获得线程的例子:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; namespace ConsoleApplication1 { class Program { 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("最大IO线程:" + completionPortThreads); ThreadPool.QueueUserWorkItem(execute, "线程1"); 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 ConsoleApplication1 { public class FileServer { private string root; private Thread listenerThread; private void worker(object state) { TcpClient client = state as TcpClient; try { client.ReceiveTimeout = 2000; NetworkStream stream = client.GetStream(); 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 ex) { Console.WriteLine(ex.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(); 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,因为,文件名要为英文名(不能有空格等特殊字符)。