C#端和C++端套接字通信中遇到的编码问题

75 篇文章 2 订阅
51 篇文章 0 订阅

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值