首页 > 其他分享 >Socket传输结构数据

Socket传输结构数据

时间:2024-12-31 14:41:58浏览次数:1  
标签:字节 System 传输 new byte 结构 string Socket

转自: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);//从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中

  

下面来完成通过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)]//按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个字符的长度,在右边填充字符‘\0’            //达到的效果就是字符串左对齐,右边填充一些字符            this.name = _name.PadRight(11, '\0').ToCharArray();            this.password = _password.PadRight(9, '\0').ToCharArray();        }//构造函数       }//struct }

  


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 {     /// <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类。

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)         {             //设定服务器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一个字节数字,并把有效数据拷贝进去。然后再转换成结构。

服务器代码:

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     {         //定义接收缓冲数组,端口号,监听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#中发送结构体数据。

标签:字节,System,传输,new,byte,结构,string,Socket
From: https://www.cnblogs.com/Jamesblog/p/18643988

相关文章

  • 设计模式--外观模式(门面模式)【结构型模式】
    设计模式的分类我们都知道有23种设计模式,这23种设计模式可分为如下三类:创建型模式(5种):单例模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式。结构型模式(7种):适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。行为型模式(11种):策略......
  • (BOSS)Poker time (使用结构)
    (......
  • Web Socket连接以及STOMP协议
    一、WebSocketWebSocket是一种网络通信协议,它允许在Web应用程序和服务器之间建立实时、双向的通信连接。WebSocket协议基于TCP,它允许客户端和服务器之间建立一个持久的连接,通过这个连接可以实时交换数据,就像聊天一样,双方可以随时发送和接收信息。WebSocket的生命周期包括四个阶......
  • 【Java编程】聊聊jvm的内存结构, 以及各种结构的作用
    一、什么是JVM定义:JavaVirtualMachine,JAVA程序的运行环境(JAVA二进制字节码的运行环境)二、内存结构JVM内存布局规定了Java在运行过程中内存申请、分配、管理的策略,保证了JVM的高效稳定运行。不同的JVM对于内存的划分方式和管理机制存在着部分差异。这也就是常说的运行......
  • 使用websocket制作一个简易的聊天系统
    创建一个简易的聊天系统前端部分使用WebSocket主要包含以下几个步骤:建立WebSocket连接处理连接打开事件发送消息接收并显示消息处理连接关闭和错误事件以下是一个简易的HTML和JavaScript示例,展示了如何使用WebSocket实现聊天系统的前端部分:HTML(index.html)<......
  • 常用的15种内外网文件传输方式大合集 总有一款是你的菜!
    一、常见的15种内外网文件传输方式常见的内外网文件传输方式包括多种技术和工具,以下是几种常见的传输方式及其应用场景:1、 SFTP(SecureFileTransferProtocol) 用途:用于在网络中安全地传输文件,特别是在需要加密的环境中。协议:基于SSH(SecureShell)协议,提供加密传输。适......
  • 【Linux】linux系统组成结构详解
    NB的架构师都要具备足够的技术深度,然后才能透过问题看本质,所谓技术深度就是扎实的基础知识,要不断深入,反复研究学习,打磨自己的硬实力。不论从事云计算、虚拟化、容器、大数据、人工智能,几乎都是基于Linux服务器部署服务。一、linux系统结构linux系统看似纷繁复杂,单其核心只有一......
  • 字典和结构化数据
    1.字典数据结构在Python中,字典(Dictionary)是一种内置的数据类型,用于存储键值对(key-valuepairs)。字典是无序的、可变的,并且键必须是唯一的。字典的每个键值对用冒号(:)分隔,键值对之间用逗号(,)分隔,整个字典包围在花括号({})中。#创建一个空字典empty_dict={}#创建一个带有初始键......
  • 【数据结构】链表(1):单向链表和单向循环链表
    链表链表是一种经典的数据结构,它通过节点的指针将数据元素有序地链接在一起,在链表中,每个节点存储数据以及指向其他节点的指针(或引用)。链表具有动态性和灵活性的特点,适用于频繁插入、删除操作的场景。定义概念将线性表L=(a0,a1,...,an-1)中各元素分布在存储器的不同存......
  • 2024秋季学期 数据结构期末实验报告(无源码版)
    前言这玩意在我看来,p用没有,纯浪费时间,但是沟槽的课有这个要求那我只能花了一点点时间水水了。如果对里面的内容感兴趣(应该不会有人没事来看这种sb玩意吧),可以私信我~实验一疏松多项式1.1问题描述使用链表结构储存疏松多项式并实现以下功能:输入并创建多项式(按指数升序或降序......