Unity C# 与C++服务器通信踩坑
目录
Unity C# 与C++服务器通信踩坑
API与C#数据结构类型对应关系表
API与C#的数据类型对应关系表 |
|||||
API数据类型 | 类型描述 | C#类型 | API数据类型 | 类型描述 | C#类型 |
WORD | 16位无符号整数 | ushort | CHAR | 字符 | char |
LONG | 32位无符号整数 | int | DWORDLONG | 64位长整数 | long |
DWORD | 32位无符号整数 | uint | HDC | 设备描述表句柄 | int |
HANDLE | 句柄,32位整数 | int | HGDIOBJ | GDI对象句柄 | int |
UINT | 32位无符号整数 | uint | HINSTANCE | 实例句柄 | int |
BOOL | 32位布尔型整数 | bool | HWM | 窗口句柄 | int |
LPSTR | 指向字符的32位指针 | string | HPARAM | 32位消息参数 | int |
LPCSTR | 指向常字符的32位指针 | String | LPARAM | 32位消息参数 | int |
BYTE | 字节 | byte | WPARAM | 32位消息参数 | int |
C#代码
消息发送类
[Serializable] //序列化对象
[StructLayout(LayoutKind.Sequential, Pack = 1)] // 按1字节对齐
public class UserMsg
{
public int messageID;
public int clientID;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 200)] //限制200字节
public byte[] message;
}
接下来是两个byte和object转换函数
/// <summary>
/// 将结构转换为字节数组
/// </summary>
/// <param name="obj">结构对象</param>
/// <returns>字节数组</returns>
public static byte[] StructToBytes(object obj)
{
//得到结构体的大小
int size = Marshal.SizeOf(obj);
//创建byte数组
byte[] bytes = new byte[size];
//分配结构体大小的内存空间
IntPtr structPtr = Marshal.AllocHGlobal(size);
//将结构体拷到分配好的内存空间
Marshal.StructureToPtr(obj, structPtr, false);
//从内存空间拷到byte数组
Marshal.Copy(structPtr, bytes, 0, size);
//释放内存空间
Marshal.FreeHGlobal(structPtr);
//返回byte数组
return bytes;
}
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;
}
}
客户端连接、发送、接收
public static void setUpClient()
{
IPAddress ip = IPAddress.Parse(IP); //将IP地址字符串转换成IPAddress实例
ClientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//使用指定的地址簇协议、套接字类型和通信协议
IPEndPoint endPoint = new IPEndPoint(ip, port); // 用指定的ip和端口号初始化IPEndPoint实例
ClientSocket.Connect(endPoint); //与远程主机建立连接
//实例化消息类
UserMsg u = new UserMsg();
u.messageID= 1;
u.clientID = 2;
//设置编码C++为ASCII
u.message = Encoding.ASCII.GetBytes("connect server!");
Debug.Log(u.message);
byte[] message= StructToBytes(u);
//向服务器发送消息
ClientSocket.Send(message);
//接收回执信息
byte[] receive = new byte[1024];
int length = ClientSocket.Receive(receive);
UserMsg s = (UserMsg)BytesToStruct(receive,u.GetType());
Debug.Log(Encoding.ASCII.GetString(s.message));
}
C++代码
结构体对齐C# Class
typedef struct
{
int messageID;
int clientID;
char message[200];
}UserMsg;
处理客户端请求函数
void talkWithClient(void *p)
{
const char *str = "hello client";
UserMsg u;
SOCKET *pSock = (SOCKET*)p;
SOCKET socka = *pSock;
/*byte *b;
b = (BYTE*)str;
int i = send(socka, str, strlen(str), 0);*/
char buff[1024];
sockaddr_in sa = { AF_INET };
int i;
while (true)
{
//接收客户端消息
i = recv(socka, buff, sizeof(buff), 0);
//如果收到消息长度小于等于0说明客户端断开
if (i <= 0)
{
if (GetLastError() == 10054)
cout << htons(sa.sin_port) << "客户端退出了:" << socka << endl;
break;
}
buff[i] = 0;
//byte数组转化为结构体
u = *(UserMsg*)buff;
cout << u.message << endl;
//回执信息
int k = 0, j = 0;
char p[20] = "hello client";
while (p[k -1] != '\0')
u.message[j++] = p[k++];
//发送回执信息
int i = send(socka, (char*)(&u), sizeof(UserMsg), 0);
}
}
main
int main()
{
WSAData wd;
WSAStartup(0x0202, &wd);
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in sa = { AF_INET };
sa.sin_port = htons(PORT);
bind(sock, (sockaddr*)&sa, sizeof(sa));
listen(sock, 5);
sockaddr_in from = { AF_INET };
int nLen = sizeof(from);
while (true) {
SOCKET socka = accept(sock, (sockaddr*)&from, &nLen);
cout << inet_ntoa(sa.sin_addr) << htons(from.sin_port) << "登录了" << endl;
_beginthread(talkWithClient, 0, &socka); //开启一个线程 talkWithClient 参数为&socka
}
return 0;
}
测试结果(开发环境 Unity)