Java与C进行Socket通信的坑

  1. 背景:
  2. 原因分析
    1. 1. 数据类型所用到的存储字节不一致:
    2. 2. 大小端字节序的问题:
    3. 3. 补充点:

背景:

睡觉前回想起一月份寒假前被那个Linux C编程实现网络游戏服务器的大作业,其中最大的坑莫属Java客户端与C服务器之间socket通讯时无法正常通信收发游戏消息。临近寒假临近DDL踩了个这样的坑,内心真是特别崩溃。

C语言中,char[]可直接与byte[]互相转换。我都把Java中要传输的游戏数据都转成char[]再转成byte[]了,为什么还是无法正常通信?啊啊啊这真让人抓狂。

疯狂尝试了一天后,无奈之下用最原始的ASC2码装成字节来通讯,可算起作用了,激动万分。

ASC2码占1个字节,发过去总算能被C服务端看懂!而Java提供了String 与 byte[] 互化的API,好像是能转换成基于asc2码的byte数组,收发消息居然也成了!(后来查资料才知道:String与byte[] 互化的结果与String的编码方式有关,JDK默认编码方式为GBK,而ASC2码是GBK的子集)

最终以 Java客户端字节流字符串互相转换的方式搭配C服务端字节数组的方式稀里糊涂的解决了这个问题,并成功在坐火车回家前的三小时完成了这项大作业。

原因分析

1. 数据类型所用到的存储字节不一致:

首先两种语言关于socket编程的API就不一样,还有一个很重要的点,那就是两种语言的基本数据类型存储字节数不完全一致。它们的比较如下:

Type Java C
short 2-Byte 2-Byte
int 4-Byte 4-Byte
long 8-Byte 4-Byte
float 4-Byte 4-Byte
double 8-Byte 8-Byte
boolean 1-bit N/A
byte 1-Byte N/A
char 2-Byte 1-Byte

所以我在Java的Socket中传 char[ ] 过去,到C那边的Socket收到后转成 char[ ] 就一段乱码。

我在Java中传ASC2码过去的话,由于一个ASC2码最大值为255=2^8-1,占1字节,所以C那边就能读懂,将其转化为对应的char。

另外,一查才知道,Java的字符串类型与字节数组类型转化也挺有讲究,阅读JDK对应源码的话,就知道它还跟字符编码有关。这里转载两篇博文:Java中String和字节数组间的转换浅析一图弄懂ASCII、GB2312、GBK、GB18030编码

所以C和Java在进行Socket通讯前,需要进行类型转换。比如对于C定义的unsign char为一个字节存储,对应Java这边用byte存储;对于C定义的int, long对应Java用int存储,具体可以参考以上的表。

2. 大小端字节序的问题:

Socket通讯是按字节传输的(即8个bit位传输),而对于超过一个字节的类型如short 为两个字节,就存在两种传输入方式,一种是高字节在前传输;一种是高字节在后传输。即Little-Endian(小端字节序)和Big-Endian(大端字节序)。
Little-Endian和Big-Endian是表示计算机字节顺序的两种格式,所谓的字节顺序指的是长度跨越多个字节的数据的存放形式。

假设从地址【0x00000000】开始一个字中保存有数据0x1234abcd,那么在两种不同的内存顺序的机器上从字节的角度去看的话分别表示为:

  1. little endian:在内存中的存放顺序是:
    【0x00000000】-0xcd, 【0x00000001】-0xab, 【0x00000002】-0x34, 【0x00000003】-0x12

  2. big endian:在内存中的存放顺序是
    【0x00000000】-0x12, 【0x00000001】-0x34, 【0x00000002】-0xab, 【0x00000003】-0xcd

需要特别说明的是,以上假设机器是每个内存单元以8位即一个字节为单位的。简单的说,ittle endian把低字节存放在内存的低位;而big endian将低字节存放在内存的高位.

现在主流的CPU,intel系列的是采用的little endian(小端字节序)的格式存放数据,而motorola系列的CPU采用的是big endian。

网络协议都是Big-Endian的,Java编译的都是Big-Endian的,C编译的程序是与机器相关的具体是否要进行转换是需要沟通的。

所以在我之前的大作业里,我在Java客户端的socket传输一个int类型的数字过去,在C服务端的Socket接收后该int打印出来的与原来的int类型数字完全不一致!

老子就不信邪了,不就是大小为4byte的int类型吗?老子让Java客户端端先将int值转化为 byte[4] 数组,再到C服务端的socket读取 byte[4] 的数据并转化为int值应该可以吧?试了好多遍,还真就不行,关键是你还屁都debug不出来,那种感觉真叫人欲哭无泪。

那为什么后来把int换成ASC2就行了?因为只占1个byte!此时用socket通讯不存在大端小端互换的问题!

3. 补充点:

  • 由于Socket通讯是按字节进行传输的,而在Java中只有byte是一个字节,故可以将其它类型都转换成byte数组来存储,如:short用两位的字节数组存储,需转换了换以上方法进行,而int用四位的字节数组来存储,对String类型,直接用String.getBytes()来得到它的字节数组。

  • Java的byte与C语言的unsign char虽然都是一个字节存储,但具体的表示内容是不同的,C的无符号char是取值的范围0–255,而Java中byte取值的范围是-128—127,故在实现C语言的字符串时(C是用char[]来表示字符串的,Java这边需要进行转换来模仿C语的unsign char,具体实现函数如下:

    // 将有符号的char转换成无符号的char
    public static char[] ToUnsignedChar(char[] signChar) {
        for (int i = 0; i < signChar.length; i++) {
            int x = ((byte) signChar[i]) >= 0 ? signChar[i] : ((byte) signChar[i]) + 256;
            signChar[i] = (char) x;
        }
        return signChar;
    }
    

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达,邮件至 708801794@qq.com

文章标题:Java与C进行Socket通信的坑

文章字数:1.5k

本文作者:梅罢葛

发布时间:2020-04-09, 04:22:15

最后更新:2020-04-09, 06:08:18

原始链接:https://qiurungeng.github.io/2020/04/09/Java%E4%B8%8EC%E8%BF%9B%E8%A1%8CSocket%E9%80%9A%E4%BF%A1%E7%9A%84%E5%9D%91/
目录
×

喜欢就点赞,疼爱就打赏