C#开发自己的Web服务器

79 篇文章 64 订阅

 

下载源代码

 

介绍

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

 

背景

        HTTP协议

        HTTP是服务器和客户机之间的通信协议。它使用TCP/IP协议来发送/接收请求/响应。

        有几个HTTP方法,我们将实现两个:GET和POST。

GET

         当我们将一个地址输入到我们的Web浏览器的地址栏中,按下回车键时,会发生什么情况?(虽然我们使用TCP/IP,但我们不指定端口号,因为HTTP默认使用80端口,我们并不需要指定80)

GET / HTTP/1.1\r\n
Host: okbase.net\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通过浏览器向服务器发送,请求的是okbase.net的根目录下的内容。我们可以添加更多的头信息,最基本的信息如下:

GET / HTTP/1.1\r\n
Host: okbase.net\r\n\r\n

 

POST

        POST请求和GET请求类似,在GET请求里,变量加到url的?下面,POST请求,变量加到两行回车的下面,并需要指定内容长度。

POST /index.html HTTP/1.1\r\n
Host: atasoyweb.net\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://okbase.net/\r\n
Content-Type: application/x-www-form-urlencoded\r\n
Content-Length: 35\r\n\r\n
variable1=value1&variable2=value2

        简化版本如下:

POST /index.html HTTP/1.1\r\n
Host: okbase.net\r\n
Content-Length: 35\r\n\r\n
variable1=value1&variable2=value2

 响应

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

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服务器。首先,让我们来定义变量,我们将使用:

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: http://www.webmaster-toolkit.com/mime-types.shtml
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"}
};

 

启动服务器的方法:

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;
}

 

停止服务器的方法

public void stop()
{
    if (running)
    {
        running = false;
        try { serverSocket.Close(); }
        catch { }
        serverSocket = null;
    }
}

最重要的部分代码

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);
    }
}

 

不同的状态代码的响应:

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);
}


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");
 
}

将响应发送到客户端的方法

// 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 { }
}

 

用法

// 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转发到我们的电脑上,你可以用http://www.web-sniffer.net/ 来测试连接的有效性,输入外部IP和端口号 http://外部IP:端口号并点击Submit按钮...

 

http://blog.okbase.net/haobao/archive/60.html

 

  • 5
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Web API,全称为Web Application Programming Interface,是一种通过HTTP协议提供服务的接口。它可以让开发者通过编程的方式使用远程服务器上的功能或数据。 一个常见的Web API开发实例是创建一个天气预报API。首先,需要建立一个具有气温、天气状况等数据的数据库。然后,通过编写后端代码,将数据库中的天气数据转化为JSON或XML格式,然后通过HTTP请求将数据返回给客户端。 在后端代码中,会使用一种编程语言(比如C#)来处理客户端发来的请求,并从数据库中提取相关数据。对于天气预报API来说,会根据客户端传来的城市或地区的参数,从数据库中找到对应的天气数据并返回给客户端。 此外,在API开发过程中,还需要考虑一些安全措施。比如,可以使用Token进行身份验证,确保只有合法用户才能访问API。另外,还可以使用HTTPS协议来加密传输数据,防止数据被截取或篡改。 一旦API开发完成,其他开发者就可以通过向API发送HTTP请求获取天气预报数据。比如,他们可以编写一个前端界面,输入城市的名称并点击查询按钮,然后通过AJAX技术向API发送请求获取对应城市的天气预报数据,并将数据显示在界面上。 这样,通过开发Web API,我们可以将复杂的功能或数据封装成易于使用的接口,供其他开发者使用,从而提高开发效率和数据共享的便利性。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值