C#:簡單的Socket非同步通訊功能(客戶端)
上一篇(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);
}
}
}
最終結果