TCP/IP 协议通信

传输控制协议(TCP):定义了两台计算机之间进行可靠的传输而交换的数据和确认信息的格式,以及计算机为了确保数据的正确到达而采取的措施。协议规定了 TCP软件怎样识别给定计算机上的多个目的进程如何对分组重复这类差错进行恢复。协议还规定了两台计算机如何初始化一个 TCP 数据流传输以及如何结束这一传输。TCP 最大的特点就是提供的是面向连接、可靠的字节流服务。
 
用户数据报协议(UDP):是一个简单的面向数据报的传输层协议。提供的是非面向连接的、不可靠的数据流传输。UDP 不提供可靠性,也不提供报文到达确认、排序以及流量控制等功能。它只是把应用程序传给 IP 层的数据报发送出去,但是并不能保证它们能到达目的地。因此报文可能会丢失、重复以及乱序等。但由于 UDP 在传输数据报前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快。
 
TCP 和 UDP 协议 是 TCP/IP 协议 的核心 。TCP 传输 协议: TCP 协议 是一 TCP(Transmission Control Protocol)和 UDP(User Datagram Protocol)协议属于传输层协议。其中 TCP 提供 IP 环境下的数据可靠传输,它提供的服务包括数据流传送、可靠性、有效流控、全双工操作和多路复用。通过面向连接、端到端和可靠的数据包发送。通俗说,它是事先为所发送的数据开辟出连接好的通道,然后再进行数据发送;而 UDP则不为 IP 提供可靠性、流控或差错恢复功能。一般来说,TCP 对应的是可靠性要求高的应用,而 UDP 对应的则是可靠性要求低、传输经济的应用。在 TCP/IP 网络体系结构中,TCP(传输控制协议,Transport Control Protocol)、UDP(用户数据报协议,User Datagram Protocol)是传输层最重要的两种协议,为上层用户提供级别的通信可靠性。
 
属性方法:
 
 
服务端.xaml
<Window x:Class="Tcp.StudyServer.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Tcp.StudyServer"
        mc:Ignorable="d"
        Title="服务器端(使用Socket)" Height="450" Width="800">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*"></ColumnDefinition>
            <ColumnDefinition Width="4*"></ColumnDefinition>
        </Grid.ColumnDefinitions>

        <!--左边-->
        <Grid Background="Azure">
            <Grid.RowDefinitions>
                <RowDefinition Height="auto"></RowDefinition>
                <RowDefinition Height="auto"></RowDefinition>
                <RowDefinition Height="auto"></RowDefinition>

            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition></ColumnDefinition>
                <ColumnDefinition></ColumnDefinition>
            </Grid.ColumnDefinitions>
            <TextBlock  VerticalAlignment="Center" HorizontalAlignment="Right" Margin="0,0,10,5">本机主机地址</TextBlock>
            <TextBox x:Name="txtIP" Grid.Column="1" Width="150" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="0,0,10,5">127.0.0.1</TextBox>
            <TextBlock  Grid.Row="1" VerticalAlignment="Center" HorizontalAlignment="Right"  Margin="0,0,10,5">本机主机端口</TextBlock>
            <TextBox x:Name="txtPort" Grid.Row="1" Grid.Column="1" Width="100" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="0,0,10,5">8888</TextBox>

            <Button  x:Name="BtnOpen"  Grid.Row="5"  Grid.ColumnSpan="2" VerticalAlignment="Center" HorizontalAlignment="Center" 
           Margin="0,0,10,5"  Click="BtnOpen_Click">打开</Button>

            <Button  x:Name="BtnClose"  Grid.Row="5"  Grid.ColumnSpan="2" VerticalAlignment="Center" HorizontalAlignment="Center" 
Margin="0,0,10,5" Visibility="Collapsed" Click="BtnClose_Click">关闭</Button>
        </Grid>

        <!--右边-->
        <Grid Grid.Column="1">
            <Grid.RowDefinitions>
                <RowDefinition Height="auto"></RowDefinition>
                <RowDefinition Height="auto"></RowDefinition>
            </Grid.RowDefinitions>

            <RichTextBox Height="300" x:Name="txtShowContent"  VerticalScrollBarVisibility="Visible"></RichTextBox>

            <Grid Grid.Row="1">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="2*"></ColumnDefinition>
                    <ColumnDefinition Width="1*"></ColumnDefinition>
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition></RowDefinition>
                    <RowDefinition></RowDefinition>
                </Grid.RowDefinitions>
                <StackPanel Grid.ColumnSpan="2" Orientation="Horizontal" Margin="0,5,0,5">
                    <TextBlock Margin="0,0,5,0">选择客户端</TextBlock>
                    <ComboBox x:Name="cbbClient" SelectedIndex="0" ItemsSource="{Binding SocketList}" Width="200">
                        <ComboBox.ItemTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding RemoteEndPoint}"></TextBlock>
                            </DataTemplate>
                        </ComboBox.ItemTemplate>
                    </ComboBox>
                </StackPanel>
                <TextBox Height="70" x:Name="txtSendContent" Grid.Row="1" Margin="0,5,0,0"></TextBox>
                <Button Grid.Column="1" Grid.Row="1"  Margin="5,5,0,0" x:Name="BtnSend" Click="BtnSend_Click">发送</Button>
            </Grid>
        </Grid>
    </Grid>
</Window>
View Code

服务端.xaml.cs

using System.Collections.ObjectModel;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using static System.Net.Mime.MediaTypeNames;

namespace Tcp.StudyServer;

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    Socket server;//服务器socket

    #region  已连接的客户端集合
    private ObservableCollection<Socket> _SocketList = new ObservableCollection<Socket>();

    public ObservableCollection<Socket> SocketList
    {
        get { return _SocketList; }
        set { _SocketList = value; }
    }
    #endregion

    public MainWindow()
    {
        InitializeComponent();

        DataContext = this;//上下文
    }

    /// <summary>
    /// 打开(开启监听)
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void BtnOpen_Click(object sender, RoutedEventArgs e)
    {
        try
        {
            //SocketType.Stream 可靠 双向 基于connect
            server = new Socket(SocketType.Stream, ProtocolType.Tcp);
            string ip = txtIP.Text;
            int post = Convert.ToInt32(txtPort.Text);
            server.Bind(new IPEndPoint(IPAddress.Parse(ip), post));
            server.Listen();

            AppendTxt($"服务器打开成功", Colors.Green);

            BtnOpen.Visibility = Visibility.Collapsed;
            BtnClose.Visibility = Visibility.Visible;

            //不断接收新的客户端连接并绑定到下拉  接收客户端消息
            Task.Run(async () =>
            {
                while (true)
                {
                    if (server != null)
                    {
                        Socket client = await server.AcceptAsync();

                        Dispatcher.Invoke(() =>
                        {
                            SocketList.Add(client);//添加进来
                            AppendTxt($"{client.RemoteEndPoint}已连接", Colors.Orange);
                        });

                        //接收客户端消息
                        ReceiveMsg(client);
                    }
                    await Task.Delay(1000);
                }
            });


            //检测客户端连接,如果客户端断开则移除
            Task.Run(async () =>
            {
                while (true)
                {
                    Dispatcher.Invoke(() =>
                    {
                        for (int i = 0; i < SocketList.Count; i++)
                        {
                            Socket client = SocketList[i];

                            //服务器端主动关闭
                            if (!client.Connected)
                            {
                                SocketList.Remove(client);
                                i--;

                                continue;
                            }

                            //客户端断开  服务器马上知道
                            if (client.Poll(1000, SelectMode.SelectRead) && client.Available == 0)
                            {
                                SocketList.Remove(client);
                                i--;

                                AppendTxt($"客户端{client.RemoteEndPoint}已断开", Colors.OrangeRed);
                            }
                        }
                    });

                    await Task.Delay(1000);
                }
            });
        }
        catch (Exception)
        {
            AppendTxt($"服务器打开异常", Colors.Red);

            BtnOpen.Visibility = Visibility.Visible;
            BtnClose.Visibility = Visibility.Collapsed;
        }
    }

    /// <summary>
    /// 关闭
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void BtnClose_Click(object sender, RoutedEventArgs e)
    {
        if (server != null)
        {
            //暗含需求 断开所有客户端连接
            foreach (Socket client in SocketList)
            {
                if (client.Connected)
                {
                    client.Close();
                }
            }

            server.Close();//问题 ?????
           
            AppendTxt($"服务器已关闭", Colors.Red);

            BtnOpen.Visibility = Visibility.Visible;
            BtnClose.Visibility = Visibility.Collapsed;
        }
    }

    /// <summary>
    /// 发送消息
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void BtnSend_Click(object sender, RoutedEventArgs e)
    {
        Socket selectedClient = cbbClient.SelectedItem as Socket;
        if (server != null && selectedClient != null)
        {
            if (selectedClient.Connected)
            {
                string msg = txtSendContent.Text;
                byte[] sendBytes = Encoding.UTF8.GetBytes(msg);
                int sendResult = selectedClient.Send(sendBytes);//返回值 发送成功的字节数
                if (sendResult > 0)
                {
                    AppendTxt($"发送消息:{msg}", Colors.Black);
                }
            }
        }
    }
    FlowDocument doc = new FlowDocument();

    /// <summary>
    /// 不停接收指定客户端的消息
    /// </summary>
    /// <param name="client"></param>
    private void ReceiveMsg(Socket client)
    {
       Task.Run(async () =>
        {
            while (true)
            {
                _ = Dispatcher.Invoke(async () =>
                {
                    byte[] recBytes = new byte[1024];//接收的结果
                    int recBytesCount = await client.ReceiveAsync(recBytes);//接收字节数量

                    if (recBytesCount > 0)//有数据
                    {
                        string recStr = Encoding.UTF8.GetString(recBytes);
                        AppendTxt($"来自{client.RemoteEndPoint}的消息:{recStr}", Colors.Blue);
                    }
                });
                await Task.Delay(1000);
            }
        });
    }

    /// <summary>
    /// 追加信息到richtextbox结尾
    /// </summary>
    /// <param name="txt"></param>
    /// <param name="txtColor">文字颜色</param>
    private void AppendTxt(string txt, Color txtColor)
    {
        var p = new Paragraph(); // Paragraph 类似于 html 的 P 标签
        var r = new Run($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} {txt}"); // Run 是一个 Inline 的标签
        p.Inlines.Add(r);
        p.Foreground = new SolidColorBrush(txtColor);//设置字体颜色
        doc.Blocks.Add(p);
        txtShowContent.Document = doc;
        txtShowContent.ScrollToEnd();
    }
}

 

 
客户端.xaml
<Window x:Class="Tcp.StudyClient.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Tcp.StudyClient"
        mc:Ignorable="d"
        Title="TCP客户端(Socket)" Height="450" Width="800">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*"></ColumnDefinition>
            <ColumnDefinition Width="4*"></ColumnDefinition>
        </Grid.ColumnDefinitions>

        <!--左边-->
        <Grid Background="Azure">
            <Grid.RowDefinitions>
                <RowDefinition Height="auto"></RowDefinition>
                <RowDefinition Height="auto"></RowDefinition>
                <RowDefinition Height="auto"></RowDefinition>

            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition></ColumnDefinition>
                <ColumnDefinition></ColumnDefinition>
            </Grid.ColumnDefinitions>
            <TextBlock  VerticalAlignment="Center" HorizontalAlignment="Right" Margin="0,0,10,5">远程主机地址</TextBlock>
            <TextBox x:Name="txtIP" Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="0,0,10,5">127.0.0.1</TextBox>
            <TextBlock  Grid.Row="1" VerticalAlignment="Center" HorizontalAlignment="Right"  Margin="0,0,10,5">远程主机端口</TextBlock>
            <TextBox x:Name="txtPort" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="0,0,10,5">8888</TextBox>

            <Button  x:Name="BtnConn"  Grid.Row="2"  Grid.ColumnSpan="2" VerticalAlignment="Center" HorizontalAlignment="Center" 
           Margin="0,0,10,5" Click="BtnConn_Click">连接</Button>

            <Button  x:Name="BtnDisConn"  Grid.Row="5"  Grid.ColumnSpan="2" VerticalAlignment="Center" HorizontalAlignment="Center" 
Margin="0,0,10,5" Visibility="Collapsed" Click="BtnDisConn_Click" >断开</Button>
        </Grid>

        <!--右边-->
        <Grid Grid.Column="1">
            <Grid.RowDefinitions>
                <RowDefinition Height="auto"></RowDefinition>
                <RowDefinition Height="auto"></RowDefinition>
            </Grid.RowDefinitions>

            <RichTextBox Height="300" x:Name="txtShowContent" Margin="0,5,0,5" VerticalScrollBarVisibility="Visible"></RichTextBox>

            <Grid Grid.Row="1">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="2*"></ColumnDefinition>
                    <ColumnDefinition Width="1*"></ColumnDefinition>
                </Grid.ColumnDefinitions>


                <TextBox Height="80" x:Name="txtSendContent" Margin="0,0,5,0"></TextBox>
                <Button Grid.Column="1" x:Name="BtnSend" Click="BtnSend_Click">发送</Button>
            </Grid>
        </Grid>
    </Grid>
</Window>
View Code

客户端.xaml.cs

using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using static System.Net.Mime.MediaTypeNames;

namespace Tcp.StudyClient;

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    Socket client;//客户端
    public MainWindow()
    {
        InitializeComponent();
    }

    bool IsCheckServerClose = false;//是否检测服务器已关闭
    /// <summary>
    /// 连接服务器
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void BtnConn_Click(object sender, RoutedEventArgs e)
    {
        try
        {
            client = new Socket(SocketType.Stream, ProtocolType.Tcp);

            int port = new Random().Next(50000, 60000);//随机生成5万-6万的随机数当做客户端端口号
            client.Bind(new IPEndPoint(IPAddress.Parse("127.0.0.1"), port));//绑定客户端自己的ip 和地址
            client.Connect(txtIP.Text, Convert.ToInt32(txtPort.Text));//连接服务器
            AppendTxt($"连接服务器成功,我的端口号{port}", Colors.Green);

            BtnConn.Visibility = Visibility.Collapsed;
            BtnDisConn.Visibility = Visibility.Visible;

            IsCheckServerClose = false;//刚连接成功,没有检测

            //不停检测连接是否断开

            Task.Run(async () =>
            {
                while (true)
                {
                    _ = Dispatcher.Invoke(async () =>
                    {
                        #region 接收信息
                        if (client != null && client.Connected)
                        {
                            byte[] recBytes = new byte[1024];//接收的字节数组
                            int recLen = await client.ReceiveAsync(recBytes);
                            if (recLen > 0)
                            {
                                string recStr = Encoding.UTF8.GetString(recBytes);
                                AppendTxt($"接收信息:{recStr}", Colors.Blue);
                            }
                        }
                        #endregion

                        #region 判断断开
                        //0、客户端自己断开/服务器断开的
                        if (client != null && !client.Connected && !IsCheckServerClose)
                        {
                            IsCheckServerClose = true;//已经检测到了
                            AppendTxt($"连接断了", Colors.Red);
                            BtnConn.Visibility = Visibility.Visible;
                            BtnDisConn.Visibility = Visibility.Collapsed;
                            return;
                        }

                        //1、服务器断开
                        if (client != null && client.Poll(1000, SelectMode.SelectRead) && client.Available == 0 && !IsCheckServerClose)
                        {
                            IsCheckServerClose = true;//已经检测到了
                            AppendTxt($"服务器已关闭", Colors.Red);
                            BtnConn.Visibility = Visibility.Visible;
                            BtnDisConn.Visibility = Visibility.Collapsed;
                        }
                        #endregion

                    });

                    await Task.Delay(1000);
                }
            });
        }
        catch (Exception)
        {
            AppendTxt($"连接服务器异常", Colors.Red);

            BtnConn.Visibility = Visibility.Visible;
            BtnDisConn.Visibility = Visibility.Collapsed;
        }
    }

    /// <summary>
    /// 断开
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void BtnDisConn_Click(object sender, RoutedEventArgs e)
    {
        if (client != null && client.Connected)
        {
            client.Disconnect(false);

            AppendTxt($"已断开服务器", Colors.Red);

            BtnConn.Visibility = Visibility.Visible;
            BtnDisConn.Visibility = Visibility.Collapsed;
        }
    }

    /// <summary>
    /// 给服务器端发送信息
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void BtnSend_Click(object sender, RoutedEventArgs e)
    {
        if (client != null && client.Connected)
        {
            byte[] sendBytes = Encoding.UTF8.GetBytes(txtSendContent.Text);
            int len = client.Send(sendBytes);
            if (len > 0)
            {
                AppendTxt($"发送信息:{txtSendContent.Text}", Colors.Black);
            }
        }
    }

    FlowDocument doc = new FlowDocument();
    /// <summary>
    /// 追加信息到richtextbox结尾
    /// </summary>
    /// <param name="txt"></param>
    /// <param name="txtColor">文字颜色</param>
    private void AppendTxt(string txt, Color txtColor)
    {
        var p = new Paragraph(); // Paragraph 类似于 html 的 P 标签
        var r = new Run($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} {txt}"); // Run 是一个 Inline 的标签
        p.Inlines.Add(r);
        p.Foreground = new SolidColorBrush(txtColor);//设置字体颜色
        doc.Blocks.Add(p);
        txtShowContent.Document = doc;
        txtShowContent.ScrollToEnd();
    }
}

 

posted @ 2026-03-23 20:44  野码  阅读(8)  评论(0)    收藏  举报