网络通信

1. Socket通信在Windows.Networking.Sockets命名空间下提供了支持Socket通信相关的类型。有趣的是,这些类型的命名中并没有出现如TCP、UDP等关键词,官方似乎有意避开这些传统的命名方式,而是按照各通信协议的功能来命名。可参考如下:DatagramSocket ——用UDP协议的Socket网络通信StreamSocket —— 通过流方式接收/发送网络数据,实际上是基于TCP协议的Socket通信。在服务器端,可以使用StreamSocketListener来监听
摘要由CSDN通过智能技术生成

1. Socket通信

在Windows.Networking.Sockets命名空间下提供了支持Socket通信相关的类型。有趣的是,这些类型的命名中并没有出现如TCP、UDP等关键词,官方似乎有意避开这些传统的命名方式,而是按照各通信协议的功能来命名。可参考如下:

  • DatagramSocket ——用UDP协议的Socket网络通信
  • StreamSocket —— 通过流方式接收/发送网络数据,实际上是基于TCP协议的Socket通信。在服务器端,可以使用StreamSocketListener来监听连接请求
  • MessageWebSocket与StreamWebSocket —— 使用WebSocket相关技术进行网络通信

(1) 基于UDP协议的通信

DatagramSocket类封装了基于UDP数据报相关的网络通信功能。由于UDP协议是无连接协议,资源消耗较少,处理效率高,经常被用于传输要求不太严格的数据,如聊天信息、网络视频等。
在服务器端,DatagramSocket类的使用步骤如下:

  1. 创建DatagramSocket实例。
  2. 处理MessageReceived事件,当收到新的消息时会引发该事件。
  3. 调用BindEndpointAsync方法绑定本地终结点,包括地址和端口;如果希望在本地任何地址上都监听到数据,可以调用BindServiceNameAsync方法,该方法仅仅绑定本地端口。

而在客户端,直接实例化一个DatagramSocket对象后,就可以直接通过GetOutputStreamAsync方法获取一个输出流对象以向远程主机发送数据(直接将数据写入该流即可),在调用时应指定远程主机的地址(IP地址或主机名)和接收端口。
接下来通过一个示例来演示UDP协议通信的应用方法。本示例既可以作为监听服务器,也可以作为客户端使用,因此可以在同一台机器上进行测试。本示例实现客户端将文本消息发送到服务器,服务器将显示收到的文本消息。
在页面布局中,可以使用Pivot控件来添加两个选项卡,一个用于服务器,另一个用于客户端。

        <Pivot>
            <PivotItem Header="服务器">
                <Grid RowSpacing="10">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="auto"/>
                        <RowDefinition/>
                    </Grid.RowDefinitions>
                    <StackPanel>
                        <TextBlock Text="在客户端输入以下IP地址:" FontSize="20"/>
                        <TextBlock x:Name="tbIp" FontSize="36" IsTextSelectionEnabled="True"/>
                    </StackPanel>
                    <ListView x:Name="lvMsg" Grid.Row="1">
                        <ListView.Header>
                            <TextBlock Foreground="LightPink" Text="收到的消息列表" FontSize="20"/>
                        </ListView.Header>
                        <ListView.ItemTemplate>
                            <DataTemplate>
                                <StackPanel Margin="3,12">
                                    <TextBlock FontSize="20" Foreground="Yellow">
                                        来自
                                        <Run Text="{Binding Path=FromIP}"/>
                                        的消息: 
                                    </TextBlock>
                                    <TextBlock TextWrapping="Wrap" FontSize="24" Text="{Binding Path=Message}"/>
                                </StackPanel>
                            </DataTemplate>
                        </ListView.ItemTemplate>
                    </ListView>
                </Grid>
            </PivotItem>
            <PivotItem Header="客户端">
                <StackPanel Spacing="10">
                    <TextBox x:Name="txtServer" Header="服务器IP: "/>
                    <TextBox x:Name="txtMessage" Header="消息内容: " TextWrapping="Wrap" Height="160"/>
                    <Button HorizontalAlignment="Center" Content="发送" Tapped="Button_Tapped"/>
                </StackPanel>
            </PivotItem>
        </Pivot>

为了在测试应用程序时能够轻松得知服务器的IP地址,可以获取本地网络的显示名称,并显示在界面上。在页面重写的OnNavigatedTo方法中加入以下代码:

        protected async override void OnNavigatedTo(NavigationEventArgs e)
        {
   
            base.OnNavigatedTo(e);
            //显示服务器的IP
            var hosts = NetworkInformation.GetHostNames();
            HostName svName = hosts.FirstOrDefault(h => h.Type == HostNameType.Ipv4 &&
            h.IPInformation.NetworkAdapter.IanaInterfaceType == 6);
            if (svName != null)
            {
   
                tbIp.Text = svName.DisplayName; 
            }
            //...
        }

GetHostNames方法会返回多个主机名,随后通过FirstOrDefault扩展方法将设备中的以太网卡连接筛选出来。h.Type == HostNameType.Ipv4表示只选出v4版本的IP地址;NetworkAdapter.IanaInterfaceType属性类型是一个整数值,此处数值6表示以太网卡,如果希望使用本地无线网卡进行通信,可以使用数值71进行筛选。
服务器所使用的监听端口号将通过一个常量值来固定:

        /// <summary>
        /// 用于接收数据的端口
        /// </summary>
        const string SERVICE_PORT = "795";

在类级别中声明必要的变量,主要是两个用于通信的Socket对象,一个用于服务器,另一个用于客户端。

        /// <summary>
        /// 用于服务器的Socket
        /// </summary>
        DatagramSocket svrSocket = null;
        /// <summary>
        /// 用于客户端的Socket
        /// </summary>

在重写的OnNavigatedTo方法中添加服务器监听实现:

        protected async override void OnNavigatedTo(NavigationEventArgs e)
        {
   
            base.OnNavigatedTo(e);
            //显示服务器的IP
            var hosts = NetworkInformation.GetHostNames();
            HostName svName = hosts.FirstOrDefault(h => h.Type == HostNameType.Ipv4 &&
            h.IPInformation.NetworkAdapter.IanaInterfaceType == 6);
            if (svName != null)
            {
   
                tbIp.Text = svName.DisplayName; 
            }
            svrSocket = new DatagramSocket();
            // 添加接收消息事件处理
            svrSocket.MessageReceived += SerSocket_Received;
            // 绑定到本地端口
            await svrSocket.BindServiceNameAsync(SERVICE_PORT);
            clientSocket = new DatagramSocket();
        }

处理DatagramSocket对象的MessageReceived事件,显示收到的消息。

        async void SerSocket_Received(DatagramSocket sender,DatagramSocketMessageReceivedEventArgs args)
        {
   
            // 获取相关的DataReader对象
            DataReader reader = args.GetDataReader();
            // 读取消息内容
            uint len = reader.UnconsumedBufferLength;
            string msg = reader.ReadString(len);
            // 远程主机
            string remoteHost = args.RemoteAddress.DisplayName;
            reader.Dispose();
            // 显示接收到的消息
            await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal,
                ()=>
                {
   
                    lvMsg.Items.Add(new {
    FromIP = remoteHost, Message = msg});
                });
        }

下面代码实现客户端向服务器发送消息:

        private async void Button_Tapped(object sender, TappedRoutedEventArgs e)
        {
   
            // 获取输出流
            using (var outStream = await clientSocket.GetOutputStreamAsync(new HostName(txtServer.Text),SERVICE_PORT))
            {
   
                using (var writer = new DataWriter(outStream))
                {
   
                    // 写入流
                    writer.WriteString(txtMessage.Text);
                    //提交写入的内容
                    await writer.StoreAsync();
                }
            }
        }

在获取到输出流后,可以使用DataWriter类来向流对象写入数据,使用该类所封装的WriteString方法可以直接写入字符串内容。默认情况下是使用UTF-8编码格式,一般不需要修改。
要注意的是,在写完数据后,要调用StoreAsync方法,被写入的数据才会提交到输出流中。
示例运行效果:UDP
使用UDP的另一篇博客,可参考:添加链接描述

(2) 通过TCP协议传输数据

与UDP不同,TCP协议是基于连接的,即在通信之前,客户端需要连接服务器。这意味着TCP对数据的次序与完整性要求更为严格,确保数据准确无误地达到目标终端。例如,文件传输就应当使用TCP协议来完成,因为少一个字节或者多一个字节都有可能损坏文件。
StreamSocket类封装了基于TCP协议的Socket通信功能,在客户端,通常遵循以下顺序来使用StreamSocket类:

  1. 实例化StreamSocket对象。
  2. 调用ConnectAsync方法连接服务器。
  3. 通过OutputStream属性返回的输出流就可以发送数据;而InputStream属性则返回输入流对象,用于接收数据。
  4. 当不再使用Socket时,调用Dispose方法释放其占用的相关资源。

在服务器中,则需要一个StreamSocketListener实例,绑定本地主机或某个端口,以监听客户端的连接请求。当有客户端发出连接请求时,会引发ConnectionReceived事件,从事件参数中可以获取一个与客户端进行通信的StreamSocket实例。
下面示例将演示如何使用基于TCP协议的Socket通信。
本例将服务器与客户端合并在一个应用程序中,应用程序既可以充当服务器角色,也可以用作客户端。在客户端选择一个图像文件并输入一些文本,应用程序会将图像与文本一起发送到服务器。随即服务器会显示收到的数据。
页面布局XAML如下:

        <Pivot>
            <PivotItem Header="服务器">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="auto"/>
                        <RowDefinition/>
                    </Grid.RowDefinitions>
                    <StackPanel Spacing="10">
                        <TextBlock Text="服务器IP地址:" Height="30"/>
                        <TextBlock x:Name="tbSvIP" FontSize="24" IsTextSelectionEnabled="True" Height="30"/>
                    </StackPanel>
                    <ListBox x:Name="lbItems" Grid.Row="1">
                        <ListBox.ItemTemplate>
                            <DataTemplate>
                                <Grid>
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="auto"/>
                                        <ColumnDefinition/>
                                    </Grid.ColumnDefinitions>
                                    <Image Width="50" Height="50" Stretch="UniformToFill" Source="{Binding Path=Image}"/>
                                    <TextBlock Grid.Column="1" TextWrapping="Wrap" FontSize="18" Text="{Binding Path=Text}"/>
                                </Grid>
                            </DataTemplate>
                        </ListBox.ItemTemplate>
                    </ListBox>
                </Grid>
            </PivotItem>
            <PivotItem Header="客户端">
                <StackPanel Spacing="10">
                    <TextBox x:Name="txtServerIp" Header="服务器地址:"/>
                    <Button Content="选择图像...
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值