socket通信实例

闲来无事,研究了下socket~  文章最后会给出实例链接,如果不想看介绍的可以直接下下来运行。   本人小白一枚,如有错误请看者不奢赐教。

客户端为unity,服务端为vs,都是用c#语言编写。
先说下我对socket的理解,建立连接、通信、释放连接。此为socket通信的三次握手。

对于socket通信,c#底层已经为我们封装好了,我们可以选择使用UDP还是TCP,这里我使用的是TCP连接。

先介绍下socket通信的基础 : **建立连接,发送数据,接收数据,释放连接**。

建立连接 :

// 该类用于阻塞当前线程,并等待信号,接收到继续进行的信号后,释放等待线程
private static ManualResetEvent connectDone = new ManualResetEvent (false);

// 设置ip地址与端口号
IPEndPoint remoteEP = new IPEndPoint (IPAddress.Parse (serverIP), port); 
// 创建socket
tcpSockt = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 
// 异步创建socket连接
tcpSockt.BeginConnect (remoteEP, new AsyncCallback (connectCallback), tcpSockt); 
if (connectDone.WaitOne (timeOutSec, false)) { // 阻塞当前线程, socket连接成功或超时,流程继续
                if (zSocketManager.IsConnected) {
                    receive (tcpSockt);
                    return;
                } else {
                    throw new ArgumentException ("连接失败,请检查网络设置");
                }
            } else {
                tcpSockt.Close ();
                throw new ArgumentException ("连接失败,请检查网络设置");
            }

        } catch (Exception e) {
            Debug.Log (e.ToString ());  
        }

发送数据
发送数据时,我们需要与服务定义好一些包头,包括服务器版本号、协议数据长度、协议号等。将这些包头数据与协议数据拼接成真正的数据后通过Socket.Send方法发送给服务器。

public void Send (string data, NetCommand _command)
    {
        byte[] bytesData = Encoding.UTF8.GetBytes (data); // 字符串转成字节
        bytesData = packMsg (bytesData, _command); // 数据添加包头
        if (tcpSockt == null || !tcpSockt.Connected)
            throw new ArgumentException ("参数socket为null,或者未连接到远程计算机");
        else if (bytesData.Length == 0) {
            throw new ArgumentException ("参数data为null ,或者长度为 0");
        } else {
            send (bytesData);
        }
    }

// 拼接包头和协议数据
byte[] packMsg (byte[] _data, NetCommand _command)
    {
        byte[] head = new byte[17];
        head [0] = 20;
        head [1] = 15;
        head [2] = 8;
        head [3] = 18;
        head [4] = 0; // protoVersion

        Array.Copy (System.BitConverter.GetBytes (0), 0, head, 5, 4); // serverVersion

        byte[] _dataLength = new byte[4]; // 把数据长度添加到 包头中
        _dataLength = System.BitConverter.GetBytes (_data.Length);
        Array.Copy (_dataLength, 0, head, 9, 4);

        byte[] _commandByte = new byte[4];
        _commandByte = System.BitConverter.GetBytes ((int)_command);
        Array.Copy (_commandByte, 0, head, 13, 4);

        byte[] _result = new byte[head.Length + _data.Length];
        Array.Copy (head, 0, _result, 0, head.Length);
        Array.Copy (_data, 0, _result, head.Length, _data.Length);

        return _result;
    }

    void send (byte[] _data)
    {
        int leftLength = _data.Length;
        int hasSend = 0;
        int flag = 0;
        while (true) { // 通过循环将数据全部发送出去
            if (tcpSockt.Poll (timeOutSec, SelectMode.SelectWrite)) {

                var _sendLen = tcpSockt.Send (_data, hasSend, leftLength, SocketFlags.None);
                leftLength -= _sendLen;
                hasSend += _sendLen;
                if (leftLength == 0) { // 协议数据已全部发送完
                    flag = 0;
                    break;
                } else {
                    if (_sendLen > 0)
                        continue;
                    else { // 发送数据时出错
                        flag = -2;
                    }
                }
            } else { // 发送超时
                flag = -1;
                break;
            }
        }

        if (flag != 0)
            Debug.LogError ("send flag :[" + flag + "]");
    }

接收数据
接收数据我用的是异步接收。接收数据时会出现粘包和半包问题(我对于粘包的理解是要接收的数据大于缓存数据大小,需要多次接收。而半包就是要接收的数据小于缓存数据大小),主要的解决思路就是设置一个整形变量a,当接收到数据时,判断a是否为0,如果为0,则该数据为新的协议数据),对其进行拆包,获取包头和协议数据。如果a大于0,则为粘包,需要接收剩余数据。

// 通过该方法让socket监听接收数据
void receive (Socket _client)
    {
        try {
            StateObject state = new StateObject ();
            state.CreateBuffer ();
            state.workSocket = _client;
            // socket开始异步接收数据,当有接收到数据时,会调用receiveCallback方法
            _client.BeginReceive (state.Buffer, 0, StateObject.BufferSize, SocketFlags.None, new AsyncCallback (receiveCallback), state);
        } catch (Exception e) {
            Debug.LogError (e.ToString ());
        }
    }

void receiveCallback (IAsyncResult _ar)
    {
        try {
            StateObject state = (StateObject)_ar.AsyncState;
            Socket client = state.workSocket;
            if (!client.Connected) {
                Debug.LogError ("服务器断开连接");
                DisConnect ();
                return;
            }

            // 开始接收数据, 本次最多接收  BufferSize 个字节
            int bytesRead = client.EndReceive (_ar);
            Debug.Log ("已接收 字节数  " + bytesRead);
            if (bytesRead > 0) {
                if (leftLength > 0) { // 继续接收剩余数据

                    leftLength -= bytesRead;
                    bytebuffer.Add (state.Buffer);

                    if (leftLength <= 0) { // 数据接收完毕
                        receiveData.bytebuffer = bytebuffer.GetBytes();
                        receiveData.data = getString(receiveData.bytebuffer);
                        if (receiveDataCallback != null)
                            receiveDataCallback (receiveData);
                    }
                } else {
                    receiveData = null;
                    receiveData = new ReceiveData ();

                    unpackMsg (state.Buffer,bytesRead,  out receiveData.command, out leftLength);

                    if (leftLength <= 0) { // 数据接收完毕
                        receiveData.bytebuffer = bytebuffer.GetBytes();
                        receiveData.data = getString(receiveData.bytebuffer);
                        if (receiveDataCallback != null)
                            receiveDataCallback (receiveData);
                    }
                }
            }
            receive (client);
        } catch (Exception e) {         
            Debug.LogError (e.ToString ());
        }
    }

// 拆包
void unpackMsg (byte[] _data, int _hasReceived, out NetCommand _command, out int _leftSize)
    {       
        byte[] head = new byte[17]; // 包头大小为17
        for (int i = 0; i < head.Length; i++) {
            head [i] = _data [i];
        }

        byte[] _dataLengthByte = new byte[4];
        for (int i = 0; i < _dataLengthByte.Length; i++) {
            _dataLengthByte [i] = head [i + 9];
        }
        // 获取协议数据大小
        _leftSize = System.BitConverter.ToInt32 (_dataLengthByte, 0);

        byte[] _commandByte = new byte[4];
        for (int i = 0; i < _commandByte.Length; i++) {
            _commandByte [i] = head [i + 13];
        }
        // 获取协议号
        _command = (NetCommand)System.BitConverter.ToInt32 (_commandByte, 0);

        if (_leftSize + 17 > _data.Length)
            _leftSize -= _data.Length - 17; // 还有数据没发完
        else
            _leftSize = 0; // 数据发送完毕

        bytebuffer.Clear ();
        bytebuffer.Add (_data, 17, _hasReceived);
    }

释放链接
释放链接就比较简单了,一句代码的事情,调用 socket.Close()方法就能断开连接。但是,这里有一个问题,比如客户端要断开连接,做完了断开连接的处理后,向服务端发送了断开连接协议,然后调用socket.Close方法断开了连接,但是服务端并不会接收到断开连接的协议,因为socket已经断开了,也没办法做断开连接的操作,且服务端照样会发送数据给该客户端,这就出现了问题。解决的方法是,当客户端要断开连接时,先发送断开连接协议,服务端收到协议后,处理断开连接的方法,之后回给客户端断开连接的协议,客户端处理断开连接的方法,之后发送向服务端发送确认断开协议,服务端收到后,会给客户端确认断开协议,并关闭socket,客户端收到回过来的确认断开协议后,调用socket.Close方法断开链接。

至此,socket通信的理论知识就讲解到这里。如有错误请看者不奢赐教。

工程地址 : 链接:http://pan.baidu.com/s/1miNyrCs 密码:jxm7

猜你喜欢

转载自blog.csdn.net/strivero/article/details/77881840