基本概念

网络编程

网络编程的目的就是指直接或间接地通过网络协议与其他计算机进行通讯。网络编程中
有两个主要的问题,一个是如何准确的定位网络上一台或多台主机,另一个就是找到主机后
如何可靠高效的进行数据传输。在TCP/IP协议中IP层主要负责网络主机的定位,数据传输的
路由,由IP地址可以唯一地确定Internet上的一台主机。而TCP层则提供面向应用的可靠的
或非可靠的数据传输机制,这是网络编程的主要对象,一般不需要关心IP层是如何处理数据
的。

目前较为流行的网络编程模型是客户机/服务器(C/S)结构。即通信双方一方作为服务
器等待客户提出请求并予以响应。客户则在需要服务时向服务器提出申请。服务器一般作为
守护进程始终运行,监听网络端口,一旦有客户请求,就会启动一个服务进程来响应该客
户,同时自己继续监听服务端口,使后来的客户也能及时得到服务。

在Internet上IP地址和主机名是一一对应的,通过域名解析可以由主机名得到机器的IP,
由于机器名更接近自然语言,容易记忆,所以使用比IP地址广泛,但是对机器而言只有IP地
址才是有效的标识符。

通常一台主机上总是有很多个进程需要网络资源进行网络通讯。网络通讯的对象准确的讲
不是主机,而应该是主机中运行的进程。这时候光有主机名或IP地址来标识这么多个进程显然
是不够的。端口号就是为了在一台主机上提供更多的网络资源而采取得一种手段,也是TCP层
提供的一种机制。只有通过主机名或IP地址和端口号的组合才能唯一的确定网络通讯中的对象:
进程。

套接字

所谓socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄。应用程
序通常通过"套接字"向网络发出请求或者应答网络请求。

套接字可以根据通信性质分类,这种性质对于用户是可见的。应用程序一般仅在同一类的
套接字间进行通信。不过只要底层的通信协议允许,不同类型的套接字间也照样可以通信。套
接字有两种不同的类型:流套接字和数据报套接字。

下面的解释比较抽象,不看也罢。
套接字是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。可以将套接字看作
不同主机间的进程进行双向通信的端点,它构成了单个主机内及整个网络间的编程界面。套接
字存在于通信域中,通信域是为了处理一般的线程通过套接字通信而引进的一种抽象概念。套
接字通常和同一个域中的套接字交换数据(数据交换也可能穿越域的界限,但这时一定要执行
某种解释程序)。各种进程使用这个相同的域互相之间用Internet协议簇来进行通信。

套接字工作原理

要通过互联网进行通信,你至少需要一对套接字,其中一个运行于客户机端,我们称之为
ClientSocket,另一个运行于服务器端,我们称之为ServerSocket。
根据连接启动的方式以及本地套接字要连接的目标,套接字之间的连接过程可以分为三个
步骤:服务器监听,客户端请求,连接确认。

所谓服务器监听,是服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的
状态,实时监控网络状态。
所谓客户端请求,是指由客户端的套接字提出连接请求,要连接的目标是服务器端的套接
字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的
地址和端口号,然后就向服务器端套接字提出连接请求。
所谓连接确认,是指当服务器端套接字监听到或者说接收到客户端套接字的连接请求,它
就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦
客户端确认了此描述,连接就建立好了。而服务器端套接字继续处于监听状态,继续接收其他
客户端套接字的连接请求。

面向连接的TCP

TCP通讯步骤

  • 服务器端
    • 申请一个socket
    • 绑定到一个IP地址和端口上
    • 开启监听,等待接收连接
  • 客户端
    • 申请一个socket
    • 连接服务器,且指定IP与端口号

注意: 服务端接收到连接请求后,产生一个新的socket(端口大于1024),与客户端建立连接并进行通讯,原来的监听socket继续监听

image-20220221211235933

服务端代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
using System.Threading;
​
namespace SocketStudy
{
    public class Server
    {
        //服务器监听套接字
        private Socket socketServer;
        //是否在监听(目的是方便退出循环)
        private bool isListenContecion = true;
        public Server()
        {
            //定义网络终结点(封装IP与端口)
            IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 1000);
            //实例化建立服务端Socket
            socketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            //服务器绑定地址
            socketServer.Bind(endPoint);
            //开始监听,Listen的参数为监听队列的最大长度
            socketServer.Listen(10);
            Console.WriteLine("服务端已经启动");
            try
            {
                while (isListenContecion)
                {
                    //Accept方法,接收客户端的连接,会阻断当前线程的运行
                    Socket socketMsgServer = socketServer.Accept();
                    Console.WriteLine("有一个客户端连接");
                    //开启后台线程,进行客户端的会话
                    Thread clientMsgThread = new Thread(ClientMsg);
                    //定义为后台线程
                    clientMsgThread.IsBackground = true;
                    clientMsgThread.Start(socketMsgServer);
                }
            }
            catch (Exception)
            {
            }
        }
        /// <summary>
        /// 服务端与客户端的通讯后台线程
        /// </summary>
        /// <param name="sockMsg">表示服务器端的会话Socket</param>
        private void ClientMsg(object sockMsg)
        {
            Socket socketMsg = sockMsg as Socket;
            while (true)
            {
                //准备一个数据缓存
                byte[] msgArray = new byte[1024 * 1024];
                //接收客户端发来的数据,返回数据真实的长度
                int trueClientMsgLength = socketMsg.Receive(msgArray);
                //byte数组转string
                string strMsg = Encoding.UTF8.GetString(msgArray, 0, trueClientMsgLength);
                //显示客户端发阿来的数据
                Console.WriteLine("客户端发来数据:" + strMsg);
            }
        }
​
​
        static void Main(string[] args)
        {
            Server obj = new Server();
        }
    }
}

客户端代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
using System.Threading;
​
namespace SocketStudy_Clinet
{
    class Client
    {
        //客户端通讯套接字
        private Socket socketClient;
        //连接到的服务器IP与端口信息
        private IPEndPoint serverEndPoint;
        private Client()
        {
            //服务端的通讯地址与端口号
            serverEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 1000);
            //实例化建立客户端的socket
            socketClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            try
            {
                socketClient.Connect(serverEndPoint);
                Console.WriteLine("连接服务端成功");
            }
            catch (Exception)
            {
            }
        }
​
        /// <summary>
        /// 连接服务器
        /// </summary>
        public void SendMsg()
        {
            while (true)
            {
                //输入信息
                string strMsg = Console.ReadLine();
                //退出
                if (strMsg == "exit")
                {
                    break;
                }
                //字节转换
                Byte[] byteArray = Encoding.UTF8.GetBytes(strMsg);
                //发送数据
                socketClient.Send(byteArray);
                Console.WriteLine("我:"+strMsg);
            }
            //关闭连接
            socketClient.Shutdown(SocketShutdown.Both);
            //清理连接的资源
            socketClient.Close();
        }
​
        static void Main(string[] args)
        {
            Client client = new Client();
            client.SendMsg();
        }
    }
}
​

效果演示

image-20220221230738211

无连接的UDP

UDP通讯步骤

  • 建立一个socket
  • 绑定本机的IP和端口作为服务器端
  • UDP不需要Listen() Accept(),直接使用SendTo()/ReceiveFrom()来执行操作

image-20220221231939067

客户端(发送端)代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
using System.Threading;
​
namespace UDP01
{
    class UDP01
    {
        //服务器监听套接字
        private Socket socketServer;
        //是否在监听(目的是方便退出循环)
        private bool isListenContecion = true;
        public UDP01()
        {
            //定义网络终结点(封装IP与端口)
            IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 1000);
            //实例化建立服务端Socket
            socketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            //服务器绑定地址
            socketServer.Bind(endPoint);
​
            Console.WriteLine("服务端已经启动");
            EndPoint ep = (EndPoint)endPoint;
            while (true)
            {
                //准备一个数据缓存
                byte[] msgArray = new byte[1024 * 1024];
                //接收客户端发来的数据,返回数据真实的长度
                int trueClientMsgLength = socketServer.ReceiveFrom(msgArray, ref ep);
                //byte数组转string
                string strMsg = Encoding.UTF8.GetString(msgArray, 0, trueClientMsgLength);
                //显示客户端发来的数据
                Console.WriteLine("客户端发来数据:" + strMsg);
            }
​
        }
​
        static void Main(string[] args)
        {
            UDP01 uDP01 = new UDP01();
        }
    }
}
​

服务端(接收端)代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
using System.Threading;
​
namespace UDP02
{
    class UDP02
    {
        //客户端通讯套接字
        private Socket socketClient;
        //连接到的服务器IP与端口信息
        private IPEndPoint serverEndPoint;
        private UDP02()
        {
            //服务端的通讯地址与端口号
            serverEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 1000);
            //实例化建立客户端的socket
            socketClient = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
​
            EndPoint ep = (EndPoint)serverEndPoint;
            while (true)
            {
                //输入信息
                string strMsg = Console.ReadLine();
                //退出
                if (strMsg == "exit")
                {
                    break;
                }
                //字节转换
                Byte[] byteArray = Encoding.UTF8.GetBytes(strMsg);
                //发送数据
                socketClient.SendTo(byteArray, ep);
                Console.WriteLine("我:" + strMsg);
            }
            //关闭连接
            socketClient.Shutdown(SocketShutdown.Both);
            //清理连接的资源
            socketClient.Close();
        }
​
        static void Main(string[] args)
        {
            UDP02 uDP02 = new UDP02();
        }
    }
}

效果演示

image-20220222222414213

参考资料

https://www.cnblogs.com/weizhixiang/p/6298523.html

https://www.cnblogs.com/fundebug/p/differences-of-tcp-and-udp.html

https://zhuanlan.zhihu.com/p/24860273

https://www.bilibili.com/video/BV1eg411G7pW