linux用java socket与c的socket通信乱码问题

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux用java socket与c的socket通信乱码问题相关的知识,希望对你有一定的参考价值。

大致架构是这样的,用java写socket客户端发起报文到c写的socket服务端,服务端再调用动态链接库文件xxx.so内的方法访问aix核心系统再返回数据.

机器1: redhat enterprise 5.1
机器2: aix
机器3: windows xp sp3系统

1.
c写的socket服务部署在机器1
java写的socket客户端部署在机器3上

返回数据正常且无中文乱码

2.
c写的socket服务部署在机器1
java写的socket客户端部署在机器1上
返回数据正常,但中文全是"????"的乱码
socket通信用的是字节流,且编码一致

注:c写的socket服务调动态链接库经过测试的,无任何异常,问题应该是出现java与c的socket通信这边,这个问题困扰我们很久了啊!希望有经验的仁兄帮忙啊,非常感谢!

应该就是字符集问题。
首先要确定Linux返回的数据中,采用了那种编码方式,如:GBK。
对应的,在Java一端,将字符串信息转换成byte数组并写入Socket,读出的数据也转换成字符串,
如:
String s = "中文" ;
sockoutputstream.write (s.getBytes ("GBK")) ;

读出来时候,
byte [] buf = sockinputStream.read ()

String s = new String (buf, "GBK") ;

这样就可以了。
参考技术A 可以通过调试 检测方案1和方案2下字节流中字节数据是否一致

如果一致 就不是socket的问题 是 java客户端接收数据的问题
如果不一致(不太可能) 可能是java客户端发送数据的问题

本人愚见~!本回答被提问者采纳
参考技术B 要么都用Java,要么都用C++ 参考技术C 只有中文不正常,是不是编码问题? 参考技术D 共同关注

Socket与系统调用深度分析

在Linux里面,可通过创建Socket,使得进程之间进行网络通信,可通过TCP或者UDP的方式进行交互。

技术图片

 

scoket系统调用主要完成socket的创建,必要字段的初始化,关联传输控制块,绑定文件等任务,完成返回socket绑定的文件描述符;

/**
 * socket函数调用关系
 * sys_socket
 *   |-->sock_create
 *   |      |-->__sock_create
 *   |            |-->inet_create           
 *   |-->sock_map_fd
 */

 

一:首先调用到sock_create

sock_create(family, type, protocol, &sock)

在这个第一层函数sock_create里, 比起socket系统调用来多出来第四个参数&sock,

这个参数是一个结构体, 定义如下:

struct socket {
    socket_state        state;
    kmemcheck_bitfield_begin(type);
    short           type;
    kmemcheck_bitfield_end(type);
    unsigned long       flags;
    struct socket_wq __rcu  *wq;
    struct file     *file;
    struct sock     *sk;
    const struct proto_ops  *ops;
};

其中:

  • state为枚举类型: SS_FREE, SS_UNCONNECTED, SS_CONNECTING, SS_CONNECTED, SS_DISCONNECTED. ( 比较好理解 );
  • type为诸如SOCK_STREAM之类的;
  • flags是标记;
  • wq是一个wait queue, 具体怎么用后面会提到;
  • file是供gc使用的文件描述符;
  • sk是比较重要的数据结构, 指向代表下层协议(network layer)数据的sock结构

 

1. __sock_create

sock_create向下调到第二层方法:

__sock_create(current->nsproxy->net_ns, family, type, protocol, res, 0);

这一层函数引入了一个新的参数struct net, 后面再做分析。

在__sock_create这一层:

1.1 调用secutiry子系统的方法secutiry_ops->socket_create去security子系统折腾了一圈;
1.2 sock_alloc: 分配struct socket
1.2.1 通过sockfs中分配一个inode ( sockfs的初始化详见net/socket.c的sock_init方法 )
1.2.2 基于inode结构取到socket结构 ( 怎么取到的? 这里使用的是著名的container_of宏 )

通过调用container_of宏, 使用inode结构取到了socket结构, 返回.

1.3 从全局net_families数组中根据下标family取到对应的struct net_proto_family结构pf;

全局net_families数组是一个维护着系统全局所有网络簇信息的一个数组结构.

net_families数组通过sock_register/sock_unregister添加/删除元素. ( 最多40个元素, 可以理解为下层协议的数据结构, 例如ipv4, ipv6, netlink, appletalk, bluetooth等, 本文后面部分采用ipv4作为示例. )

sock_register/sock_unregister通常在net/xxx/Af_xxx.c中调用.

例如对于INET, 在net/ipv4/Af_inet.c中将INET的struct net_proto_family结构添加到全局net_families数组中.

INET的struct net_proto_family定义如下:

static const struct net_proto_family inet_family_ops = {
    .family = PF_INET,
    .create = inet_create,
    .owner  = THIS_MODULE,
};
 
1.4 关键的一步调用到pf->create, 按照上面的描述, 调用到了net/ipv4/Af_inet.c的inet_create方法.
 
2 根据struct socket的type(SOCK_STREAM之类), 遍历inetsw[type], 找到对应到protocol的结构体;
 

注意:

 

inetsw是一个链表数组, key为SOCK_STREAM, SOCK_DGRAM, SOCK_RAW等等.

 

inetsw的初始化在net/ipv4/Af_inet.c的inet_init方法中:

 
  • 先初始化inesw为SOCK_STREAM/SOCK_DGRAM/SOCK_RAW等作为key的list_head数组;

  • 遍历inetsw_array, 将其挂入inetsw中.

 

inetsw_array的元素封装了TCP, UDP, PING, RAW等协议, 即为上文中描述的”对应到protocol的结构体”.

 

inetsw_array的元素结构如下:

 
static struct inet_protosw inetsw_array[] =
{
    {
        .type =       SOCK_STREAM,
        .protocol =   IPPROTO_TCP,
        .prot =       &tcp_prot,
        .ops =        &inet_stream_ops,
        .no_check =   0,
        .flags =      INET_PROTOSW_PERMANENT |
                  INET_PROTOSW_ICSK,
    }
...
}

3 将”对应到protocol的结构体”的ops赋给struct socket结构的ops.

例如如果type是SOCK_STREAM, protocol是TCP, 将&inet_stream_ops赋给struct socket结构的ops.

各个数据结构关系如下图:

 技术图片

 

 

4 调用sock_init_data(struct socket, struct sock)

从函数原型上可以看出, 是借助struct socket来初始化网络层核心数据结构struct sock:

  • sk->sk_receive_queue, sk_write_queue, sk_error_queue ( 如果配了NET_DMA, 还有sk_async_wait_queue )
  • sk->sk_send_head
  • sk->sk_timer
  • sk->sk_rcvbuf ( 这里指的是rmem )
  • sk->sk_sndbuf ( 这里指的是wmem )
  • sk->sk_state ( 初始值为TCP_CLOSE )
  • 接管struct socket的wq
  • sk->sk_dst_lock
  • sk->sk_callback_lock
  • sk->sk_rcvlowat ( SO_RCVLOWAT )
  • sk->sk_rcvtimeo ( SO_RCVTIMEO )
  • sk->sk_sndtimeo ( SO_SNDTIMEO )
  • sk->sk_stamp ( 上一个packet收到的timestamp )
5 sk->sk_backlog_rcv ( 收到backlog的回调函数 ) 初始化为sk->sk_prot->backlog_rcv

例如对于TCP, backlog_rcv指向net/ipv4/Tcp_ipv4.c的全局结构体struct proto tcp_prot中的tcp_v4_do_rcv

6  inet->ut_ttl ( Unicast的TTL )为1
7 inet->mc_loop ( Multicast的loop ) 为1
8 inet->mc_ttl ( Multicast的TTL ), mc_all为1
9 inet->mc_index ( Multicast的device index )为0
10 如果inet->inet_num ( local port? )不为空,

意味着该protocol允许并且已经在socket创建时指定local Port, 于是调用sk->sk_prot->hash(sk).

例如对于TCP, hash()指向net/ipv4/Tcp_ipv4.c的全局结构体struct proto tcp_prot中的inet_hash:

该方法按local Port计算hash key, 在&tcp_hashinfo->listening_hash按hash key, 将struct sock插入tcp的listening_hash链表.

但是似乎这个hashtable元素只有32个, 为什么这么小? (待看)

11 调用sk->sk_prot->init, 例如对于TCP, 指向net/ipv4/Tcp_ipv4.c的全局结构体struct proto tcp_port中的tcp_v4_init_sock, 此方法完成该socket在内核网络子系统TCP层的初始化:

注意: TCP层有自己的数据结构struct tcp_sock, 从struct sock强转而来(下图图示中”tcp_sock specified”部分为struct tcp_sock的专有数据, 内存布局于common字段的后面, 故可以使用强制类型转换于struct sock互转)

  • out_of_order_queue初始化 ( 该queue用于存放Out of order的segment )
  • 初始化三个timer: retransmit_timer <-> net/ipv4/tcp_timer.c的tcp_write_timer delack <-> net/ipv4/tcp_timer.c的tcp_delack_timer keepalive <-> net/ipv4/tcp_timer.c的tcp_keepalive_timer
  • “Data for direct copy to user” ( 称为prequeue )的init ( 机制待细看 )
  • RTO ( Retransmit Time Out ) 初始化为1*HZ
  • mdev 初始化为1*HZ ( mdev是什么待细看 )
  • snd_cwnd初始化为10 ( Sending Congestion Window )
  • snd_ssthresh初始化为0x7fffffff ( 慢启动size threshold )
  • snd_cwnd_clamp初始化为~0 ( snd_cwnd的上限 )
  • mss_cache初始化为536U
  • reordering初始化为sysctl_tcp_reordering ( packet reording metric )
  • icsk->icsk_ca_ops ( pluggable congestion control hook 可插拔的拥塞控制hook )
  • sk->sk_state设为TCP_CLOSE …

至此pf->create调用结束, 也就是inet_create方法调用结束.

图示如下:

技术图片

 

 

12 调用secutiry子系统的方法secutiry_ops->socket_post_create去security子系统折腾了一圈;

至此__sock_create调用结束.

至此socket系统调用中sock_create调用结束.

二:调用sock_map_fd将struct socket挂接到fd供进程使用.

1. 调用sock_alloc_file, 传入struct socket, struct file和flag

将struct socket的file设为struct file;

将struct file的private_data设为struct socket;

这样struct socket和struct file便互相关联起来了.

技术图片

2. 调用fd_install将file descriptor加入fd array

至此, 整个socket系统调用结束.

 

 

以上是关于linux用java socket与c的socket通信乱码问题的主要内容,如果未能解决你的问题,请参考以下文章

linux socke编程实例:一个简单的echo服务器程序

如何在 C 中创建一个 UDP 服务器?

网络编程socke

python之路-socke开发

带有 Socks4 代理的 Java Socket 更改为 Socks5

java开发Socks5代理C/S