专业网站建设品牌,十四年专业建站经验,服务6000+客户--广州京杭网络
免费热线:400-963-0016      微信咨询  |  联系我们

C#开发自己的Web服务器

当前位置:网站建设 > 技术支持
资料来源:网络整理       时间:2023/2/14 0:57:14       共计:3649 浏览

介绍

我们将学习如何写一个简单的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\n Host: \r\n User-Agent: Mozilla/5.0 (Windows NT 6.1; rv:14.0) Gecko/20100101 Firefox/14.0.1\r\n Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n Accept-Language: tr-tr,tr;q=0.8,en-us;q=0.5,en;q=0.3\r\n Accept-Encoding: gzip, deflate\r\n Connection: keep-alive\r\n\r\n

该GET请求使用TCP/IP通过浏览器向服务器发送,请求的是的根目录下的内容。

我们可以添加更多的头信息,最基本的信息如下:

 

1 2 GET / HTTP/1.1\r\n Host: \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\n Host: \r\n User-Agent: Mozilla/5.0 (Windows NT 6.1; rv:15.0) Gecko/20100101 Firefox/15.0.1\r\n Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n Accept-Language: tr-tr,tr;q=0.8,en-us;q=0.5,en;q=0.3\r\n Accept-Encoding: gzip, deflate\r\n Connection: keep-alive\r\n Referer: http:///\r\n Content-Type: application/x-www-form-urlencoded\r\n Content-Length: 35\r\n\r\n variable1=value1&variable2=value2

简化版本如下:

 

1 2 3 4 POST /index.html HTTP/1.1\r\n Host: \r\n Content-Length: 35\r\n\r\n variable1=value1&variable2=value2

响应

 

当服务器接收到一个请求进行解析,并返回一个响应状态代码:

 

1 2 3 4 5 6 HTTP/1.1 200 OK\r\n Server: Apache/1.3.3.7 (Unix) (Red-Hat/Linux)\r\n Content-Length: {content_length}\r\n Connection: close\r\n Content-Type: text/html; charset=UTF-8\r\n\r\n the 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 string private Socket serverSocket; // Our server socket private string contentPath; // Root path of our contents   // Content types that are supported by our server // You can add more... // To see other types: private 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 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 strings private void sendResponse(Socket clientSocket, string strContent, string responseCode,                           string contentType) {     byte[] bContent = charEncoder.GetBytes(strContent);     sendResponse(clientSocket, bContent, responseCode, contentType); }   // For byte arrays private 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 it server.start(ipAddress, port, maxconnections, contentpath); // to stop it server.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转发到我们的电脑上,你可以用 来测试连接的有效性,输入外部IP和端口号 http://外部IP:端口号并点击Submit按钮...

(全文完)

 

版权说明:
本网站凡注明“广州京杭 原创”的皆为本站原创文章,如需转载请注明出处!
本网转载皆注明出处,遵循行业规范,如发现作品内容版权或其它问题的,请与我们联系处理!
欢迎扫描右侧微信二维码与我们联系。
·上一条:基于SuperSocket实现客户端与客户端的消息推送以及服务端与客户端互发心跳数据包维持长连接、离线消息存储并转发,涉及技术点:C# WinForm、SuperSocket、Redis | ·下一条:c#实现服务器性能监控并发送邮件保存日志

Copyright © 广州京杭网络科技有限公司 2005-2025 版权所有    粤ICP备16019765号 

广州京杭网络科技有限公司 版权所有