欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

基于C#实现一个最简单的HTTP服务器实例

程序员文章站 2023-12-15 10:19:10
本文详细分析了基于c#实现一个最简单的http服务器的方法。分享给大家供大家参考。具体如下: 一、简介 本文用c#实现了一个最简单的http服务器类,你可以将它嵌入到自...

本文详细分析了基于c#实现一个最简单的http服务器的方法。分享给大家供大家参考。具体如下:

一、简介

本文用c#实现了一个最简单的http服务器类,你可以将它嵌入到自己的项目中,或者也可以阅读代码来学习关于http协议的知识。

二、背景

高性能的web应用一般都架设在强大的web服务器上,例如iis, apache, 和tomcat。然而,html是非常灵活的ui标记语言,也就是说任何应用和后端服务都可以提供html的生成支持。在这个小小的例子中,像iis,、 apache这样的服务器消耗的资源太大了,我们需要自己实现一个简单的http服务器,将它嵌入到我们的应用中用来处理web请求。我们仅需要一个类就 可以实现了,很简单。

三、代码实现

首先我们来回顾一下如何使用类,然后我们再来分析实现的具体细节。这里我们创建了一个继承于httpserver的类,并实现了handlegetrequest 和handlepostrequest  这两个抽象方法:

复制代码 代码如下:
public class myhttpserver : httpserver {
    public myhttpserver(int port)
        : base(port) {
    }
    public override void handlegetrequest(httpprocessor p) {
        console.writeline("request: {0}", p.http_url);
        p.writesuccess();
        p.outputstream.writeline("<html><body><h1>test server</h1>");
        p.outputstream.writeline("current time: " + datetime.now.tostring());
        p.outputstream.writeline("url : {0}", p.http_url);
 
        p.outputstream.writeline("<form method=post action=/form>");
        p.outputstream.writeline("<input type=text name=foo value=foovalue>");
        p.outputstream.writeline("<input type=submit name=bar value=barvalue>");
        p.outputstream.writeline("</form>");
    }
 
    public override void handlepostrequest(httpprocessor p, streamreader inputdata) {
        console.writeline("post request: {0}", p.http_url);
        string data = inputdata.readtoend();
 
        p.outputstream.writeline("<html><body><h1>test server</h1>");
        p.outputstream.writeline("<a href=/test>return</a><p>");
        p.outputstream.writeline("postbody: <pre>{0}</pre>", data);
    }
}

当开始处理一个简单的请求时,我们就需要单独启动一个线程来监听一个端口,比如8080端口:

复制代码 代码如下:
httpserver httpserver = new myhttpserver(8080);
thread thread = new thread(new threadstart(httpserver.listen));
thread.start();

如果你编译运行这个项目,你会在浏览器http://localhost:8080地址下看到页面上生成的示例内容。让我们来简单看一下这个http服务器引擎是怎么实现的。

这个web服务器由两个组件构成,一个是负责启动tcplistener来监听指定端口的httpserver类,并且用 accepttcpclient()方法循环处理tcp连接请求,这是处理tcp连接的第一步。然后请求到达“已指定“的端口,接着就会创建一对新的端 口,用来初始化客户端到服务器端的tcp连接。这对端口便是tcpclient的session,这样就可以保持我们的主端口可以继续接收新的连接请求。 从下面的代码中我们可以看到,每一次监听程序都会创建一个新的tcpclien,httpserver类又会创建一个新的httpprocessor,然 后启动一个线程来操作。httpserver类中还包含两个抽象方法,你必须实现这两个方法。

复制代码 代码如下:
public abstract class httpserver {
    protected int port;
    tcplistener listener;
    bool is_active = true;
 
    public httpserver(int port) {
        this.port = port;
    }
 
    public void listen() {
        listener = new tcplistener(port);
        listener.start();
        while (is_active) {               
            tcpclient s = listener.accepttcpclient();
            httpprocessor processor = new httpprocessor(s, this);
            thread thread = new thread(new threadstart(processor.process));
            thread.start();
            thread.sleep(1);
        }
    }
 
    public abstract void handlegetrequest(httpprocessor p);
    public abstract void handlepostrequest(httpprocessor p, streamreader inputdata);
}

这样,一个新的tcp连接就在自己的线程中被httpprocessor处理了,httpprocessor的工作就是正确解析http头,并且控制正确实现的抽象方法。下面我们来看看http头的处理过程,http请求的第一行代码如下:

复制代码 代码如下:
get /myurl http/1.0

在设置完process()的输入和输出后,httpprocessor就会调用parserequest()方法。

复制代码 代码如下:
public void parserequest() {
    string request = inputstream.readline();
    string[] tokens = request.split(' ');
    if (tokens.length != 3) {
        throw new exception("invalid http request line");
    }
    http_method = tokens[0].toupper();
    http_url = tokens[1];
    http_protocol_versionstring = tokens[2];
 
    console.writeline("starting: " + request);
}

http请求由3部分组成,所以我们只需要用string.split()方法将它们分割成3部分即可,接下来就是接收和解析来自客户端的http头 信息,头信息中的每一行数据是以key-value(键-值)形式保存,空行表示http头信息结束标志,我们代码中用readheaders方法来读取 http头信息:

复制代码 代码如下:
public void readheaders() {
    console.writeline("readheaders()");
    string line;
    while ((line = inputstream.readline()) != null) {
        if (line.equals("")) {
            console.writeline("got headers");
            return;
        }
 
        int separator = line.indexof(':');
        if (separator == -1) {
            throw new exception("invalid http header line: " + line);
        }
        string name = line.substring(0, separator);
        int pos = separator + 1;
        while ((pos < line.length) && (line[pos] == ' ')) {
            pos++; // 过滤掉所有空格
        }
 
        string value = line.substring(pos, line.length - pos);
        console.writeline("header: {0}:{1}",name,value);
        httpheaders[name] = value;
    }
}

到这里,我们已经了解了如何处理简单的get和post请求,它们分别被分配给正确的handler处理程序。在本例中,发送数据的时候有一个棘手的 问题需要处理,那就是请求头信息中包含发送数据的长度信息content-length,当我们希望子类httpserver中的 handlepostrequest方法能够正确处理数据时,我们需要将数据长度content-length信息一起放入数据流中,否则发送端会因为等 待永远不可能到达的数据和阻塞等待。我们用了一种看起来不那么优雅但非常有效的方法来处理这种情况,即将数据发送给post处理方法前先把数据读入到 memorystream中。这种做法不太理想,原因如下:如果发送的数据很大,甚至是上传一个文件,那么我们将这些数据缓存在内存就不那么合适甚至是不 可能的。理想的方法是限制post的长度,比如我们可以将数据长度限制为10mb。

这个简易版http服务器另一个简化的地方就是content-type的返回值,在http协议中,服务器总是会将数据的mime-type发送给 客户端,告诉客户端自己需要接收什么类型的数据。在writesuccess()方法中,我们看到,服务器总是发送text/html类型,如果你需要加 入其他的类型,你可以扩展这个方法。

希望本文所述对大家的c#程序设计有所帮助。

上一篇:

下一篇: