Linux C与Java实现Socket通信:结构体传输与字节流对比详解
Linux C和Java实施套接字通信。在线通信消息格式的一些示例将传递字节流消息,这些消息直接传递了char型数组消息。在网络协议,通信控制,嵌入式系统,驱动程序开发等中,我们经常想要传输的不是一个简单的字节流(char-type数组),而是整个多个数据,其表现形式是一种结构。如果此时传输的内容存储在char型数组中,则该编程非常复杂且容易出错。此外,一旦控制方法和通信协议更改,就需要非常仔细地修改程序,这很容易犯错。目前,只需要一个结构来处理它。当然,如果它是用C语言编写的客户端和服务器传输结构,那么解析非常简单。只要两端定义的结构相同,就不会有问题。 Java中没有结构,我们如何正确解析结构阵列?让我们看一下下面的代码。

以下代码是我在LinuxC中测试的代码。该结构包含几个基本类型变量。我们使用Java来解析结构中每种基本类型的数据。
Linux C发送结构/客户
#include
#include
#include
#include
#include
/**
* Linux C socket UDP通信,传输结构体。2017/8/20
*@author Lian
*/
#include
typedef struct{
uint8_t lxx;
int nodeID;
int updateFlg;
uint16_t slot;
uint32_t slotBitmap;
float BitMap;
} test_stru,*p_stru;
int main(){
test_stru test1 = {200,121,127,4,4294967279,5648.525};//结构体初始化
printf("test_stru size is %d\n",sizeof(test_stru));
p_stru p_test = &test1;
size_t inlen = sizeof(test_stru);
char outbuf[1024]={};
size_t outlen = sizeof(outbuf);
int brdcFd;
if((brdcFd=socket(PF_INET,SOCK_DGRAM,0))<0){
printf("socket fail\n");
return -1;
}
int optval =1;
setsockopt(brdcFd,SOL_SOCKET,SO_BROADCAST|SO_REUSEADDR,&optval,sizeof(int));
struct sockaddr_in theirAddr;
memset(&theirAddr,0,sizeof(struct sockaddr_in));
theirAddr.sin_family =AF_INET;
theirAddr.sin_addr.s_addr =inet_addr("192.168.16.36");//设定接收的地址
theirAddr.sin_port = htons(8887);//设定接收的端口号
int sendBytes;
while(1){
if((sendBytes=sendto(brdcFd,p_test,sizeof(test_stru),0,(struct sockaddr *)&theirAddr,sizeof(struct sockaddr)))==-1){//sendto将结构体发送出去
printf("sendto fail,errno=%d\n",errno);
return -1;
}
printf("send success!!!\n");
//printf("msg=%s,msgLen=%d,sendBytes=%d\n",msg,(int)strlen(msg),sendBytes);
sleep(5);
}
close(brdcFd);
}
Java服务器端
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
class Dgram {
public static DatagramPacket toDatagram(String s, InetAddress destIA,
int destPort) {
byte[] buf = new byte[s.length() + 1];
s.getBytes(0, s.length(), buf, 0);
return new DatagramPacket(buf, buf.length, destIA, destPort);
}
public static String toString(DatagramPacket p) {
return new String(p.getData(), 0, p.getLength());
}
}
class ChatterServer {
static final int INPORT = 8887;
private byte[] buf = new byte[1000];
private DatagramPacket dp = new DatagramPacket(buf, buf.length);
private DatagramSocket socket;
public ChatterServer() {
try {
socket = new DatagramSocket(INPORT);//
System.out.println("Server started");
while (true) {
socket.receive(dp);
String rcvd = Dgram.toString(dp) + ",from address:"
+ dp.getAddress() + ",port:" + dp.getPort();
System.out.println("From Client:"+rcvd);
//dp.getData()获取消息内容
MAC_struct mac_data = new MAC_struct(dp.getData());
mac_data.parseFrame();
String echoString = "From Server Echoed:" + rcvd;
DatagramPacket echo = Dgram.toDatagram(echoString,
dp.getAddress(), dp.getPort());
socket.send(echo);
}
} catch (SocketException e) {
System.err.println("Can't open socket");
System.exit(1);
} catch (IOException e) {
System.err.println("Communication error");
e.printStackTrace();
}
}
}
class MAC_struct{
byte[] recv = new byte[2048];
public int nodeID;
public int nodeID2;
public int stampFlags;
public long tmp;
public MAC_struct(byte[] recv){
this.recv = recv;
}
/**
解析收到的结构体消息。
*/
public void parseFrame(){
InputStream in_withcode;
try {
for(int i=0;i<24;i++)
System.out.println("source["+i+"]="+recv[i]);
in_withcode = new ByteArrayInputStream(recv);
DataInputStream inputStream = new DataInputStream(in_withcode);
int read=0;
int updateFlg=0;
int nodeID=0;
int[] Bits_4 = new int[4];
read = inputStream.readUnsignedByte();//读取一个无符号字节
System.out.println("read= "+read);
inputStream.skipBytes(3);//跳过N个字节
updateFlg = inputStream.readInt();//readInt()读取一个4个字节。
System.out.println("updateFlg= "+int_EndianBigtoLittle(updateFlg));
nodeID = inputStream.readInt();
System.out.println("nodeID= "+int_EndianBigtoLittle(nodeID));
int slotBitMap = inputStream.readUnsignedShort();//读取2个无符号字节。
System.out.println("slotBitMap= "+uShort_EndianBigtoLittle(slotBitMap));
inputStream.skipBytes(2);
System.out.println("Long_num= "+Long_EndianBigtoLittle(inputStream.readInt()));
for(int i=0;i<4;i++)
Bits_4[i] = inputStream.readUnsignedByte();
System.out.println("Float_num= "+Float_EndianBigtoLittle(Bits_4));
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 将byte[]数组转为String
* @param valArr 待转换byte数组,
* @param startpoint 待转换byte数组开始位置
* @param maxLen 待转换byte数组长度
* @return
*/
private static String toStr(byte[] valArr,int startpoint,int maxLen) {
int index = 0;
while(index + startpoint < valArr.length && index < maxLen) {
if(valArr[index+startpoint] == 0) {
break;
}
index++;
}
byte[] temp = new byte[index];
System.arraycopy(valArr, startpoint, temp, 0, index);
return new String(temp);
}
/**
* 读取4位,并转化为无符号的4位数据
* @param value
* @return 将读取的数据转为小端模式下存储的数据
*/
private static long Long_EndianBigtoLittle(int value)
{
byte[] src = new byte[4];
src[0] = (byte) ((value>>24)&0xFF);
src[1] = (byte) ((value>>16)&0xFF);
src[2] = (byte) ((value>>8)&0xFF);
src[3] = (byte) ((value)&0xFF);
return (long) ((src[0]&0xFF)|((src[1]&0xFF)<<8)|((src[2]&0xFF)<<16)|((src[3]&0xFF)<<24))&0xFFFFFFFFl;
}
/**
* 大端数据转小端数据
* @param value
* @return 将读取的数据转为小端模式下的数据,
*/
private static float Float_EndianBigtoLittle(int[] value)
{
if (value == null || value.length != 4) {
throw new IllegalArgumentException("value数组必须不为空,并且是4位!");
}
int i = ((value[0]&0xFF)|((value[1]&0xFF)<<8)|((value[2]&0xFF)<<16)|((value[3]&0xFF)<<24));
return Float.intBitsToFloat(i);
}
/**
* 大端数据转小端数据
* @param value
* @return 将读取的数据转为小端模式下的数据,
*/
private static int int_EndianBigtoLittle(int value)
{
byte[] src = new byte[4];
src[0] = (byte) ((value>>24)&0xFF);
src[1] = (byte) ((value>>16)&0xFF);
src[2] = (byte) ((value>>8)&0xFF);
src[3] = (byte) ((value)&0xFF);
return (int) ((src[0]&0xFF)|((src[1]&0xFF)<<8)|((src[2]&0xFF)<<16)|((src[3]&0xFF)<<24));
}
/**
* 读物2位数据,转为无符号数据
* @param value
* @return 将读取的数据转为小端模式下的数据,
*/
private static int uShort_EndianBigtoLittle(int value)
{
byte[] src = new byte[2];
src[0] = (byte) ((value>>8)&0xFF);
src[1] = (byte) ((value)&0xFF);
return (int) ((src[0]&0xFF)|((src[1]&0xFF)<<8));
}
/**
*
*/
public static int vtolh(byte[] bArr) {
int n = 0;
for(int i=0;i4;i++){
int left = i*8;
n+= (bArr[i] << left);
}
return n;
}
}
public class JavaCstruct {
public static void main(String[] args){
new ChatterServer();
}
}
多于:
如图所示,它正在分析以C语言传输的结构数据。
接下来,让我们谈谈这个过程。 Java服务器中的DP.GetData()从C语言获取消息内容。将接收的消息包裹到DatainputStream中,然后在类中调用readxxxx()以获取相应的变量。应该注意的是,您必须能够分析C语言中结构中变量的存储形式,并了解结构的对齐。只有通过阐明C在C中的结构中的位置,您才能正确解析内部。数据。
例如,在c语言的客户端程序中,uint8_t占据一个字节,而第二个变量类型占int占四个字节,因为存在字节对齐,两个变量占据了8个字节。 UINT8_T的空间占据了5个字节的空间,而不是紧密排列。尽管UINT8_T占用1个字节,但接下来的3个字节是空的,但仍未使用,并且仍然占用空间。假设UINT8_T的起始地址为0x0000,则该结构中第一个INT变量的nodeid的起始地址为0x0004。因此,在Java服务器端,我们读取了第一个Unsigned变量(inputStream.ReadunSignedByte());之后,您需要跳过3个字节(inputstream.skipbytes(3);),然后继续读取成为下一个变量启动的位置。
另一个问题是小末端的转换。如果客户端和服务器端之间的存储格式不同,则一端是大端模式,另一端是小端模式,那么读数肯定会不同。当我进行测试时,客户端是小型模式,服务器是大型模式。如果我的Java代码中读取的数据与我的结果不同,则可能是这个问题。
当然,我们可以看到服务器打印的源[]数组的线索。