环境: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