Socket通信总结(C#)

Socket通信总结(C#

最近参与了一个项目,里面用到了socket,串口通信和GDI+等等,本以为socket是里面比较容易的部分,结果到项目结束,里面问题出得最多的就是socket,个人感觉C#里面的socket类过于简单了,很多问题不得不靠开发人员的实际经验去解决,不过到最后项目好歹是完成了,现在把经验总结一下,希望大家以后能够少走点弯路。完整的代码(我不是代码的作者)我就不提供了,我只讲讲设计思路,如果需要完整代码的可以去参考这两篇文章:

Socket编程基础:http://www.cnblogs.com/playboy840616/archive/2007/02/15/651077.html

异步socket通信总结:http://blog.csdn.net/lnheel/archive/2009/10/31/4751443.aspx

至于什么是socketsocket的通信原理是什么这类基础问题,不在本文讨论范围之内,请读者自行查阅相关资料。

 

(一)   基础篇

要使用socket,首先需要要知道socket的基础通讯方法:

客户端代码的基本思路就是先建立连接,然后用一个字节缓冲区接收数据。

连接:

        socket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);

        IPEndPoint ip = new IPEndPoint(IPAddress.Parse(Environment.IP), Environment.Port);

        socket.Connect(ip);

数据接收(服务端类似):

                if (socket != null)

                {

                    string data = string.Empty;

                    byte[] bytes = new byte[1024];

                    int bytesRec = this.socket.Receive(bytes);

                    if (bytesRec == 0) {  }

                    data += Encoding.ASCII.GetString(bytes, 0, bytesRec);

               }

服务端先启动监听,一旦有客户端连接,就会获得连接,发送数据。

启动监听:

                int port = Convert.ToInt32(cfg.GetServerParm().Port);

                //byte[] ips = { Convert.ToByte(ipt[0]), Convert.ToByte(ipt[1]), Convert.ToByte(ipt[2]), Convert.ToByte(ipt[3]) };

                //IPAddress ip = new IPAddress(ips);o

                IPEndPoint ip = new IPEndPoint(long.Parse("0"), port);

                if (listener == null)

                    listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                listener.Bind(ip);

                listener.Listen(20);

 

获得连接:

Socket clientSocket = listener.Accept();

发送数据:

byte[] sceneData = Encoding.ASCII.GetBytes(sceneMsg);

             clientSocket.Send(sceneData, sceneData.Length, 0);

上面这些代码就是socket的基础通信代码(直接从项目里面摘抄下来的,可能有些命名比较怪异),但是仅仅靠这些代码是完全不够的,需要对服务端和客户端的各个通信阶段进行改写,才能完成一些基本任务。下面探讨socket的同步和异步通信问题。

 

(二)   进阶篇

从服务端的监听模式来看,socket通信似乎更适合异步,因为服务端只监听某个端口,而并不关心连接是否存在。但是有些时候我们需要知道连接的状态,特别是当一个服务端连接多个客户端的时候,甚至还需要根据连接来辨识客户端的ID,否则服务端无法向多个客户端主动发起通信要求。那么,怎么解决这个问题呢?首先,我们需要用一个线程来管理一个连接,这样多个客户端建立的连接就能够在一个服务端并行不悖。

RemoteClient wapper = new RemoteClient(clientSocket);

Thread wapperThread = new Thread(new ThreadStart(wapper.ReceivedFromClient));

wapperThread.Start();

如上述代码,每建立一个连接就建立一个线程对该连接进行管理,并且标识该连接。但是问题来了,你并不知道过了一段时间之后,这个连接是否存在,客户端可能因为其他原因把连接关掉了,但是服务端这个线程还存在,因此在客户端也需要用一个线程来保持与服务端的同步,只要客户端的这个线程存在,就能够不断接受从服务端发来的消息,一旦线程结束,它就通知服务端连接结束。在下面的代码中,isConnected用来标识连接状态,isLogin用来标识数据接收。在客户端上的过程是这样的,客户端首先打开一个线程,保持连接状态,一旦连接建立,开启一个新的线程接收数据,只要login状态还在,客户端就能持续地从服务端接收数据。相当于客户端用一个线程“监听”了连接状态。

public  void KeepAlieve()

        {

            while (true)

            {

                while (!isConnected)

                {

                    try {

                        socket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);

                        IPEndPoint ip = new IPEndPoint(IPAddress.Parse(Environment.IP), Environment.Port);

                        socket.Connect(ip);

                        //socket.Connect("localhost", 8500);

                        if (socket.Connected)

                        {

                            isConnected = true;

                            isLogin = false;

                        }

                    }

                    catch(Exception ex) {

                        Thread.Sleep(100);

                        isConnected = false; }

                }

                while (!isLogin)

                {

                    try

                    {

                        isLogin = true;// a Recive线?¨¬

                        string msg = string.Format("<MSG CNo=/"{0}/" Type=/"Login/" SceneName=/"/" Time=/"{1}/"></MSG>",Environment.CNo,Comm.ToTinyDateTime(DateTime.Now));

                        byte[] data = Encoding.ASCII.GetBytes(msg);

                        socket.Send(data,data.Length,0);

                        receiveThread = new Thread(new ThreadStart(Received));

                        receiveThread.Start();

                    }

                    catch { isConnected = false; isLogin = true; }

                }

                //ts = DateTime.Now.Subtract(lastReceiveTime);

                //if (ts.Seconds > 30)

                //{

                //    isConnected = false;

                //    isLogin = false;

                //    lastReceiveTime = DateTime.Now;

                //}

            }

        }

相应的,服务端每获得一个连接就创建一个RemoteClient,分配一个线程来管理这个连接,然后将该连接加入一个列表,如果连接结束,就将该连接移出列表。

连接维护

public void ReceivedFromClient()

        {

            try

            {

                while (clientSocket != null)

                {

                    string data = string.Empty;

                    byte[] bytes = new byte[1024];

                    int bytesRec = this.clientSocket.Receive(bytes);

                    if (bytesRec == 0) { LeftJoin(); }

                    data += Encoding.ASCII.GetString(bytes, 0, bytesRec);

                    switch (node.Attributes["Type"].Value.ToString().ToUpper())

                    {

                        case"LOGIN":

                            Cno = node.Attributes["CNo"].Value.ToString();

                            Join();

                            string scuMsg = string.Format("<MSG CNo=/"{0}/" Type=/"SuccessConn/" SceneName=/"/" Time=/"{1}/"></MSG>",Cno,Comm.ToTinyDateTime(DateTime.Now));

                            byte[] back = Encoding.ASCII.GetBytes(scuMsg);

                            ClientSocket.Send(back, back.Length, 0);

                            break;

                        case "LOGOUT":

                            break;

                    }

                }

            }

            catch (Exception ex) { LeftJoin(); }

        }

值得注意的是,在维护连接表的时候,加入和删除是不能同时进行的,必须要将二者设为互斥操作。

public void Join()

        {

            //IList<RemoteClient> tempList = WinformTest.MainWindow.connectionList;

            bool bNeedAdd = true;

            lock (WinformTest.MainWindow.connectionList)

            {

                foreach (var item in WinformTest.MainWindow.connectionList)

                {

                    if (item.CNo == this.Cno)

                    {

                        //item._Socket = ClientSocket;

                        bNeedAdd = false;

                        break;

                    }

                }

                if (bNeedAdd)

                {

                    WinformTest.ClientSocketStruce css = new WinformTest.ClientSocketStruce();

                    css.CNo = Cno;

                    css._Socket = ClientSocket;

                    WinformTest.MainWindow.connectionList.Add(css);

                }

            }

        }

 

        public void LeftJoin()

        {

            lock (WinformTest.MainWindow.connectionList)

            {

                foreach (var item in WinformTest.MainWindow.connectionList)

                {

                    if (item.CNo == Cno)

                    {

                        WinformTest.MainWindow.connectionList.Remove(item);

                        break;

                    }

                }

            }

        }

这样,借助线程就解决了socket服务端和客户端的同步问题,还有其他一些细节问题,如结束线程,数据接收事件等就不一一列出了。希望能给大家带来参考。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值