• TCP、UDP、Socket 通信(原)


          说明:本随笔主要演示自己给自己发送消息例子,分别使用了TCP协议、UDP协议以及socket套接字通信。使用socket套接字了模拟TCP、UDP通信实现原理。其中有些源码都来自《C#高级编程 第7版》,并附加了自己的理解,有的也进行了一些简单的拓展。

          第一次原创随笔,很多地方可能考虑不周或理解有误,希望大家留言指正,与大家共同进步。也希望大家不喜勿喷,给点鼓励还是比较好的~~ 闲话少说,进入主题!

    一、TCP 类

      TCP是基于连接的一种通信模式,我们需要创建客户端与服务器端来进行通信。在客户端读取文件,并将文件内容发送到服务器端进行显示。

          客户端:

     1 using System;
     2 using System.Windows.Forms;
     3 using System.Net;
     4 using System.Net.Sockets;
     5 using System.IO;
     6 
     7 namespace TcpSender
     8 {
     9     public partial class Form1 : Form
    10     {
    11         public Form1()
    12         {
    13             InitializeComponent();
    14         }
    15 
    16         private void btnSend_Click(object sender, EventArgs e)
    17         {
    18             TcpClient tcpClient = new TcpClient("127.0.0.1",2112);  // 配置主机号及端口 127.0.0.1 2112
    19             NetworkStream nws = tcpClient.GetStream(); // 获取连接流
    20             string path = txbFile.Text; // 配置文件路径
    21             FileStream fs = new FileStream(path, FileMode.Open);
    22             int data = fs.ReadByte();
    23 
    24             while (data != -1)
    25             {
    26                 nws.WriteByte((byte)data);
    27                 data = fs.ReadByte();
    28             }
    29             fs.Close();
    30             nws.Close();
    31             tcpClient.Close();
    32         }
    33     }
    34 }

      服务器:

     1 using System;
     2 using System.Net;
     3 using System.Net.Sockets;
     4 using System.IO;
     5 using System.Threading;
     6 using System.Windows.Forms;
     7 
     8 namespace TcpReceiver
     9 {
    10     public partial class Form1 : Form
    11     {
    12         public Form1()
    13         {
    14             InitializeComponent();
    15             Thread thread = new Thread(new ThreadStart(Listen)); // 防止主线程在监听到请求之前处于卡死状态
    16             thread.Start();
    17         }
    18 
    19         // 监听功能
    20         public void Listen()
    21         {
    22             IPAddress localAddr = IPAddress.Parse("127.0.0.1");
    23             Int32 port = 2112;
    24             TcpListener tcpListener = new TcpListener(localAddr, port); // 也可以仅绑定端口,因为发送者与接收者均是本地
    25             tcpListener.Start();
    26 
    27             while (true) // 持续监听
    28             {
    29                 TcpClient tcpClient = tcpListener.AcceptTcpClient(); // 监听到请求前处于阻塞状态
    30                 NetworkStream nws = tcpClient.GetStream(); // 获取监听流
    31 
    32                 StreamReader sr = new StreamReader(nws, System.Text.Encoding.UTF8);
    33                 string content = sr.ReadToEnd();
    34 
    35                 // ★★★ very important ★★★
    36                 Invoke(new Action<string>(UpdateDisplay), new object[] { content }); // 该线程拥有用户界面的句柄,因此无需从后台线程直接访问对话框
    37                 tcpClient.Close();
    38             }
    39             // tcpListener.Stop();
    40         }
    41  
    42         // 将接收到内容显示到控件中
    43         public void UpdateDisplay(string text)
    44         {
    45             txbContent.Clear();
    46             txbContent.Text = text;
    47         }
    48     }
    49 }

      运行效果图:

    客户端

     

    服务器

    二、UDP

      UDP是一个无连接的协议,因此可以在一个程序中实现收发消息。

      情形1:使用同一个 UdpClient 实例实现收发消息

     1 using System;
     2 using System.Net;
     3 using System.Net.Sockets;
     4 using System.Text;
     5 using System.Threading;
     6 using System.IO;
     7 
     8 namespace UDPSender
     9 {
    10     class Program
    11     {
    12         static void Main(string[] args)
    13         {
    14             #region 模拟自己发送自己 使用UDPClient(通过)
    15             UdpClient client = new UdpClient(11000); //相当于给socket实例指定端口,参见socket模拟UDP实现原理(后续代码)       
    16             IPEndPoint iep = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 11000); // 服务器网址及端口
    17 
    18             Console.WriteLine("Type in what you want to send :");
    19             string sendMsg = Console.ReadLine();
    20             byte[] sendBytes = Encoding.ASCII.GetBytes(sendMsg);
    21             client.Send(sendBytes, sendBytes.Length, iep); //将消息推送到 127.0.0.1:11000
    22 
    23 
    24             byte[] rcvBytes = client.Receive(ref iep);   // 接收127.0.0.1:11000 端口消息,非连接的消息需要缓存存放
    25             Console.WriteLine("Received the following message:");
    26             string rcvMessage = Encoding.ASCII.GetString(rcvBytes, 0, rcvBytes.Length);
    27             Console.WriteLine(rcvMessage);
    28             Console.ReadKey();
    29             #endregion
    30           }
    31     }
    32 }

      情形2:使用一个 UdpClient 实例发送消息, 使用另一个 UdpClient 实例接收消息

     1 using System;
     2 using System.Net;
     3 using System.Net.Sockets;
     4 using System.Text;
     5 using System.Threading;
     6 using System.IO;
     7 
     8 namespace UDPSender
     9 {
    10     class Program
    11     {
    12         static void Main(string[] args)
    13         {
    14             #region 模拟自己发送自己 使用UDPClient(通过)
    15             UdpClient client = new UdpClient(); // 没有为socket指定端口,则在client.Send()时,系统默认为socket分配一个端口,参见Socket模拟UDP(后续代码)
    16             // IPEndPoint remote = new IPEndPoint(IPAddress.Any, 0);
    17             UdpClient server = new UdpClient(11000); // 在客户端发送之前确保服务器已经进行端口绑定,为客户端发送的消息创建载体
    18 
    19             IPEndPoint iep = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 11000); // 192.168.106.231 服务器网址及端口
    20 
    21             Console.WriteLine("Type in what you want to send :");
    22             string sendMsg = Console.ReadLine();
    23             byte[] sendBytes = Encoding.ASCII.GetBytes(sendMsg);
    24             client.Send(sendBytes, sendBytes.Length, iep); // 将消息推送到 127.0.0.1:11000
    25 
    26 
    27             byte[] rcvBytes = server.Receive(ref iep);   // 接收127.0.0.1:11000 端口消息,也可以使用 remote 端点,表示接收所有端口消息
    28             Console.WriteLine("Received the following message:");
    29             string rcvMessage = Encoding.ASCII.GetString(rcvBytes, 0, rcvBytes.Length);
    30             Console.WriteLine(rcvMessage);
    31             Console.ReadKey();
    32             #endregion
    33         }
    34     }
    35 }

      运行效果图:

    三、Socket 通信

      TCP、UDP 底层通信都是通过 socket 套接字实现。

      1、模拟 TCP 通信实现

      客户端:

     1 using System;
     2 using System.Net;
     3 using System.Net.Sockets;
     4 using System.Text;
     5 
     6 namespace SocketSender
     7 {
     8     // 使用socket类模拟TCP发送原理
     9     class Program
    10     {
    11         static void Main(string[] args)
    12         {
    13             byte[] ReceiveBytes = new byte[1024];
    14             IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 2112); // 创建远程端点
    15 
    16             Console.WriteLine("Starting: Creating Socket Object ");
    17             while (true)
    18             {
    19                 // 创建socket对象
    20                 Socket SocketSender = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    21                 // 连接远程端点
    22                 SocketSender.Connect(ipEndPoint); // 连接到远程端点,如果服务器端没有响应,则此处会挂起。若超时,则抛出远程服务器拒绝的异常。                    
    23 
    24                 Console.WriteLine("Successfully Connected to {0}",SocketSender.RemoteEndPoint);
    25 
    26                 Console.WriteLine("Input the Message you want to send :");
    27                 string SendingMessage = Console.ReadLine();
    28                 byte[] ForwardMessage = Encoding.ASCII.GetBytes(SendingMessage + "[FINAL]");
    29 
    30                 // 发送消息
    31                 SocketSender.Send(ForwardMessage);
    32 
    33                 // 接收从服务器传回相应消息,因为是基于连接的,因此使用同一个socket实例直接接收即可
    34                 int TotalBytesReceived = SocketSender.Receive(ReceiveBytes);
    35 
    36                 // 打印输出接收到的消息
    37                 Console.WriteLine("Message Provided By Server: {0}", Encoding.ASCII.GetString(ReceiveBytes, 0, TotalBytesReceived));
    38                 Console.WriteLine(Environment.NewLine);
    39                 SocketSender.Shutdown(SocketShutdown.Both); // 关闭该socket的发送和接收功能
    40                 SocketSender.Close();
    41             }
    42             //Console.ReadKey();
    43         }
    44     }
    45 }
    46     

      服务器端:

     1 using System;
     2 using System.Text;
     3 using System.Net;
     4 using System.Net.Sockets;
     5 
     6 namespace SocketReceiver
     7 {
     8     // 使用socket模拟TCP接收原理
     9     class Program
    10     {
    11         static void Main(string[] args)
    12         {
    13             // 本实例是一个基于连接的socket通信,模拟 TCPClient 进行通信
    14             Console.WriteLine("Starting: Creating Socket object");
    15             Socket Listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    16 
    17             Listener.Bind(new IPEndPoint(IPAddress.Any, 2112)); // 创建了基于本机IP地址的,端口号为任意值的,可用于所有网络接口(网线、电话线及其他形式网络接口)的绑定
    18             Listener.Listen(10); // 挂起连接队列的最大长度
    19 
    20             while (true)
    21             {
    22                 Console.WriteLine("Waiting for connetion on port 2112 ");
    23                 Socket socket = Listener.Accept();  // 若无socket,则处于阻塞状态,等待有效的socket实例
    24                 string ReceiveValue = string.Empty;
    25 
    26                 while (true)
    27                 {
    28                     byte[] ReceivedByte = new byte[1024]; // 缓存区
    29                     int NumBytes = socket.Receive(ReceivedByte); // 接收到的是二进制数据,将接收到的数据放入到缓存中。也可以直接读到流中,使用 Stream stream = new networkstream(socket)
    30                     Console.WriteLine("Receiving . . .");
    31                     ReceiveValue += Encoding.ASCII.GetString(ReceivedByte, 0, NumBytes);
    32                     if (ReceiveValue.IndexOf("[FINAL]") > -1)
    33                     {
    34                         break;
    35                     }
    36                 }
    37                 Console.WriteLine("Received Value: {0}", ReceiveValue);
    38                 Console.WriteLine(Environment.NewLine);
    39                 string ReplyValue = "Message successfully Received.";
    40                 byte[] ReplyByte = Encoding.ASCII.GetBytes(ReplyValue);
    41                 socket.Send(ReplyByte); // 发送的是二进制数据
    42 
    43                 // 对于面向连接的协议,建议先调用 Shutdown,然后再调用 Close。 这可以确保在已连接的套接字关闭之前,已发送和接收该套接字上的所有数据。
    44                 socket.Shutdown(SocketShutdown.Both); // 关闭该socket的关闭和接收功能
    45                 socket.Close();
    46             }
    47             // Listener.Close();
    48         }
    49     }
    50 }

      运行效果图:

    客户端

    服务器端

      说明:基于链接的通信,应先启动服务器端程序进行监听,后启动客户端程序。否则客户端在尝试连接时,没有服务器进行相应,尝试一定的时间后,若一直无响应,则抛出远程主机拒绝的异常。

      2、模拟 UDP 通信实现

      不基于连接的通信,只需要一个程序端即可。

     1 using System;
     2 using System.Net;
     3 using System.Net.Sockets;
     4 using System.Text;
     5 using System.Threading;
     6 using System.IO;
     7 
     8 namespace UDPSender
     9 {
    10     class Program
    11     {
    12         static void Main(string[] args)
    13         {
    14             #region 模拟自己发送给自己,使用socket模拟UDP实现原理(通过)
    15 
    16             // Server
    17             Thread server = new Thread(new ThreadStart(ReceiveServer));
    18             server.Start();
    19 
    20             // ★★★确保服务端已建立网络监听★★★
    21             Thread.Sleep(50);
    22 
    23             // Client
    24             IPHostEntry ipHostEntry = Dns.GetHostEntry("127.0.0.1");
    25             IPAddress ipAddress = ipHostEntry.AddressList[0];
    26             IPEndPoint ipEndPoint = new IPEndPoint(ipAddress, 2112);
    27 
    28             while (true)
    29             {
    30                 // UDP 的socket类型不支持stream
    31                 Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
    32                 //socket.Bind(ipEndPoint);
    33                 socket.Connect(ipEndPoint); // 若在此之前不给socket手动绑定一个端口,则程序会在此时默认给socket随机绑定一个端口
    34                 Console.WriteLine("Type in what you want to send:");
    35                 string SendString = Console.ReadLine();
    36                 byte[] SendBytes = Encoding.Default.GetBytes(SendString);
    37                 //Thread.Sleep(3000);
    38                 socket.Send(SendBytes);               
    39                 // 网络接收有延时,因此需要等待服务器进行接收
    40                 socket.Close(100);//等待 timeout 秒以发送所有剩余数据,然后关闭该套接字。
    41                 // 等同于socket.Close(100) 的效果
    42                 //Thread.Sleep(100);
    43                 //socket.Close();
    44 
    45             }
    46             #endregion
    47         }
    48 
    49         public static void ReceiveServer()
    50         {
    51             IPHostEntry ipHostEntry = Dns.GetHostEntry("127.0.0.1");
    52             IPAddress ipAddress = ipHostEntry.AddressList[0];
    53             IPEndPoint ipEndPoint = new IPEndPoint(ipAddress, 2112);
    54 
    55             #region 使用缓存接收数据
    56             // Receive Message
    57             Socket socketListen = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);  // set the protocol
    58             socketListen.Bind(ipEndPoint); // binding to the local point which need to be listened
    59 
    60             try
    61             {
    62                 // UDP 协议不需要监听,直接接收消息即可
    63                 //socketListen.Listen(5); // 最多监听到5个队列的请求
    64                 while (true)
    65                 {
    66                     string result = string.Empty;
    67                     if (socketListen.Available < 1) // 判断是否有数据,若没有数据而直接进行receive,则会使线程阻塞等待数据的到来。return 无:0 有:>0 
    68                     {
    69                         Thread.Sleep(20);
    70                     }
    71                     else
    72                     {
    73                         byte[] data = new byte[1024];
    74                         int NumBytes = socketListen.Receive(data);  // 如果网络监听不到数据,则该语句会使线程处于阻塞状态而等待消息,因此要添加判断语句判断数据是否有效   
    75                         Console.WriteLine("Receiving..");
    76                         if (NumBytes > 0)
    77                         {
    78                             result = Encoding.Default.GetString(data, 0, NumBytes);
    79                             Console.WriteLine("Server Received the follow message:");
    80                             Console.WriteLine(result);
    81                             Console.WriteLine(Environment.NewLine);
    82                             //Thread.Sleep(50000);
    83                         }
    84                     }
    85                 }
    86             }
    87             catch (SocketException e)
    88             {
    89                 string a = e.ErrorCode.ToString();
    90                 Console.WriteLine(e.Message + e.ErrorCode);
    91             }
    92             #endregion
    93         }
    94     }
    95 }

      运行效果图:

      注意:对于非连接的通信,在服务器端socket绑定端口(Bind()方法,而非Receive()方法,注意区分)之前,若客户端发送了消息A,则在服务器端口绑定后收不到该消息A,只能收到在绑定端口后客户端发送来的消息!

  • 相关阅读:
    Ghost Button制作教程及设计趋势分析
    onhashchange事件--司徒正美
    window.location.hash属性介绍
    优质UI的7条准则(一)
    当在浏览器地址栏输入一个网址的时候,究竟发生了什么?
    全球最快的JS模板引擎
    眨眼登录表单
    DIV+CSS规范命名
    es6--export,import
    es6--class
  • 原文地址:https://www.cnblogs.com/gavin-num1/p/4748352.html
Copyright © 2020-2023  润新知