zoukankan      html  css  js  c++  java
  • [C#技术参考]Socket传输结构数据


    最近在做一个机器人项目,要实时的接收机器人传回的坐标信息,并在客户端显示当前的地图和机器人的位置。当然坐标的回传是用的Socket,用的是C++的结构体表示的坐标信息。但是C#不能像C++那样很easy的把字节数组byte[]直接的转换成结构,来发送和接收。在C#中要多做一些工作。但是在C或者C++中这是一件很容易的事,只需要一个函数:

    void *memcpy(void *dest, const void *src, size_t n);//从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中
    

      

    下面来完成通过C#实现Socket传输结构数据。


    1. 仿照C++的结构写出C#的结构体来:为了便于复用,把它放在一个单独类里面

    public class SocketStruct
    {
       [Serializable]//指示可以序列化
       [StructLayout(LayoutKind.Sequential, Pack = 1)]//按1字节对齐
       public struct Operator
       {
           public ushort id;
     
           [MarshalAs(UnmanagedType.ByValArray, SizeConst = 11)]//大小11个字节
           public char[] name;
     
           [MarshalAs(UnmanagedType.ByValArray, SizeConst = 9)]//大小9个字节
           public char[] password;
     
           //结构体的构造函数
           public Operator(string _name, string _password)
           {
               this.id = 1000;
               //string.PadRight(int length, char ch);
               //把这个字符串扩充到11个字符的长度,在右边填充字符‘’
               //达到的效果就是字符串左对齐,右边填充一些字符
               this.name = _name.PadRight(11, '').ToCharArray();
               this.password = _password.PadRight(9, '').ToCharArray();
           }//构造函数
     
       }//struct
    }
    

      


    2. 既然要接收C++发送过来的数据,就要注意C#和C++数据类型的对应关系:

    C++与C#的数据类型对应关系表:

    QQ截图20150106001846

    所以上面定义的整个结构的字节数是22个bytes.

    注意区分上面的字节和字符。计算机存储容量基本单位是字节(Byte),8个二进制位组成1个字节,一个标准英文字母占一个字节位置,一个标准汉字占二个字节位置。字符是一种符号,同存储单位不是一回事,它是一种抽象的、逻辑的类型,与int等一样。byte是物理的单位。

    对应的C++结构体是:

    typedef struct
    {
        WORD id;
        CHAR namep[11];
        CHAR password[9];
    }Operator;
    

      

    3. 在发送数据时,要先把结果转换成字节数组,在接收到数据之后要把字节数组还原成原本的结构。具体的代码如下,为了便于复用,写成一个类:

    public class BytesAndStruct
    {
        /// <summary>
        /// 将结构转化为字节数组
        /// </summary>
        /// <param name="obj">结构对象</param>
        /// <returns>字节数组</returns>
        public static byte[] StructToBytes(object obj)
        {
            //得到结构体的大小
            int size = Marshal.SizeOf(obj);
            //分配结构体大小的内容空间
            IntPtr structPtr = Marshal.AllocHGlobal(size);
            //将结构体copy到分配好的内存空间
            Marshal.StructureToPtr(obj, structPtr, false);
     
            //创建byte数组
            byte[] bytes = new byte[size];
     
            //从内存空间拷贝到byte数组
            Marshal.Copy(structPtr, bytes, 0, size);
     
            //释放内存空间
            Marshal.FreeHGlobal(structPtr);
            //返回byte数组
            return bytes;
        }//StructToBytes
     
        /// <summary>
        /// byte数组转换为结构
        /// </summary>
        /// <param name="bytes">byte数组</param>
        /// <param name="type">结构类型</param>
        /// <returns>转换后的结构</returns>
        public static object BytesToStruct(byte[] bytes, Type type)
        {
            //得到结构体的大小
            int size = Marshal.SizeOf(type);
            //byte数组的长度小于结构的大小,不能完全的初始化结构体
            if (size > bytes.Length)
            {
                //返回空
                return null;
            }
     
            //分配结构大小的内存空间
            IntPtr structPtr = Marshal.AllocHGlobal(size);
            //将byte数组拷贝到分配好的内存空间
            Marshal.Copy(bytes, 0, structPtr, size);
            //将内存空间转换为目标结构
            object obj = Marshal.PtrToStructure(structPtr, type);
            //释放内存空间
            Marshal.FreeHGlobal(structPtr);
            //返回结构
            return obj;
        }
    }
    

      

    这是个工具类,里面的方法都是静态的。


    写一点注意的技巧:

    在结构转换成字节数据的时候,要把结构的类型作为参数传递到函数中去,所以函数接收的参数是一个类型。这时用到了C#中的Type类。

    C#中通过Type类可以访问任意数据类型信息。
    1.获取给定类型的Type引用有3种方式:
      a.使用typeof运算符,如Type t = typeof(int);
      b.使用GetType()方法,如int i;Type t = i.GetType();
      c.使用Type类的静态方法GetType(),如Type t =Type.GetType("System.Double");
    2.Type的属性:
      Name:数据类型名;
      FullName:数据类型的完全限定名,包括命名空间;
      Namespace:数据类型的命名空间;
      BaseType:直接基本类型;
      UnderlyingSystemType:映射类型;
    3.Type的方法:
      GetMethod():返回一个方法的信息;
      GetMethods():返回所有方法的信息。
    

      

    这里其实就是:

    Type type = myOper.GetType();//其中myOper是一个结构

    然后就能利用type做一些反射的操作了,我们这里只是用它得到结构的大小。


    下面就是实际的操作使用:

    在贴代码之前,先学习一个方法,我们能把字符串和字节数组很好的转换了,现在也能把结构体和字节数组转换了,但是字符数组和字符串怎么转换呢:

    string 转换成 Char[]
        string ss="abcdefg";
        char[] cc=ss.ToCharArray();
     
    Char[] 转换成string
        string s=new string(cc);
    

      

    先来看客户端代码:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
     
    using System.Net;
    using System.Net.Sockets;
    using System.Threading;
     
    namespace ConsoleApplication7
    {
        class Program
        {
            private static byte[] buffer = new byte[1024];
     
            static void Main(string[] args)
            {
                //设定服务器ip地址
                IPAddress ip = IPAddress.Parse("127.0.0.1");
                Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                try 
                {
                    clientSocket.Connect(new IPEndPoint(ip, 8887));
                    Console.WriteLine("连接服务器成功");
                }
                catch(Exception ex)
                {
                    Console.WriteLine("服务器连接失败,请按回车退出");
                    return;
                }
     
                //通过clientSocket接收数据
                int receiveNumber = clientSocket.Receive(buffer);
                byte[] receiveBytes = new byte[receiveNumber];
                //利用Array的Copy方法,把buffer的有效数据放置到一个新的字节数组
                Array.Copy(buffer, receiveBytes, receiveNumber);
     
                //建立一个新的Operator类
                SocketStruct.Operator myOper = new SocketStruct.Operator();
                myOper = (SocketStruct.Operator)(BytesAndStruct.BytesToStruct(receiveBytes, myOper.GetType()));
                string id = myOper.id.ToString();
                string name = new string(myOper.name);
                string password = new string(myOper.password);
                Console.WriteLine("结构体收到:" + id + " " + name + " " + password );
             
                //启动新的线程,给Server连续发送数据
                Thread sendThread = new Thread(SendMessage);
                //把线程设置为前台线程,不然Main退出了线程就会死亡
                sendThread.IsBackground = false;
                sendThread.Start(clientSocket);
     
                Console.ReadKey();
     
            }//Main
     
            /// <summary>
            /// 启动新的线程,发送数据
            /// </summary>
            /// <param name="clientSocket"></param>
            private static void SendMessage(object clientSocket)
            {
                Socket sendSocket = (Socket)clientSocket;
                //利用新线程,通过sendSocket发送数据
                for (int i = 0; i < 10; i++)
                {
                    try
                    {
                        Thread.Sleep(1000);
                        string sendMessage = "client send Message Hellp" + DateTime.Now;
     
                        sendSocket.Send(Encoding.ASCII.GetBytes(sendMessage));
                        Console.WriteLine("向服务器发送消息:{0}", sendMessage);
     
                    }
                    catch (Exception ex)
                    {
                        sendSocket.Shutdown(SocketShutdown.Both);
                        sendSocket.Close();
                        //一旦出错,就结束循环
                        break;
                    }
                }//for
            }//SendMessage()
     
        }//class
    }
    

      

    这里注意一下,我们的接收数据缓冲区一般都设置的要比实际接收的数据要大,所以会空出一部分。但是在把字节数组转换成结构的时候,要丢弃这些空白,所以按照接收到的字节的大小,重新new一个字节数字,并把有效数据拷贝进去。然后再转换成结构。

    服务器代码:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
     
    using System.Net;
    using System.Net.Sockets;
    using System.Threading;
     
    namespace ConsoleApplication6
    {
        class Program
        {
            //定义接收缓冲数组,端口号,监听socket
            private static byte[] buffer = new byte[1024];
            private static int port = 8887;
            private static Socket serverSocket;
            private static byte[] Message = BytesAndStruct.StructToBytes(new SocketStruct.Operator("stemon", "@xiao"));
     
            static void Main(string[] args)
            {
                //服务器IP地址
                IPAddress ip = IPAddress.Parse("127.0.0.1");
     
                serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                serverSocket.Bind(new IPEndPoint(ip, port));//绑定IP地址:端口
                serverSocket.Listen(10);//设定最多10个连接请求排队
                Console.WriteLine("监听:" + serverSocket.LocalEndPoint.ToString());
     
                //建立线程监听client连接请求
                Thread myThread = new Thread(ListenClientConnection);
                //myThread.IsBackground = true;
                myThread.Start();
     
            }//Main()
     
            /// <summary>
            /// 新线程:监听客户端连接
            /// </summary>
            private static void ListenClientConnection()
            {
                while (true)
                {
                    Socket clientSocket = serverSocket.Accept();
                    //把转换好的字节数组发送出去
                    clientSocket.Send(Message);
                    //没接收到一个连接,启动新线程接收数据
                    Thread receiveThread = new Thread(ReceiveMessage);
                    receiveThread.Start(clientSocket);
                }//while
     
            }//ListenClientConnection()
     
            /// <summary>
            /// 接收数据消息
            /// </summary>
            /// <param name="clientSocket">监听socket生成的普通通信socket</param>
            private static void ReceiveMessage(object clientSocket)
            { 
                Socket myClientSocket = (Socket)clientSocket;
                while (true)
                {
                    try
                    {
                        //通过myClientSocket接收数据
                        int receiveNumber = myClientSocket.Receive(buffer);
                        Console.WriteLine("接收客户端{0}消息{1}", myClientSocket.RemoteEndPoint.ToString(), Encoding.ASCII.GetString(buffer, 0, receiveNumber));
                    }
                    catch(Exception ex)
                    {
                        Console.WriteLine(ex.Message);
                        //关闭所有的Socket连接功能Receive、Send、Both
                        myClientSocket.Shutdown(SocketShutdown.Both);
                        myClientSocket.Close();
                        break;
                    }
                }//while
            }//ReceiveMessage()
     
        }//class
    }
    

      

    这个程序代码的不好之处在于没有很好的处理好socket的关闭。不过这不是重点。

    重点是实现C#中发送结构体数据。

  • 相关阅读:
    ZOJ 1002 Fire Net (火力网)
    UVa OJ 117 The Postal Worker Rings Once (让邮差只走一圈)
    UVa OJ 118 Mutant Flatworld Explorers (变体扁平世界探索器)
    UVa OJ 103 Stacking Boxes (嵌套盒子)
    UVa OJ 110 MetaLoopless Sorts (无循环元排序)
    第一次遇到使用NSNull的场景
    NSURL使用浅析
    从CNTV下载《小小智慧树》
    NSDictionary and NSMutableDictionary
    Category in static library
  • 原文地址:https://www.cnblogs.com/stemon/p/4204950.html
Copyright © 2011-2022 走看看