您的位置:首页 > 编程语言 > C#

C# | socket实现简单的web服务器

2014-05-24 10:51 585 查看
最近在搬砖做web相关的东西。 因为要解除对一些框架的依赖, 所以有机会稍微接触一些底层的东西。

传统的web服务器包括IIS, tomcat, apache,Nginx都是基于http请求与响应来实现数据对接的。

在C#和java中都可以由httpwebRequest或者servlet相关的类或者包实现封装http的一些数据, 这样我们就不需要自己手动创建http格式的数据了。

而本文要透过这些类, 去看http到底包含了哪些东西, 然后封装属于自己的http响应数据,再通过socket发送给客户端(浏览器), 实现web服务器的一些简单的响应功能。

关于http协议的一些具体细节, 推荐这篇blog:HTTP协议详细

关于http我们要了解:

1.在TCP/IP体系结构中,HTTP属于应用层协议,位于TCP/IP协议的顶层。浏览Web时,浏览器通过HTTP协议与Web服务器交换信息。这些信息(文档)类型的格式由MIME定义。

2.HTTP按客户/服务器模式工作,HTTP是无状态的.

3.HTTP使用元信息作为头标。这里的元信息就是指那些用来指明http具体要做什么的数据,包括请求行或者报头等等。

4.HTTP的请求

方 法 说 明

GET 请求读取一个Web页面

HEAD 请求读取一个Web页面的头标

PUT 请求存储一个Web页面

POST 附加到命名资源中

DELETE 删除Web页面

LINK 连接两个已有资源
UNLINK 取消两个资源之间的已有连接

5.Http常见的请求数据格式:

(1)请求数据:(最常用的两种请求是get跟post)

通常包括:请求行 + 请求报头 + 请求正文

请求行:动作(get等), URL(请求文件的位置), HTTP协议的版本号

请求报头:一些请求的具体信息。(这个可以自己看到的, 抓包)

请求正文: 注意请求正文与请求报头之间必须要有一个空行来区分。

(2)响应数据:

通常:状态行 + 响应报头 + 响应正文

状态行:HTTP协议版本号 + 状态码 + 状态描述 

这里常见状态码有:

200 (OK): 找到了该资源,并且一切正常。

304 (NOT MODIFIED): 该资源在上次请求之后没有任何修改。这通常用于浏览器的缓存机制。

401 (UNAUTHORIZED):<strong></strong>客户端无权访问该资源。这通常会使得浏览器要求用户输入用户名和密码,以登录到服务器。

403 (FORBIDDEN):<strong></strong>客户端未能获得授权。这通常是在401之后输入了不正确的用户名或密码。

404 (NOT FOUND):<strong></strong>在指定的位置不存在所申请的资源。

响应报头: 响应的一些格式。通常包含传输数据的格式,数据长度等等。

响应正文:要返回给客户端的数据, 这里也注意与响应报头留有空行。

有了这些格式, 我们就可以通过C#构建自己的WEB服务器了。

需要用到socket用来传输数据, 当接受到浏览器端传来的数据时, 我们解析这个http协议, 获取一些需要知道的信息, 比如是什么类型的请求, 需要请求服务器上什么物理位置的文件等。 然后我们按照http的标准协议封装响应数据的三个部分, 发送给浏览器。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using System.IO;

namespace WebServer
{
class MyWebServer
{
private readonly string webServerRoot = @"E:\Pages\";
private readonly int port = 4096;
private readonly string host = "127.0.0.1";
private Socket socket = null;
public MyWebServer()
{
try
{
IPAddress ip = IPAddress.Parse(host);
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Bind(new IPEndPoint(ip, port));
socket.Listen(10);
Thread th = new Thread(new ThreadStart(Listen));
th.Start();
Console.WriteLine("Server Is Listening ...");
}
catch(Exception e)
{
Console.WriteLine("Fail When Listening :  " + e.ToString());
}
}

public void SendHttpHead(string httpVersion, string statusCode, string mime, int codeLength, ref Socket socket)
{
string sendBuffer = "";
sendBuffer += httpVersion + statusCode + "\r\n";
sendBuffer += "Query Ok In May GMT";
if (mime.Length == 0)
{
mime = "text/html";
}
sendBuffer += "Content-Type: " + mime + "\r\n";
sendBuffer += "Accept-Ranges: bytes\r\n";
sendBuffer += "Content-Length: " + codeLength + "\r\n\r\n";

Byte[] sendDataByte = Encoding.ASCII.GetBytes(sendBuffer);

SendToBrowser(sendDataByte, ref socket);
// SendToBrowser(sendBuffer, socket);
}

public void SendToBrowserByString(string sendData, ref Socket socket)
{
SendToBrowser(Encoding.ASCII.GetBytes(sendData), ref socket);
}

public void SendToBrowser(Byte[] sendData,  ref Socket socket)
{
try
{
if(socket.Connected)
{
int dataNumber = socket.Send(sendData);
Console.WriteLine("Send Bytes {0}", dataNumber);
}
else
{
Console.WriteLine("Fail in Connecting ...");
}
}
catch(Exception e)
{
Console.WriteLine("Error !");
}
}

public void Listen()
{
string httpVersion, statusCode, mime;
int dataLength;

while(true)
{
Socket mySocket = socket.Accept();
if(mySocket.Connected)
{
Byte[] dataFromBrow = new Byte[100];
mySocket.Receive(dataFromBrow, dataFromBrow.Length, 0);
string receiveBuf = Encoding.ASCII.GetString(dataFromBrow);
Console.WriteLine(receiveBuf);

if(receiveBuf.Substring(0, 3) != "GET")
{
Console.WriteLine("sorry, we just can accept the \"get\" request");
mySocket.Close();
return;
}

int startPos = receiveBuf.IndexOf("HTTP", 1);
httpVersion = receiveBuf.Substring(startPos, 8);//get HttpVersion

string subString = receiveBuf.Substring(0, startPos - 1);
startPos = subString.LastIndexOf("/")  + 1;
string requestFile = subString.Substring(startPos);//get the FileName

string localDir = webServerRoot;

if(requestFile.Length == 0)
{
requestFile = "Index.html";
}
string requestFilePath = localDir + requestFile;///get the definite path of the request file

if(File.Exists(requestFilePath) == false)
{
string errorMessage = "404 Error! File Does Not Exists";
SendHttpHead(httpVersion," 404 Not Found", "", errorMessage.Length, ref mySocket);
SendToBrowserByString("Error", ref mySocket);
//  Console.WriteLine("Error : 404");
}
else
{
dataLength = 0;
string responseData = "";

FileStream fs = new FileStream(requestFilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
BinaryReader reader = new BinaryReader(fs);
byte[] bytes = new byte[fs.Length];
int read;
while ((read = reader.Read(bytes, 0, bytes.Length)) != 0)
{
responseData = responseData +  Encoding.ASCII.GetString(bytes, 0, read);
dataLength += read;
}
reader.Close();
fs.Close();

mime = "text/html";
statusCode = " 200 OK";
SendHttpHead(httpVersion, mime, statusCode, dataLength, ref mySocket);//200 OK
SendToBrowserByString(responseData, ref mySocket);
}
mySocket.Close();
}
}
}
}
class Program
{
static void Main(string[] args)
{
MyWebServer myWebServer = new MyWebServer();
Console.ReadKey();

}
}
}


如果想要详细深入理解Web的请求与响应的流程, 推荐:浏览器与服务器数据交换细节, 写的相当好。

这里存在一些小的Bug:应该是请求数据时候的缓冲区大小出现了问题, 等待解决。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  http socket c#