从Windows Phone 8 开始,Socket 的支持扩大到允许开发者撰写Socket Server,也就是说,在不透过中介服务的情况下,开发者可以撰写一个Windows Phone Server Application来允许其它设备连入,
这在开发对战连线游戏时是个非常有用的机制。
文/黄忠成
Socket Support in Windows Phone 8
从Windows Phone 7.1开始便支持了Socket Programming,开发者可以直接使用Socket来开发需要快速通讯的网络应用程序,不过那时Socket仅支持到Client-Side,简单的说就是你无法在Windows Phone上建立一个Socket Server
来接入其它的Windows Phone App连线,必须透过一个由其它平台所提供的中介服务(像是Windows Client、网站等等),这大大的限制了Windows Phone Apps Socket的应用范围。
从Windows Phone 8 开始,Socket 的支持扩大到允许开发者撰写Socket Server,也就是说,在不透过中介服务的情况下,开发者可以撰写一个Windows Phone Server Application来允许其它设备连入,
这在开发对战连线游戏时是个非常有用的机制。
A Simple Socket Listener
用法很简单,你只需要利用StreamSocketListener来建立一个Server Socket,就可以接入来自其它设备的连线,如以下程序。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
using SocketServer.Resources;
using Windows.Networking.sockets;
using Windows.Storage.Streams;
using Windows.Networking;
using Windows.Networking.Connectivity;
using System.Text;
namespace SocketServer
{
public class Listeninformation
{
public HostName Host { get; set; }
public string displayName { get; set; }
}
public partial class MainPage : PhoneApplicationPage
{
private StreamSocketListener _listener = new StreamSocketListener();
private List _ips = new List();
// Constructor
public MainPage()
{
InitializeComponent();
var hostNames = networkinformation.GetHostNames();
foreach (HostName hn in hostNames)
{
Listeninformation info = new Listeninformation() { Host = hn };
if (hn.IPinformation.NetworkAdapter.IanaInterfaceType == 71) //Wi-Fi
info.displayName = hn.CanonicalName + "(Wi-Fi)";
else if (hn.IPinformation.NetworkAdapter.IanaInterfaceType == 244) //cellular
info.displayName = hn.CanonicalName + "(cellular)";
else
info.displayName = hn.CanonicalName;
_ips.Add(info);
}
lstIPs.displayMemberPath = "displayName";
lstIPs.ItemsSource = _ips;
}
async private void WaitForData(StreamSocket socket)
{
var dr = new DataReader(socket.InputStream);
var dataReaded = await dr.LoadAsync(sizeof(uint));
if (dataReaded == 0)
return;
uint strLen = dr.ReadUInt32();
await dr.LoadAsync(strLen);
string msg = dr.ReadString(strLen);
if (msg.ToLower().Equals("bye"))
{
socket.dispose();
return;
}
dispatcher.BeginInvoke(() =>
{
lstOutput.Items.Add("recv:" + msg);
});
WaitForData(socket);
}
void listener_ConnectionReceived(StreamSocketListener sender, StreamSocketListenerConnectionReceivedEventArgs args)
{
WaitForData(args.socket);
}
private async void Button_Click(object sender, RoutedEventArgs e)
{
if (lstIPs.SelectedItem != null)
{
((Button)sender).IsEnabled = false;
_listener.ConnectionReceived += listener_ConnectionReceived;
await _listener.BindEndpointAsync(((Listeninformation)lstIPs.SelectedItem).Host, "1067");
appTitle.Text = "listen on " + _listener.information.LocalPort;
}
else
MessageBox.Show("please select a ip to listen.");
}
}
}
让我稍微解释一下这个程序几个关键片段。
public MainPage()
{
InitializeComponent();
var hostNames = networkinformation.GetHostNames();
foreach (HostName hn in hostNames)
{
Listeninformation info = new Listeninformation() { Host = hn };
if (hn.IPinformation.NetworkAdapter.IanaInterfaceType == 71) //Wi-Fi
info.displayName = hn.CanonicalName + "(Wi-Fi)";
else if (hn.IPinformation.NetworkAdapter.IanaInterfaceType == 244) //cellular
info.displayName = hn.CanonicalName + "(cellular)";
else
info.displayName = hn.CanonicalName;
_ips.Add(info);
}
lstIPs.displayMemberPath = "displayName";
lstIPs.ItemsSource = _ips;
}
理论上,每个Windows Phone 8设备应该都会有一个以上的网络位址,除非该设备没有数据连线及Wi-Fi,因此要起始一个Socket Server,得先决定要系结至哪个网络位址,这里透过GetHostNames()来取得所有可用的网络位址,
然后透过IanaInterfaceType来标示该网络位址的型态,一般来说, 71是Wi-Fi、244是数据连线。
private async void Button_Click(object sender, RoutedEventArgs e)
{
if (lstIPs.SelectedItem != null)
{
((Button)sender).IsEnabled = false;
_listener.ConnectionReceived += listener_ConnectionReceived;
await _listener.BindEndpointAsync(((Listeninformation)lstIPs.SelectedItem).Host, "1067");
appTitle.Text = "listen on " + _listener.information.LocalPort;
}
else
MessageBox.Show("please select a ip to listen.");
}
当使用者选择了要系结的网络位址后,这里透过BindEndpointAsync来启用Socket Server,第一个参数是HostName,第二个则是要聆听的Port资讯。
当连线接入时,ConnectionReceived事件会触发,在此处透过DataReader来读入由Client端传入的数据。
async private void WaitForData(StreamSocket socket)
{
var dr = new DataReader(socket.InputStream);
var dataReaded = await dr.LoadAsync(sizeof(uint));
if (dataReaded == 0)
return;
uint strLen = dr.ReadUInt32();
await dr.LoadAsync(strLen);
string msg = dr.ReadString(strLen);
if (msg.ToLower().Equals("bye"))
{
socket.dispose();
return;
}
dispatcher.BeginInvoke(() =>
{
lstOutput.Items.Add("recv:" + msg);
});
WaitForData(socket);
}
void listener_ConnectionReceived(StreamSocketListener sender, StreamSocketListenerConnectionReceivedEventArgs args)
{
WaitForData(args.socket);
}
Client: Windows Phone 8
完成Server端后,Client端相对简单多了。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
using ClientSocketDemo.Resources;
using Windows.Networking.sockets;
using Windows.Networking;
using Windows.Storage.Streams;
namespace ClientSocketDemo
{
public partial class MainPage : PhoneApplicationPage
{
private StreamSocket _socket = null;
// Constructor
public MainPage()
{
InitializeComponent();
// Sample code to localize the ApplicationBar
//BuildLocalizedApplicationBar();
}
private async void Button_Click(object sender, RoutedEventArgs e)
{
_socket = new StreamSocket();
await _socket.ConnectAsync(new HostName(txtIP.Text), "1067");
((Button)sender).IsEnabled = false;
txtIP.IsEnabled = false;
btnSend.IsEnabled = true;
txtSendData.IsEnabled = true;
}
private async void btnSend_Click(object sender, RoutedEventArgs e)
{
DataWriter dr = new DataWriter(_socket.OutputStream);
string data = txtSendData.Text;
dr.WriteUInt32((uint)data.Length);
await dr.StoreAsync();
dr.WriteString(data);
await dr.StoreAsync();
dr.DetachStream();
}
}
}
透过ConnectAsync可以连接至Socket Server,第一个参数是HostName(IP),第二个是Port。
图1
Client: Windows 8 App
同理,你也可以撰写Windows Store App的客户端来连入Windows Phone。
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.Networking;
using Windows.Networking.sockets;
using Windows.Storage.Streams;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
namespace WinSocketClient
{
public sealed partial class MainPage : Page
{
private StreamSocket _socket = null;
public MainPage()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
}
private async void Button_Click(object sender, RoutedEventArgs e)
{
_socket = new StreamSocket();
await _socket.ConnectAsync(new HostName(txtIP.Text), "1067");
((Button)sender).IsEnabled = false;
txtIP.IsEnabled = false;
btnSend.IsEnabled = true;
txtSendData.IsEnabled = true;
}
private async void btnSend_Click(object sender, RoutedEventArgs e)
{
DataWriter dr = new DataWriter(_socket.OutputStream);
string data = txtSendData.Text;
dr.WriteUInt32((uint)data.Length);
await dr.StoreAsync();
dr.WriteString(data);
await dr.StoreAsync();
dr.DetachStream();
}
}
}
图2
原文:大专栏 Socket Server in Windows Phone 8