C#开发自己的Web服务器
http://blog.okbase.net/haobao/archive/60.html下载源代码 介绍我们将学习如何写一个简单的web服务器,用于响应知名的HTTP请求(GET和POST),用C#发送响应。然后,我们从网络访问这台服务器,这次我们会说“Hello world!” 背景HTTP协议HTTP是服务器和客户机之间的通信协议。它使用TCP/IP协议来
http://blog.okbase.net/haobao/archive/60.html
介绍
我们将学习如何写一个简单的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: 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的根目录下的内容。
我们可以添加更多的头信息,最基本的信息如下:
1
2
|
GET / HTTP/1.1\r\n
Host: okbase.net\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: 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
|
简化版本如下:
1
2
3
4
|
POST /index.html HTTP/1.1\r\n
Host: okbase.net\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: 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"
}
};
|
启动服务器的方法:
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转发到我们的电脑上,你可以用http://www.web-sniffer.net/ 来测试连接的有效性,输入外部IP和端口号 http://外部IP:端口号并点击Submit按钮...
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)