Windows Phone学习笔记(7) — — TCP套接字
TCP套接字的主要步骤:创建连接、发送数据或接受数据、关闭连接,下面开始做一个TCP的示例。
首先是WP界面代码:
View Code
<!--ContentPanel - 在此处放置其他内容--> <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,-8,12,8"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <TextBlock Grid.Row="0" Grid.Column="0" Text="Host Name:" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="{StaticResource PhoneFontSizeNormal}" /> <TextBox x:Name="txtRemoteHost" Grid.Row="0" Grid.Column="1" Height="70" Width="200" VerticalAlignment="Top" HorizontalAlignment="Left" FontSize="{StaticResource PhoneFontSizeNormal}" /> <TextBlock Grid.Row="1" Grid.Column="0" Text="Text To Echo:" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="{StaticResource PhoneFontSizeNormal}" /> <TextBox x:Name="txtInput" Grid.Row="1" Grid.Column="1" Height="70" Width="200" VerticalAlignment="Top" HorizontalAlignment="Left" FontSize="{StaticResource PhoneFontSizeNormal}" /> <Button x:Name="btnEcho" Grid.Row="1" Grid.Column="2" Height="70" Width="120" Content="Echo" FontSize="{StaticResource PhoneFontSizeNormal}" Click="btnEcho_Click"/> <Button x:Name="btnGetQuote" Grid.Row="2" Grid.ColumnSpan="4" Height="70" Content="Get Quote of the Day" FontSize="{StaticResource PhoneFontSizeNormal}" Click="btnGetQuote_Click"/> <TextBox x:Name="txtOutput" Grid.Row="3" Grid.ColumnSpan="4" Background="Black" BorderBrush="Green" AcceptsReturn="False" Foreground="LightGray" FontFamily="Courier New" IsHitTestVisible="False" FontSize="{StaticResource PhoneFontSizeSmall}" TextWrapping="Wrap" /> </Grid>
之后创建一个SocketClient类用以实现套接字的请求流程,首先是要声明四个变量:_socket、_clientDone、TIMEOUT_MILLISECONDS、MAX_BUFFER_SIZE。_socket是一个套接字对象,_clientDone可以通过设置通信的接收状态来协调套接字的异步调用,TIMEOUT_MILLISECONDS规定最大的异步时间,MAX_BUFFER_SIZE是数据缓冲区的最大空间。代码如下:
View Code
Socket _socket = null; static ManualResetEvent _clientDone = new ManualResetEvent(false); const int TIMEOUT_MILLISECONDS = 5000; const int MAX_BUFFER_SIZE = 2048;
之后就是创建服务器的连接:
View Code
/// <summary> /// 尝试一个TCP套接字连接到指定的主机端口 /// </summary> /// <param name="hostName">主机名称</param> /// <param name="portNumber">要连接的端口号</param> /// <returns>代表一个连接请求的返回结果字符串</returns> public string Connect(string hostName, int portNumber) { string result = string.Empty; //创建一个终结点DnsEndPoint对象。将主机名和端口号传递给它 DnsEndPoint hostEntry = new DnsEndPoint(hostName, portNumber); //创建一个引用InterNetwork地址的基于流的TCP协议 _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //此处是创建了一个套接字的异步操作,并且设定它远程的IP或者终结点 SocketAsyncEventArgs socketEventArg = new SocketAsyncEventArgs(); socketEventArg.RemoteEndPoint = hostEntry; //添加套接字的异步操作完成的事件 socketEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(delegate(object s, SocketAsyncEventArgs e) { //检测请求的结果 result = e.SocketError.ToString(); //请求信号完成,接触用户UI线程 _clientDone.Set(); }); //将事件状态市值为未收到信号状态,阻止UI进程 _clientDone.Reset(); //开始对远程主机的一个请求 _socket.ConnectAsync(socketEventArg); //阻止UI进程的时间 _clientDone.WaitOne(TIMEOUT_MILLISECONDS); return result; }
向服务器发送数据:
View Code
/// <summary> /// 为指定的数据发送到服务器建立连接 /// </summary> /// <param name="data">发送到服务器的数据</param> /// <returns>发送请求的结果</returns> public string Send(string data) { string response = "Operation Timeout"; //我们用_socket对象初始化的连接对象 if (_socket != null) { //创建SocketAsyncEventArgs对象的上下文 SocketAsyncEventArgs socketEventArg = new SocketAsyncEventArgs(); //为上下文对象设置属性 socketEventArg.RemoteEndPoint = _socket.RemoteEndPoint; socketEventArg.UserToken = null; //设置异步套接字操作的完成的事件 socketEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(delegate(object s, SocketAsyncEventArgs e) { response = e.SocketError.ToString(); _clientDone.Set(); }); //将数据发送到缓冲区 byte[] payload = Encoding.UTF8.GetBytes(data); socketEventArg.SetBuffer(payload, 0, payload.Length); // 将事件状态市值为未收到信号状态,导致线程受阻 _clientDone.Reset(); //使用异步发送请求的套接字 _socket.SendAsync(socketEventArg); _clientDone.WaitOne(TIMEOUT_MILLISECONDS); } else { response = "Socket is not initialized"; } return response; }
接收套接字的数据:
View Code
/// <summary> /// 从套接字所建立连接的服务器接收数据 /// </summary> /// <returns>从服务器接收到的数据</returns> public string Receive() { string response = "Operation Timeout"; //接收一个套接字连接 if (_socket != null) { //创建一个SocketAsyncEventArgs上下文 SocketAsyncEventArgs socketEventArg = new SocketAsyncEventArgs(); socketEventArg.RemoteEndPoint = _socket.RemoteEndPoint; // 设置缓冲区接收数据 socketEventArg.SetBuffer(new Byte[MAX_BUFFER_SIZE], 0, MAX_BUFFER_SIZE); socketEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(delegate(object s, SocketAsyncEventArgs e) { if (e.SocketError == SocketError.Success) { //检测缓存区中的数据 response = Encoding.UTF8.GetString(e.Buffer, e.Offset, e.BytesTransferred); response = response.Trim('\0'); } else { response = e.SocketError.ToString(); } _clientDone.Set(); }); _clientDone.Reset(); _socket.ReceiveAsync(socketEventArg); _clientDone.WaitOne(TIMEOUT_MILLISECONDS); } else { response = "Socket is not initialized"; } return response; }
以上三个方法中的ConnectAsync、SendAsync、ReceiveAsync方法都是以委托的形式回调Completed事件。
在页面的.cs中,首先声明两个int类型的常量设置两个端口号,之后就是调用以上的三个方法实现TCP通信。
View Code
const int ECHO_PORT = 80; // 在Echo按钮中回调协议使用端口 const int QOTD_PORT = 80; // Quote of the Day按钮使用端口 // 构造函数 public MainPage() { InitializeComponent(); } private void btnEcho_Click(object sender, RoutedEventArgs e) { ClearLog(); //验证主机名和数据文本框不为空 if (ValidateRemoteHost() && ValidateInput()) { //实例化一个SocketClient SocketClient client = new SocketClient(); //尝试连接服务器的回调 Log(String.Format("Connecting to server '{0}' over port {1} (echo) ...", txtRemoteHost.Text, ECHO_PORT), true); string result = client.Connect(txtRemoteHost.Text, ECHO_PORT); Log(result, false); //发送数据 Log(String.Format("Sending '{0}' to server ...", txtInput.Text), true); result = client.Send(txtInput.Text); Log(result, false); //接收服务器数据 Log("Requesting Receive ...", true); result = client.Receive(); Log(result, false); //关闭连接 client.Close(); } } private void btnGetQuote_Click(object sender, RoutedEventArgs e) { ClearLog(); if (ValidateRemoteHost()) { SocketClient client = new SocketClient(); Log(String.Format("Connecting to server '{0}' over port {1} (Quote of the Day) ...", txtRemoteHost.Text, QOTD_PORT), true); string result = client.Connect(txtRemoteHost.Text, QOTD_PORT); Log(result, false); Log("Requesting Receive ...", true); result = client.Receive(); Log(result, false); // Close the socket conenction explicitly client.Close(); } } #region UI Validation /// <summary> /// txtInput文本框非空验证 /// </summary> /// <returns>True if the txtInput TextBox contains valid data, False otherwise</returns> private bool ValidateInput() { if (String.IsNullOrWhiteSpace(txtInput.Text)) { MessageBox.Show("Please enter some text to echo"); return false; } return true; } /// <summary> /// txtRemoteHost文本框非空验证 /// </summary> /// <returns>True if the txtRemoteHost contains valid data, False otherwise</returns> private bool ValidateRemoteHost() { if (String.IsNullOrWhiteSpace(txtRemoteHost.Text)) { MessageBox.Show("Please enter a host name"); return false; } return true; } #endregion #region Logging /// <summary> /// 日志的文本到txtOutput文本框 /// </summary> private void Log(string message, bool isOutgoing) { string direction = (isOutgoing) ? ">> " : "<< "; txtOutput.Text += Environment.NewLine + direction + message; } /// <summary> /// 清除txtOutput文本框 /// </summary> private void ClearLog() { txtOutput.Text = String.Empty; } #endregion
这就是TCP的一个简单示例,此示例是请求自己机器上的一个端口。程序截图如下图
参考:http://msdn.microsoft.com/zh-cn/library/hh202858(v=vs.92).aspx
http://msdn.microsoft.com/en-us/library/system.threading.manualresetevent(v=VS.95).aspx