如何使用流套接字进行连接 (HTML)

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

本主题介绍 如何使 Windows 运行时应用可以使用 StreamSocket 通过 TCP 套接字发送和接收网络数据。 TCP 套接字对于生存期长的连接提供双向低级网络数据传输。 TCP 套接字是由大部分网络协议在 Internet 上使用的基础功能。

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

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

  • 使用 StreamSocket 类创建 TCP 套接字。
  • 使用 StreamSocket.ConnectAsync 方法之一建立与 TCP 网络服务器的网络连接。
  • 使用 Streams.DataWriter 对象将数据发送到服务器,该对象允许程序员在任何流上写入常用类型(例如整数和字符串)。
  • 关闭套接字。

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

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

 

先决条件

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

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

说明

创建新项目

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

设置启用网络访问的功能

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

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

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

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

 

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

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

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

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

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

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

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

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

添加 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>
                StreamSocketListener will create the "server" side of a connection. It listens on
                a "service name" (often a port number) and calls a callback when it accepts a connection;
                this happens when some other application tries to connect. Once a connection is
                accepted, the acceptAsync() method needs to be called again.
            </p>
            <p>
                <label for="serviceNameAccept">Service Name:</label>
                <input id="serviceNameAccept" type="text" />
            </p>
            <p>
                <button id="buttonStartListener">Create StreamSocketListener and start to 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 international
                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" />
            </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. You can also hook up the socket stream
                to other streams in Windows 8.
            </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>
    

定义示例和方案

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

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

        var sampleTitle = "StreamSocket";
    
        var scenarios = [
            { url: "/html/startListener.html", title: "Start StreamSocketListener" },
            { 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();
    

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

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

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

    var socketsSample = {};
    
    (function () {
        "use strict";
    
        socketsSample.listener = null; // A StreamSocketListener that acts as our server.
        socketsSample.serverSocket = null; // The server socket that's been accepted.
        socketsSample.serverReader = null; // The reader for the server socket.
        socketsSample.clientSocket = null; // The client socket that will connect to the server socket.
        socketsSample.connected = false;
        socketsSample.closing = false;
    
        socketsSample.serviceNameAccept = "22112";
        socketsSample.hostNameConnect = "localhost";
        socketsSample.serviceNameConnect = "22112";
    
        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;
            }
        };
    })();
    

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

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

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

 

  • 打开 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() {
            if (socketsSample.listener) {
                socketsSample.displayStatus("Already have a listener; call close to close the listener.");
                return;
            }
            socketsSample.closing = false;
            var serviceName = document.getElementById("serviceNameAccept").value;
            socketsSample.listener = new Windows.Networking.Sockets.StreamSocketListener(serviceName);
            socketsSample.listener.addEventListener("connectionreceived", onServerAccept);
            socketsSample.displayStatus("Server: listener creation started.");
            socketsSample.listener.bindServiceNameAsync(serviceName).done(function () {
                socketsSample.displayStatus("Server: listener creation completed.");
            }, onError);
        }
    
        // This has to be a real function ; it will "loop" back on itself with the
        // call to acceptAsync at the very end.
        function onServerAccept(eventArgument) {
            socketsSample.displayStatus("Server: connection accepted.");
            socketsSample.serverSocket = eventArgument.socket;
            socketsSample.serverReader = new Windows.Storage.Streams.DataReader(socketsSample.serverSocket.inputStream);
            startServerRead();
        }
    
        // The protocol here is simple: a four-byte 'network byte order' (big-endian) integer
        // that says how long a string is, and then a string that is that long.
        // We wait for exactly 4 bytes, read in the count value, and then wait for
        // count bytes, and then display them.
        function startServerRead() {
            socketsSample.serverReader.loadAsync(4).done(function (sizeBytesRead) {
                // Make sure 4 bytes were read.
                if (sizeBytesRead !== 4) {
                    socketsSample.displayStatus("Server: connection lost.");
                    return;
                }
    
                // Read in the 4 bytes count and then read in that many bytes.
                var count = socketsSample.serverReader.readInt32();
                return socketsSample.serverReader.loadAsync(count).then(function (stringBytesRead) {
                    // Make sure the whole string was read.
                    if (stringBytesRead !== count) {
                        socketsSample.displayStatus("Server: connection lost.");
                        return;
                    }
                    // Read in the string.
                    var string = socketsSample.serverReader.readString(count);
                    socketsSample.displayOutput("Server read: " + string);
                    // Restart the read for more bytes.
                    startServerRead();
                }); // End of "read in rest of string" function.
            }, 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);
            }
        }
    

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

此步骤中的代码将会添加一个函数,用于创建套接字并使用 StreamSocket.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() {
            if (socketsSample.clientSocket) {
                socketsSample.displayStatus("Already have a client; call close to close the listener and the client.");
                return;
            }
            socketsSample.closing = false;
            var serverHostName = new Windows.Networking.HostName(document.getElementById("hostNameConnect").value);
            var serviceName = document.getElementById("serviceNameConnect").value;
            socketsSample.clientSocket = new Windows.Networking.Sockets.StreamSocket();
            socketsSample.displayStatus("Client: connection started.");
            socketsSample.clientSocket.connectAsync(serverHostName, serviceName).done(function () {
                socketsSample.displayStatus("Client: connection completed.");
                socketsSample.connected = true;
            }, onError);
        }
    
        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);
            }
        }
    

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

此步骤中的代码将会添加一个函数,用于使用 Windows.Storage.Stream.DataWriter 类上的方法将数据发送到服务器。

  • 打开 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;
            }
            var writer = new Windows.Storage.Streams.DataWriter(socketsSample.clientSocket.outputStream);
            var string = "Hello World";
            var len = writer.measureString(string); // Gets the UTF-8 string length.
            writer.writeInt32(len);
            writer.writeString(string);
            socketsSample.displayStatus("Client sending: " + string + ".");
            writer.storeAsync().done(function () {
                socketsSample.displayStatus("Client sent: " + string + ".");
                writer.detachStream();
            }, 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);
            }
        }
    

关闭套接字

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

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

        var page = WinJS.UI.Pages.define("/html/closeSocket.html", {
            ready: function (element, options) {
                document.getElementById("buttonClose").addEventListener("click", closeListenerAndSockets, false);
            }
        });
    
        function closeListenerAndSockets() {
            socketsSample.closing = true;
            if (socketsSample.listener) {
                socketsSample.listener.close();
                socketsSample.listener = null;
            }
            if (socketsSample.serverSocket) {
                socketsSample.serverSocket.close();
                socketsSample.serverSocket = null;
            }
            if (socketsSample.clientSocket) {
                socketsSample.clientSocket.close();
                socketsSample.clientSocket = null;
                socketsSample.connected = false;
            }
            socketsSample.displayStatus("Client and server closed.");
        }
    

运行应用程序

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

摘要和后续步骤

在本主题中,你创建了一个应用。该应用使用 TCP 流套接字,通过 StreamSocket 对象建立网络连接和发送数据。应用还演示了如何侦听 TCP 连接,并接受来自流套接字的连接以发送或接收数据。

本主题的源代码和生成文件将作为 StreamSocket 示例提供。

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

相关主题

其他资源

使用套接字进行连接

如何配置网络功能

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

如何启用环回和调试网络隔离

如何借助 TLS/SSL 确保套接字连接的安全

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

如何使用高级套接字控件

解决网络连接问题并调试

参考

StreamSocket

StreamSocketListener

Windows.Networking

Windows.Networking.Sockets

Windows.Storage.Stream.DataWriter

示例

StreamSocket 示例