环境:Windows, VS2010
注意事项:Windows下的wchar_t与Linux下的wchar_t不同(在Windows下占2字节;而在Linux下则占4字节)
正题:
由于C#端和C++端的编码方式不同,因此在通过套接字编程时,会有一些问题
C#使用Unicode码,一个char占两个byte;而C++使用ANSI码,一个char占用一个byte
所以,为了统一两者之间的不匹配,重新创建了一个消息头格式,采用wchar_t(unsigned byte)类型,占两个byte
结构体定义如下:
typedef struct CommMsgHead { wchar_t cSenderName[16]; wchar_t cRecverName[16]; wchar_t cSendTime[32]; int iMsgType; int msglen; }CommMsgHead, *pCommMsgHead;1. 用C++发送时,mbstowcs(sendInfo->cSenderName, "server", 16);
cSenderName的内存如下所示:
115(s) 101(e) 114(r) 118(v) 101(e) 114(r) 0 ... 0 (11个‘0’)
即,一个字符占用了一个字节空间
发送类型为char*型
而在C#接收时,接收类型为byte[] 型
指向的内存空间如下:
[0]: 115(s)
[1]: 0
[2]: 101(e)
[3]: 0
[4]: 114(r)
[5]: 0
[6]: 118(v)
[7]: 0
[8]: 101(e)
[9]: 0
[10]: 114(r)
[11]: 0
[12]: 0
[13]: 0
[14]: 0
[15]: 0
[16]: 0
[17]: 0
[18]: 0
[19]: 0
[20]: 0
[21]: 0
[22]: 0
[23]: 0
[24]: 0
[25]: 0
[26]: 0
[27]: 0
[28]: 0
[29]: 0
[30]: 0
[31]: 0
结论1:看上去两者的内存表示是不同的,但是,别忽略指针的智能型。所以,在C++中,看到的是下一个wchar_t类指针所指向的内容,而不是实际的内存表示。
两者的内存表示应该是相同的!
刚开始做实验时,忘了指针的问题,还以为两者的内存表示不一致,所以花了很长时间
2. 在发送或接收数据时,把消息体直接跟在了消息头后面。
3. 在C#端,无论是接收还是发送,都是Unicode编码,而在C++端,有时则需要进行两者的转换
wcstombs(_Dest, _Source, _Dsize) 从unicode编码转化为ANSI编码 ;mbstowcs(_Dest, _Source, _Dsize)从ANSI编码转化为unicode编码
size_t wcstombs(char *dest, const wchar_t *src, size_t n);
size_t mbstowcs(wchar_t *dest, const char *src, size_t n);
注意:参数n指定要写到dest所指内存中目标类型的值的个数
代码:
C#端代码如下:
发送:
//填充消息头 MsgHead msgHead = new MsgHead(); msgHead.cSenderName = sendName.PadRight(16, '\0').ToCharArray(); msgHead.cRecverName = recvName.PadRight(16, '\0').ToCharArray(); msgHead.iMsgType = iMsgType; int nMsgHeadSize = Marshal.SizeOf(typeof(MsgHead)); //将信息头复制到pMsgHead所指的内存中 IntPtr pMsgHead = Marshal.AllocHGlobal(nMsgHeadSize); Marshal.StructureToPtr(msgHead, pMsgHead, false); //把消息体复制到pMsgData所指的内存中(注意所占的内存空间) int nMsgDataSize = data.Length; IntPtr pMsgData = Marshal.AllocHGlobal(2 * nMsgHeadSize); Marshal.Copy(data.ToCharArray(), 0, pMsgData, nMsgDataSize); //发送缓冲,并将之前两个内存中的内容全部复制到byte[]中,以发送信息 byte[] sendBuf = new byte[nMsgHeadSize + 2 * nMsgDataSize]; Marshal.Copy(pMsgHead, sendBuf, 0, nMsgHeadSize); Marshal.Copy(pMsgData, sendBuf, nMsgHeadSize, 2 * nMsgDataSize); //发送信息 int nSend = tcpSendSock.Send(sendBuf);接收消息:
//将消息头放到供接收的消息头部 int nMsgHeadSize = Marshal.SizeOf(typeof(MsgHead)); IntPtr pMsgHead = Marshal.AllocHGlobal(nMsgHeadSize); Marshal.Copy(recvBuf, 0, pMsgHead, nMsgHeadSize); //将消息头转为供接收的消息头 MsgHead recvHead = (MsgHead)Marshal.PtrToStructure(pMsgHead, typeof(MsgHead)); //把消息体放到pMsgData所分配的内存中(注意所占内存) IntPtr pMsgData = Marshal.AllocHGlobal(2 * recvHead.msgLength); Marshal.Copy(recvBuf, nMsgHeadSize, pMsgData, 2 * procHead.msgLength); //将消息体存放到消息中 char[] cData = new char[procHead.msgLength]; Marshal.Copy(pMsgData, procHead.cData, 0, procHead.msgLength);C++代码:
typedef struct MsgHead { char cSenderName[16]; char cRecverName[16]; char cSendTime[32]; int iMsgType; int msglen; }MsgHead, *pMsgHead; typedef struct CommMsgHead { wchar_t cSenderName[16]; wchar_t cRecverName[16]; wchar_t cSendTime[32]; int iMsgType; int msglen; }CommMsgHead, *pCommMsgHead; void main() { CinitSock oinitSock; SOCKET srvSock=socket(AF_INET, SOCK_STREAM, 0); int addrLen=sizeof(SOCKADDR); //设置服务器的套接字 SOCKADDR_IN srvSockAddr; srvSockAddr.sin_family=AF_INET; srvSockAddr.sin_addr.S_un.S_addr=htonl(INADDR_ANY); srvSockAddr.sin_port=htons(2030); //printf("服务器IP地址:%d", INADDR_ANY); if(bind(srvSock, (SOCKADDR *)&srvSockAddr, addrLen)) { printf("绑定出错\n"); return; } printf("服务器在127.0.0.1,端口2030上进行监听\n"); //进行监听 listen(srvSock, 5); wchar_t recvBuf[1024]={0}; //char recvBuf[1024]={0}; SOCKET connSock=accept(srvSock, NULL, NULL); while(true) { int nRecv=recv(connSock, (char *)recvBuf, 1024, 0); if(recvBuf==NULL) { printf("接收到空字符串\n"); } if(nRecv>0) { printf("接收到数据%d字节\n", nRecv); pMsgHead myInfo=new MsgHead; memset(myInfo, 0, sizeof(MsgHead)); //从Unicode码转到ANSI码 //无论是用wchar_t接收,还是用char接收,都需要手动转换 //用unicode码接收时,int占2个位置,而不是四个 //用ANSI码接收时,由于发送过来的是unicode码,而每个unicode码占2个字节 //转换成接收消息头 pCommMsgHead recvHead=(pCommMsgHead)recvBuf; //填充消息头 wcstombs(myInfo->cSenderName, recvHead->cSenderName, 16); wcstombs(myInfo->cRecverName, recvHead->cRecverName, 16); wcstombs(myInfo->cSendTime, recvHead->cSendTime, 32); myInfo->iMsgType=recvHead->iMsgType; myInfo->msglen=recvHead->msglen; //注意所占内存 char * msg=new char[myInfo->msglen+1]; //初始化内存 memset(msg, 0, myInfo->msglen+1); wcstombs(msg, recvBuf+68, myInfo->msglen); printf("senderName=%s,recverName=%s, sendTime=%s\n", myInfo->cSenderName, myInfo->cRecverName, myInfo->cSendTime); printf("msgLen=%d, message=%s\n", myInfo->msglen, msg); ///<sumary> //发送信息出去 ///<sumary> char cData[128]={0}; sprintf(cData, "congratulation, this is a gift", strlen("congratulation, this is a gift")+1); int cDataLen=strlen(cData); //发送的是双字节字符数组,所以这里是(sizeof(MsgHead)) int msgLen=16+16+32+2+2+strlen(cData); //注意,这里不能使用sizeof(MsgHead),这样int类型占4个字节,放在wchar_t类型就变成了8个字节,会出错 wchar_t * sendBuf=new wchar_t[msgLen]; //清空发送区 memset(sendBuf, 0, 2*msgLen); //专用来发送的结构体 pCommMsgHead sendInfo=(pCommMsgHead)sendBuf; mbstowcs(sendInfo->cSenderName, "server", 16); mbstowcs(sendInfo->cRecverName, (const char *)myInfo->cSenderName, 16); mbstowcs(sendInfo->cSendTime, "2011.12.12-13:04", 32); sendInfo->iMsgType=1; sendInfo->msglen=strlen(cData); //别忘了指针的智能型 int nTrans=mbstowcs(sendBuf+68, cData, strlen(cData)); printf("Success to trans %d bytes\n", nTrans); int nSend=send(connSock, (const char *)sendBuf, 2*msgLen, 0); printf("发送%d字节\n", nSend); } } closesocket(srvSock); }需要注意如下语句:
wcstombs(msg, recvBuf+68, myInfo->msglen);把recvBuf+68(wchar_t)类型的数值转为msg(char)类型,需要转换myInfo->msgLen个。系统会自动把2*myInfo->msgLen个byte的内容转为myInfo->msgLen个char