C#开发自己的Web服务器
介绍
我们将学习如何写一个简单的web服务器,用于响应知名的HTTP请求(GET和POST),用C#发送响应。然后,我们从网络访问这台服务器,这次我们会说“Hello world!”

背景
HTTP协议
HTTP是服务器和客户机之间的通信协议。它使用TCP/IP协议来发送/接收请求/响应。
有几个HTTP方法,我们将实现两个:GET和POST。
GET
当我们将一个地址输入到我们的Web浏览器的地址栏中,按下回车键时,会发生什么情况?(虽然我们使
用TCP/IP,但我们不指定端口号,因为HTTP默认使用80端口,我们并不需要指定80)
|
1
2
3
4
5
6
7
|
GET / HTTP/1.1\r\nHost: okbase.net\r\nUser-Agent: Mozilla/5.0 (Windows NT 6.1; rv:14.0) Gecko/20100101 Firefox/14.0.1\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: tr-tr,tr;q=0.8,en-us;q=0.5,en;q=0.3\r\nAccept-Encoding: gzip, deflate\r\nConnection: keep-alive\r\n\r\n |
该GET请求使用TCP/IP通过浏览器向服务器发送,请求的是okbase.net的根目录下的内容。
我们可以添加更多的头信息,最基本的信息如下:
|
1
2
|
GET / HTTP/1.1\r\nHost: okbase.net\r\n\r\n |
POST
POST请求和GET请求类似,在GET请求里,变量加到url的?下面,POST请求,变量加到两行回车的下面,并需要指定内容长度。
|
1
2
3
4
5
6
7
8
9
10
11
|
POST /index.html HTTP/1.1\r\nHost: atasoyweb.net\r\nUser-Agent: Mozilla/5.0 (Windows NT 6.1; rv:15.0) Gecko/20100101 Firefox/15.0.1\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: tr-tr,tr;q=0.8,en-us;q=0.5,en;q=0.3\r\nAccept-Encoding: gzip, deflate\r\nConnection: keep-alive\r\nReferer: http://okbase.net/\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 35\r\n\r\nvariable1=value1&variable2=value2 |
简化版本如下:
|
1
2
3
4
|
POST /index.html HTTP/1.1\r\nHost: okbase.net\r\nContent-Length: 35\r\n\r\nvariable1=value1&variable2=value2 |
响应
当服务器接收到一个请求进行解析,并返回一个响应状态代码:
|
1
2
3
4
5
6
|
HTTP/1.1 200 OK\r\nServer: Apache/1.3.3.7 (Unix) (Red-Hat/Linux)\r\nContent-Length: {content_length}\r\nConnection: close\r\nContent-Type: text/html; charset=UTF-8\r\n\r\nthe content of which length is equal to {content_length} |
这是一个响应头,"200 OK"便是一切OK,请求的内容将被返回。状态码有很多,我们经常使用200,501,404。
“501 Not Implemented”方法未实现。我们将只实现GET和POST。如果通过其它方法请求,我们将发送此代码。
“404 Not Found”:没有找到请求的内容。
内容类型
服务器必须指定它们的响应中的内容的类型。有许多内容类型,这些也被称为“MIME(多用途互联网邮件扩展)类型”(因为它们也可以用来识别非ASCII的电子邮件)。以下是在我们的实现中,我们将使用的内容类型:(您可以修改代码并添加更多)
text/html
text/xml
text/plain
text/css
image/png
image/gif
image/jpg
image/jpeg
application/zip
如果服务器指定了错误的内容类型的内容会被曲解。例如,如果一台服务器发送纯文本,使用“图像/ png”类型,客户端试图显示的文字图像。
多线程
如果我们使我们的服务器可以同时响应多个客户端,我们必须为每个请求创建新的线程。因此,每个线程处理一个请求,并退出完成它的使命。(多线程也加快了页面加载,因为如果我们请求一个页面,页面中使用了CSS和图像,每个图像和CSS文件会以GET方式发送请求)。
一个简单的Web服务器的实现
现在,我们准备实现一个简单的Web服务器。首先,让我们来定义变量,我们将使用:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
public bool running = false; // Is it running?private int timeout = 8; // Time limit for data transfers.private Encoding charEncoder = Encoding.UTF8; // To encode stringprivate Socket serverSocket; // Our server socketprivate string contentPath; // Root path of our contents// Content types that are supported by our server// You can add more...// To see other types: http://www.webmaster-toolkit.com/mime-types.shtmlprivate Dictionary<string, string> extensions = new Dictionary<string, string>(){ //{ "extension", "content type" } { "htm", "text/html" }, { "html", "text/html" }, { "xml", "text/xml" }, { "txt", "text/plain" }, { "css", "text/css" }, { "png", "image/png" }, { "gif", "image/gif" }, { "jpg", "image/jpg" }, { "jpeg", "image/jpeg" }, { "zip", "application/zip"}}; |
启动服务器的方法:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
public bool start(IPAddress ipAddress, int port, int maxNOfCon, string contentPath){ if (running) return false; // If it is already running, exit. try { // A tcp/ip socket (ipv4) serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); serverSocket.Bind(new IPEndPoint(ipAddress, port)); serverSocket.Listen(maxNOfCon); serverSocket.ReceiveTimeout = timeout; serverSocket.SendTimeout = timeout; running = true; this.contentPath = contentPath; } catch { return false; } // Our thread that will listen connection requests // and create new threads to handle them. Thread requestListenerT = new Thread(() => { while (running) { Socket clientSocket; try { clientSocket = serverSocket.Accept(); // Create new thread to handle the request and continue to listen the socket. Thread requestHandler = new Thread(() => { clientSocket.ReceiveTimeout = timeout; clientSocket.SendTimeout = timeout; try { handleTheRequest(clientSocket); } catch { try { clientSocket.Close(); } catch { } } }); requestHandler.Start(); } catch{} } }); requestListenerT.Start(); return true;} |
停止服务器的方法
|
1
2
3
4
5
6
7
8
9
10
|
public void stop(){ if (running) { running = false; try { serverSocket.Close(); } catch { } serverSocket = null; }} |
最重要的部分代码:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
private void handleTheRequest(Socket clientSocket){ byte[] buffer = new byte[10240]; // 10 kb, just in case int receivedBCount = clientSocket.Receive(buffer); // Receive the request string strReceived = charEncoder.GetString(buffer, 0, receivedBCount); // Parse method of the request string httpMethod = strReceived.Substring(0, strReceived.IndexOf(" ")); int start = strReceived.IndexOf(httpMethod) + httpMethod.Length + 1; int length = strReceived.LastIndexOf("HTTP") - start - 1; string requestedUrl = strReceived.Substring(start, length); string requestedFile; if (httpMethod.Equals("GET") || httpMethod.Equals("POST")) requestedFile = requestedUrl.Split('?')[0]; else // You can implement other methods... { notImplemented(clientSocket); return; } requestedFile = requestedFile.Replace("/", @"\").Replace("\\..", ""); start = requestedFile.LastIndexOf('.') + 1; if (start > 0) { length = requestedFile.Length - start; string extension = requestedFile.Substring(start, length); if (extensions.ContainsKey(extension)) // Do we support this extension? if (File.Exists(contentPath + requestedFile)) //If yes check existence of the file // Everything is OK, send requested file with correct content type: sendOkResponse(clientSocket, File.ReadAllBytes(contentPath + requestedFile), extensions[extension]); else notFound(clientSocket); // We don't support this extension. // We are assuming that it doesn't exist. } else { // If file is not specified try to send index.htm or index.html // You can add more (default.htm, default.html) if (requestedFile.Substring(length - 1, 1) != @"\") requestedFile += @"\"; if (File.Exists(contentPath + requestedFile + "index.htm")) sendOkResponse(clientSocket, File.ReadAllBytes(contentPath + requestedFile + "\\index.htm"), "text/html"); else if (File.Exists(contentPath + requestedFile + "index.html")) sendOkResponse(clientSocket, File.ReadAllBytes(contentPath + requestedFile + "\\index.html"), "text/html"); else notFound(clientSocket); }} |
不同的状态代码的响应:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
private void notImplemented(Socket clientSocket){ sendResponse(clientSocket, "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"> </head><body><h2>Atasoy Simple Web Server</h2><div>501 - Method Not Implemented</div></body></html>", "501 Not Implemented", "text/html");}private void notFound(Socket clientSocket){ sendResponse(clientSocket, "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"></head><body><h2>Atasoy Simple Web Server</h2><div>404 - Not Found</div></body></html>", "404 Not Found", "text/html");}private void sendOkResponse(Socket clientSocket, byte[] bContent, string contentType){ sendResponse(clientSocket, bContent, "200 OK", contentType);} |
将响应发送到客户端的方法
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
// For stringsprivate void sendResponse(Socket clientSocket, string strContent, string responseCode, string contentType){ byte[] bContent = charEncoder.GetBytes(strContent); sendResponse(clientSocket, bContent, responseCode, contentType);}// For byte arraysprivate void sendResponse(Socket clientSocket, byte[] bContent, string responseCode, string contentType){ try { byte[] bHeader = charEncoder.GetBytes( "HTTP/1.1 " + responseCode + "\r\n" + "Server: Atasoy Simple Web Server\r\n" + "Content-Length: " + bContent.Length.ToString() + "\r\n" + "Connection: close\r\n" + "Content-Type: " + contentType + "\r\n\r\n"); clientSocket.Send(bHeader); clientSocket.Send(bContent); clientSocket.Close(); } catch { }} |
用法
|
1
2
3
4
5
6
|
// to create new one:Server server = new Server();// to start itserver.start(ipAddress, port, maxconnections, contentpath);// to stop itserver.stop(); |
向全世界说"Hello"
我们简单的Web服务器已准备就绪。现在,我们将从Internet访问它。为了实现这一目标,我们必须将请求从Modem重定向到我们的计算机。如果我们的调制解调器支持UPnP那就很简单。
1. 下载UPnp Port Forwarder ,并运行它。
2. 点击“Search For Devices”按钮。如果您的调制解调器支持UPnP,它会被添加到ComboBox。
3. 点击“Update List”按钮,列出转发端口。
4. 然后点击“Add New”按钮,填写表格。
5. 如果选中“IP”复选框,并输入一个IP地址,只有从这个IP的请求将被重定向。所以,千万不要填写。
6. 内部端口必须等于我们服务器的端口。
7.“Port”和“ Internal port”可以不相同。

这样,所有来自externalip:port的请求都将通过modem转发到我们的电脑上,你可以用http://www.web-sniffer.net/ 来测试连接的有效性,输入外部IP和端口号 http://外部IP:端口号并点击Submit按钮...

C#开发自己的Web服务器的更多相关文章
- asp.net 开发问题:Web 服务器上的请求筛选被配置为拒绝该请求,因为内容长度超过配置的值。
"Web 服务器上的请求筛选被配置为拒绝该请求,因为内容长度超过配置的值." 这个问题在开发需要上传文件的时候可能会遇到,今天遇到这个问题,百度过也有挺多的修改方法. 方法1: 修 ...
- 【安富莱专题教程第3期】开发板搭建Web服务器,利用花生壳让电脑和手机可以外网远程监控
说明:1. 开发板Web服务器的设计可以看我们之前发布的史诗级网络教程:链接.2. 需要复杂些的Web设计模板,可以使用我们V6开发板发布的综合Demo:链接.3. 教程中使用的是花生壳免费版, ...
- C#自制Web 服务器开发:用C#开发自己的Web服务器
当输入: GET / HTTP/1.1 Accept: text/html, application/xhtml+xml, */* Accept-Language: zh-CN User-Agent: ...
- (转)C#自制Web 服务器开发:用C#开发自己的Web服务器
当输入:127.0.0.1:5050 GET / HTTP/1.1 Accept: text/html, application/xhtml+xml, */* Accept-Language: zh- ...
- android开发 如何通过web服务器访问MYSQL数据库并且使其数据同步到android SQLite数据库?
通过web服务器访问MYSQL数据库有以下几个过程: 1.在MySql下创建自己的数据库和自己的表单 2.连接数据库. 3.访问数据库 1.创建web工程 (服务器端) 在Myeclipse下新建一个 ...
- Servlet和web服务器关系
前面的博客我详细的罗列了下Servlet的常用的类和接口,然后在前面的前面我类似tomcat模拟了一套web服务器,这里来做一个统一的整理,这样子可以更好的把握Servlet,也可以更好的了解下web ...
- [原创]一种专门用于前后端分离的web服务器(JerryServer)
如果你还不了解现在的前后端分离,推荐阅读淘宝前端团队的前后端分离的思考与实践 1.问题 随着现在整个软件开发行业的发展,在开发模式上逐渐由以前的一个人完成服务端和前端web页面,演变为前端和后端逐渐分 ...
- IIS Web 服务器/ASP.NET 运行原理基本知识概念整理 转
转http://www.cnblogs.com/loongsoft/p/7272830.html IIS Web 服务器/ASP.NET 运行原理基本知识概念整理 前言: 记录 IIS 相 ...
- IIS Web 服务器/ASP.NET 运行原理基本知识概念整理
前言: 记录 IIS 相关的笔记还是从公司笔试考核题开始的,问 Application Pool 与 AppDomain 的区别? 促使我对进程池进了知识的学习,所以记录一下学习 ...
随机推荐
- 爬虫用到的库Beautiful Soup
Beautiful Soup 是一个可以从HTML或XML文件中提取数据的Python库.它能够通过你喜欢的转换器实现惯用的文档导航,查找,修改文档的方式.Beautiful Soup会帮你节省数小时 ...
- 【Python算法】哈希存储、哈希表、散列表原理
哈希表的定义: 哈希存储的基本思想是以关键字Key为自变量,通过一定的函数关系(散列函数或哈希函数),计算出对应的函数值(哈希地址),以这个值作为数据元素的地址,并将数据元素存入到相应地址的存储单元中 ...
- registered the JDBC driver [com.mysql.jdbc.Driver] but failed to unregister it when the web application was stopped.
最近在用maven整合SSH做个人主页时候,在eclipse里面使用tomcat7插件发布项目是没有问题的,但当打包成war之后,使用tomcat7单独发布项目,就出现了以下的错误. 严重: Cont ...
- 搭建高性能Jboss负载均衡集群
版权声明:本文为博主原创文章.未经博主同意不得转载. https://blog.csdn.net/a1314517love/article/details/26836357 负载均衡集群是由两台或者两 ...
- java-mybaits-00503-延迟加载
1.什么是延迟加载 resultMap可以实现高级映射(使用association.collection实现一对一及一对多映射),association.collection具备延迟加载功能. 需求: ...
- FindBugs——帮助查找隐藏的bug
FindBugs 1.什么是FindBugs FindBugs 是一个静态分析工具,它检查类或者 JAR 文件,将字节码与一组缺陷模式进行对比以发现可能的问题.有了静态分析工具,就可以在不实际运行程序 ...
- SSH secure shell 原理与运用
转: http://www.ruanyifeng.com/blog/2011/12/ssh_remote_login.html 作者: 阮一峰 日期: 2011年12月21日 SSH是每一台Linux ...
- nginx配置ThinkPHP5二级目录访问
可以通过 http://www.mracale.com/项目名/模块名/方法名 进行访问 第一步 首先,你要确保在不配置二级目录的情况下,可以通过浏览器访问到.例如:http://www.mracal ...
- FTP服务器文件上传的代码实现
方式一: @Test public void testFtpClient() throws Exception { // 1.创建一个FtpClient对象 FTPClient ftpClient = ...
- index full scan和index fast full scan区别
触发条件:只需要从索引中就可以取出所需要的结果集,此时就会走索引全扫描 Full Index Scan 按照数据的逻辑顺序读取数据块,会发生单块读事件, Fast Full Index Scan ...