网络知识

简易的自定义Web服务器

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

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

using System.Net;
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( $"已开启侦听, 本机端点为: { ipPoint }  正在等待远程主机的请求……" );

                //接收……

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

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

                //响应……
                string HttpDataLine = "HTTP/1.1 200 OK "//报文状态行
                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  Content-Length: {HttpBody.Length} ";


                byte[] HttpDataLineByte = Encoding.UTF8.GetBytes( HttpDataLine );
                byte[] HttpBodyByte = Encoding.UTF8.GetBytes( HttpBody );
                byte[] HttpHeaderByte = Encoding.UTF8.GetBytes( HttpHeader );
                byte[] HttpNullLineByte = new byte[] { 1310 };

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

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

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

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

using System.Net;
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( $"已开启侦听, 本机端点为: { ipPoint }  正在等待远程主机的请求……" );
                //接收……
                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( $"请求数据:  {DataStr}" ); //输出接收的数据

                    //响应……
                    string HttpDataLine = "HTTP/1.1 200 OK "; //报文状态行
                    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  Content-Length: {HttpBody.Length} ";//报头

                    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( );              
            }
        }
    }
}
View Code

 

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

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

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

服务端源码

 
using System.Threading;
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 ShowStatusMessagestring msg );
        public ShowStatusMessage showStatusMessage;
        public delegate void ShowGetOrSendMessagestring 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 +=$" 来自{client.Client.RemoteEndPoint}的消息:";
            ShowMessageBox.Text += " " + 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.Threading;
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 ShowStatusMessagestring msg );
        public ShowStatusMessage showStatusMessage;
        public delegate void ShowGetOrSendMessagestring 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 += $" 来自{server.Client.RemoteEndPoint}的消息:";
            ShowMessageBox.Text += " " + 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协议的简单通信程序

 网络知识 - 学习总目录 

 

原文地址:https://www.cnblogs.com/myrocknroll/p/8458609.html