[C# 网络编程系列]专题三:自定义Web服务器
转自:http://www.cnblogs.com/zhili/archive/2012/08/23/2652460.html
前言:
经过前面的专题中对网络层协议和HTTP协议的简单介绍相信大家对网络中的协议有了大致的了解的, 本专题将针对HTTP协议定义一个Web服务器,我们平常浏览网页通过在浏览器中输入一个网址就可以看到我们想要的网页,这个过程中浏览器只是一个客户端,浏览器(应用层应用程序)通过HTTP协议把用户请求发送到服务端, 服务器接受到发送来的HTTP请求,然后对请求进行处理和响应,最后把响应的内容发送给客户端(浏览器这里充当了用户代理的客户端),浏览器再对接受到的响应内容(一般是HTML文件)进行解释并且显示出来。这就是一次完整的用户请求/响应模型,本专题所讲述的是一个简单的Web服务器,其他一些大型的Web服务器(IIS,Apache)也是这样的一个原理, 本专题只是简单讲述Web服务器的实现原理。
一、Socket编程实现一个简单的Web服务器
Socket这个概念是在Unix系统中提出来的。在Unix的时代,为了解决传输层的编程问题,Unix提供了类似于文件操作的网络操作方式——Socket,通过Socket,我们就可以像操作文件一样通过打开、写入、读取、关闭等操作完成网络编程,这样就使得网络编程可以统一到文件操作方面,这样就使我们更容易地编写网络应用程序。需要注意的是,应用层的协议需要网络程序专门处理,Socket不负责应用层协议,仅仅负责传输层的协议。
现在介绍下网络端口号(port)的概念,在同一个网络地址中,为了区分使用相同协议的不同应用程序,为不同的应用程序分配一个数字编号,我们把这个编号就成为网络端口号(就是区分同一个网络地址中不同的进程)。端口号是由一个两个字节的整数,所以取值范围为0~65535,这些端口号又分为三类:
- 第一类的范围是0~1023,称为众所周知的端口,这些端口号由特定的网络程序使用,例如,TCP协议使用80端口来完成Http协议的传输。
- 第二类的范围是1024~49151,称为登记端口,一般情况下不应该在程序中使用。
- 第三类的范围是49152~65535,称为私有端口, 这些端口可以由普通用户程序使用。
在我们用Socket开发网络应用程序中,还有一个就是端点的概念,在网络中,通过IP地址,协议和端口号可以唯一地确定网络上的一个应用程序,其中把IP地址和端口的组合叫做端点(EndPoint)。每个Socket需要绑定到一个端点上与其他端点进行通信。
介绍完基本的一些概念后,下面演示通过Socket编程实现一个简单的Web服务器,此实例中就是简单向浏览器返回一个固定的静态页面,实现代码如下:
using System;
using System.Net;
using System.Net.Sockets;
using System.Text; namespace WebServer
{
/// <summary>
/// 实现一个简单的Web服务器
/// 该服务器向请求的浏览器返回一个静态的HTML页面
/// </summary>
class Program
{
static void Main(string[] args)
{
// 获得本机的Ip地址,即127.0.0.1
IPAddress localaddress =IPAddress.Loopback; // 创建可以访问的断点,49155表示端口号,如果这里设置为0,表示使用一个由系统分配的空闲的端口号
IPEndPoint endpoint = new IPEndPoint(localaddress,49155); // 创建Socket对象,使用IPv4地址,数据通信类型为数据流,传输控制协议TCP协议.
Socket socket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp); //将Socket绑定到断点上
socket.Bind(endpoint);
// 设置连接队列的长度
socket.Listen(10); while (true)
{
Console.WriteLine("Wait an connect Request...");
// 开始监听,这个方法会堵塞线程的执行,直到接受到一个客户端的连接请求
Socket clientsocket =socket.Accept(); // 输出客户端的地址
Console.WriteLine("Client Address is: {0}", clientsocket.RemoteEndPoint);
// 把客户端的请求数据读入保存到一个数组中
byte[] buffer =new byte[2048]; int receivelength = clientsocket.Receive(buffer, 2048, SocketFlags.None);
string requeststring = Encoding.UTF8.GetString(buffer, 0, receivelength); // 在服务器端输出请求的消息
Console.WriteLine(requeststring); // 服务器端做出相应内容
// 响应的状态行
string statusLine ="HTTP/1.1 200 OK\r\n";
byte[] responseStatusLineBytes = Encoding.UTF8.GetBytes(statusLine);
string responseBody = "<html><head><title>Default Page</title></head><body><p style='font:bold;font-size:24pt'>Welcome you</p></body></html>";
string responseHeader =
string.Format(
"Content-Type: text/html; charset=UTf-8\r\nContent-Length: {0}\r\n",responseBody.Length); byte[] responseHeaderBytes = Encoding.UTF8.GetBytes(responseHeader);
byte[] responseBodyBytes = Encoding.UTF8.GetBytes(responseBody); // 向客户端发送状态行
clientsocket.Send(responseStatusLineBytes); // 向客户端发送回应头信息
clientsocket.Send(responseHeaderBytes); // 发送头部和内容的空行
clientsocket.Send(new byte[] { 13, 10 }); // 想客户端发送主体部分
clientsocket.Send(responseBodyBytes); // 断开连接
clientsocket.Close();
Console.ReadKey();
break;
} // 关闭服务器
socket.Close(); }
}
}
运行结果:
首先运行服务端后的界面:
在浏览器中输入http://localhost:49155/ 则浏览器可以看到如下的所示的结果:
此时在服务器端显示的输出为:
这里只是简单实现了一个web服务器的功能,当然实际的Web服务器通过用户的发来的Http请求中获得请求文件类型,请求文件名以及请求目录等信息,然后Web服务器根据这些请求信息从服务器的物理目录中寻找请求的文件,如果在服务器中找到请求的文件,然后服务器把响应内容发送给客户端。这里只是通过这个简单的Web服务器让大家理解请求/响应模型以及Web服务器的工作原理,一些复杂的Web服务器也是在此基础进行一些其他功能的扩展。
二、基于TcpListener的Web服务器
在.net平台下, 为了简化网络编程,.net对套接字又进行了一次封装,封装后的类是在System.Net.Sockets命名空间下的TcpListener类和TcpClient类,使用TcpListener类用来监听和接收传入的连接请求,在该类的构造函数中只需要传递一组网络端点信息就可以准备好监听参数,而不需要设置使用的网络协议等细节,调用Start方法后,监听工作就开始(间接调用了Socket.Listen方法),AcceptTcpClient方法将阻塞进程,直到一个客户端发来连接请求为止,这个方法返回一个
封装了Socket的TcpClient对象,同时从传入的连接队列中删除该客户端的连接请求。此时通过这个TcpClient对象与客户端进行通信。
下面是基于TcpListener和TcpClient的一个简单的Web服务器的代码:
using System;
using System.Net;
using System.Net.Sockets;
using System.Text; namespace TcpWebserver
{
class Program
{
static void Main(string[] args)
{
// 获得本机的Ip地址,即127.0.0.1
IPAddress localaddress =IPAddress.Loopback; // 创建可以访问的断点,49155表示端口号,如果这里设置为0,表示使用一个由系统分配的空闲的端口号
IPEndPoint endpoint = new IPEndPoint(localaddress, 49155); // 创建Tcp 监听器
TcpListener tcpListener = new TcpListener(endpoint); // 启动监听
tcpListener.Start();
Console.WriteLine("Wait an connect Request...");
while (true)
{
// 等待客户连接
TcpClient client =tcpListener.AcceptTcpClient();
if (client.Connected == true)
{
// 输出已经建立连接
Console.WriteLine("Created connection");
} // 获得一个网络流对象
// 该网络流对象封装了Socket的输入和输出操作
// 此时通过对网络流对象进行写入来返回响应消息
// 通过对网络流对象进行读取来获得请求消息
NetworkStream netstream = client.GetStream();
// 把客户端的请求数据读入保存到一个数组中
byte[] buffer = new byte[2048]; int receivelength = netstream.Read(buffer, 0, 2048);
string requeststring = Encoding.UTF8.GetString(buffer, 0, receivelength); // 在服务器端输出请求的消息
Console.WriteLine(requeststring); // 服务器端做出相应内容
// 响应的状态行
string statusLine = "HTTP/1.1 200 OK\r\n";
byte[] responseStatusLineBytes = Encoding.UTF8.GetBytes(statusLine);
string responseBody = "<html><head><title>Default Page</title></head><body><p style='font:bold;font-size:24pt'>Welcome you</p></body></html>";
string responseHeader =
string.Format(
"Content-Type: text/html; charset=UTf-8\r\nContent-Length: {0}\r\n", responseBody.Length); byte[] responseHeaderBytes = Encoding.UTF8.GetBytes(responseHeader);
byte[] responseBodyBytes = Encoding.UTF8.GetBytes(responseBody); // 写入状态行信息
netstream.Write(responseStatusLineBytes, 0, responseStatusLineBytes.Length);
// 写入回应的头部
netstream.Write(responseHeaderBytes, 0, responseHeaderBytes.Length);
// 写入回应头部和内容之间的空行
netstream.Write(new byte[] { 13, 10 }, 0, 2); // 写入回应的内容
netstream.Write(responseBodyBytes, 0, responseBodyBytes.Length); // 关闭与客户端的连接
client.Close();
Console.ReadKey();
break;
} // 关闭服务器
tcpListener.Stop();
}
}
}
程序的输出结果和前面的用Socket实现的效果相同,这里就不再贴图了,这里实现的Web服务器都是建立控制台的应用程序来实现的,感兴趣的朋友也可以用Windows窗体进行实现,同时这里也只是简单列出了采用同步的方式进行实现的,同时TcpListener类和TcpClient类同时支持异步操作的方法,下面列出这个两个类中异步操作的方法如下表:
类 |
方法 |
说明 |
TcpListener |
BeginAcceptTcpClient |
开始一个异步操作接受一个传入的连接 |
EndAcceptTcpClient |
异步接受传入的连接,并创建新的TcpClient对象来处理客户端的通信 |
|
TcpClient |
BeginConnect |
开始一个对远程主机连接的异步请求 |
EndConnect |
异步接受传入的连接尝试。 |
如果想了解线程同步和异步的朋友可以参考我的多线程处理系列:http://www.cnblogs.com/zhili/archive/2012/07/21/ThreadsSynchronous.html
三、总结
到这里这篇文章就差不多介绍到这里了,本专题是介绍如何自定义一个简单Web服务器,通过这个专题希望大家可以对Web服务器的工作过程有一个简单的了解。
另外在这个专题里面我们是用IE浏览器进行发送客户请求的,所以后面专题将介绍自定义一个浏览器,通过我们自定义的浏览器来对Web服务器发送请求,然后在自己自定义的浏览器中把响应消息显示出来。
[C# 网络编程系列]专题三:自定义Web服务器的更多相关文章
- [C#]网络编程系列专题二:HTTP协议详解
转自:http://www.cnblogs.com/zhili/archive/2012/08/18/2634475.html 我们在用Asp.net技术开发Web应用程序后,当用户在浏览器输入一个网 ...
- [C# 网络编程系列]专题四:自定义Web浏览器
转自:http://www.cnblogs.com/zhili/archive/2012/08/24/WebBrowser.html 前言: 前一个专题介绍了自定义的Web服务器,然而向Web服务器发 ...
- [C#网络编程系列]专题一:网络协议简介
转自:http://www.cnblogs.com/zhili/archive/2012/08/11/NetWorkProgramming.html 因为这段时间都在研究C#网络编程的一些知识, 所以 ...
- [C# 网络编程系列]专题八:P2P编程
引言: 前面的介绍专题中有朋友向我留言说介绍下关于P2P相关的内容的,首先本人对于C#网络编程也不是什么大牛,因为能力的关系,也只能把自己的一些学习过程和自己的一些学习过程中的理解和大家分享下的,下面 ...
- [C# 网络编程系列]专题五:TCP编程
转自:http://www.cnblogs.com/zhili/archive/2012/08/25/2656840.html 前言 前面专题的例子都是基于应用层上的HTTP协议的介绍, 现在本专题来 ...
- [C# 网络编程系列]专题六:UDP编程
转自:http://www.cnblogs.com/zhili/archive/2012/09/01/2659167.html 引用: 前一个专题简单介绍了TCP编程的一些知识,UDP与TCP地位相当 ...
- [C# 网络编程系列]专题十:实现简单的邮件收发器
转自:http://www.cnblogs.com/zhili/archive/2012/09/24/2689892.html 引言: 在我们的平常工作中,邮件的发送和接收应该是我们经常要使用到的功能 ...
- [C# 网络编程系列]专题九:实现类似QQ的即时通信程序
转自:http://www.cnblogs.com/zhili/archive/2012/09/23/2666987.html 引言: 前面专题中介绍了UDP.TCP和P2P编程,并且通过一些小的示例 ...
- [C# 网络编程系列]专题七:UDP编程补充——UDP广播程序的实现
转自:http://www.cnblogs.com/zhili/archive/2012/09/03/2666974.html 上次因为时间的关系,所以把上一个专题遗留下的一个问题在本专题中和大家分享 ...
随机推荐
- OC加强-day05
#program mark - 0_11 NSRange结构体介绍 [掌握] 是Foundation框架中的一个结构体 NSRange 定义的一个变量的两个属性 location:起始下标 lengt ...
- 2014年12月20日00:33:14-遮罩+进度条-extjs form.isvalid
1.Extjs : 遮罩+进度条 2.Extjs: extjs form.isvalid http://stackoverflow.com/questions/19354433/extjs-form- ...
- C++ GUI Programming with Qt4 笔记 -- chap1
1. Hello Qt #include <QApplication> #include <QLabel> int main(int argc, char *argv[]){ ...
- c语言"a<b<c"条件值的判定
示例代码: #include <stdio.h> int main() { , b = , c = ; ; while (a<b<c) { t = a; a = b; b = ...
- margin系列之内秀篇(二)
本系列摘自 飘零雾雨的博客 可挖掘性 之前已经写过一篇关于 margin 应用场景的文章:margin系列之内秀篇,当然,它的应用场景会远大于文中所述,无法一一列举. 所以本篇权当是对此的补遗好了, ...
- MySQL事务隔离级别初探
MySQL有四种隔离级别,分别是: READ UNCOMMITTED(未提交读) READ COMMITTED(提交读) REPEATABLE READ (可重复读) SERIALIZABLE(可串行 ...
- PHP异常处理
一.异常处理——可以有效地控制多条出现错误或异常的代码 基本语法如下: try{ //可能出现异常的代码 } catch(Exception $e){ //对异常处理 //1.自己处理 //2.不作处 ...
- cli下的php(并传递参数)
传递参数有两种方式: 第一种使用文件操作,STDOUT作为标准输出,STDIN作为标准输入 使用fwrite($file,$string)作输出,使用fgets($file)作输入.这种应该算是继承自 ...
- [转]PHP Session原理分析及使用
之前在一个叫魔法实验室的博客中看过一篇<php session原理彻底分析>的文章,作者从session的使用角度很好阐述了在代码运行过程中,每个环节的变化以及相关参数的设置及作用.本来想 ...
- delete删除多表
1.DELETE a.*, aa.* FROM student a, person aa WHERE a.id = aa.city_id AND a.name = '' 2.DELETE a.*, a ...