常用类之TCP连接类-socket编程
程序员文章站
2022-06-03 11:02:09
tcp一般用于维持一个可信任的连接,比起udp更为安全可靠,在vs.net,分别有tcpclient和udpclient以及tcplistener,一般开发中基本可以满足需...
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的异步通讯,可以再增加一个协议类,你可以利用两类来实现符合你业务逻辑的协议,相互通讯
先实现服务器段:
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的异步通讯,可以再增加一个协议类,你可以利用两类来实现符合你业务逻辑的协议,相互通讯
上一篇: 详解Linux中退出编辑模式的命令
下一篇: VBS教程:函数-InStrRev 函数
推荐阅读
-
常用类之TCP连接类-socket编程
-
荐 Java——数据库编程JDBC之数据库连接池技术(C3P0与Druid,提供了Druid的工具类)
-
基于Java的Socket类Tcp网络编程实现实时聊天互动程序:QQ聊天界面的搭建
-
基于Java的Socket类Tcp网络编程实现实时聊天互动程序(三):回车实现数据到发送(详细代码完结)
-
基于Java的Socket类Tcp网络编程实现实时聊天互动程序(二):Tcp通信的过程及代码编写
-
简单socket编程服务端和客户端流程以及TCP类的封装
-
常用类之TCP连接类-socket编程
-
荐 Java——数据库编程JDBC之数据库连接池技术(C3P0与Druid,提供了Druid的工具类)
-
基于Java的Socket类Tcp网络编程实现实时聊天互动程序:QQ聊天界面的搭建
-
基于Java的Socket类Tcp网络编程实现实时聊天互动程序(二):Tcp通信的过程及代码编写