上一篇(http://www.rexcao.net/archives/159)讲了服务端使用Socket发送消息的方法,这一篇来解决一下客户端如何接收服务端消息的问题。
目标
1、异步接收服务端消息并显示
2、发送自定义消息给服务端(由于上一篇做的是Windows服务,没有界面,这个需要另行处理才可以显示客户端的消息)
思路
1、异步从一个已连接的Socket对象中获取消息
2、在按钮的点击事件中向服务端发送消息
注意事项
1、因为使用的是异步通信,回调方法是由系统执行的,这个牵扯到跨进程调用资源的问题
2、一旦一个Socket连接断开后,再次建立连接需要创建一个新的Socket对象来与服务端建立连接。
代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Net.Sockets;
namespace FormClient
{
public partial class Form1 : Form
{
private delegate void FlushClient(string msg);//使用委托解决跨线程调用问题
private delegate void DelegateControls(bool val, string msg);
Socket socket;
static string host = "127.0.0.1";
static int port = 4530;
static byte[] buffer = new byte[1024];
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
this.ConServer();
}
private void btnRetry_Click(object sender, EventArgs e)
{
this.ConServer();
}
/// <summary>
/// 向textbox写入信息并使其滚动至底部
/// 支持跨线程调用(使用委托实现,参考:http://bbs.csdn.net/topics/280001358 )
/// </summary>
/// <param name="msg">要显示的消息文本</param>
private void WriteScroll(string msg)
{
if (this.InvokeRequired)
{
FlushClient fc = new FlushClient(WriteScroll);
object[] ps = new object[] { msg };
this.Invoke(fc, ps);
return;
}
else
{
msg = msg + "\r\n";
textBox1.Text += msg;
textBox1.Focus();
textBox1.Select(textBox1.TextLength, 0);
textBox1.ScrollToCaret();
}
}
/// <summary>
/// 连接服务器
/// </summary>
private void ConServer()
{
this.WriteScroll("Trying to connect the server...");
try
{
this.socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
this.socket.Connect(host, port);
this.socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveMessage), this.socket);
ControlsSwitch(false, "");
}
catch (SocketException ex)
{
this.ControlsSwitch(true, ex.Message);
}
catch (Exception ex)
{
this.WriteScroll(ex.Message);
}
}
/// <summary>
/// 接收来自服务器的消息
/// </summary>
/// <param name="ar"></param>
public void ReceiveMessage(IAsyncResult ar)
{
try
{
var socket = ar.AsyncState as Socket;
var length = socket.EndReceive(ar);
//读取出来消息内容
var message = Encoding.Unicode.GetString(buffer, 0, length);
//显示消息
this.WriteScroll("From host:" + message);
socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveMessage), socket);
}
catch (SocketException ex)
{
ControlsSwitch(true, ex.Message);
}
catch (InvalidOperationException ex)
{
ControlsSwitch(true, ex.Message);
}
}
private void ControlsSwitch(bool val, string msg)
{
if (this.InvokeRequired)
{
DelegateControls dc = new DelegateControls(ControlsSwitch);
object[] ps = new object[] { val, msg };
this.Invoke(dc, ps);
}
else
{
btnRetry.Visible = val;
lblStatus.Text = msg;
}
}
/// <summary>
/// 任何时候想要WriteScroll时,都调用这个方法,而不是直接调用WriteScroll
/// 这个方法可以根据你的调用是否跨线程而进行相应的处理
/// </summary>
/// <param name="msg"></param>
public void InDirectOutput(string msg)
{
if (this.InvokeRequired)//根据这个来判断是否跨线程
{
//是跨线程,则用Invoke的方式,在创建的线程内执行
FlushClient fc = new FlushClient(WriteScroll);
//这里初始化参数并传入Invoke函数。
object[] ps = new object[] { msg };
this.Invoke(fc, ps);
return;
}
else
{
//不是跨线程,直接访问
WriteScroll(msg);
}
}
private void btnSend_Click(object sender, EventArgs e)
{
var outputBuffer = Encoding.Unicode.GetBytes(txtSend.Text);
this.socket.BeginSend(outputBuffer, 0, outputBuffer.Length, SocketFlags.None, null, null);
}
}
}
最终结果