简易的自定义Web服务器
基于浏览器向服务端发起请求
两台主机各自的进程之间相互通信,需要协议、IP地址和端口号,IP表示了主机的网络地址,而端口号则表示了主机上的某个进程的地址,IP加Port统称为端点(EndPoint),在网络编程的世界里,.NET提供了Socket(套接字)类,此类处于传输层之中,Socket使开发人员可以以编程的方式侦听远程主机向本机发送的数据,并对到达传输层的数据包做出处理,同时它还可以向远程发送数据包。也即,Socket用于处理传输的数据。
using System.Net.Sockets;
namespace ConsoleHttp
{
class Program
{
static void Main( string[] args )
{
//本机IP
IPAddress address = IPAddress.Loopback;
//本程序的IP和端口(端点)
IPEndPoint ipPoint = new IPEndPoint( address, 49155 );
//ipv4
var netWork = AddressFamily.InterNetwork;
//创建Socket对象
Socket socket = new Socket( netWork, SocketType.Stream, ProtocolType.Tcp );
//将Socket绑定到端点
socket.Bind( ipPoint );
socket.Listen( 100 );//侦听请求的队列的最大长度为100
while (true)
{
Console.WriteLine( $"已开启侦听,\n本机端点为: { ipPoint } \n正在等待远程主机的请求……" );
//接收……
Socket clientSocket = socket.Accept( );//阻塞线程直到至少有一台远程主机发送的数据包被socket接收
byte[] dataBuffer = new byte[1024];//数据存储区,最大存储1M的数据
int len = clientSocket.Receive( dataBuffer, 1024, SocketFlags.None );//将接收的数据存入存储区,返回数据的字节长度
string DataStr = Encoding.UTF8.GetString( dataBuffer, 0, len );//将字节转换为字符串
Console.WriteLine( $"远程主机端点:{clientSocket.RemoteEndPoint}" );//输出远程主机的端点
Console.WriteLine( $"数据字节长度:{len}" ); //输出接收的数据的字节长度
Console.WriteLine( $"请求数据:\n {DataStr}" ); //输出接收的数据
//响应……
string HttpDataLine = "HTTP/1.1 200 OK\r\n"; //报文状态行
string HttpBody = "<html><head><title>Default Page</title></head><body><p style=‘font:bold;font-size:24pt‘>寂静的春天</p></body></html>"; //报文主体
string HttpHeader = $"Content-Type: text/html; charset=UTf-8\n Content-Length: {HttpBody.Length}\n";
byte[] HttpDataLineByte = Encoding.UTF8.GetBytes( HttpDataLine );
byte[] HttpBodyByte = Encoding.UTF8.GetBytes( HttpBody );
byte[] HttpHeaderByte = Encoding.UTF8.GetBytes( HttpHeader );
byte[] HttpNullLineByte = new byte[] { 13, 10 };
clientSocket.Send( HttpDataLineByte );
clientSocket.Send( HttpHeaderByte );
clientSocket.Send( HttpNullLineByte );
clientSocket.Send( HttpBodyByte );
//断开连接
clientSocket.Close( );
}
}
}
}
在浏览器输入端点进行访问,因为浏览器实已经实现了Http协议,浏览器处于应用层,封装好请求后会往下传递给传输层,封装TCP端口再传递给网络层直到请求发送至服务端,所以可以直接看到服务端返回的结果:
TcpListener封装了Socket,所以也可以使用TcpListener来监听请求
using System.Net.Sockets;
namespace ConsoleHttp
{
class Program
{
static void Main( string[] args )
{
//本机IP
IPAddress address = IPAddress.Loopback;
//本程序的IP和端口(端点)
IPEndPoint ipPoint = new IPEndPoint( address, 49155 );
//ipv4
var netWork = AddressFamily.InterNetwork;
//创建Tcp监听
TcpListener tcp = new TcpListener( ipPoint );
tcp.Start( );
while (true)
{
Console.WriteLine( $"已开启侦听,\n本机端点为: { ipPoint } \n正在等待远程主机的请求……" );
//接收……
TcpClient clientTcp = tcp.AcceptTcpClient( );
if(clientTcp.Connected)
{
Console.WriteLine( "连接已经建立……" );
NetworkStream networkStream = clientTcp.GetStream( ); //此类可自动从Socket中读取远程主机发起的请求数据,也可以输出数据
byte[] dataBuffer = new byte[1024];//数据存储区,最大存储1M的数据
int len = networkStream.Read(dataBuffer,0,1024);//将接收的数据存入存储区,返回数据的字节长度
string DataStr = Encoding.UTF8.GetString( dataBuffer, 0, len );//将字节转换为字符串
Console.WriteLine( $"远程主机端点:{clientTcp.Client.RemoteEndPoint}" );//输出远程主机的端点
Console.WriteLine( $"数据字节长度:{len}" ); //输出接收的数据的字节长度
Console.WriteLine( $"请求数据:\n {DataStr}" ); //输出接收的数据
//响应……
string HttpDataLine = "HTTP/1.1 200 OK\r\n"; //报文状态行
string HttpBody = "<html><head><title>Default Page</title></head><body><p style=‘font:bold;font-size:24pt‘>寂静的春天</p></body></html>"; //报文主体
string HttpHeader = $"Content-Type: text/html; charset=UTf-8\n Content-Length: {HttpBody.Length}\n";//报头
byte[] HttpDataLineByte = Encoding.UTF8.GetBytes( HttpDataLine );
byte[] HttpBodyByte = Encoding.UTF8.GetBytes( HttpBody );
byte[] HttpHeaderByte = Encoding.UTF8.GetBytes( HttpHeader );
byte[] HttpNullLineByte = new byte[] { 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( );
}
}
}
}
基于windows窗体实现双方发送即时通信
分别创建两个windows窗体项目,命名为TCPServer和TCPClient。两个项目的窗体控件的名称是一样的,如下:
服务端通过TcpListener开启监听,然后通过开启新的线程并使用TcpListener的AcceptTcpClient方法去监听客户端的请求,而客户端则开启新线程并通过TcpClient发起远程连接请求。这样双方就可以建立一个连接。接着,服务端的AcceptTcpClient方法会阻塞线程直到接受到一个请求为止,此时它会返回一个NetworkStream实例,此类提供了读取远程数据、发送数据的方法,此后,双方的互动都是通过这个唯一的NetworkStream实例的方法(Read、Write)来完成,发送数据和接收数据时都使用新线程来处理,并且应将发送数据和接收数据的逻辑都放入try块,这样一旦互动过程出现异常则可以关闭当前的Tcp连接、清空NetworkStream资源,然后服务端重新开启新线程继续监听客户端的连接请求,而客户端则重新发送远程连接的请求即可。
服务端源码
using System.Net;
using System.Net.Sockets;
using System.IO;
namespace TCPServer
{
public partial class Server : Form
{
public const int Port = 51388;
public TcpListener lister;
public TcpClient client;
public IPAddress ipAddress;
public NetworkStream networkStream;
public BinaryReader reader;
public BinaryWriter writer;
public IPEndPoint ipPoint;
public delegate void ShowStatusMessage( string msg );
public ShowStatusMessage showStatusMessage;
public delegate void ShowGetOrSendMessage( string msg );
public ShowGetOrSendMessage showGetOrSendMessage;
//构造器
public Server( )
{
InitializeComponent( );
//状态栏信息和公共消息框信息
showStatusMessage = new ShowStatusMessage( ShowStatusCallBack );
showGetOrSendMessage = new ShowGetOrSendMessage( ShowGetOrSendCallBack );
//本机IP
IPAddress address = IPAddress.Loopback;
//端点
ipPoint = new IPEndPoint( address, Port );
//创建Socket监听
lister = new TcpListener( ipPoint );
//显示本机IP和端口
IPAddressBox.ReadOnly = true;
PortBox.ReadOnly = true;
IPAddressBox.Text = address.ToString( );
PortBox.Text = Port.ToString();
}
//状态栏显示目前的连接状态和数据发送、接收的状态
public void ShowStatusCallBack( string msg )
{
toolStripStatusLabel.Text = msg;
}
//设置公共消息框的数据
public void ShowGetOrSendCallBack(string msg)
{
ShowMessageBox.Text +=$"\r\n来自{client.Client.RemoteEndPoint}的消息:";
ShowMessageBox.Text += "\r\n" + msg;
}
//开启监听
private void TcpListenStart_Click( object sender, EventArgs e )
{
lister.Start( );
//开启新线程
Thread thread = new Thread( Request );
thread.Start( );
}
//接收请求
private void Request( )
{
statusStrip.Invoke( showStatusMessage, "正在监听……" );
Thread.Sleep( 1000 );
try
{
statusStrip.Invoke( showStatusMessage, "等待连接……" );
client = lister.AcceptTcpClient( ); //阻塞线程,接收队列中的客户端请求
if (client!=null)
{
statusStrip.Invoke( showStatusMessage, "连接已经建立……" );
networkStream = client.GetStream( );
reader = new BinaryReader( networkStream );
writer = new BinaryWriter( networkStream );
}
}
catch
{
statusStrip.Invoke( showStatusMessage, "连接失败……" );
}
}
//接收消息
private void GetMessage_Click( object sender, EventArgs e )
{
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, "连接已经断开……" );
//重新开启新线程来接收请求
Thread thread = new Thread( Request );
thread.Start( );
}
}
//发送消息
private void SenMessage_Click( object sender, EventArgs e )
{
string senMsg = SendMessageBox.Text;
if(senMsg == string.Empty)
{
MessageBox.Show("发送的消息不能为空" );
return;
}
statusStrip.Invoke( showStatusMessage, "正在发送消息……" );
Thread proxyThread = new Thread( ( ) =>
{
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( );
//重新开启新线程来接收请求
Thread thread = new Thread( Request );
thread.Start( );
}
} );
proxyThread.Start( );
}
//关闭监听
private void CloseTcpListen_Click( object sender, EventArgs e )
{
if (client != null) client.Close( );
if (reader != null) reader.Close( );
if (writer != null) writer.Close( );
lister.Stop( );
statusStrip.Invoke( showStatusMessage, "监听已经关闭……" );
}
//断开连接
private void NoConnect_Click( object sender, EventArgs e )
{
if (client != null) client.Close( );
if (reader != null) reader.Close( );
if (writer != null) writer.Close( );
statusStrip.Invoke( showStatusMessage, "连接已断开……" );
}
//清空消息
private void ClearMessage_Click( object sender, EventArgs e )
{
ShowMessageBox.Clear( );
}
//点击关闭窗口按钮时,关闭TCP侦听,否则它会一直开启
private void Server_FormClosing( object sender, FormClosingEventArgs e )
{
lister.Stop( );
}
}
}
客户端源码
using System.Net;
using System.Net.Sockets;
using System.IO;
namespace TCPClient
{
public partial class Client : Form
{
public TcpClient server;
public IPAddress ipAddress;
public NetworkStream networkStream;
public BinaryReader reader;
public BinaryWriter writer;
public IPEndPoint ipPoint;
public delegate void ShowStatusMessage( string msg );
public ShowStatusMessage showStatusMessage;
public delegate void ShowGetOrSendMessage( string msg );
public ShowGetOrSendMessage showGetOrSendMessage;
//构造器
public Client( )
{
InitializeComponent( );
//状态栏信息和公共消息框信息
showStatusMessage = new ShowStatusMessage( ShowStatusCallBack );
showGetOrSendMessage = new ShowGetOrSendMessage( ShowGetOrSendCallBack );
}
//状态栏显示目前的连接状态和数据发送、接收的状态
public void ShowStatusCallBack( string msg )
{
toolStripStatusLabel.Text = msg;
}
//设置公共消息框的数据
public void ShowGetOrSendCallBack( string msg )
{
ShowMessageBox.Text += $"\r\n来自{server.Client.RemoteEndPoint}的消息:";
ShowMessageBox.Text += "\r\n" + msg;
}
//连接服务器
private void ConnectToServer_Click( object sender, EventArgs e )
{
if (ServerIP.Text == string.Empty || Port.Text == string.Empty)
{
MessageBox.Show( "IP和端口都必须提供" );
return;
}
string IPData = ServerIP.Text;
int PortData = Convert.ToInt32( Port.Text );
//开启新线程发起连接请求
Thread thread = new Thread(()=>
{
statusStrip.Invoke( showStatusMessage, "正在连接服务器……" );
try
{
server = new TcpClient( );//服务端开启了TCP监听,正等待着接收客户端的连接请求,此处根据服务端的端点调用连接方法,就可以建立连接
server.Connect( IPData, PortData );
Thread.Sleep( 1000 ); //模拟发送延时
if (server != null)
{
statusStrip.Invoke( showStatusMessage, "连接服务器成功……" );
networkStream = server.GetStream( ); //接收到ASK数据包后会得到一个NetworkStream,利用它可以读取远程数据或向远程发送数据
reader = new BinaryReader( networkStream );
writer = new BinaryWriter( networkStream );
}
}
catch
{
statusStrip.Invoke( showStatusMessage, "连接服务器失败……" );
}
} );
thread.Start( );
}
//接收消息
private void GetMessage_Click( object sender, EventArgs e )
{
//开启新线程来接收远程消息
Thread proxyThread = new Thread( ( ) =>
{
statusStrip.Invoke( showStatusMessage, "消息接收中……" );
try
{
ShowMessageBox.Invoke( showGetOrSendMessage, reader.ReadString( ) );
statusStrip.Invoke( showStatusMessage, "消息接收成功……" );
}
catch
{
//如果出现异常则关闭现有连接,清除所有资源
statusStrip.Invoke( showStatusMessage, "对方没有发送消息或接收消息失败……" );
if (server != null) server.Close( );
if (reader != null) reader.Close( );
if (writer != null) writer.Close( );
statusStrip.Invoke( showStatusMessage, "连接已经断开,请重新点击‘连接服务器‘按钮……" );
}
} );
proxyThread.Start( );
}
//发送消息
private void SenMessage_Click( object sender, EventArgs e )
{
if (SendMessageBox.Text == string.Empty)
{
MessageBox.Show( "发送消息不能为空" );
}
string sendMsg = SendMessageBox.Text;
//开启新线程来发送消息
Thread proxyThread = new Thread( ( ) =>
{
try
{
statusStrip.Invoke( showStatusMessage, "消息发送中……" );
writer.Write( sendMsg );
writer.Flush( );
Thread.Sleep( 2000 );
ShowMessageBox.Invoke( showGetOrSendMessage, sendMsg );
statusStrip.Invoke( showStatusMessage, "消息发送成功……" );
}
catch
{
//如果出现异常则关闭现有连接,清除所有资源
statusStrip.Invoke( showStatusMessage, "消息发送失败……" );
if (server != null) server.Close( );
if (reader != null) reader.Close( );
if (writer != null) writer.Close( );
statusStrip.Invoke( showStatusMessage, "连接已经断开,请重新点击‘连接服务器‘按钮……" );
}
} );
proxyThread.Start( );
}
//断开连接
private void NoConnect_Click( object sender, EventArgs e )
{
if (server != null) server.Close( );
if (reader != null) reader.Close( );
if (writer != null) writer.Close( );
statusStrip.Invoke( showStatusMessage, "连接已断开……" );
}
//清空消息
private void ClearMessage_Click( object sender, EventArgs e )
{
ShowMessageBox.Clear( );
}
//关闭窗口
private void CloseWin_Click( object sender, EventArgs e )
{
this.Close( );
}
}
}
参考资料
msdn:基于TCP协议的简单通信程序