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

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

  

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


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

  1. public class SocketStruct
  2. {
  3. [Serializable]//指示可以序列化
  4. [StructLayout(LayoutKind.Sequential, Pack = 1)]//按1字节对齐
  5. public struct Operator
  6. {
  7. public ushort id;
  8.  
  9. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 11)]//大小11个字节
  10. public char[] name;
  11.  
  12. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 9)]//大小9个字节
  13. public char[] password;
  14.  
  15. //结构体的构造函数
  16. public Operator(string _name, string _password)
  17. {
  18. this.id = 1000;
  19. //string.PadRight(int length, char ch);
  20. //把这个字符串扩充到11个字符的长度,在右边填充字符‘\0’
  21. //达到的效果就是字符串左对齐,右边填充一些字符
  22. this.name = _name.PadRight(11, '\0').ToCharArray();
  23. this.password = _password.PadRight(9, '\0').ToCharArray();
  24. }//构造函数
  25.  
  26. }//struct
  27. }

  


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

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

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

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

对应的C++结构体是:

  1. typedef struct
  2. {
  3. WORD id;
  4. CHAR namep[11];
  5. CHAR password[9];
  6. }Operator;

  

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

  1. public class BytesAndStruct
  2. {
  3. /// <summary>
  4. /// 将结构转化为字节数组
  5. /// </summary>
  6. /// <param name="obj">结构对象</param>
  7. /// <returns>字节数组</returns>
  8. public static byte[] StructToBytes(object obj)
  9. {
  10. //得到结构体的大小
  11. int size = Marshal.SizeOf(obj);
  12. //分配结构体大小的内容空间
  13. IntPtr structPtr = Marshal.AllocHGlobal(size);
  14. //将结构体copy到分配好的内存空间
  15. Marshal.StructureToPtr(obj, structPtr, false);
  16.  
  17. //创建byte数组
  18. byte[] bytes = new byte[size];
  19.  
  20. //从内存空间拷贝到byte数组
  21. Marshal.Copy(structPtr, bytes, 0, size);
  22.  
  23. //释放内存空间
  24. Marshal.FreeHGlobal(structPtr);
  25. //返回byte数组
  26. return bytes;
  27. }//StructToBytes
  28.  
  29. /// <summary>
  30. /// byte数组转换为结构
  31. /// </summary>
  32. /// <param name="bytes">byte数组</param>
  33. /// <param name="type">结构类型</param>
  34. /// <returns>转换后的结构</returns>
  35. public static object BytesToStruct(byte[] bytes, Type type)
  36. {
  37. //得到结构体的大小
  38. int size = Marshal.SizeOf(type);
  39. //byte数组的长度小于结构的大小,不能完全的初始化结构体
  40. if (size > bytes.Length)
  41. {
  42. //返回空
  43. return null;
  44. }
  45.  
  46. //分配结构大小的内存空间
  47. IntPtr structPtr = Marshal.AllocHGlobal(size);
  48. //将byte数组拷贝到分配好的内存空间
  49. Marshal.Copy(bytes, 0, structPtr, size);
  50. //将内存空间转换为目标结构
  51. object obj = Marshal.PtrToStructure(structPtr, type);
  52. //释放内存空间
  53. Marshal.FreeHGlobal(structPtr);
  54. //返回结构
  55. return obj;
  56. }
  57. }

  

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


写一点注意的技巧:

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

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

  

这里其实就是:

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

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


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

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

  1. string 转换成 Char[]
  2. string ss="abcdefg";
  3. char[] cc=ss.ToCharArray();
  4.  
  5. Char[] 转换成string
  6. string s=new string(cc);

  

先来看客户端代码:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5.  
  6. using System.Net;
  7. using System.Net.Sockets;
  8. using System.Threading;
  9.  
  10. namespace ConsoleApplication7
  11. {
  12. class Program
  13. {
  14. private static byte[] buffer = new byte[1024];
  15.  
  16. static void Main(string[] args)
  17. {
  18. //设定服务器ip地址
  19. IPAddress ip = IPAddress.Parse("127.0.0.1");
  20. Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
  21. try
  22. {
  23. clientSocket.Connect(new IPEndPoint(ip, 8887));
  24. Console.WriteLine("连接服务器成功");
  25. }
  26. catch(Exception ex)
  27. {
  28. Console.WriteLine("服务器连接失败,请按回车退出");
  29. return;
  30. }
  31.  
  32. //通过clientSocket接收数据
  33. int receiveNumber = clientSocket.Receive(buffer);
  34. byte[] receiveBytes = new byte[receiveNumber];
  35. //利用Array的Copy方法,把buffer的有效数据放置到一个新的字节数组
  36. Array.Copy(buffer, receiveBytes, receiveNumber);
  37.  
  38. //建立一个新的Operator类
  39. SocketStruct.Operator myOper = new SocketStruct.Operator();
  40. myOper = (SocketStruct.Operator)(BytesAndStruct.BytesToStruct(receiveBytes, myOper.GetType()));
  41. string id = myOper.id.ToString();
  42. string name = new string(myOper.name);
  43. string password = new string(myOper.password);
  44. Console.WriteLine("结构体收到:" + id + " " + name + " " + password );
  45.  
  46. //启动新的线程,给Server连续发送数据
  47. Thread sendThread = new Thread(SendMessage);
  48. //把线程设置为前台线程,不然Main退出了线程就会死亡
  49. sendThread.IsBackground = false;
  50. sendThread.Start(clientSocket);
  51.  
  52. Console.ReadKey();
  53.  
  54. }//Main
  55.  
  56. /// <summary>
  57. /// 启动新的线程,发送数据
  58. /// </summary>
  59. /// <param name="clientSocket"></param>
  60. private static void SendMessage(object clientSocket)
  61. {
  62. Socket sendSocket = (Socket)clientSocket;
  63. //利用新线程,通过sendSocket发送数据
  64. for (int i = 0; i < 10; i++)
  65. {
  66. try
  67. {
  68. Thread.Sleep(1000);
  69. string sendMessage = "client send Message Hellp" + DateTime.Now;
  70.  
  71. sendSocket.Send(Encoding.ASCII.GetBytes(sendMessage));
  72. Console.WriteLine("向服务器发送消息:{0}", sendMessage);
  73.  
  74. }
  75. catch (Exception ex)
  76. {
  77. sendSocket.Shutdown(SocketShutdown.Both);
  78. sendSocket.Close();
  79. //一旦出错,就结束循环
  80. break;
  81. }
  82. }//for
  83. }//SendMessage()
  84.  
  85. }//class
  86. }

  

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

服务器代码:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5.  
  6. using System.Net;
  7. using System.Net.Sockets;
  8. using System.Threading;
  9.  
  10. namespace ConsoleApplication6
  11. {
  12. class Program
  13. {
  14. //定义接收缓冲数组,端口号,监听socket
  15. private static byte[] buffer = new byte[1024];
  16. private static int port = 8887;
  17. private static Socket serverSocket;
  18. private static byte[] Message = BytesAndStruct.StructToBytes(new SocketStruct.Operator("stemon", "@xiao"));
  19.  
  20. static void Main(string[] args)
  21. {
  22. //服务器IP地址
  23. IPAddress ip = IPAddress.Parse("127.0.0.1");
  24.  
  25. serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
  26. serverSocket.Bind(new IPEndPoint(ip, port));//绑定IP地址:端口
  27. serverSocket.Listen(10);//设定最多10个连接请求排队
  28. Console.WriteLine("监听:" + serverSocket.LocalEndPoint.ToString());
  29.  
  30. //建立线程监听client连接请求
  31. Thread myThread = new Thread(ListenClientConnection);
  32. //myThread.IsBackground = true;
  33. myThread.Start();
  34.  
  35. }//Main()
  36.  
  37. /// <summary>
  38. /// 新线程:监听客户端连接
  39. /// </summary>
  40. private static void ListenClientConnection()
  41. {
  42. while (true)
  43. {
  44. Socket clientSocket = serverSocket.Accept();
  45. //把转换好的字节数组发送出去
  46. clientSocket.Send(Message);
  47. //没接收到一个连接,启动新线程接收数据
  48. Thread receiveThread = new Thread(ReceiveMessage);
  49. receiveThread.Start(clientSocket);
  50. }//while
  51.  
  52. }//ListenClientConnection()
  53.  
  54. /// <summary>
  55. /// 接收数据消息
  56. /// </summary>
  57. /// <param name="clientSocket">监听socket生成的普通通信socket</param>
  58. private static void ReceiveMessage(object clientSocket)
  59. {
  60. Socket myClientSocket = (Socket)clientSocket;
  61. while (true)
  62. {
  63. try
  64. {
  65. //通过myClientSocket接收数据
  66. int receiveNumber = myClientSocket.Receive(buffer);
  67. Console.WriteLine("接收客户端{0}消息{1}", myClientSocket.RemoteEndPoint.ToString(), Encoding.ASCII.GetString(buffer, 0, receiveNumber));
  68. }
  69. catch(Exception ex)
  70. {
  71. Console.WriteLine(ex.Message);
  72. //关闭所有的Socket连接功能Receive、Send、Both
  73. myClientSocket.Shutdown(SocketShutdown.Both);
  74. myClientSocket.Close();
  75. break;
  76. }
  77. }//while
  78. }//ReceiveMessage()
  79.  
  80. }//class
  81. }

  

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

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

[C#技术参考]Socket传输结构数据的更多相关文章

  1. Java开发笔记(一百一十四)利用Socket传输文本消息

    前面介绍了HTTP协议的网络通信,包括接口调用.文件下载和文件上传,这些功能固然已经覆盖了常见的联网操作,可是HTTP协议拥有专门的通信规则,这些规则一方面有利于维持正常的数据交互,另一方面不可避免地 ...

  2. Asp.NET MVC 技术参考:http://kb.cnblogs.com/zt/mvc/

    Asp.NET MVC 技术参考:http://kb.cnblogs.com/zt/mvc/

  3. Entity Framework 技术参考:http://kb.cnblogs.com/zt/ef/

    Entity Framework 技术参考:http://kb.cnblogs.com/zt/ef/

  4. Java使用Socket传输文件遇到的问题(转)

    1.写了一个socket传输文件的程序,发现传输过去文件有问题.找了一下午终于似乎找到了原因,记录下来警示一下: 接受文件的一端,向本地写文件之前使用Thread.sleep(time)休息一下就解决 ...

  5. Java使用Socket传输文件遇到的问题

    1.写了一个socket传输文件的程序,发现传输过去文件有问题.找了一下午终于似乎找到了原因,记录下来警示一下: 接受文件的一端,向本地写文件之前使用Thread.sleep(time)休息一下就解决 ...

  6. C#网络编程技术微软Socket实战项目演练(三)

    一.课程介绍 本次分享课程属于<C#高级编程实战技能开发宝典课程系列>中的第三部分,阿笨后续会计划将实际项目中的一些比较实用的关于C#高级编程的技巧分享出来给大家进行学习,不断的收集.整理 ...

  7. C++ socket 传输不同类型数据的四种方式

    使用socket传输组织好的不同类型数据,有四种不同的方式(我知道的嘿嘿): a. 结构体 b. Json序列化 c. 类对象 d. protobuf 下面逐一整理一下,方便以后进行项目开发. 1. ...

  8. 利用OpenSSL库对Socket传输进行安全加密(RSA+AES)

    轉自:http://blog.chinaunix.net/uid-9543173-id-3921143.html 利用OpenSSL库对Socket传输进行安全加密(RSA+AES) 1. 利用RSA ...

  9. python socket 传输文件

    推荐资料 https://www.cnblogs.com/xiaokang01/p/9865724.html socket传输文件 思路: # 先将报头转换成字符串(json.dumps), 再将字符 ...

随机推荐

  1. android开发之Animations的使用(二)

    android开发之Animations的使用(二) 本博文主要讲述的是android开发中的animation动画效果的使用,和上一篇博文不同的是,此次四种动画效果,主要使用的是xml文件实现的,提 ...

  2. UBUNTU 下如何升级 gcc, g++

    正如大家所知道的GCC并不支持"make uninstall". 一种推荐安装方式就是把GCC 安装在你自己指定的一个路径,当你不须要某个GCC版本号的时候你仅仅须要移除相应版本号 ...

  3. csharp中DateTime总结

    Table of Contents 1 时间格式输出 2 求某天是星期几 3 字符串转换为DateTime 3.1 String->DateTime 的弹性做法 4 计算2个日期之间的天数差 5 ...

  4. hdu 4639 Hehe (dp)

    一道dp题,转移方程不是自己推出来的. 题目的意思是用‘qnmlgb’替换‘hehe’,可以替换也可以不替换,问有多少种情况. 如果结尾不是‘hehe’,那么dp[i]=dp[i-1],如果是是‘he ...

  5. Linux学习之nl命令

    nl命令在linux系统中用来计算文件中行号.nl 可以将输出的文件内容自动的加上行号!其默认的结果与 cat -n 有点不太一样, nl 可以将行号做比较多的显示设计,包括位数与是否自动补齐 0 等 ...

  6. JVM学习之类的卸载机制

    类的生命周期 当Sample类被加载.连接和初始化后,它的生命周期就开始了,当代表Sample类的Class对象不再被引用,即不可触及时,Class对象就会结束生命周期,Sample类在方法区内的数据 ...

  7. MySQL函数笔记

    MySQL函数笔记 日期函数 SELECT t1.xcjyrq, t1.* FROM view_sbxx t1 WHERE t1.syzt ; SELECT t1.xcjyrq, t1.* FROM ...

  8. JavaWeb核心编程之Tomcat安装和配置

    什么是JavaWeb 在Sun的Java Servlet规范中, 对Java Web应用做了这样的定义: "Java Web应用由一组Servlet, HTML页面, 类, 以及其他可以被绑 ...

  9. Android R.layout. 找不到已存在的布局文件

    今天写新页面的时候,突然发现R.layout.  无法找到我已经写好的页面,于是顿时就不淡定了. 把R文件翻了一遍  发现也没有.... 然后我就看到了这个. android.R 原来是我错把Andr ...

  10. 测试通用的InsertOrUpdate