分享web开发知识

注册/登录|最近发布|今日推荐

主页 IT知识网页技术软件开发前端开发代码编程运营维护技术分享教程案例
当前位置:首页 > IT知识

网络知识 - 简易的自定义Web服务器

发布时间:2023-09-06 01:43责任编辑:顾先生关键词:Web

简易的自定义Web服务器

基于浏览器向服务端发起请求

两台主机各自的进程之间相互通信,需要协议、IP地址和端口号,IP表示了主机的网络地址,而端口号则表示了主机上的某个进程的地址,IP加Port统称为端点(EndPoint),在网络编程的世界里,.NET提供了Socket(套接字)类,此类处于传输层之中,Socket使开发人员可以以编程的方式侦听远程主机向本机发送的数据,并对到达传输层的数据包做出处理,同时它还可以向远程发送数据包。也即,Socket用于处理传输的数据。

usingSystem.Net;
usingSystem.Net.Sockets;

namespaceConsoleHttp
{
classProgram
{
staticvoidMain(string[]args)
{
//本机IP
IPAddressaddress=IPAddress.Loopback;
//本程序的IP和端口(端点)
IPEndPointipPoint=newIPEndPoint(address,49155);
//ipv4
varnetWork=AddressFamily.InterNetwork;
//创建Socket对象
Socketsocket=newSocket(netWork,SocketType.Stream,ProtocolType.Tcp);
//将Socket绑定到端点
socket.Bind(ipPoint);
socket.Listen(100);//侦听请求的队列的最大长度为100
while(true)
{
Console.WriteLine($"已开启侦听,\n本机端点为:{ipPoint}\n正在等待远程主机的请求……");

//接收……

SocketclientSocket=socket.Accept();//阻塞线程直到至少有一台远程主机发送的数据包被socket接收
byte[]dataBuffer=newbyte[1024];//数据存储区,最大存储1M的数据
intlen=clientSocket.Receive(dataBuffer,1024,SocketFlags.None);//将接收的数据存入存储区,返回数据的字节长度
stringDataStr=Encoding.UTF8.GetString(dataBuffer,0,len);//将字节转换为字符串

Console.WriteLine($"远程主机端点:{clientSocket.RemoteEndPoint}");//输出远程主机的端点
Console.WriteLine($"数据字节长度:{len}");//输出接收的数据的字节长度
Console.WriteLine($"请求数据:\n{DataStr}");//输出接收的数据

//响应……
stringHttpDataLine="HTTP/1.1200OK\r\n";//报文状态行
stringHttpBody="<html><head><title>DefaultPage</title></head><body><pstyle=‘font:bold;寂静的春天</p></body></html>";//报文主体
stringHttpHeader=$"Content-Type:text/html;charset=UTf-8\nContent-Length:{HttpBody.Length}\n";


byte[]HttpDataLineByte=Encoding.UTF8.GetBytes(HttpDataLine);
byte[]HttpBodyByte=Encoding.UTF8.GetBytes(HttpBody);
byte[]HttpHeaderByte=Encoding.UTF8.GetBytes(HttpHeader);
byte[]HttpNullLineByte=newbyte[]{13,10};

clientSocket.Send(HttpDataLineByte);
clientSocket.Send(HttpHeaderByte);
clientSocket.Send(HttpNullLineByte);
clientSocket.Send(HttpBodyByte);

//断开连接
clientSocket.Close();
}
}
}
}

在浏览器输入端点进行访问,因为浏览器实已经实现了Http协议,浏览器处于应用层,封装好请求后会往下传递给传输层,封装TCP端口再传递给网络层直到请求发送至服务端,所以可以直接看到服务端返回的结果:

TcpListener封装了Socket,所以也可以使用TcpListener来监听请求

usingSystem.Net;
usingSystem.Net.Sockets;

namespaceConsoleHttp
{
classProgram
{
staticvoidMain(string[]args)
{
//本机IP
IPAddressaddress=IPAddress.Loopback;
//本程序的IP和端口(端点)
IPEndPointipPoint=newIPEndPoint(address,49155);
//ipv4
varnetWork=AddressFamily.InterNetwork;
//创建Tcp监听
TcpListenertcp=newTcpListener(ipPoint);
tcp.Start();

while(true)
{
Console.WriteLine($"已开启侦听,\n本机端点为:{ipPoint}\n正在等待远程主机的请求……");
//接收……
TcpClientclientTcp=tcp.AcceptTcpClient();
if(clientTcp.Connected)
{
Console.WriteLine("连接已经建立……");
NetworkStreamnetworkStream=clientTcp.GetStream();//此类可自动从Socket中读取远程主机发起的请求数据,也可以输出数据

byte[]dataBuffer=newbyte[1024];//数据存储区,最大存储1M的数据
intlen=networkStream.Read(dataBuffer,0,1024);//将接收的数据存入存储区,返回数据的字节长度
stringDataStr=Encoding.UTF8.GetString(dataBuffer,0,len);//将字节转换为字符串

Console.WriteLine($"远程主机端点:{clientTcp.Client.RemoteEndPoint}");//输出远程主机的端点
Console.WriteLine($"数据字节长度:{len}");//输出接收的数据的字节长度
Console.WriteLine($"请求数据:\n{DataStr}");//输出接收的数据

//响应……
stringHttpDataLine="HTTP/1.1200OK\r\n";//报文状态行
stringHttpBody="<html><head><title>DefaultPage</title></head><body><pstyle=‘font:bold;寂静的春天</p></body></html>";//报文主体
stringHttpHeader=$"Content-Type:text/html;charset=UTf-8\nContent-Length:{HttpBody.Length}\n";//报头

byte[]HttpDataLineByte=Encoding.UTF8.GetBytes(HttpDataLine);
byte[]HttpBodyByte=Encoding.UTF8.GetBytes(HttpBody);
byte[]HttpHeaderByte=Encoding.UTF8.GetBytes(HttpHeader);
byte[]HttpNullLineByte=newbyte[]{13,10};

networkStream.Write(HttpDataLineByte,0,HttpDataLineByte.Length);
networkStream.Write(HttpHeaderByte,0,HttpHeaderByte.Length);
networkStream.Write(HttpNullLineByte,0,HttpNullLineByte.Length);
networkStream.Write(HttpBodyByte,0,HttpBodyByte.Length);
}
//断开连接
clientTcp.Close();
}
}
}
}
View Code

基于windows窗体实现双方发送即时通信

分别创建两个windows窗体项目,命名为TCPServer和TCPClient。两个项目的窗体控件的名称是一样的,如下:

服务端通过TcpListener开启监听,然后通过开启新的线程并使用TcpListener的AcceptTcpClient方法去监听客户端的请求,而客户端则开启新线程并通过TcpClient发起远程连接请求。这样双方就可以建立一个连接。接着,服务端的AcceptTcpClient方法会阻塞线程直到接受到一个请求为止,此时它会返回一个NetworkStream实例,此类提供了读取远程数据、发送数据的方法,此后,双方的互动都是通过这个唯一的NetworkStream实例的方法(Read、Write)来完成,发送数据和接收数据时都使用新线程来处理,并且应将发送数据和接收数据的逻辑都放入try块,这样一旦互动过程出现异常则可以关闭当前的Tcp连接、清空NetworkStream资源,然后服务端重新开启新线程继续监听客户端的连接请求,而客户端则重新发送远程连接的请求即可。

服务端源码

usingSystem.Threading;
usingSystem.Net;
usingSystem.Net.Sockets;
usingSystem.IO;

namespaceTCPServer
{
publicpartialclassServer:Form
{
publicconstintPort=51388;
publicTcpListenerlister;
publicTcpClientclient;
publicIPAddressipAddress;
publicNetworkStreamnetworkStream;
publicBinaryReaderreader;
publicBinaryWriterwriter;
publicIPEndPointipPoint;
publicdelegatevoidShowStatusMessage(stringmsg);
publicShowStatusMessageshowStatusMessage;
publicdelegatevoidShowGetOrSendMessage(stringmsg);
publicShowGetOrSendMessageshowGetOrSendMessage;

//构造器
publicServer()
{
InitializeComponent();

//状态栏信息和公共消息框信息
showStatusMessage=newShowStatusMessage(ShowStatusCallBack);
showGetOrSendMessage=newShowGetOrSendMessage(ShowGetOrSendCallBack);

//本机IP
IPAddressaddress=IPAddress.Loopback;
//端点
ipPoint=newIPEndPoint(address,Port);
//创建Socket监听
lister=newTcpListener(ipPoint);

//显示本机IP和端口
IPAddressBox.ReadOnly=true;
PortBox.ReadOnly=true;
IPAddressBox.Text=address.ToString();
PortBox.Text=Port.ToString();
}

//状态栏显示目前的连接状态和数据发送、接收的状态
publicvoidShowStatusCallBack(stringmsg)
{
toolStripStatusLabel.Text=msg;
}

//设置公共消息框的数据
publicvoidShowGetOrSendCallBack(stringmsg)
{
ShowMessageBox.Text+=$"\r\n来自{client.Client.RemoteEndPoint}的消息:";
ShowMessageBox.Text+="\r\n"+msg;
}

//开启监听
privatevoidTcpListenStart_Click(objectsender,EventArgse)
{
lister.Start();
//开启新线程
Threadthread=newThread(Request);
thread.Start();
}

//接收请求
privatevoidRequest()
{
statusStrip.Invoke(showStatusMessage,"正在监听……");
Thread.Sleep(1000);

try
{
statusStrip.Invoke(showStatusMessage,"等待连接……");
client=lister.AcceptTcpClient();//阻塞线程,接收队列中的客户端请求
if(client!=null)
{
statusStrip.Invoke(showStatusMessage,"连接已经建立……");
networkStream=client.GetStream();
reader=newBinaryReader(networkStream);
writer=newBinaryWriter(networkStream);
}
}
catch
{
statusStrip.Invoke(showStatusMessage,"连接失败……");
}
}

//接收消息
privatevoidGetMessage_Click(objectsender,EventArgse)
{
try
{
statusStrip.Invoke(showStatusMessage,"消息接收中……");
ShowMessageBox.Invoke(showGetOrSendMessage,reader.ReadString());//在创建"公共消息框控件"的线程上调用showGetOrSendMessage委托来显示消息
}
catch
{
//如果出现异常则关闭现有连接,清除所有资源
statusStrip.Invoke(showStatusMessage,"对方没有发送消息或接收消息失败……");
if(client!=null)client.Close();
if(reader!=null)reader.Close();
if(writer!=null)writer.Close();
statusStrip.Invoke(showStatusMessage,"连接已经断开……");
//重新开启新线程来接收请求
Threadthread=newThread(Request);
thread.Start();
}
}

//发送消息
privatevoidSenMessage_Click(objectsender,EventArgse)
{
stringsenMsg=SendMessageBox.Text;
if(senMsg==string.Empty)
{
MessageBox.Show("发送的消息不能为空");
return;
}

statusStrip.Invoke(showStatusMessage,"正在发送消息……");
ThreadproxyThread=newThread(()=>
{
try
{
writer.Write(senMsg);
Thread.Sleep(3000);//模拟发送延时
writer.Flush();
statusStrip.Invoke(showStatusMessage,"消息发送成功……");
ShowMessageBox.Invoke(showGetOrSendMessage,senMsg);//在创建"公共消息框控件"的线程上调用showGetOrSendMessage委托来显示消息
}
catch
{
//如果出现异常则需要关闭现有连接,清除所有资源后重新开始
statusStrip.Invoke(showStatusMessage,"消息发送失败……");
if(client!=null)client.Close();
if(reader!=null)reader.Close();
if(writer!=null)writer.Close();
//重新开启新线程来接收请求
Threadthread=newThread(Request);
thread.Start();
}
});

proxyThread.Start();
}

//关闭监听
privatevoidCloseTcpListen_Click(objectsender,EventArgse)
{
if(client!=null)client.Close();
if(reader!=null)reader.Close();
if(writer!=null)writer.Close();
lister.Stop();
statusStrip.Invoke(showStatusMessage,"监听已经关闭……");
}

//断开连接
privatevoidNoConnect_Click(objectsender,EventArgse)
{
if(client!=null)client.Close();
if(reader!=null)reader.Close();
if(writer!=null)writer.Close();
statusStrip.Invoke(showStatusMessage,"连接已断开……");
}

我的编程学习网——分享web前端后端开发技术知识。 垃圾信息处理邮箱 tousu563@163.com 网站地图
icp备案号 闽ICP备2023006418号-8 不良信息举报平台 互联网安全管理备案 Copyright 2023 www.wodecom.cn All Rights Reserved