如何使用数据报套接字进行连接 (HTML)

[ 本文适用于编写 Windows 运行时应用的 Windows 8.x 和 Windows Phone 8.x 开发人员。如果你要针对 Windows 10 进行开发,请参阅 最新文档 ]

本主题介绍 如何使用 UDP 通过 DatagramSocket 在 Windows 运行时应用中发送和接收网络数据。

示例的客户端组件将创建一个 UDP 套接字,使用该套接字发送和接收数据,然后关闭套接字。示例的服务器组件将创建一个 UDP 套接字以侦听传入的网络数据包,从客户端接收传入的 UDP 数据包,向客户端发送数据,然后关闭套接字。此示例采用 JavaScript、C# 和 C++ 编程语言提供。

示例的客户端组件演示了以下功能:

  • 使用 DatagramSocket 类为客户端创建一个 UDP 套接字以发送和接收数据。
  • DatagramSocket.MessageReceived 事件添加一个处理程序,用于指示 UDP 数据报是否已在 DatagramSocket 对象上接收。
  • 为 UDP 网络服务器设置远程终结点,在该终结点中,应该使用 DatagramSocket.ConnectAsync 方法之一发送数据包。
  • 使用 Streams.DataWriter 对象将数据发送到服务器,该对象允许程序员在任何流上写入常用类型(例如整数和字符串)。
  • 关闭套接字。

示例的服务器组件演示了以下功能:

注意  使用此示例需要通过环回接口进行网络访问。

 

目标: 使用 DatagramSocket 套接字创建到另一台计算机或设备的网络连接。

先决条件

下面是一些采用 JavaScript 的示例。有关首次尝试创建应用的帮助,请参阅首次创建使用 JavaScript 的 Windows 应用商店应用

为了确保你的 Windows 应用商店应用能够使用网络,你必须在项目 Package.appxmanifest 文件中设置此功能。 有关每个网络功能的定义,请参阅如何配置网络隔离功能

说明

1. 创建新项目

  1. 打开 Microsoft Visual Studio 2013,然后从“文件”菜单中选择“新建项目”****。
  2. 在模板列表中,选择 JavaScript
  3. 在该部分下,选择 Store apps
  4. 在该部分下,选择 Universal AppsWindows appsWindows Phone apps(取决于你面向的平台),然后选择“空白应用程序”。
  5. 将该应用程序命名为 socketsSample,然后单击“确定”****。

2. 设置启用网络访问的功能

如果你的应用需要网络访问,则需要为该应用设置网络功能。使用 DatagramSocket 连接到网络服务的某个应用将需要网络功能集。

如果应用需要能够作为客户端连接到 Internet 上的远程服务,则“Internet (客户端)”功能是必需的。如果应用需要能够作为客户端连接到家庭网络或工作网络上的远程服务,则“专用网络(客户端和服务器)”****功能是必需的。

如果应用需要使用 DatagramSocket 从 Internet 上的远程终结点侦听传入的连接,则“Internet (客户端和服务器)”功能是必需的。如果应用需要使用 DatagramSocket 从家庭网络或工作网络上的远程终结点侦听传入的连接,则“专用网络(客户端和服务器)”功能是必需的。

注意  在 Windows Phone 上,只存在“Internet (客户端和服务器)”这一种网络功能,该功能支持对该应用的所有网络访问。

 

如果此示例用于侦听传入连接的服务器组件与客户端组件在相同的设备上运行,则需要环回访问。在 Visual Studio 2013 中开发和运行的应用将自动注册为已免除环回限制。有关详细信息,请参阅如何启用环回和调试网络隔离

有关网络访问的更多信息,请参阅如何配置网络隔离功能

如果应用要访问位于 Internet 或者家庭或工作网络上的网络服务,则需要按照以下步骤在部署之前为应用设置网络功能。

  1. 使用 Microsoft Visual Studio 打开 package.appxmanifest 文件。

  2. 选择“功能”****选项卡。

  3. 若要构建 Windows 版本的示例,请选择“Internet (客户端)”和“专用网络(客户端和服务器)”****功能。

    若要构建 Windows Phone 版本的示例,请选择“Internet (客户端和服务器)”功能。

  4. 保存并关闭清单文件。

3. 添加 HTML UI

  1. 打开 html 文件夹。打开一个新的 startListener.html 文件,将下列 HTML 添加到 <head> 和 <body> 部分。

    
    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <script src="/js/socketsSample.js"></script>
        <script src="/js/startListener.js"></script>
    </head>
    <body>
        <div data-win-control="SdkSample.ScenarioInput">
            <p>
                DatagramSocket is used to create the 'server' side of a connection. It listens on a 'service name' (often a port number) and 
                each time a datagram is received on the port number it fires MessageReceived event.
            </p>
            <p>
                <label for="serviceNameAccept">Service Name:</label>
                <input id="serviceNameAccept" type="text" />
            </p>
            <p>
                <button id="buttonStartListener">Listen</button>
            </p>
        </div>
        <div data-win-control="SdkSample.ScenarioOutput">
            <p id="statusBox"></p>
            <p id="outputBox"></p>
        </div>
    </body>
    </html>
    
  2. 打开 html 文件夹。打开一个新的 connectToListener.html 文件,将下列 HTML 添加到 <head> 和 <body> 部分。

    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <script src="/js/connectToListener.js"></script>
    </head>
    <body>
        <div data-win-control="SdkSample.ScenarioInput">
            <p>
                Next, you need the 'other side of the connection' -- you need to connect to a listener. The host name
                and service name (often a port number) to connect to are the 'Host name:' and 'Service name:' entries.
                The service name should match what you started to listen to!
            </p>
            <p>
                The connection will automatically use IPv6 as needed. It will also resolve internationalized
                domain names.
            </p>
            <p>
                Due to the network security system, you cannot connect to other applications running on the same
                machine. This means that you can only use 'localhost' to connect to the same application (specifically,
                you can connect to a listener on the same machine running in the same app container)
            </p>
            <p>
                <label for="hostNameConnect">Host Name:</label>
                <input id="hostNameConnect" type="text" disabled="disabled" />
            </p>
            <p>
                <label for="serviceNameConnect">Service Name:</label>
                <input id="serviceNameConnect" type="text" />
            </p>
            <p>
                <button id="buttonOpen">Connect Now</button>
            </p>
        </div>
        <div data-win-control="SdkSample.ScenarioOutput">
            <p id="statusBox"></p>
            <p id="outputBox"></p>
        </div>
    </body>
    </html>
    
  3. 打开 html 文件夹。打开一个新的 sendData.html 文件,将下列 HTML 添加到 <head> 和 <body> 部分。

    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <script src="/js/sendData.js"></script>
    </head>
    <body>
        <div data-win-control="SdkSample.ScenarioInput">
            <p>
                Now you can send data to the "server". Sending data is often done with the DataWriter
                object; it will write to the socket stream.
            </p>
            <p>
                <button id="buttonSend">Send 'hello' now</button>
            </p>
        </div>
        <div data-win-control="SdkSample.ScenarioOutput">
            <p id="statusBox"></p>
            <p id="outputBox"></p>
        </div>
    </body>
    </html>
    
  4. 打开 html 文件夹。打开一个新的 closeSocket.html 文件,将下列 HTML 添加到 <head> 和 <body> 部分。

    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <script src="/js/closeSocket.js"></script>
    </head>
    <body>
        <div data-win-control="SdkSample.ScenarioInput">
            <p>
                Lastly, you can close all sockets.
            </p>
            <p>
                If you don't close your socket, it will be closed for you when the application exits.
            </p>
            <p>
                <button id="buttonClose">Close all sockets</button>
            </p>
        </div>
        <div data-win-control="SdkSample.ScenarioOutput">
            <p id="statusBox"></p>
            <p id="outputBox"></p>
        </div>
    </body>
    </html>
    

4. 定义示例和方案

此步骤中的代码将会定义示例、HTML 文件和示例所使用的方案。此代码还会添加事件侦听器并启动应用。方案选项允许用户启动套接字侦听器、启动客户端连接到侦听器、让客户端发送数据到服务器和关闭套接字。

  1. 打开 js 文件夹。打开 default.js 文件,然后将以下代码添加到该文件中。

        var sampleTitle = "DatagramSocket";
    
        var scenarios = [
            { url: "/html/startListener.html", title: "Start DatagramSocket Listener" },
            { url: "/html/connectToListener.html", title: "Connect to Listener" },
            { url: "/html/sendData.html", title: "Send Data" },
            { url: "/html/closeSocket.html", title: "Close Socket" }
        ];
    
        function activated(eventObject) {
            if (eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch) {
                // Use setPromise to indicate to the system that the splash screen must not be torn down
                // until after processAll and navigate complete asynchronously.
                eventObject.setPromise(WinJS.UI.processAll().then(function () {
                    // Navigate to either the first scenario or to the last running scenario
                    // before suspension or termination.
                    var url = WinJS.Application.sessionState.lastUrl || scenarios[0].url;
                    return WinJS.Navigation.navigate(url);
                }));
            }
        }
    
        WinJS.Navigation.addEventListener("navigated", function (eventObject) {
            var url = eventObject.detail.location;
            var host = document.getElementById("contentHost");
            // Call unload method on current scenario, if there is one
            host.winControl && host.winControl.unload && host.winControl.unload();
            WinJS.Utilities.empty(host);
            eventObject.detail.setPromise(WinJS.UI.Pages.render(url, host, eventObject.detail.state).then(function () {
                WinJS.Application.sessionState.lastUrl = url;
            }));
        });
    
        WinJS.Namespace.define("SdkSample", {
            sampleTitle: sampleTitle,
            scenarios: scenarios
        });
    
        WinJS.Application.addEventListener("activated", activated, false);
        WinJS.Application.start();
    

5. 为套接字和事件函数定义变量

此步骤中的代码将会创建一定数量的变量,包括侦听器套接字、客户端套接字以及各种错误和事件变量。这些变量被创建用于跟踪客户端套接字是处于已联网还是关闭状态。此步骤还定义用于连接和将数据发送到的主机名和服务名(UDP 端口)以及用于接受和接收数据的本地服务名(UDP 端口)。 远程主机名、远程服务名和本地服务名的值都被设置为默认值,该值可在 UI 中进行更改。

  • 打开 js 文件夹。打开一个新的 socketsSample.js 文件,将下列代码添加到此文件。

    var socketsSample = {};
    
    (function () {
        "use strict";
    
        socketsSample.listener = null;
        socketsSample.listenerOutputStream = null;
        socketsSample.listenerPeerAddress = null;
        socketsSample.listenerPeerPort = null;
        socketsSample.clientSocket = null;
        socketsSample.clientDataWriter = null;
        socketsSample.connected = false;
        socketsSample.closing = false;
        socketsSample.bindingToService = false;
    
        socketsSample.serviceNameAccept = "22112";
        socketsSample.hostNameConnect = "localhost";
        socketsSample.serviceNameConnect = "22112";
    
        socketsSample.close = function () {
    
            socketsSample.closing = true;
    
            if (socketsSample.listener) {
                socketsSample.listener.close();
            }
    
            if (socketsSample.clientSocket) {
                socketsSample.clientSocket.close();
            }
    
            socketsSample.listener = null;
            socketsSample.listenerOutputStream = null;
            socketsSample.listenerPeerAddress = null;
            socketsSample.listenerPeerPort = null;
            socketsSample.clientSocket = null;
            socketsSample.clientDataWriter = null;
            socketsSample.connected = false;
        };
    
        socketsSample.displayStatus = function (message) {
            document.getElementById("statusBox").innerHTML = message;
        };
    
        socketsSample.displayOutput = function (message) {
            document.getElementById("outputBox").innerHTML = message;
        };
    
        socketsSample.setValues = function () {
            var serviceNameAcceptInput = document.getElementById("serviceNameAccept");
            var hostNameConnectInput = document.getElementById("hostNameConnect");
            var serviceNameConnectInput = document.getElementById("serviceNameConnect");
    
            if (serviceNameAcceptInput) {
                serviceNameAcceptInput.value = socketsSample.serviceNameAccept;
            }
            if (hostNameConnectInput) {
                hostNameConnectInput.value = socketsSample.hostNameConnect;
            }
            if (serviceNameConnectInput) {
                serviceNameConnectInput.value = socketsSample.serviceNameConnect;
            }
        };
    
        socketsSample.getValues = function (evt) {
            switch (evt.target.id) {
                case "serviceNameAccept":
                    socketsSample.serviceNameAccept = evt.target.value;
                    break;
                case "hostNameConnect":
                    socketsSample.hostNameConnect = evt.target.value;
                    break;
                case "serviceNameConnect":
                    socketsSample.serviceNameConnect = evt.target.value;
                    break;
            }
        };
    })();
    

6. 创建一个侦听器,并开始侦听一个服务名(端口)

本节中的代码将会创建一个侦听器,并开始侦听。还会添加一些函数,用于在用户请求侦听器绑定到 IP 地址和 UDP 端口、接受连接和读取从客户端发送的数据时处理事件。

注意  虽然这一具体示例是自包含型(客户端和服务器在同一应用中),但通常客户端应用和服务器应用是各自独立的。

 

  • 打开 js 文件夹。打开一个新的 startListener.js 文件,将下列代码添加到此文件:

        var page = WinJS.UI.Pages.define("/html/startListener.html", {
            ready: function (element, options) {
                document.getElementById("buttonStartListener").addEventListener("click", startListener, false);
                document.getElementById("serviceNameAccept").addEventListener("change", socketsSample.getValues, false);
                socketsSample.setValues();
            }
        });
    
        function startListener() {
            var serviceName = document.getElementById("serviceNameAccept").value;
            if (serviceName === "") {
                socketsSample.displayStatus("Please provide a service name.");
                return;
            }
    
            if (socketsSample.listener) {
                socketsSample.displayStatus("Already have a listener; call close to close the listener.");
                return;
            }
    
            socketsSample.closing = false;
            socketsSample.bindingToService = true;
            socketsSample.listener = new Windows.Networking.Sockets.DatagramSocket();
            socketsSample.listener.addEventListener("messagereceived", onServerMessageReceived);
            socketsSample.displayStatus("Server: listener creation started.");
            socketsSample.listener.bindServiceNameAsync(serviceName).done(function () {
                socketsSample.displayStatus("Server: listener creation completed.");
                socketsSample.bindingToService = false;
            }, onError);
        }
    
        function onServerMessageReceived(eventArgument) {
            if (socketsSample.listenerOutputStream) {
                echoMessage(socketsSample.listenerOutputStream, eventArgument);
                return;
            }
    
            socketsSample.listener.getOutputStreamAsync(eventArgument.remoteAddress, eventArgument.remotePort).done(function (outputStream) {
                if (!socketsSample.listenerOutputStream) {
                    socketsSample.listenerOutputStream = outputStream;
                    socketsSample.listenerPeerAddress = eventArgument.remoteAddress;
                    socketsSample.listenerPeerPort = eventArgument.remotePort;
                }
    
                echoMessage(socketsSample.listenerOutputStream, eventArgument);
            });
        }
    
        function echoMessage(outputStream, eventArgument) {
            if (socketsSample.listenerPeerAddress !== eventArgument.remoteAddress ||
                socketsSample.listenerPeerPort !== eventArgument.remotePort) {
                socketsSample.displayStatus("Got datagram from " + eventArguments.remoteAddress + ":" + eventArguments.remotePort +
                    ", but already 'connected' to " + socketsSample.listenerPeerAddress + ":" + socketsSample.listenerPeerPort);
                return;
            }
    
            outputStream.writeAsync(eventArgument.getDataReader().detachBuffer()).done(function () {
                // Do nothing - client will print out a message when data is received.
            });
        }
    
        function onError(reason) {
            // Clean up a listener if we failed to bind to a port.
            if (socketsSample.bindingToService) {
                socketsSample.listener = null;
                socketsSample.bindingToService = false;
            }
    
            // When we close a socket, outstanding async operations will be canceled and the
            // error callbacks called.  There's no point in displaying those errors.
            if (!socketsSample.closing) {
                socketsSample.displayStatus(reason);
            }
        }
    

7. 创建套接字并连接到远程端点

此步骤中的代码将会添加一个函数,用于创建套接字并使用 DatagramSocket.ConnectAsync 方法连接到远程终结点(通常是一个服务器)。 将添加一个函数以在客户端收到消息时处理。还将添加一个函数用于处理当客户端尝试连接时出现错误的情况。

  • 打开 js 文件夹。 打开一个新的 connectToListener.js 文件,将下列代码添加到此文件:

        var page = WinJS.UI.Pages.define("/html/connectToListener.html", {
            ready: function (element, options) {
                document.getElementById("buttonOpen").addEventListener("click", openClient, false);
                document.getElementById("hostNameConnect").addEventListener("change", socketsSample.getValues, false);
                document.getElementById("serviceNameConnect").addEventListener("change", socketsSample.getValues, false);
                socketsSample.setValues();
            }
        });
    
        function openClient() {
            var serviceName = document.getElementById("serviceNameConnect").value;
            if (serviceName === "") {
                socketsSample.displayStatus("Please provide a service name.");
                return;
            }
    
            // By default 'hostNameConnect' is disabled and host name validation is not required. When enabling the text
            // box validating the host name is required since it was received from an untrusted source (user input).
            // Note that when enabling the text box users may provide names for hosts on the intErnet that require the
            // "Internet (Client)" capability.
            var hostName;
            try {
                hostName = new Windows.Networking.HostName(document.getElementById("hostNameConnect").value);
            } catch (error) {
                socketsSample.displayStatus("Error: Invalid host name.");
                return;
            }
    
            if (socketsSample.clientSocket) {
                socketsSample.displayStatus("Already have a client; call close to close the listener and the client.");
                return;
            }
    
            socketsSample.closing = false;
            socketsSample.clientSocket = new Windows.Networking.Sockets.DatagramSocket();
            socketsSample.clientSocket.addEventListener("messagereceived", onMessageReceived);
            socketsSample.displayStatus("Client: connection started.");
            socketsSample.clientSocket.connectAsync(hostName, serviceName).done(function () {
                socketsSample.displayStatus("Client: connection completed.");
                socketsSample.connected = true;
            }, onError);
        }
    
        function onMessageReceived(eventArgument) {
            try {
                var messageLength = eventArgument.getDataReader().unconsumedBufferLength;
                var message = eventArgument.getDataReader().readString(messageLength);
                socketsSample.displayStatus("Client: receive message from server \"" + message + "\"");
            } catch (exception) {
                status = Windows.Networking.Sockets.SocketError.getStatus(exception.number);
                if (status === Windows.Networking.Sockets.SocketErrorStatus.connectionResetByPeer) {
                    socketsSample.displayStatus("Peer does not listen on the specific port. Please make sure that you run step 1 first " +
                    "or you have a server properly working on a remote server.");
                } else {
                    socketsSample.displayStatus("Error happened when receiving a datagram: " + exception.message);
                }
            }
        }
    
        function onError(reason) {
            socketsSample.clientSocket = null;
    
            // When we close a socket, outstanding async operations will be canceled and the
            // error callbacks called.  There's no point in displaying those errors.
            if (!socketsSample.closing) {
                socketsSample.displayStatus(reason);
            }
        }
    

8. 在客户端上发送和接收数据

此步骤中的代码将会添加一个函数,用于使用 Windows.Storage.Streams.DataWriter 类上的方法将数据发送到远程 UDP 终结点。

  • 打开 js 文件夹。打开一个新的 sendData.js 文件,将下列代码添加到此文件:

        var page = WinJS.UI.Pages.define("/html/sendData.html", {
            ready: function (element, options) {
                document.getElementById("buttonSend").addEventListener("click", sendHello, false);
            }
        });
    
        function sendHello() {
            if (!socketsSample.connected) {
                socketsSample.displayStatus("Client: you must connect the client before using it.");
                return;
            }
    
            if (!socketsSample.clientDataWriter) {
                socketsSample.clientDataWriter = new Windows.Storage.Streams.DataWriter(socketsSample.clientSocket.outputStream);
            }
    
            var string = "Hello World";
            socketsSample.clientDataWriter.writeString(string);
    
            socketsSample.displayStatus("Client sending: " + string + ".");
            socketsSample.clientDataWriter.storeAsync().done(function () {
                socketsSample.displayStatus("Client sent: " + string + ".");
            }, onError);
        }
    
        function onError(reason) {
            // When we close a socket, outstanding async operations will be canceled and the
            // error callbacks called.  There's no point in displaying those errors.
            if (!socketsSample.closing) {
                socketsSample.displayStatus(reason);
            }
        }
    

9. 关闭套接字

此步骤中的代码将使用 DatagramSocket.Close 方法关闭套接字。 当套接字关闭时,所有挂起的操作将会结束并调用错误例程。

  • 打开 js 文件夹。 打开一个新的 socketClose.js 文件,将下列代码添加到此文件:

        var page = WinJS.UI.Pages.define("/html/sendData.html", {
            ready: function (element, options) {
                document.getElementById("buttonSend").addEventListener("click", sendHello, false);
            }
        });
    
        function sendHello() {
            if (!socketsSample.connected) {
                socketsSample.displayStatus("Client: you must connect the client before using it.");
                return;
            }
    
            if (!socketsSample.clientDataWriter) {
                socketsSample.clientDataWriter = new Windows.Storage.Streams.DataWriter(socketsSample.clientSocket.outputStream);
            }
    
            var string = "Hello World";
            socketsSample.clientDataWriter.writeString(string);
    
            socketsSample.displayStatus("Client sending: " + string + ".");
            socketsSample.clientDataWriter.storeAsync().done(function () {
                socketsSample.displayStatus("Client sent: " + string + ".");
            }, onError);
        }
    
        function onError(reason) {
            // When we close a socket, outstanding async operations will be canceled and the
            // error callbacks called.  There's no point in displaying those errors.
            if (!socketsSample.closing) {
                socketsSample.displayStatus(reason);
            }
        }
    

10. 运行应用程序

  • 要运行该应用,请在 Visual Studio 中按 F5 以运行该项目。 选择按钮以启动侦听器、将客户端连接到侦听器、发送数据和关闭套接字。

摘要和后续步骤

在本主题中,你创建了一个应用。该应用使用 UDP 数据报套接字,通过 DatagramSocket 对象建立网络连接并发送数据。该应用还演示了如何侦听 UDP 端口和接收数据。

本主题的源代码和生成文件位于 DatagramSocket 示例

你还可使用流套接字建立网络连接,以发送和接收数据。有关示例,请参阅如何使用流套接字进行连接

相关主题

其他资源

使用套接字进行连接

如何配置网络隔离功能

如何使用流套接字进行连接

如何针对套接字操作设置超时

如何使用高级套接字控件

诊断和调试网络连接问题

参考

DatagramSocket

Windows.Networking

Windows.Networking.Sockets

示例

DatagramSocket 示例