UDP 通信简单测试记录

现有2台手机,小米和魅族。魅族分享热点给小米用,魅族做服务端,小米做客户端。

一开始我以为要获取2台手机的ip地址,但是后来发现不用。
ip地址获取是通过DhcpInfo去获取的。ipAddress代表本机ip地址,gateway代表网关地址,即本机设备所连接的路由器ip地址。

        WifiManager manager = (WifiManager)getSystemService(WIFI_SERVICE);
        DhcpInfo info = manager.getDhcpInfo();
        Log.d(TAG,"gateway " + formatIP(info.gateway));
        Log.d(TAG,"ipAddress " + formatIP(info.ipAddress));

    private String formatIP(int ip) {
        return String.format(
                "%d.%d.%d.%d",
                (ip & 0xff),
                (ip >> 8 & 0xff),
                (ip >> 16 & 0xff),
                (ip >> 24 & 0xff)
        );
    }

并且要给权限。

    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
    <uses-permission android:name="android.permission.INTERNET"/>
魅族ip地址为192.168.43.1
小米ip地址为192.168.43.213

由于小米手机连接的是魅族手机分享的热点,因此小米手机连接网络的路由器地址就是192.168.43.1

要在2台手机上进行UDP通讯的话,,由于魅族做服务端,因此需要192.168.43.1这个地址。

    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />

Android默认热点分享的ip是192.168.43.1,源码里写死了的,所以这里可以我直接写死而不是通过动态获取赋值。在UDP通讯过程中,只需要一个ip地址,那就是192.168.43.1

下面新建2个工程,serviceclient。注意通讯端口要保持一致,这里我都选择了3000
service代码如下
首先加权限:

    <uses-permission android:name="android.permission.INTERNET" />

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private static final int SERVER_PORT = 3000;
    private byte bufServer[] = new byte[1024];
    private static final int BUF_LENGTH = 1024;
    private boolean mIsRunning;

    private DatagramSocket mDatagramSocket;
    private DatagramPacket mDatagramPacket;
    private TextView mTextView;
    private Thread mThread;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = findViewById(R.id.text_view);
        createServer();
    }

    private void createServer() {
        mIsRunning = true;
        try {
            mDatagramSocket = new DatagramSocket(SERVER_PORT);
            mDatagramPacket = new DatagramPacket(bufServer, BUF_LENGTH);
            startServerThread();
        } catch (SocketException e) {
            e.printStackTrace();
        }
    }

    private void startServerThread() {
        mThread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    while (mIsRunning) {
                        mDatagramSocket.receive(mDatagramPacket);
                        final String result = new String(mDatagramPacket.getData(), mDatagramPacket.getOffset(), mDatagramPacket.getLength());
                        Log.d(TAG,"result " + result);

                        // 每次接收完UDP数据后,重置长度。否则可能会导致下次收到数据包被截断。
                        if (mDatagramPacket != null) {
                            mDatagramPacket.setLength(BUF_LENGTH);
                        }

                        mTextView.post(new Runnable() {
                            @Override
                            public void run() {
                                mTextView.setText(result);
                            }
                        });
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
        mThread.start();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mIsRunning = false;
        mDatagramSocket.close();
    }
}

client代码如下。
首先,加权限

    <uses-permission android:name="android.permission.INTERNET" />
public class HeartbeatTimer {

    private Timer timer;
    private TimerTask task;
    private OnScheduleListener mListener;

    public HeartbeatTimer() {
        timer = new Timer();
    }

    public void startTimer(long delay, long period) {
        task = new TimerTask() {
            @Override
            public void run() {
                if (mListener != null) {
                    mListener.onSchedule();
                }
            }
        };
        timer.schedule(task, delay, period);
    }

    public void exit() {
        if (timer != null) {
            timer.cancel();
        }
        if (task != null) {
            task.cancel();
        }
    }

    public interface OnScheduleListener {
        void onSchedule();
    }

    public void setOnScheduleListener(OnScheduleListener listener) {
        this.mListener = listener;
    }
}
public class UDPSocket {
    private static final String TAG = "UDPSocket";
    private static final String SERVICE_IP = "192.168.43.1";

    // 单个CPU线程池大小
    private static final int POOL_SIZE = 5;
    public static final int CLIENT_PORT = 3000;

    private ExecutorService mThreadPool;

    private DatagramSocket mDatagramSocket;

    private HeartbeatTimer mHeartbeatTimer;

    private long lastReceiveTime = 0;
    private static final long TIME_OUT = 120 * 1000;
    private static final long HEARTBEAT_MESSAGE_DURATION = 10 * 1000;

    public UDPSocket() {
        int cpuNumbers = Runtime.getRuntime().availableProcessors();
        // 根据CPU数目初始化线程池
        mThreadPool = Executors.newFixedThreadPool(cpuNumbers * POOL_SIZE);
        // 记录创建对象时的时间
        lastReceiveTime = System.currentTimeMillis();

    }

    public void startUDPSocket() {
        if (mDatagramSocket != null) return;
        try {
            // 表明这个 Socket 在设置的端口上监听数据。
            mDatagramSocket = new DatagramSocket(CLIENT_PORT);
            startHeartbeatTimer();
        } catch (SocketException e) {
            e.printStackTrace();
        }
    }


    public void stopUDPSocket() {
        if (mDatagramSocket != null) {
            mDatagramSocket.close();
            mDatagramSocket = null;
        }
        if (mHeartbeatTimer != null) {
            mHeartbeatTimer.exit();
        }
    }

    /**
     * 启动心跳,timer 间隔十秒
     */
    private void startHeartbeatTimer() {
        mHeartbeatTimer = new HeartbeatTimer();
        mHeartbeatTimer.setOnScheduleListener(new HeartbeatTimer.OnScheduleListener() {
            @Override
            public void onSchedule() {
                Log.d(TAG, "timer is onSchedule...");
                long duration = System.currentTimeMillis() - lastReceiveTime;
                Log.d(TAG, "duration:" + duration);
                if (duration > TIME_OUT) {//若超过两分钟都没收到我的心跳包,则认为对方不在线。
                    Log.d(TAG, "超时,对方已经下线");
                    // 刷新时间,重新进入下一个心跳周期
                    lastReceiveTime = System.currentTimeMillis();
                } else if (duration > HEARTBEAT_MESSAGE_DURATION) {//若超过十秒他没收到我的心跳包,则重新发一个。
                    String string = "hello,this is a heartbeat message";
                    sendMessage(string);
                }
            }

        });
        mHeartbeatTimer.startTimer(0, 1000 * 10);
    }
    /**
     * 发送心跳包
     *
     * @param message
     */
    public void sendMessage(final String message) {
        mThreadPool.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    InetAddress targetAddress = InetAddress.getByName(SERVICE_IP);
                    byte[] sendData = createSendData(message);
                    //创建用来发送的 DatagramPacket 数据报,其中应该包含要发送的信息,以及本地地址,目标端口号。
                    DatagramPacket packet = new DatagramPacket(sendData, sendData.length, targetAddress, CLIENT_PORT);

                    String result = new String(packet.getData(), 0, packet.getLength());
                    Log.d(TAG,"result " + result);
                    mDatagramSocket.send(packet);

                    // 数据发送事件
                    Log.d(TAG, "数据发送成功");

                } catch (UnknownHostException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        });
    }

    //中文字符发送前要处理,否则会出现乱码,因此这里统一处理
    private byte[] createSendData(String strSend) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        DataOutputStream dataStream = new DataOutputStream(baos);
        try {
            dataStream.writeUTF(strSend);
            dataStream.close();
            return baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new byte[0];
    }
}
public class MainActivity extends AppCompatActivity {
    private EditText mEditText;
    private Button mButton;
    private UDPSocket mUDPSocket;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mEditText = findViewById(R.id.edit_text);
        mButton = findViewById(R.id.send_button);
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mUDPSocket.sendMessage(mEditText.getText().toString());
            }
        });
        mUDPSocket = new UDPSocket();
        mUDPSocket.startUDPSocket();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mUDPSocket.stopUDPSocket();
    }
}

参考链接:
Android 网络编程(1)——Socket编程So easy
Android利用UDP、TCP进行局域网数据传输
Android基于UDP的局域网聊天通信(有完整Demo)
在 Android 上,一个完整的 UDP 通信模块应该是怎样的?
Socket 通信之 UDP 通信
Android UDP

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容