欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

常用类之TCP连接类-socket编程

程序员文章站 2023-09-04 09:59:13
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的异步通讯,可以再增加一个协议类,你可以利用两类来实现符合你业务逻辑的协议,相互通讯