skip to Main Content

The server uses Java’s datagram socket and datagram packet for implementation, deployed on a server with a public IP address. One client also uses Java’s datagram socket and datagram packet, bound to port 22222, running on a Windows 11 PC, and the other is implemented in C language, bound to port 10105, running on Ubuntu.
The overall implementation process is as follows:
The two clients first send information to the server using udp, and the server saves the information of both clients. Then one of the clients will send a connection request, and the server will exchange the information between the two clients. Afterwards, the two clients send and receive udp to each other based on the received information.
Both clients use the network provided by mobile hotspot, and have tested that the NAT on Windows 11 is a full cone NAT, while the NAT on Ubuntu is a port limited cone NAT.

  1. When using Wireshark to capture packets on clients under Windows 11, it was found that the source and target ports of udp packets sent to the other party were incorrect.

enter image description here
2. When using Wireshark to capture packets on clients under Ubuntu, it was found that the source and target ports of the udp sent to the other party were also incorrect. Later, after using the htons function in C language to change the byte order, the source and target ports displayed in the packet capture were normal.
enter image description here
3. However, the port displayed in Windows 11 packet capture is incorrect (I know that after NAT conversion, the port number will change), but the source port displayed in packet capture is different from the port displayed on the public server.

2

Answers


  1. Chosen as BEST ANSWER

    This is the code for my server:

    package com.lulu.socket;
    
    import com.alibaba.fastjson.JSONObject;
    import lombok.Data;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;
    
    import javax.annotation.PostConstruct;
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.*;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * @author zhanghy
     */
    @Slf4j
    @Component
    @Data
    public class SocketServer {
        @Value("${socket.poolSize}")
        private int threadPoolSize;  // 线程池的大小
        @Value("${socket.tcpPort}")
        private int tcpPortNumber;  // socket绑定的端口号
        @Value("${socket.udpPort}")
        private int udpPortNumber; // udp绑定的端口号
    
        private static ExecutorService executorService;  // 线程池
    
        private static ConcurrentHashMap<String, DeviceInfo> luluMap; // 存储活跃的lulu机信息
        private static ConcurrentHashMap<String, DeviceInfo> phoneMap; // 存储活跃的手机信息
    
        @PostConstruct
        public void init() {
            // 在应用启动时执行此方法
            executorService = Executors.newFixedThreadPool(threadPoolSize); // 线程池
            luluMap = new ConcurrentHashMap<>(); // 存储活跃的lulu机信息
            phoneMap = new ConcurrentHashMap<>(); // 存储活跃的手机信息
            new Thread(this::startTcpSocketServer).start();  // 创建线程来运行tcpSocket
            new Thread(this::startUdpSocketServer).start();  // 创建线程来运行udpSocket
        }
    
        private void startTcpSocketServer() {
            // 创建socket server端
            try (ServerSocket serverSocket = new ServerSocket(tcpPortNumber)) {
                System.out.println("Socket服务器启动,监听端口:" + tcpPortNumber);
    
                while (true) {
                    Socket clientSocket = serverSocket.accept();  // 从连接请求队列中取出一个客户的连接请求,没有请求则会阻塞
                    log.info("接收到客户的socket请求: " + clientSocket.getInetAddress().getHostAddress(), clientSocket.getPort());
                    // 线程池分配线程进行处理
                    executorService.submit(new TcpClientHandler(clientSocket));
                }
            } catch (IOException e) {
                log.error("tcpSocket初始化失败:", e);
                e.printStackTrace();
            }
        }
    
        private void startUdpSocketServer() {
            // 初始化udp的socket
            try (DatagramSocket datagramSocket = new DatagramSocket(udpPortNumber)) {
                System.out.println("DataGramSocket服务器启动,监听端口:" + udpPortNumber);
                byte[] receiveData;
                while (true) {
                    receiveData = new byte[1024];
                    DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
                    datagramSocket.receive(receivePacket);
                    // 线程池分配线程进行处理
                    executorService.submit(new UdpPunchTask(datagramSocket, receivePacket));
                }
            } catch (SocketException e) {
                log.error("udpSocket初始化失败:", e);
                e.printStackTrace();
            } catch (IOException e) {
                log.error("udp接受数据失败:", e);
                e.printStackTrace();
            }
        }
    
        private static class TcpClientHandler implements Runnable {
            private final Socket clientSocket;
    
            public TcpClientHandler(Socket socket) {
                this.clientSocket = socket;
            }
    
            @Override
            public void run() {
                try {
                    InputStream inputStream = clientSocket.getInputStream();
                    byte[] buffer = new byte[1024];
                    int bytesReadLength = inputStream.read(buffer);
                    String receivedData = new String(buffer, 0, bytesReadLength);
                    processInput(receivedData);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
    
            private void processInput(String inputLine) {
                // 在这里处理客户端发送的消息
                // 这里可以解析设备信息,并更新deviceMap
                // 例如,假设设备信息格式为 "DeviceID:IP:Port"
                // TODO: 判断是设备还是手机
                log.info("收到数据:" + inputLine);
                JSONObject jsonObject = JSONObject.parseObject(inputLine); // 将string转换成JSON格式
                log.info("收到数据: " + jsonObject);
                if(jsonObject.getString("ID")!=null) {
                    // 是噜噜机
                    log.info("收到噜噜机的tcp连接");
                    String state = jsonObject.getString("state");
                    if("on".equals(state)) {
                        // 噜噜机设备在线
                        String deviceId = jsonObject.getString("ID");
                        luluMap.put(deviceId, new DeviceInfo(clientSocket.getInetAddress().getHostAddress(), clientSocket.getPort()));
                        log.info(jsonObject.getString("connect"));
                        log.info(jsonObject.getString("user_ip"));
                    }
                }
            }
    
        }
    
        /**
         * @author zhanghy
         * &#064;date  2023/10/30
         * 处理每一个udp数据包
         */
        private static class UdpPunchTask implements Runnable {
            private final DatagramPacket datagramPacket;
            private final DatagramSocket datagramSocket;
    
            private UdpPunchTask(DatagramSocket datagramSocket, DatagramPacket datagramPacket) {
                this.datagramSocket = datagramSocket;
                this.datagramPacket = datagramPacket;
            }
    
            @Override
            public void run() {
                // 客户端的端口
                int port = datagramPacket.getPort();
                // 客户端的IP
                String hostAddress = datagramPacket.getAddress().getHostAddress();
                System.out.println("Received UDP packet from " + hostAddress + ":" + port);
                // 获得客户端发送过来的信息
                byte[] data = datagramPacket.getData();
                String jsonStr = new String(data); // 将字节数组转换为字符串
                log.info("收到的数据:" + jsonStr);
                JSONObject jsonObject = JSONObject.parseObject(jsonStr); // 将string转换成JSON格式
                log.info("收到的数据:" + jsonStr);
                if (jsonObject.getString("connect_ID") != null) {
                    // 收到手机要连接的噜噜机ID,此时先检测双方设备是否在线,如果都在线,则交换双方信息
                    DeviceInfo deviceInfo = luluMap.getOrDefault(jsonObject.getString("connect_ID"), null);
                    JSONObject responseToPhoneJson = new JSONObject();
                    if (deviceInfo != null) {
                        // 如果该ID的噜噜机活跃
                        responseToPhoneJson.put("command", "P2Pconnect");
                        responseToPhoneJson.put("ID", jsonObject.getString("connect_ID"));
                        responseToPhoneJson.put("device_ip", deviceInfo.getIp());
                        responseToPhoneJson.put("device_port", deviceInfo.getPort());
                        // 给手机端发送数据
                        udpSendResponse(port, hostAddress, responseToPhoneJson);
    
                        JSONObject responseToLuluJson = new JSONObject();
                        responseToLuluJson.put("command", "P2Pconnect");
                        responseToLuluJson.put("user_name", jsonObject.getString("user_name"));
                        responseToLuluJson.put("device_ip", hostAddress);
                        responseToLuluJson.put("device_port", port);
                        // 给噜噜机发送数据
                        udpSendResponse(deviceInfo.getPort(), deviceInfo.getIp(), responseToLuluJson);
    
                        log.info("手机的connect请求,且双方在线");
                        log.info(String.valueOf(responseToPhoneJson), responseToLuluJson);
                    } else {
                        // 如果该ID的噜噜机不活跃
                        responseToPhoneJson.put("command", "P2Pconnect");
                        responseToPhoneJson.put("ID", jsonObject.getString("connect_ID"));
                        responseToPhoneJson.put("device_ip", null);
                        responseToPhoneJson.put("device_port", null);
                        // 发送数据
                        udpSendResponse(port, hostAddress, responseToPhoneJson);
                        log.info("手机的connect请求,但噜噜机不在线");
                        log.info(String.valueOf(responseToPhoneJson));
                    }
                } else if (jsonObject.getString("ID") != null) {
                    // 噜噜机第一次发送的UDP,要返回{"ID":xxxxxxx}的回应
                    // 在luluMap中记录/更新活跃的噜噜机信息
                    luluMap.put(jsonObject.getString("ID"), new DeviceInfo(hostAddress, port));
                    JSONObject responseJson = new JSONObject();
                    // 给噜噜机返回信息
                    responseJson.put("recive", "ID:" + jsonObject.getString("ID"));
                    udpSendResponse(port, hostAddress, responseJson);
    
                    log.info("收到噜噜机的ID");
                } else if (jsonObject.getString("user_name") != null) {
                    // 手机第一次发送的UDP,要返回{"recive":"user_name:xxxx"}的回应
                    // 在phoneMap中记录/更新活跃的手机信息
                    phoneMap.put(jsonObject.getString("user_name"), new DeviceInfo(hostAddress, port));
                    JSONObject responseJson = new JSONObject();
                    // 给手机返回信息
                    responseJson.put("recive", "user_name:" + jsonObject.getString("user_name"));
                    udpSendResponse(port, hostAddress, responseJson);
    
                    log.info("收到手机的username");
                }
    
            }
    
            /**
             * udp返回数据
             *
             * @param port         客户端的端口
             * @param hostAddress  客户端的IP
             * @param responseJson 返回的数据信息
             */
            private void udpSendResponse(int port, String hostAddress, JSONObject responseJson) {
                byte[] bs = responseJson.toString().getBytes();
                DatagramPacket response = new DatagramPacket(bs, bs.length, new InetSocketAddress(hostAddress, port));
                try {
                    // socket发回数据给客户端
                    datagramSocket.send(response);
                } catch (IOException e) {
                    log.error("udpSocket发送数据失败:", e);
                    throw new RuntimeException(e);
                }
            }
        }
    
        private static class DeviceInfo {
            private final String ip;
            private final int port;
    
            public DeviceInfo(String ip, int port) {
                this.ip = ip;
                this.port = port;
            }
    
            public String getIp() {
                return ip;
            }
    
            public int getPort() {
                return port;
            }
        }
    }
    
    

    This is the code for my Windows client:

     @Test
        public void udpTestPhone() {
            DatagramSocket datagramSocket;
            DatagramPacket datagramPacket;
            try {
                datagramSocket = new DatagramSocket(22222);
                String message1 = "{"user_name":"ashuai"}";
                byte[] bytes1 = message1.getBytes();
                datagramPacket = new DatagramPacket(bytes1, bytes1.length, new InetSocketAddress("8.142.10.225", 9111));
                datagramSocket.send(datagramPacket);
    
                byte[] bs2 = new byte[1024];
                datagramPacket = new DatagramPacket(bs2, bs2.length);
                // 接收第一次的服务器返回
                datagramSocket.receive(datagramPacket);
                byte[] data = datagramPacket.getData();
                String jsonStr = new String(data);
                log.info(jsonStr);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
    
    
            try {
                boolean flag = false;
                JSONObject jsonObject = null;
                while(!flag) {
                    // 手机端发起连接请求
                    String message2 = "{"connect_ID":202310240001, "user_name": "ashuai"}";
                    byte[] bytes1 = message2.getBytes();
                    datagramPacket = new DatagramPacket(bytes1, bytes1.length, new InetSocketAddress("8.142.10.225", 9111));
                    datagramSocket.send(datagramPacket);
                    byte[] receive = new byte[1024];
                    datagramPacket = new DatagramPacket(receive, receive.length);
                    // 返回噜噜机的信息
                    datagramSocket.receive(datagramPacket);
                    byte[] tmp1 = datagramPacket.getData();
                    String remoteNet = new String(tmp1);
                    log.info("remoteNet: " + remoteNet);
    
                    jsonObject = JSONObject.parseObject(remoteNet);
                    // 噜噜机不在线
                    if(!jsonObject.containsKey("device_port") || !jsonObject.containsKey("device_ip")) {
                        continue;
                    }
                    flag = true;
    
                }
    
                int port = jsonObject.getIntValue("device_port");
                String hostIp = jsonObject.getString("device_ip");
                String message = "{"phone_test":"ashuai"}";
                byte[] helloMessage = message.getBytes();
                datagramSocket.setSoTimeout(5000);
                datagramPacket = new DatagramPacket(helloMessage, helloMessage.length, new InetSocketAddress(hostIp, port));
                log.info("发送一次");
                log.info("对方的IP: " + hostIp + " 端口: " + port);
                datagramSocket.send(datagramPacket);
    //            datagramSocket.send(datagramPacket);
    //            datagramSocket.send(datagramPacket);
    
    
                log.info("开始接收");
    
                byte[] receive1 = new byte[1024];
                DatagramPacket datagramPacket1 = new DatagramPacket(receive1, receive1.length);
                boolean receiveFlag = true;
                while (receiveFlag) {
                    try {
                        datagramSocket.receive(datagramPacket1);
                        byte[] luluMessage = datagramPacket1.getData();
                        log.info("收到噜噜信息:" + new String(luluMessage));
                        receiveFlag = false;
                    } catch (SocketTimeoutException e) {
                        // resend
                        log.info("超时阻塞,重新发");
    //                    datagramSocket.send(datagramPacket);
    //                    datagramSocket.send(datagramPacket);
                        datagramSocket.send(datagramPacket);
                    }
                }
    
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
    
        }
    

  2. Sorry, I wrote so much that I didn’t describe the problem clearly.

    The current issue is that the code of both clients cannot receive data from the other party. Wireshark can be used on Windows to capture udp packets sent by Ubuntu clients, but the source port displayed in the packet is different from the port of the Ubuntu client obtained on the server, and the target port is also different from the port of the Windows client obtained on the server. Additionally, the data part of the packet appears garbled (when using Wireshark to analyze the packet).
    enter image description here

    When Wireshark grabs packets sent by the local machine to the other client on Windows, the source port is not the bound 22222 port, and the target port is not the port returned by the server to the Ubuntu client.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search