tcp一般用於維持一個可信任的連接,比起udp更為安全可靠,在vs.net,分別有tcpclient和udpclient以及tcplistener,一般開發中基本可以滿足需要,但是這個有個很大的弊端,對於維持一個時間較長的,相互交互的來說,數據處理不是很明朗,vs/net中還有一個socket類,用他來做一個客戶/服務器段,同時在接發數據的時候,能相互獨立,這需要一個異步通訊過程
先實現服務器段:
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
// State object for reading client data asynchronously
namespace TcpServer
{
public class StateObject
{
// Client socket.
public Socket workSocket = null;
// Size of receive buffer.
public const int BufferSize = 1024;
// Receive buffer.
public byte[] buffer = new byte[BufferSize];
// Received data string.
public StringBuilder sb = new StringBuilder();
}
public class AsynchronousSocketListener
{
// Thread signal.
public static ManualResetEvent allDone = new ManualResetEvent(false);
private static Socket listener;
private int _port=9010;
public AsynchronousSocketListener()
{
}
public void StopListening()
{
listener.Close();
}
public int Port
{
set
{
_port=value;
}
}
public void StartListening()
{
// Data buffer for incoming data.
byte[] bytes = new Byte[1024];
// Establish the local endpoint for the socket.
// The DNS name of the computer
// running the listener is "host.contoso.com".
IPHostEntry ipHostInfo = Dns.Resolve(Dns.GetHostName());
IPAddress ipAddress = ipHostInfo.AddressList[0];
IPEndPoint localEndPoint = new IPEndPoint(ipAddress, _port);
// Create a TCP/IP socket.
listener = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp );
// Bind the socket to the local endpoint and listen for incoming connections.
try
{
listener.Bind(localEndPoint);
listener.Listen(100);
while (true)
{
// Set the event to nonsignaled state.
allDone.Reset();
// Start an asynchronous socket to listen for connections.
Console.WriteLine("接收連接..");
listener.BeginAccept(
new AsyncCallback(AcceptCallback),
listener );
// Wait until a connection is made before continuing.
allDone.WaitOne();
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
Console.WriteLine("\nPress ENTER to continue...");
Console.Read();
}
private void AcceptCallback(IAsyncResult ar)
{
// Signal the main thread to continue.
allDone.Set();
// Get the socket that handles the client request.
Socket listener = (Socket) ar.AsyncState;
Socket handler = listener.EndAccept(ar);
// Create the state object.
StateObject state = new StateObject();
state.workSocket = handler;
handler.BeginReceive( state.buffer, 0, StateObject.BufferSize, 0,
new AsyncCallback(ReadCallback), state);
}
private void ReadCallback(IAsyncResult ar)
{
String content = String.Empty;
// Retrieve the state object and the handler socket
// from the asynchronous state object.
StateObject state = (StateObject) ar.AsyncState;
Socket handler = state.workSocket;
int bytesRead=0 ;
// Read data from the client socket.
if(handler.Connected )
{
try
{
bytesRead = handler.EndReceive(ar);
}
catch(Exception ex)
{
handler.Close();
}
if (bytesRead > 0)
{
// There might be more data, so store the data received so far.
// Check for end-of-file tag. If it is not there, read
// more data.
content = Encoding.ASCII.GetString(
state.buffer,0,bytesRead);
if (content.Length>0 && content.EndsWith("<EOF>") )
{
// All the data has been read from the
// client. Display it on the console.
Console.WriteLine("從客戶端收到 {0} bytes 數據. \n Data : {1}",
content.Length, content );
// Echo the data back to the client.
Send(handler, "-數據確認,已經收到-<EOF>");
}
else
{
// Not all data received. Get more.
handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
new AsyncCallback(ReadCallback), state);
}
}
}
}
private void Send(Socket handler, String data)
{
// Convert the string data to byte data using ASCII encoding.
byte[] byteData = Encoding.UTF8.GetBytes(data);
// Begin sending the data to the remote device.
handler.BeginSend(byteData, 0, byteData.Length, 0,
new AsyncCallback(SendCallback), handler);
}
private void SendCallback(IAsyncResult ar)
{
try
{
// Retrieve the socket from the state object.
Socket handler = (Socket) ar.AsyncState;
// Complete sending the data to the remote device.
int bytesSent = handler.EndSend(ar);
Console.WriteLine("發送 {0} bytes 到客戶端.", bytesSent);
handler.Shutdown(SocketShutdown.Both);
handler.Close();
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
}
}
具體調用如下:
string p="";
AsynchronousSocketListener _server=new AsynchronousSocketListener();
_server.StartListening();
if((p=Console.ReadLine().ToLower() )!="exit" )
{
_server.StopListening();
}
緊接著實現客戶端,客戶端稍微復雜點,用一個session類來維持一個會話過程,coder類實現多種編碼,Datagram類定義一個具體的數據報文,默認為64個字節大小,
using System;
using System.Collections;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Net.Sockets;
using System.Net;
using System.Text;
using System.Threading;
using System.Data;
using System.Xml;
using System.Xml.XPath;
namespace Client
{
#region 通訊對象
public delegate void NetEvent(object sender, NetEventArgs e);
public class CSocket
{
#region 字段
/// <summary>
/// 客戶端與服務器之間的會話類
/// </summary>
private Session _session;
/// <summary>
/// 客戶端是否已經連接服務器
/// </summary>
private bool _isConnected = false;
private bool _isEcho = false;
private StringBuilder sb=new StringBuilder();
/// <summary>
/// 接收數據緩沖區大小64K
/// </summary>
public const int DefaultBufferSize = 64*1024;
/// <summary>
/// 報文解析器
/// </summary>
private DatagramResolver _resolver;
/// <summary>
/// 通訊格式編碼解碼器
/// </summary>
private Coder _coder;
/// <summary>
/// 接收數據緩沖區
/// </summary>
private byte[] _recvDataBuffer = new byte[DefaultBufferSize];
public ManualResetEvent allDone = new ManualResetEvent(false);
#endregion
#region 事件定義
//需要訂閱事件才能收到事件的通知,如果訂閱者退出,必須取消訂閱
/// <summary>
/// 已經連接服務器事件
/// </summary>
/// <summary>
/// 接收到數據報文事件
/// </summary>
public event NetEvent ReceivedDatagram;
public event NetEvent DisConnectedServer;
public event NetEvent ConnectedServer;
/// <summary>
/// 連接斷開事件
/// </summary>
#endregion
#region 屬性
/// <summary>
/// 返回客戶端與服務器之間的會話對象
/// </summary>
public Session ClientSession
{
get
{
return _session;
}
}
/// <summary>
/// 返回客戶端與服務器之間的連接狀態
/// </summary>
public bool IsConnected
{
get
{
return _isConnected;
}
}
public bool IsEchoBack
{
get
{
return _isEcho;
}
}
/// <summary>
/// 數據報文分析器
/// </summary>
public DatagramResolver Resovlver
{
get
{
return _resolver;
}
set
{
_resolver = value;
}
}
/// <summary>
/// 編碼解碼器
/// </summary>
public Coder ServerCoder
{
get
{
return _coder;
}
}
#endregion
#region 公有方法
/// <summary>
/// 默認構造函數,使用默認的編碼格式
/// </summary>
public CSocket()
{
_coder = new Coder( Coder.EncodingMothord.gb2312 );
}
/// <summary>
/// 構造函數,使用一個特定的編碼器來初始化
/// </summary>
/// <param name="_coder">報文編碼器</param>
public CSocket( Coder coder )
{
_coder = coder;
}
/// <summary>
/// 連接服務器
/// </summary>
/// <param name="ip">服務器IP地址</param>
/// <param name="port">服務器端口</param>
public virtual void Connect( string ip, int port)
{
if(IsConnected)
{
Close();
}
Socket newsock= new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
IPEndPoint iep = new IPEndPoint( IPAddress.Parse(ip), port);
newsock.BeginConnect(iep, new AsyncCallback(Connected), newsock);
}
/// <summary>
/// 發送數據報文
/// </summary>
/// <param name="datagram"></param>
public virtual void Send( string datagram)
{
try
{
if(datagram.Length ==0 )
{
return;
}
allDone.WaitOne();
//獲得報文的編碼字節
byte [] data = _coder.GetEncodingBytes(datagram);
_session.ClientSocket.BeginSend( data, 0, data.Length, SocketFlags.None,
new AsyncCallback( SendDataEnd ), _session.ClientSocket);
}
catch(Exception ex)
{
Console.WriteLine(ex.ToString() );
}
}
/// <summary>
/// 關閉連接
/// </summary>
public virtual void Close()
{
if(!_isConnected)
{
return;
}
_session.Close();
_session = null;
_isConnected = false;
}
#endregion
#region 受保護方法
/// <summary>
/// 數據發送完成處理函數
/// </summary>
/// <param name="iar"></param>
protected virtual void SendDataEnd(IAsyncResult iar)
{
try
{
Socket remote = (Socket)iar.AsyncState;
int sent = remote.EndSend(iar);
}
catch(Exception ex)
{
Console.WriteLine(ex.ToString() );
}
}
/// <summary>
/// 建立Tcp連接後處理過程
/// </summary>
/// <param name="iar">異步Socket</param>
protected virtual void Connected(IAsyncResult iar)
{
Socket socket = (Socket)iar.AsyncState;
//返回一個與之廉潔的連接
socket.EndConnect(iar);
//創建新的會話
_session = new Session(socket);
_isConnected = true;
allDone.Set();
try
{
_session.ClientSocket.BeginReceive(_recvDataBuffer, 0,
DefaultBufferSize, SocketFlags.None,
new AsyncCallback(RecvData), socket);}
catch(Exception ex)
{
socket.Close();
}
}
/// <summary>
/// 數據接收處理函數
/// </summary>
/// <param name="iar">異步Socket</param>
public string recevie()
{
return this.sb.ToString() ;
}
protected virtual void RecvData(IAsyncResult iar)
{
Socket remote = (Socket)iar.AsyncState;
try
{
string receivedData="" ;
int recv = remote.EndReceive(iar);
if(recv>0)
{
receivedData = System.Text.Encoding.UTF8.GetString(_recvDataBuffer,0,recv ) ;
if(receivedData.EndsWith("<EOF>") )
{
_isEcho=true;
sb.Append(receivedData);
this._session.Datagram= receivedData;
if(ReceivedDatagram==null)
{
ReceivedDatagram(this,new NetEventArgs(_session) ) ;
}
Console.WriteLine(string.Format("{0},來自{1}",receivedData,_session.ClientSocket.RemoteEndPoint.ToString() ) ) ;
this.allDone.Set();
}
else
{
Console.WriteLine("listen");
_session.ClientSocket.BeginReceive(_recvDataBuffer, 0, DefaultBufferSize, SocketFlags.None,
new AsyncCallback(RecvData), _session.ClientSocket);
}
}
}
catch(SocketException ex)
{
Console.WriteLine(ex.ToString() );
}
}
#endregion
}
/// <summary>
/// 通訊編碼格式提供者,為通訊服務提供編碼和解碼服務
/// 你可以在繼承類中定制自己的編碼方式如:數據加密傳輸等
/// </summary>
public class Coder
{
/// <summary>
/// 編碼方式
/// </summary>
private EncodingMothord _encodingMothord;
protected Coder()
{
}
public Coder(EncodingMothord encodingMothord)
{
_encodingMothord = encodingMothord;
}
public enum EncodingMothord
{
gb2312=0,
Default ,
Unicode,
UTF8,
ASCII,
}
/// <summary>
/// 通訊數據解碼
/// </summary>
/// <param name="dataBytes">需要解碼的數據</param>
/// <returns>編碼後的數據</returns>
public virtual string GetEncodingString( byte [] dataBytes,int size)
{
switch( _encodingMothord )
{
case EncodingMothord.gb2312:
{
return Encoding.GetEncoding("gb2312").GetString(dataBytes,0,size);
}
case EncodingMothord.Default:
{
return Encoding.Default.GetString(dataBytes,0,size);
}
case EncodingMothord.Unicode:
{
return Encoding.Unicode.GetString(dataBytes,0,size);
}
case EncodingMothord.UTF8:
{
return Encoding.UTF8.GetString(dataBytes,0,size);
}
case EncodingMothord.ASCII:
{
return Encoding.ASCII.GetString(dataBytes,0,size);
}
default:
{
throw( new Exception("未定義的編碼格式"));
}
}
}
/// <summary>
/// 數據編碼
/// </summary>
/// <param name="datagram">需要編碼的報文</param>
/// <returns>編碼後的數據</returns>
public virtual byte[] GetEncodingBytes(string datagram)
{
switch( _encodingMothord)
{
case EncodingMothord.gb2312:
{
return Encoding.GetEncoding("gb2312").GetBytes(datagram);
}
case EncodingMothord.Default:
{
return Encoding.Default.GetBytes(datagram);
}
case EncodingMothord.Unicode:
{
return Encoding.Unicode.GetBytes(datagram);
}
case EncodingMothord.UTF8:
{
return Encoding.UTF8.GetBytes(datagram);
}
case EncodingMothord.ASCII:
{
return Encoding.ASCII.GetBytes(datagram);
}
default:
{
throw( new Exception("未定義的編碼格式"));
}
}
}
}
/// <summary>
/// 數據報文分析器,通過分析接收到的原始數據,得到完整的數據報文.
/// 繼承該類可以實現自己的報文解析方法.
/// 通常的報文識別方法包括:固定長度,長度標記,標記符等方法
/// 本類的現實的是標記符的方法,你可以在繼承類中實現其他的方法
/// </summary>
public class DatagramResolver
{
/// <summary>
/// 報文結束標記
/// </summary>
private string endTag;
/// <summary>
/// 返回結束標記
/// </summary>
string EndTag
{
get
{
return endTag;
}
}
/// <summary>
/// 受保護的默認構造函數,提供給繼承類使用
/// </summary>
protected DatagramResolver()
{
}
/// <summary>
/// 構造函數
/// </summary>
/// <param name="endTag">報文結束標記</param>
public DatagramResolver(string endTag)
{
if(endTag == null)
{
throw (new ArgumentNullException("結束標記不能為null"));
}
if(endTag == "")
{
throw (new ArgumentException("結束標記符號不能為空字符串"));
}
this.endTag = endTag;
}
/// <summary>
/// 解析報文
/// </summary>
/// <param name="rawDatagram">原始數據,返回未使用的報文片斷,
/// 該片斷會保存在Session的Datagram對象中</param>
/// <returns>報文數組,原始數據可能包含多個報文</returns>
public virtual string [] Resolve(ref string rawDatagram)
{
ArrayList datagrams = new ArrayList();
//末尾標記位置索引
int tagIndex =-1;
while(true)
{
tagIndex = rawDatagram.IndexOf(endTag,tagIndex+1);
if( tagIndex == -1 )
{
break;
}
else
{
//按照末尾標記把字符串分為左右兩個部分
string newDatagram = rawDatagram.Substring(
0, tagIndex+endTag.Length);
datagrams.Add(newDatagram);
if(tagIndex+endTag.Length >= rawDatagram.Length)
{
rawDatagram="";
break;
}
rawDatagram = rawDatagram.Substring(tagIndex+endTag.Length,
rawDatagram.Length - newDatagram.Length);
//從開始位置開始查找
tagIndex=0;
}
}
string [] results= new string[datagrams.Count];
datagrams.CopyTo(results);
return results;
}
}
/// <summary>
/// 客戶端與服務器之間的會話類
///
/// 版本: 1.1
/// 替換版本: 1.0
///
/// 說明:
/// 會話類包含遠程通訊端的狀態,這些狀態包括Socket,報文內容,
/// 客戶端退出的類型(正常關閉,強制退出兩種類型)
/// </summary>
public class Session:ICloneable
{
#region 字段
/// <summary>
/// 會話ID
/// </summary>
private SessionId _id;
/// <summary>
/// 客戶端發送到服務器的報文
/// 注意:在有些情況下報文可能只是報文的片斷而不完整
/// </summary>
private string _datagram;
/// <summary>
/// 客戶端的Socket
/// </summary>
private Socket _cliSock;
/// <summary>
/// 客戶端的退出類型
/// </summary>
private ExitType _exitType;
/// <summary>
/// 退出類型枚舉
/// </summary>
public enum ExitType
{
NormalExit ,
ExceptionExit
};
#endregion
#region 屬性
/// <summary>
/// 返回會話的ID
/// </summary>
public SessionId ID
{
get
{
return _id;
}
}
/// <summary>
/// 存取會話的報文
/// </summary>
public string Datagram
{
get
{
return _datagram;
}
set
{
_datagram = value;
}
}
/// <summary>
/// 獲得與客戶端會話關聯的Socket對象
/// </summary>
public Socket ClientSocket
{
get
{
return _cliSock;
}
}
/// <summary>
/// 存取客戶端的退出方式
/// </summary>
public ExitType TypeOfExit
{
get
{
return _exitType;
}
set
{
_exitType = value;
}
}
#endregion
#region 方法
/// <summary>
/// 使用Socket對象的Handle值作為HashCode,它具有良好的線性特征.
/// </summary>
/// <returns></returns>
public override int GetHashCode()
{
return (int)_cliSock.Handle;
}
/// <summary>
/// 返回兩個Session是否代表同一個客戶端
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public override bool Equals(object obj)
{
Session rightObj = (Session)obj;
return (int)_cliSock.Handle == (int)rightObj.ClientSocket.Handle;
}
/// <summary>
/// 重載ToString()方法,返回Session對象的特征
/// </summary>
/// <returns></returns>
public override string ToString()
{
string result = string.Format("Session:{0},IP:{1}",
_id,_cliSock.RemoteEndPoint.ToString());
//result.C
return result;
}
/// <summary>
/// 構造函數
/// </summary>
/// <param name="cliSock">會話使用的Socket連接</param>
public Session( Socket cliSock)
{
Debug.Assert( cliSock !=null );
_cliSock = cliSock;
_id = new SessionId( (int)cliSock.Handle);
}
/// <summary>
/// 關閉會話
/// </summary>
public void Close()
{
Debug.Assert( _cliSock !=null );
//關閉數據的接受和發送
_cliSock.Shutdown( SocketShutdown.Both );
//清理資源
_cliSock.Close();
}
#endregion
#region ICloneable 成員
object System.ICloneable.Clone()
{
Session newSession = new Session(_cliSock);
newSession.Datagram = _datagram;
newSession.TypeOfExit = _exitType;
return newSession;
}
#endregion
}
/// <summary>
/// 唯一的標志一個Session,輔助Session對象在Hash表中完成特定功能
/// </summary>
public class SessionId
{
/// <summary>
/// 與Session對象的Socket對象的Handle值相同,必須用這個值來初始化它
/// </summary>
private int _id;
/// <summary>
/// 返回ID值
/// </summary>
public int ID
{
get
{
return _id;
}
}
/// <summary>
/// 構造函數
/// </summary>
/// <param name="id">Socket的Handle值</param>
public SessionId(int id)
{
_id = id;
}
/// <summary>
/// 重載.為了符合Hashtable鍵值特征
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public override bool Equals(object obj)
{
if(obj != null )
{
SessionId right = (SessionId) obj;
return _id == right._id;
}
else if(this == null)
{
return true;
}
else
{
return false;
}
}
/// <summary>
/// 重載.為了符合Hashtable鍵值特征
/// </summary>
/// <returns></returns>
public override int GetHashCode()
{
return _id;
}
/// <summary>
/// 重載,為了方便顯示輸出
/// </summary>
/// <returns></returns>
public override string ToString()
{
return _id.ToString ();
}
}
/// <summary>
/// 服務器程序的事件參數,包含了激發該事件的會話對象
/// </summary>
public class NetEventArgs:EventArgs
{
#region 字段
/// <summary>
/// 客戶端與服務器之間的會話
/// </summary>
private Session _client;
#endregion
#region 構造函數
/// <summary>
/// 構造函數
/// </summary>
/// <param name="client">客戶端會話</param>
public NetEventArgs(Session client)
{
if( null == client)
{
throw(new ArgumentNullException());
}
_client = client;
}
#endregion
#region 屬性
/// <summary>
/// 獲得激發該事件的會話對象
/// </summary>
public Session Client
{
get
{
return _client;
}
}
#endregion
}
#endregion
}
具體調用為:
using System;
using System.Collections;
using System.Diagnostics;
using System.Net.Sockets;
using System.Net;
using System.Text;
using System.Threading;
namespace test
{
/// <summary>
/// Class1 的摘要說明。
/// </summary>
class Class1
{
/// <summary>
/// 應用程序的主入口點。
/// </summary>
[STAThread]
static void Main(string[] args)
{
//
// TODO: 在此處添加代碼以啟動應用程序
//
string op="";
while((op=Console.ReadLine())!="exit" )
{
if(op!="")
{
s( op);
}
}
}
static void s(string d)
{
Client.CSocket _socket=new Client.CSocket();
_socket.Connect("192.168.0.100",9010);
_socket.Send(d +"<EOF>");
sd ds=new sd();
_socket.ReceivedDatagram+=new Client.NetEvent(ds.asd);
}
}
class sd
{
public void asd(object send,Client.NetEventArgs e)
{
}
}
}
用<eof>標記來說明一段報文的結束,同時在各個階段可以構造事件讓兩個類更通用些,基本上完成了socket的異步通訊,可以再增加一個協議類,你可以利用兩類來實現符合你業務邏輯的協議,相互通訊