CH09_网络编程

第9章:网络编程


本章目标

  1. 熟悉网络编程相关协议

  2. 了解TCP协议 的通信原理

  3. 了解UDP协议的通信原理

  4. 掌握基于Socket 方式的网络编程

本章内容

相关概念

协议

TCP协议:

TCP是一种面向连接的、可靠的,基于字节流的传输层通信协议。为两台主机提供高可靠性的数据通信服务。它可以将源主机的数据无差错地传输到目标主机。当有数据要发送时,对应用进程送来的数据进行分片,以适合于在网络层中传输;当接收到网络层传来的分组时,它要对收到的分组进行确认,还要对丢失的分组设置超时重发等。为此TCP需要增加额外的许多开销,以便在数据传输过程中进行一些必要的控制,确保数据的可靠传输。因此,TCP传输的效率比较低。

UDP协议:

UDP是一种简单的、面向数据报的无连接的协议,提供的是不一定可靠的传输服务。所谓"无连接"是指在正式通信前不必与对方先建立连接,不管对方状态如何都直接发送过去。这与发手机短信非常相似,只要知道对方的手机号就可以了,不要考虑对方手机处于什么状态。UDP虽然不能保证数据传输的可靠性,但数据传输的效率较高。

IP

IP是Internet Protocol(网际互连协议)的缩写,是TCP/IP体系中的网络层协议。设计IP的目的是提高网络的可扩展性:一是解决互联网问题,实现大规模、异构网络的互联互通;二是分割顶层网络应用和底层网络技术之间的耦合关系,以利于两者的独立发展。根据端到端的设计原则,IP只为主机提供一种无连接、不可靠的、尽力而为的数据包传输服务。

A类 B类 C类 D类 E类
网络标志位 0 10 110 1110 11110
IP地址范围 1.0.0.0~ 127.255.255.255 128.0.0.0~ 191.255.255.255 192.0.0.0~ 223.255.255.255 224.0.0.0~ 239.255.255.255 240.0.0.0~ 247.255.255.255
可用IP地址范围 1.0.0.1~ 126.255.255.254 128.0.0.1~ 191.255.255.254 192.0.0.1~ 223.255.255.254
是否可以分配给主机使用
网络数量(个) 126 (2^7-2) 16384 (2^14) 2097152 (2^21) -- --
每个网络中可容纳主机数(个) 16777214 (2^24-2) 65534 (2^16-2) 254 (2^8-2) -- --
适用范围 大量主机的大型网络 中等规模主机数的网络 小型局域网 留给Internet体系结构委员会(IAB)使用【组播地址】 保留,仅作为搜索、Internet的实验和开发用
端口

"端口"是英文port的意译,可以认为是设备与外界通讯交流的出口。端口可分为虚拟端口和物理端口,其中虚拟端口指计算机内部或交换机路由器内的端口,不可见。例如计算机中的80端口、21端口、23端口等。物理端口又称为接口,是可见端口,计算机背板的RJ45网口,交换机路由器集线器等RJ45端口。电话使用RJ11插口也属于物理端口的范畴。

核心类

DNS类

Dns类是一个静态类,它从 Internet 域名系统 (DNS) 检索关于特定主机的信息。在 IPHostEntry 类的实例中返回来自 DNS 查询的主机信息。 如果指定的主机在 DNS 数据库中有多个入口,则 IPHostEntry 包含多个 IP 地址和别名。

IPAddress类

IPAddress类提供了对IP地址的转换、处理等功能。其Parse方法可将IP地址字符串转换为IPAddress实例。如:

IPAddress ip = IPAddress.Parse("192.168.1.1");

IPEndPoint类

IPEndPoint类包含了应用程序连接到主机上的服务所需的IP地址和端口信息。

IPHostEntry类

IPHostEntry类将一个域名系统 (DNS) 主机名与一组别名和一组匹配的 IP 地址关联。

常用属性有:AddressList属性和HostName属性。

AddressList属性作用:获取或设置与主机关联的IP地址列表,是一个IPAddress类型的数组,包含了指定主机的所有IP地址;HostName属性则包含了服务器的主机名。

在Dns类中,有一个专门获取IPHostEntry对象的方法,通过IPHostEntry对象,可以获取本地或远程主机的相关IP地址。

Socket-TCP方式通信

服务端
c# 复制代码
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Text;

namespace Socket_TCP_Server
{
    /// <summary>
    /// 基于TCP协议的Socket服务端
    /// </summary>
    class Program
    {
        static void Main(string[] args)
        {
            //1.创建服务端Socket
            Socket serverSockekt = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            //2.绑定IP和端口
            IPEndPoint point = new IPEndPoint(IPAddress.Parse("10.168.1.167"), 5555);
            serverSockekt.Bind(point);

            //3.启动监听(设置最大监听数量)
            serverSockekt.Listen(100);
            Console.WriteLine("服务端已启动,正在监听客户端....");

            //4.等待客户端连接
            while (true)
            {
                //客户端
                Socket clientSocket= serverSockekt.Accept();

                //为指定客户端开辟一个线程接收消息
                new Thread(new ParameterizedThreadStart(Receive)).Start(clientSocket);
            }
        }

        /// <summary>
        /// 接收消息
        /// </summary>
        /// <param name="obj"></param>
        static void Receive(object obj)
        {
            Socket socket = (Socket)obj;

            //客户端终结点
            IPEndPoint clientPoint = socket.RemoteEndPoint as IPEndPoint;
            string ip = clientPoint.Address.ToString();

            byte[] b = new byte[1024];


            while (true)
            {
                int length = socket.Receive(b);
                if (length == -1) continue;

                string str = Encoding.UTF8.GetString(b,0,length);

                Console.WriteLine(string.Format("{0}:{1}", ip, str));
            }
        }        
    }
}
客户端
c# 复制代码
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace Socket_TCP_Client
{
    /// <summary>
    /// 基于TCP协议的Socket客户端
    /// </summary>
    class Program
    {
        static void Main(string[] args)
        {
            //1.Socket
            Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            //2.连接到服务器(Ip地址和端口号要和服务器保持一致)
            IPAddress ip = IPAddress.Parse("10.168.1.167");
            IPEndPoint point = new IPEndPoint(ip, 5555);

            try
            {
                socket.Connect(point);
                Console.WriteLine("连接成功,请输入要发送的消息!");

                //3.发送消息
                Send(socket);
            }
            catch (Exception)
            {
                Console.WriteLine("服务端未启动");

            }

            Console.ReadKey();
        }

        static void Send(Socket socket)
        {
            while (true)
            {
                string msg = Console.ReadLine();
                socket.Send(Encoding.UTF8.GetBytes(msg));
            }
        }
    }
}
运行效果

Socket-UDP方式通信

服务端
c# 复制代码
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace Socket_UDP_Server
{
    /// <summary>
    /// 基于UDP协议的服务端
    /// </summary>
    class Program
    {
        static Socket serverSocket = null;

        static void Main(string[] args)
        {
            //创建Socket
            serverSocket = new Socket(AddressFamily.InterNetwork,
                SocketType.Dgram, ProtocolType.Udp);

            //绑定ip和端口号
            serverSocket.Bind(new IPEndPoint(IPAddress.Parse("10.168.1.167"), 5555));
            Console.WriteLine("服务端已启动!");

            //接收数据
            new Thread(Receive).Start();

            Console.ReadKey();
        }

        /// <summary>
        /// 接收消息
        /// </summary>
        static void Receive()
        {
            //远程终结点
            EndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);

            byte[] b = new byte[1024];


            while (true)
            {
                //接受消息
                int length = serverSocket.ReceiveFrom(b, ref remoteEndPoint);

                if (length == -1) continue;

                string ip = ((IPEndPoint)remoteEndPoint).Address.ToString();
                string str = Encoding.UTF8.GetString(b, 0, length);

                Console.WriteLine(string.Format("{0}:{1}",ip , str));
            }
        }
    }
}
客户端
c# 复制代码
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace Socket_UDP_Client
{
    /// <summary>
    /// 基于UDP协议的Socket客户端
    /// </summary>
    class Program
    {
        static void Main(string[] args)
        {
            //1.客户端Socket
            Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

            IPEndPoint targetPoint = new IPEndPoint(IPAddress.Parse("10.168.1.167"), 5555);

            //2.发送数据            
            Console.WriteLine("请输入要发送的数据!");
            while (true)
            {
                string line = Console.ReadLine();
                clientSocket.SendTo(Encoding.UTF8.GetBytes(line), targetPoint);
            }

        }
    }
}
运行效果

Listener-TCP方式通信

服务端
c# 复制代码
using System;
using System.Net;
using System.Net.Sockets;
using System.Collections;
using System.Text;

namespace TcpListener_Server
{
    /// <summary>
    /// 基于TCP协议的监听器 服务端
    /// </summary>
    class Program
    {
        static void Main(string[] args)
        {
            //1.对socket进行封装,tcpListener内本身包含套接字
            TcpListener listener = new TcpListener(IPAddress.Parse("10.168.1.167"),5555);

            //2.开始监听
            listener.Start();
            Console.WriteLine("服务端已启动监听...");

            //3.等待客户端连接
            TcpClient client = listener.AcceptTcpClient();

            //4.获取客户端数据流通道
            NetworkStream stream= client.GetStream();

            //5.接收数据
            byte[] b = new byte[1024];
            while (true)
            {
                
                int length = stream.Read(b, 0, b.Length);
                //string ip = client.Client.RemoteEndPoint.ToString().Split(':')[0];
                string ip = ((IPEndPoint)client.Client.RemoteEndPoint).Address.ToString();
                string msg = Encoding.UTF8.GetString(b, 0, length);

                Console.WriteLine(string.Format("{0}:{1}", ip, msg));
            }

            //6.释放资源
            //tream.Close();
            //client.Close();
            //listener.Stop();

        }
    }
}
客户端
c# 复制代码
using System;
using System.Net.Sockets;
using System.Text;

namespace TcpListener_Client
{
    /// <summary>
    /// 基于TCP协议的监听器 客户端端
    /// </summary>
    class Program
    {
        static void Main(string[] args)
        {
            //1.创建TCP对象的时候,就会与服务器建立联系
            TcpClient client = new TcpClient("10.168.1.167", 5555);

            //2.通过网络流获取数据信息
            NetworkStream stream = client.GetStream();

            Console.WriteLine("与服务端连接成功,请输入要发送的数据...");

            while (true)
            {
                //手动输入需要发送的信息
                string message = Console.ReadLine();
                byte[] data = Encoding.UTF8.GetBytes(message);

                //将信息写入网络流
                stream.Write(data, 0, data.Length);
            }

            //3.释放资源
            //stream.Close();
            //client.Close();

            Console.ReadKey();
        }
    }
}
运行效果

UdpClient方式通信

消息接收方
c# 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace UdpClient_Receive
{
    /// <summary>
    /// 基于UDP协议的UDPClinet 消息接收方
    /// </summary>
    class Program
    {
        static void Main(string[] args)
        {
            //1.本地UDPClient
            UdpClient udpLocal = new UdpClient(new IPEndPoint(IPAddress.Parse("10.168.1.167"), 5555));

            //2.接收数据
            IPEndPoint point = new IPEndPoint(IPAddress.Any, 0);

            byte[] b = null;
            Console.WriteLine("等待接收消息...");
            while (true)
            {
                b = udpLocal.Receive(ref point);//通过point确定数据来自哪个ip和端口号,并返回接收的数据

                string ip = point.Address.ToString();
                string msg = Encoding.UTF8.GetString(b);

                Console.WriteLine(string.Format("{0}:{1}", ip, msg));

            }
            
            Console.ReadKey();
        }
    }
}
消息发送方
c# 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace UdpClient_Send
{
    /// <summary>
    /// 基于UDP协议的UDPClinet 消息发送方
    /// </summary>
    class Program
    {
        static void Main(string[] args)
        {
            //1.本地UdpClient
            UdpClient client = new UdpClient();

            //2.发送消息
            Console.WriteLine("请输入要发送的消息...");
            while (true)
            {
                string message = Console.ReadLine();
                byte[] data = Encoding.UTF8.GetBytes(message);

                client.Send(data, data.Length, new IPEndPoint(IPAddress.Parse("10.168.1.167"), 5555));
            }
            
        }
    }
}
运行效果

综合案例1-局域网聊天室

N对N的方式实现局域网聊天室。

单播

单播地址是IP网络中最常见的。包含单播目标地址的分组发送给特定主机,一个这样的例子是,IP地址为192.168.1.5(源地址)的主机向IP地址为192.168.1.200(目标地址)的服务器请求网页,如下图所示

广播

广播分组的目标IP地址的主机部分全为1,这意味着本地网络(广播域)中的所有主机都将接收并查看该分组。诸如ARP和DHCP等很多网络协议都使用广播。

例如:

C类网络192.168.1.0的默认子网掩码为255.255.255.0(掩码的255个数对应网络的网络地址个数),其广播地址为192.168.1.255,其主机部分为十进制数255或二进制数11111111(全为1);

B类网络172.16.0.0的默认子网掩码为255.255.0.0,其广播地址为172.16.255.255

A类网络10.0.0.0的默认子网掩码为255.0.0.0,其广播地址为10.255.255.255

在以太网帧中,必须包含与广播IP地址对应的广播MAC地址。在以太网中,广播MAC地址长48位,其十六进制表示为FF-FF-FF-FF-FF-FF(全1为广播mac,主机地址为全1即广播ip地址)。图5.9所示的是一个广播IP分组。

多播

多播地址让源设备能够将分组发送给一组设备。属于多播组的设备将被分配一个多播组IP地址,多播地址范围为224.0.0.0~239.255.255.255。由于多播地址表示一组设备(有时被称为主机组),因此只能用作分组的目标地址。源地址总是为单播地址。

远程游戏就是一个使用多播地址的例子,很多玩家通过远程连接玩同一个游戏;另一例子是通过视频会议进行远程教学,其中很多学生连接到同一个教室。还有一个例子是硬盘映像应用程序,这种程序用于同时恢复众多硬盘的内容。

同单播地址和广播地址一样,多播IP地址也需要相应的多播MAC地址在本地网络中实际传送帧。多播MAC地址以十六进制值01-00-5E打头,余下的6个十六进制位是根据IP多播组地址的最后23位转换得到的。一个MAC多播地址是01-00-5E-0F-64-C5,如图5.10所示。每个十六进制位相对于4个二进制位。

代码案例
c# 复制代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Room2
{
    public partial class Form1 : Form
    {
        
        //定义回调函数的委托
        private delegate void AppentContentCallBack(string content);

        //声明委托
        private AppentContentCallBack appentContentCallBack;

        public Form1()
        {
            InitializeComponent();
        }

        /// <summary>
        /// 窗体加载
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Form1_Load(object sender, EventArgs e)
        {
            //实例化委托对象
            appentContentCallBack = new AppentContentCallBack(AppendContent);

            //开启线程,接收消息
            Thread th = new Thread(ReciveMessage);
            th.IsBackground = true;
            th.Start();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            try
            {
                // 广播地址,对于IPv4来说,255代表广播地址
                IPAddress targetAddress=IPAddress.Parse("255.255.255.255");

                //端口
                int targetPort=5555;

                //广播结点
                IPEndPoint groupEP= new IPEndPoint(targetAddress, targetPort);

                // UdpClient实例
                UdpClient sendClient= new UdpClient();

                // 设置广播选项
                sendClient.EnableBroadcast = true;

                //获取要发送的数据   
                string message = this.rtxtMsgSend.Text;
                byte[] data = Encoding.UTF8.GetBytes(message);

                // 发送广播数据
                sendClient.Send(data, data.Length, groupEP);

                this.rtxtMsgSend.Text = null;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        /// <summary>
        /// 接收消息
        /// </summary>
        public void ReciveMessage()
        {
            //1.本地UDPClient
            UdpClient reciveClient = new UdpClient(new IPEndPoint(IPAddress.Any, 5555));//IPAddress.Parse("172.16.220.26")

            //2.接收数据
            IPEndPoint point = new IPEndPoint(IPAddress.Any, 0);

            byte[] b = null;
            
            while (true)
            {
                b = reciveClient.Receive(ref point);//通过point确定数据来自哪个ip和端口号,并返回接收的数据

                string ip = point.Address.ToString();
                int port = point.Port;
                string msg = Encoding.UTF8.GetString(b);

                string content = string.Format("{0}:{1}\n{2}", ip,port, msg);

                //使用指定委托
                this.rtxtMsgList.Invoke(appentContentCallBack, content);

            }
        }

        /// <summary>
        /// 追加内容
        /// </summary>
        /// <param name="content"></param>
        public void AppendContent(string content)
        {
            this.rtxtMsgList.AppendText(content+"\n");
        }
    }
}

综合案例2-文件上传

服务端
c# 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace FileUpLoad_Server
{
    internal class Program
    {
        static async Task Main(string[] args)
        {
            FileTransferServer server = new FileTransferServer(5555);
            await server.StartAsync();

            Console.ReadKey();
        }
    }
}
c# 复制代码
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Net;
using System.Text;
using System.Threading.Tasks;

namespace FileUpLoad_Server
{
    /// <summary>
    /// 文件上传服务类
    /// </summary>
    internal class FileTransferServer
    {
        private TcpListener listener;
        private int port;

        public FileTransferServer(int port)
        {
            this.port = port;
            listener = new TcpListener(IPAddress.Any, port);
        }

        /// <summary>
        /// 启动
        /// </summary>
        /// <returns></returns>
        public async Task StartAsync()
        {
            listener.Start();
            Console.WriteLine($"服务器已启动,IP:{this.GetIP()}  端口: {port}.");
            while (true)
            {
                TcpClient client = await listener.AcceptTcpClientAsync();
                await Task.Run(() => HandleClient(client));
            }
        }

        /// <summary>
        /// 处理客户端
        /// </summary>
        /// <param name="client"></param>
        private void HandleClient(TcpClient client)
        {
            NetworkStream stream = client.GetStream();
            byte[] buffer = new byte[1024];
            int bytesRead;

            IPEndPoint point = client.Client.RemoteEndPoint as IPEndPoint;
            try
            {

                string fileName =  $"{point.Address}-{DateTime.Now.ToString("hhmmss")}.zip";
                Console.WriteLine($"正在接收文件{fileName}...");

                using (FileStream fileStream = new FileStream(fileName, FileMode.Create, FileAccess.Write))
                {
                    while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) != 0)
                    {
                        fileStream.Write(buffer, 0, bytesRead);
                    }
                }
                Console.WriteLine($"文件{fileName}接收完成!");
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            finally
            {
                client.Close();
            }
        }

        /// <summary>
        /// 获取本机IP
        /// </summary>
        /// <returns></returns>
        public string GetIP()
        {
            // 获取本地主机的相关信息
            IPHostEntry host = Dns.GetHostEntry(Dns.GetHostName());

            string ip = "127.0.0.1";

            // 遍历所有IP地址
            foreach (IPAddress ipAddress in host.AddressList)
            {
                // 过滤掉IPv6地址和非本地链接地址
                if (ipAddress.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork && !IPAddress.IsLoopback(ipAddress))
                {
                    ip = ipAddress.ToString();
                }
            }
            return ip;
        }
    }
}
客户端
c# 复制代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace FileUpLoad_Client
{
    public partial class Form1 : Form
    {
        private string host;
        private int port;

        TcpClient server;

        //定义回调函数的委托
        private delegate void UpdateProgressCallBack(int length);

        //声明委托
        private UpdateProgressCallBack updateProgressCallBack;

        int maxLength;

        public Form1()
        {
            InitializeComponent();
        }

        /// <summary>
        /// 连接
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button1_Click(object sender, EventArgs e)
        {
            this.host = this.txtIP.Text.Trim();
            this.port = int.Parse(this.txtPort.Text.Trim());

            try
            {
                server = new TcpClient(host, port);
                this.button1.Text = "OK";
                this.button1.BackColor = Color.Green;
            }
            catch (Exception ex)
            {
                this.button1.Text = "Error";
                this.button1.BackColor = Color.Red;
                MessageBox.Show(ex.Message);
            }
        }

        private void button2_Click(object sender, EventArgs e)
        {
            if (this.server == null)
            {
                MessageBox.Show("请先连接服务器!");
                return;
            }

            //实例化回调委托对象
            updateProgressCallBack = new UpdateProgressCallBack(UpdateProgress);

            FileStream fileStream = new FileStream(this.txtFilePath.Text, FileMode.Open, FileAccess.Read);
            this.progressBar1.Maximum = (int)fileStream.Length;
            this.maxLength = (int)fileStream.Length;

            //启动线程,发送文件
            Thread th = new Thread(SendFile);
            th.IsBackground = true;
            th.Start();
        }

        /// <summary>
        /// 发送文件
        /// </summary>
        public void SendFile()
        {
            
            //文件路径
            string filePath = this.txtFilePath.Text.Trim();

            try
            {
                //网络流
                NetworkStream stream = server.GetStream();

                //文件流
                using (FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
                {
                    byte[] buffer = new byte[1024];
                    int bytesRead;

                    //从文件读取数据
                    while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0)
                    {
                        //将数据写入网络流
                        stream.Write(buffer, 0, bytesRead);

                        //更新进度条
                        this.progressBar1.Invoke(updateProgressCallBack, bytesRead);
                    }
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
            finally
            {
                server.Close();
            }
        }

        /// <summary>
        /// 更新进度条
        /// </summary>
        /// <param name="length"></param>
        public void UpdateProgress(int length)
        {
            this.progressBar1.Value += length;
            if (this.progressBar1.Value == this.maxLength)
            {
                MessageBox.Show("文件上传成功!");
            }
        }

        /// <summary>
        /// 选择文件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void txtFilePath_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            if (this.openFileDialog1.ShowDialog() == DialogResult.OK)
            {
                this.txtFilePath.Text= this.openFileDialog1.FileName;
            }
        }
    }
}

课后作业

相关推荐
坐井观老天4 小时前
在C#中使用资源保存图像和文本和其他数据并在运行时加载
开发语言·c#
pchmi6 小时前
C# OpenCV机器视觉:模板匹配
opencv·c#·机器视觉
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭8 小时前
C#都可以找哪些工作?
开发语言·c#
boligongzhu10 小时前
Dalsa线阵CCD相机使用开发手册
c#
向宇it1 天前
【从零开始入门unity游戏开发之——C#篇23】C#面向对象继承——`as`类型转化和`is`类型检查、向上转型和向下转型、里氏替换原则(LSP)
java·开发语言·unity·c#·游戏引擎·里氏替换原则
sukalot1 天前
windows C#-命名实参和可选实参(下)
windows·c#
小码编匠1 天前
.NET 下 RabbitMQ 队列、死信队列、延时队列及小应用
后端·c#·.net
我不是程序猿儿1 天前
【C#】Debug和Release的区别和使用
开发语言·c#
lzhdim1 天前
2、C#基于.net framework的应用开发实战编程 - 设计(二、二) - 编程手把手系列文章...
开发语言·c#·.net