IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> C# TCP/IP网络数据传输及实现 -> 正文阅读

[网络协议]C# TCP/IP网络数据传输及实现

C# TCP/IP网络数据传输及实现



一、概念简述

1、什么是OSI 和TCP/IP

??国际标准化组织(ISO)在1985年研究的网络互连模型,定义了OSI(Open System Interconnect),即开放式系统互联。 也叫OSI参考模型,OSI定义了网络互连的七层框架(物理层、数据链路层、网络层、传输层、会话层、表示层、应用层)。这个框架定义了理论的标准模型,也是通讯学习基础的必修内容。

??实际网络中TCP/IP协议中使用“TCP/IP五层模型”,与其各层有各自的协议, 来保证能互联网中正常通讯。下面用表格简述了模型的对应关系。

在这里插入图片描述

2、什么是套接字Socket

??百度百科的解释:所谓套接字(Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。从所处的地位来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议栈进行交互的接口。
??简单理解就是 Socket就是应用层与TCP/IP协议族通信的中间软件抽象层。将传输层封装后便与应用层更方便的调用。

在这里插入图片描述

3、TCP 和 UDP

??TCP(Transmission Control Protocol) —— 传输控制协议
??UDP(User Datagram Protocol) —— 用户数据报协议
??从协议层我们可以看出TCP和UDP协议已经处在传输层,也就是说该层次已经可以完成数据的传输。他们的两者的主要差异如下面表格:

属性UDPTCP
连接方式非连接面向连接
可靠性不可靠传输可靠传输
连接对象一对一、一对多、多对一、多对多只能一对一
流量拥塞
数据类型面向报文面向字节流
适用场景适用于实时数据传输(视频、电话等)适用于可靠准确的数据传输(文件、邮件)

??TCP 是面向连接的传输协议,建立连接时要经过三次握手,断开连接时要经过四次握手,中间传输数据时也要回复 ACK 包确认,多种机制保证了数据能够正确到达,不会丢失或出错。
??UDP 是非连接的传输协议,没有建立连接和断开连接的过程,它只是简单地把数据丢到网络中,也不需要 ACK 包确认。
??与 UDP 相比,TCP 有较为复杂的通讯交互和流控制也只能是1对1的方式,但是这保证了数据传输的正确可靠性。而TCP 的速度是无法超越 UDP,两组协议各有千秋,应用层按照自己的需求选择使用他们,发挥他俩各自的优势。

4、IP 、MAC、PORT

(1) IP地址

??IP地址是 Internet Protocol Address 的缩写,译为“网际协议地址”。
I??P协议是为计算机网络相互连接进行通信而设计的协议。它处在TCP/IP 协议栈模型的网络层。在因特网中,它是能使连接到网上的所有计算机网络实现相互通信的一套规则,规定了计算机在因特网上进行通信时应当遵守的规则。任何厂家生产的计算机系统,只要遵守IP协议就可以与因特网互连互通。
??一台计算机可以拥有一个独立的 IP 地址,一个局域网也可以拥有一个独立的 IP 地址(如同只有一台计算机)。对于目前广泛使用 IPv4 地址,它的资源是非常有限的。在因特网上进行通信时,必须要知道对方的 IP 地址。他就好像是对方的名牌号码,如同收件地址一样。实际上数据包中已经附带了 IP 地址,把数据包发送给路由器以后,路由器会根据 IP 地址找到对方的地里位置,完成一次数据的传递。
??IP地址类别分公有地址和私有地址。公有地址(Public address)由Inter NIC(Internet Network Information Center因特网信息中心)负责。这些IP地址分配给注册并向Inter NIC提出申请的组织机构。通过它直接访问因特网。私有地址(Private address)属于非注册地址,专门为组织机构内部使用。
以下列出留用的内部私有地址:
??A类 10.0.0.0–10.255.255.255
??B类 172.16.0.0–172.31.255.255
??C类 192.168.0.0–192.168.255.255
??另外,还有另一类特殊的D类地址也叫组播地址,范围从224.0.0.0到239.255.255.255。
??IP地址通过子网掩码(NetMask)来区分自己所在的网段。

(2) MAC地址

??MAC 地址是 Media Access Control Address 的缩写,直译为“媒体访问控制地址”,也称为局域网地址(LAN Address),以太网地址(Ethernet Address)或物理地址(Physical Address)。它是一个用来确认网络设备位置的位址。 通讯模型中网络层负责IP地址, 据链路层则负责MAC位址 。
MAC地址用于在网络中唯一标示一个网卡,一台设备如果存在多个网卡,则每个网卡都会有一个唯一的MAC地址。网络数据包中除了会附带对方的 IP 地址,ARP协议会将数据包达到局域网以后,路由器/交换机会根据数据包中的 MAC 地址找到对应的计算机,然后把数据包转交给它,这样就完成了数据的传递。IPv4地址为32位二进制数,MAC地址则是为48位二进制数表示,一般用16进制显示。

(3) Port端口号

?? IP 地址和 MAC 地址,虽然可以找到目标计算机,但仍然不能进行通信。因为一台计算机可以同时提供多种网络服务,例如 Web 服务(网站)、FTP 服务(文件传输服务)、SMTP 服务(邮箱服务)等。为了区分不同的网络程序,计算机会为每个网络程序分配一个独一无二的端口号(Port Number),例如,Web 服务的端口号是 80,FTP 服务的端口号是 21,SMTP 服务的端口号是 25。
??端口(Port)是一个虚拟的、逻辑上的概念。可以将端口理解为一道门或者一个通道,数据通过的这个门或者通道的不同的编号,就是端口号,程序是可以自己定义的。个IP地址的端口通过16bit进行编号,最多可以有65536个端口 。端口号只有整数,范围是从0 到65535。


二、 UDP上位机的实现

1、 准备

?? 当我们使用网线连接网络有较好的通信网络时,用C# 来实现UDP通信来做上位机的应用是一个不错的选择,它既能支持多路通讯和也有速度快的优势。
?? 首先,要安装好C#的编译编辑平台,我这里是使用的VS2019。安装过程不再描述可以自己搜索; 其次,建立好自己的C# .NETframework工程;然后,就可以开始修改添加自己的代码了。
?? 码代码前,强调!强调!强调! 务必要理解前面一章描述的基本概念,以及按照工程需求建立起自己想要的逻辑实现框架流程,完全不懂可以在网上找一些实现流程和基础代码来解读。我的实现是直接加载Form框架load中,这也正符合我实现的需求。将需要的TCPIP的socket实现或其它功能块写在独立文件的类中,这里注意哟,要包含到同一个空间命名中。

2、 Form主流程实现

上代码 !先描述主流程


		//网络Socket类实例化
        TCPSocket _TcpSocket = new TCPSocket();
        //文件操作FileOperation类实例化
        FileOperation _FileOperation = new FileOperation();
        
        private void Form_Moniter_Load(object sender, EventArgs e)
        {
             if (CheckLocalIpAddressExist() == false)//检查本地IP
            {
                return;
            }                    
                      
            //初始化TCPSocket
            this.InitTcpSocket();
            //设置打开UDP线程
            if (_TcpSocket.UdpOpen() == false)
            {
                // 关闭程序
                return;
            }
            //启动显示时间定时器
            timer_system.Start();
            //初始化控件
            this.InitControlTool();

        }
     	/// <summary>
        /// 初始化设置网络连接类型 
        /// </summary>
        private void InitTcpSocket()
        {
            string GetFileConnectType = _FileOperation._iniConnectType;//获取连接类型
            int GetFileConnectType_int = 0; //连接类型

            if (GetFileConnectType == "") //连接类型为空
            {
                goto TypeErrEND;
            }
            if (!Utils.IsNumeric(GetFileConnectType))//不为数字
            {
                goto TypeErrEND;
            }

            GetFileConnectType_int = Convert.ToInt32(GetFileConnectType);
            
            switch (GetFileConnectType_int )
            {
#if false		//为后续添加代码预留
                case 0:                
                    _TcpSocket._tcpType = TCPType.TCPServer;
                    return;

                case 1:                
                    _TcpSocket._tcpType = TCPType.TCPClient;
                    return;
#endif
                case 2:            
                    _TcpSocket._tcpType = TCPType.UDP;
                    _TcpSocket._localIP = GetIniFileUdpLocalIp();
                    _TcpSocket._localPort = GetIniFileUdpLocalPort();
                    _TcpSocket._targetIP = GetIniFileUdpTargetIp();
                    _TcpSocket._targetPort = GetIniFileUdpTargetPort();
                    return;
                default:
                    break;
            }

      TypeErrEND:
            //读取内容有问题或者编号不存在//有异常都跑到这里来
            _TcpSocket._tcpType = TCPType.UDP;//数字都为默认2
            _FileOperation._iniConnectType = ((int)TCPType.UDP).ToString();//UDP写入文件
            //读取本地IP和端口以及目标IP地址和端口          
            _TcpSocket._localIP = GetIniFileUdpLocalIp();
            _TcpSocket._localPort = GetIniFileUdpLocalPort();
            _TcpSocket._targetIP = GetIniFileUdpTargetIp();
            _TcpSocket._targetPort = GetIniFileUdpTargetPort();

        }
  

?? 上面的Form_Moniter_Load()窗体加载函数,已经包含了开启UDP开启的整个流程 。 虽然简单,还是给个流程图吧:

Created with Rapha?l 2.3.0 Form_Moniter_Load()开始 PC是否有正确的IP地址? 初始化TCPSocket 确设置打开UDP线程? 初始化定时器 初始化控件状态 Form_Moniter_Load()结束 yes no yes no

?? 其中InitTcpSocket()则是初始化了我们需要的参数,我是从配置文件读取的,也可以采用自己定义的方法,如果没有正确参数,则写入默认值,因此也不需要返回正确错误的结果。接着Open UDP就可以开始了,正确打开后就完成其它的界面初始化的工作了。流程也很简单。不做过多讲解。

3、 Socket类的实现

?? Socket的实现的代码我们把它分为几部分函数:参数部分、UDP的打开、数据发送、数据接收以及关闭。

(1) 参数部分

	    /// <summary>
	    /// TCP枚举类型
	    /// </summary>
	    public enum TCPType
	    {
	        TCPServer,
	        TCPClient,
	        UDP
	    }
	    
		 //======> 自定义委托事件 捕获数据变化   
        public event EventHandler BufferReceChange;     //Socket接收数据事件
        public event EventHandler ConnStateChange;      //Socket连接状态变化事件
        private void OnBufferReceChange(EventArgs eventArgs)
        {
            this.BufferReceChange?.Invoke(this, eventArgs);
        }
        private void OnConnStateChange(EventArgs eventArgs)
        {
            this.ConnStateChange?.Invoke(this, eventArgs);
        }
        #endregion

        #region    ====> UDP 各类参数
        private bool connState = false;     //连接状态
        public bool _connState
        {
            get { return connState; }
            set
            {
                this.OnConnStateChange(new EventArgs());
                connState = value;
            }
        }
        private string targetIp = "255.255.255.255";  //发送数据目标IP
        public string _targetIP
        {
            get { return targetIp; }
            set { targetIp = value; }
        }
        private int targetPort = 8000;      //发送数据目标端口
        public int _targetPort
        {
            get { return targetPort; }
            set { targetPort = value; }
        }
        private string localIP_Single= Utils.GetIPAddressSingle();    //获取本机电脑一个IP 
        public string _localIP_Single
        {
            get { return localIP_Single; }
        }
        private string  localIP_All = Utils.GetIPAddressAll();      //获取本机电脑所有IP 
        public string  _localIP_ALL
        {
            get { return localIP_All; }
        }
        
        private string currentIP;     //当前接收数据源  IP
        public string _currentIP
        {
            get { return currentIP; }
            set { currentIP = value; }
        }
        private int currentPort;    //当前接收数据源端口 Port
        public int _currentPort
        {
            get { return currentPort; }
            set { currentPort = value; }
        }

        private string localIP;//本地IP
        public string _localIP  
        {
            get { return localIP; }
            set { localIP = value; }
        }

        private int localPort;       //本地端口
        public int _localPort
        {
            get { return localPort; }
            set { localPort = value; }
        }

        private byte[] udpRx; //缓存接收到的数据
        public byte[] _udpReceiveData
        {
            get { return udpRx; }
            set { udpRx = value; }
        }
        //网络通信类型 默认TCPServer
        private TCPType tcpType = TCPType.UDP;
        public TCPType _tcpType
        {
            get { return tcpType; }
            set { tcpType = value; }
        }

        Thread UdpReceiveDataThread = null; //udp clienct接受数据线程
        private UdpClient udp = new UdpClient();//实例化udp服务
        private IPEndPoint localIpep = null;//本地IP结构实例
 

??参数部分首先定义了委托的事件,程序指定了数据不为空就跳转到委托函数中运行。为UDP接收数据传递即时消息给到上层使用,就好像中断消息处理的意思。
??连接状态、TCP类型等等各类参数,通过GET{}和SET{}方法来方便供外部赋值和获取。
??最后,实例化线程和UDP服务。

(2) UDP的数据打开、接收、发送和关闭

        //UPD通信 ======> 打开udp目标主机 、接受数据线程、 关闭UDP
        /// <summary>
        /// 打开UDP通信
        /// </summary>
        /// <returns>true or false</returns>
        public bool UdpOpen()
        {
            try
            {
                UdpCLose();//关闭udp
                localIpep = new IPEndPoint(IPAddress.Parse(localIP), localPort); // 本机IP和监听端口号
                udp = new UdpClient(localIpep);//绑定本地UDP端口,0为自动分配
                              
                //创建UDP接收线程
                UdpReceiveDataThread = new Thread(new ThreadStart(UdpRxThread));//指定线程函数
                UdpReceiveDataThread.IsBackground = true;//可后台运行
                UdpReceiveDataThread.Name = "UDP接收线程";//线程名
                UdpReceiveDataThread.Start();//线程开始
                connState = true;//更新连接状态
                return true;
            }
            catch (Exception ex)            
            {
                
                _connState = false;
                if (MessageBox.Show("连接失败:\n Msg:" + ex.Message + "\nSource:" + ex.Source + "\n[OK]继续尝试,或[Cancel]退出程序?", "异常提示/询问",
                                    MessageBoxButtons.OKCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.OK)
                {
                    UdpOpen();
                }
                return false;
            }
            finally
            {
                this.OnConnStateChange(new EventArgs());
            }
        }

        /// <summary>
        /// UDP发送数据
        /// </summary>
        /// <param name="buffer"></param>
        /// <returns></returns>
        public bool UdpSend(byte[] buffer)
        {
            try
            {
                //向目标IP发数据
                udp.Send(buffer, buffer.Length, _targetIP, _targetPort);
                return true;
            }
            catch (Exception ex)
            {
                _connState = false;
                ExceptionContent = ex.Message + "\n" + ex.ToString();
                this.OnConnStateChange(new EventArgs());
                return false;
            }
        }
        //UDP接收数据线程
        public void UdpRxThread()
        {  
            
            try
            {
                 while (true)
                {

                    byte[] receiveBytes = udp.Receive(ref localIpep);//自动获取接收数据信息//获取数据则跳出

                    //记录当前当前接收数据源数据
                    currentIP = localIpep.Address.ToString();
                    currentPort = localIpep.Port;
                    
                    udpReceiveData = new byte[receiveBytes.Length];//初始化长度
                    Buffer.BlockCopy(receiveBytes, 0, _udpReceiveData, 0, receiveBytes.Length);//拷贝数据

                    this.OnBufferReceChange(new EventArgs());//数据改变事件
                   
                }
            }
            catch (Exception ex)
            {                
                _connState = false;
                ExceptionContent = ex.Message + "\n" + ex.ToString();
                this.OnConnStateChange(new EventArgs());
            }
        }


        /// <summary>
        /// 关闭udp
        /// </summary>
        private void UdpCLose()
        {
            try
            {
                udp.Close();                
            }
            catch (Exception ex)
            {
                ExceptionContent = ex.Message + "\n" + ex.ToString();
                //异常不做处理
                return;
            }
        }

??打开UdpOpen方法是这里最为关键的一环,首先要知道UDP也是有server(服务器)端和client(客户)端,而上位机这是client端,因此他也是首先发起数据的一方,服务器端的操作需要绑定(bind)设备IP和端口,客户端则不需要,因此这里IP地址可以使用自己的IP也可以使用0.0.0.0作为IP地址。在线程开始前,localIpep 指定了自己的IP以及端口。需要注意的是,网线连接状态或者UDP服务是否被占用的问题异常处理,以及及时要更新自己的连接状态。
??发送数据UdpSend()太简单,将udp.Send(buffer, buffer.Length, _targetIP, _targetPort)函数的几个参数代入就能实现将buffer中的数据发送出去。
??接收的线程方法UdpRxThread最关键的就是udp.Receive(ref localIpep),这个函数将监听接收到的数据,否者将会一直等待,有数据时,将其拷贝出来即可,通过委托消息传递给上层显示。
??退出UdpCLose()功能就是关闭Open状态。


三、TCP上位机实现

(待更新)

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2021-12-15 18:40:09  更:2021-12-15 18:41:13 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 11:52:42-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码