惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

腾讯CDC
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 叶小钗
人人都是产品经理
人人都是产品经理
博客园 - 聂微东
The Cloudflare Blog
爱范儿
爱范儿
阮一峰的网络日志
阮一峰的网络日志
WordPress大学
WordPress大学
小众软件
小众软件
博客园 - 三生石上(FineUI控件)
Last Week in AI
Last Week in AI
Jina AI
Jina AI
V
V2EX
罗磊的独立博客
V
Visual Studio Blog
A
About on SuperTechFans
IT之家
IT之家
P
Proofpoint News Feed
B
Blog
博客园 - Franky
Blog — PlanetScale
Blog — PlanetScale
Google DeepMind News
Google DeepMind News
Y
Y Combinator Blog

博客园 - lzlvv

debug1: Authentications that can continue: publickey sgrptprod@114.141.166.109: Permission denied (publickey). Connection closed. linux sftp 设置了用户对目录有 0700权限,但在上传时报 Permission denied错误 linux 服务器设置 时 /etc/passwd设置为nologin时,登录SFTP会报 No supported authentication methods available win10系统已经映射的网络驱动器在其他应用软件无法读到解决方案 Linux SFTP服务器部署 linux添加用户并赋予root权限 C# Socket通信 Windows Server 2016如何关闭和打开防火墙 | 详细教程 在‌Windows Server 2016中显示‌桌面图标的方法 C#常用正则表达式 完全图解RAID存储技术:RAID 0、1、5、6、10、50、60 MySQL 5.7 安装教程(Win 10) 在 Windows 使用 PuTTYgen 上生成 SSH 密钥 NPOI使用手册 MSSQL中添加链接数据库 Windows 批处理(bat)语法大全 Oracle ODP.NET ConnectionString接池及连接参数 SQL update select结合语句详解及应用 从FoxPro数据库中提取结构/索引到Excel
Socket传输结构数据
lzlvv · 2024-12-31 · via 博客园 - lzlvv

转自:https://www.cnblogs.com/stemon/p/4204950.html

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

1

void *memcpy(void *dest, const void *src, size_t n);

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


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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

public class SocketStruct

{

   [Serializable]

   [StructLayout(LayoutKind.Sequential, Pack = 1)]

   public struct Operator

   {

       public ushort id;

       [MarshalAs(UnmanagedType.ByValArray, SizeConst = 11)]

       public char[] name;

       [MarshalAs(UnmanagedType.ByValArray, SizeConst = 9)]

       public char[] password;

       public Operator(string _name, string _password)

       {

           this.id = 1000;

           this.name = _name.PadRight(11, '\0').ToCharArray();

           this.password = _password.PadRight(9, '\0').ToCharArray();

       }

   }

}


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

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

QQ截图20150106001846

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

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

对应的C++结构体是:

1

2

3

4

5

6

typedef struct

{

    WORD id;

    CHAR namep[11];

    CHAR password[9];

}Operator;

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

public class BytesAndStruct

{

    public static byte[] StructToBytes(object obj)

    {

        int size = Marshal.SizeOf(obj);

        IntPtr structPtr = Marshal.AllocHGlobal(size);

        Marshal.StructureToPtr(obj, structPtr, false);

        byte[] bytes = new byte[size];

        Marshal.Copy(structPtr, bytes, 0, size);

        Marshal.FreeHGlobal(structPtr);

        return bytes;

    }

    public static object BytesToStruct(byte[] bytes, Type type)

    {

        int size = Marshal.SizeOf(type);

        if (size > bytes.Length)

        {

            return null;

        }

        IntPtr structPtr = Marshal.AllocHGlobal(size);

        Marshal.Copy(bytes, 0, structPtr, size);

        object obj = Marshal.PtrToStructure(structPtr, type);

        Marshal.FreeHGlobal(structPtr);

        return obj;

    }

}

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


写一点注意的技巧:

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

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做一些反射的操作了,我们这里只是用它得到结构的大小。


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

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

1

2

3

4

5

6

string 转换成 Char[]

    string ss="abcdefg";

    char[] cc=ss.ToCharArray();

Char[] 转换成string

    string s=new string(cc);

先来看客户端代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

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)

        {

            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;

            }

            int receiveNumber = clientSocket.Receive(buffer);

            byte[] receiveBytes = new byte[receiveNumber];

            Array.Copy(buffer, receiveBytes, receiveNumber);

            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 );

            Thread sendThread = new Thread(SendMessage);

            sendThread.IsBackground = false;

            sendThread.Start(clientSocket);

            Console.ReadKey();

        }

        private static void SendMessage(object clientSocket)

        {

            Socket sendSocket = (Socket)clientSocket;

            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;

                }

            }

        }

    }

}

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

服务器代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

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

    {

        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)

        {

            IPAddress ip = IPAddress.Parse("127.0.0.1");

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

            serverSocket.Bind(new IPEndPoint(ip, port));

            serverSocket.Listen(10);

            Console.WriteLine("监听:" + serverSocket.LocalEndPoint.ToString());

            Thread myThread = new Thread(ListenClientConnection);

            myThread.Start();

        }

        private static void ListenClientConnection()

        {

            while (true)

            {

                Socket clientSocket = serverSocket.Accept();

                clientSocket.Send(Message);

                Thread receiveThread = new Thread(ReceiveMessage);

                receiveThread.Start(clientSocket);

            }

        }

        private static void ReceiveMessage(object clientSocket)

        {

            Socket myClientSocket = (Socket)clientSocket;

            while (true)

            {

                try

                {

                    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);

                    myClientSocket.Shutdown(SocketShutdown.Both);

                    myClientSocket.Close();

                    break;

                }

            }

        }

    }

}

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

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