[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#的結構體來:爲了便於複用,把它放在一個單獨類裏面app

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++數據類型的對應關係:socket

C++與C#的數據類型對應關係表:函數

QQ截圖20150106001846

因此上面定義的整個結構的字節數是22個bytes.工具

注意區分上面的字節和字符。計算機存儲容量基本單位是字節(Byte),8個二進制位組成1個字節,一個標準英文字母佔一個字節位置,一個標準漢字佔二個字節位置。字符是一種符號,同存儲單位不是一回事,它是一種抽象的、邏輯的類型,與int等同樣。byte是物理的單位。學習

對應的C++結構體是:this

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

  

3. 在發送數據時,要先把結果轉換成字節數組,在接收到數據以後要把字節數組還原成本來的結構。具體的代碼以下,爲了便於複用,寫成一個類:spa

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#中發送結構體數據。

相關文章
相關標籤/搜索