添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
发怒的黄瓜  ·  在 Tableau Server ...·  1 年前    · 
安静的冰棍  ·  textContent、innerHTML、 ...·  1 年前    · 
备案 控制台
学习
实践
活动
专区
工具
TVP
写文章
专栏首页 李蔚蓬的专栏 Android | UDP的C/S通信实战案例(简易聊天室)
4 0

海报分享

Android | UDP的C/S通信实战案例(简易聊天室)

创建UDP服务端

  • new一个Module: 模块名为:sample

    • 创建一个package,名为udp:

    InetAddress.InetAddressHolder源码:

    InetAddressHolder(String hostName, int address, int family) {
                this.originalHostName = hostName;
                this.hostName = hostName;
                this.address = address;
                this.family = family;
            }
    • 在包下创建UdpServer.class,编写:
    package com.lwp.sample.udp;
    import java.io.IOException;
    import java.net.DatagramPacket;
    import java.net.DatagramSocket;
    import java.net.InetAddress;
    import java.net.SocketException;
    import java.net.UnknownHostException;
    import java.util.Scanner;
     * <pre>
     *     author : 李蔚蓬(简书_凌川江雪)
     *     time   : 2019/10/27 17:08
     *     desc   :
     * </pre>
    public class UdpServer {
        private InetAddress mInetAddress;
        private int mPort = 7777;//尽可能用5000以后的
        private DatagramSocket mSocket;
        private Scanner mScanner;
        //构造方法中初始化
        public UdpServer() {
            try {
                mInetAddress = InetAddress.getLocalHost();
                //传入,设置好本服务器ip 和 本服务程序指定的端口,虚拟“链接”的服务器一端
                mSocket = new DatagramSocket(mPort, mInetAddress);
                //用于控制面板的输入
                mScanner = new Scanner(System.in);
                mScanner.useDelimiter("\n");//指定控制面板的输入以换行来结束
            } catch (UnknownHostException e) {
                e.printStackTrace();
            } catch (SocketException e) {
                e.printStackTrace();
        public void start() {
            //让Server端持续运行
            while (true) {
                try {
                    //类似于缓存区的一个字节数组
                    //UDP每次通信的数据大小受限制
                    //限制就来自于服务端传给DatagramPacket的字节数组
                    //因为UDP是通过DatagramPacket封装数据的,
                    // 而DatagramPacket的创建必须传入一个字节数组,这个数组便是通信数据包的大小限制
                    //这里指定的是1024,也就是客户端发送过来的数据包,
                    // 每次不能超过1024个字节,1byte = 8bit
                    byte[] buf = new byte[1024];
                    //接收客户端数据
                    DatagramPacket receivedPacket = new DatagramPacket(buf, buf.length);
                    //如果没有数据包到来的话,程序会一直阻塞在receive()这里,receive()会阻塞,
                    // 如果有一个客户端发送一个数据包到这个程序中,
                    // 程序就会去执行receive()方法,将接收到的数据传输到receivedPacket中进而传输给receive()
                    mSocket.receive(receivedPacket);
                    //所以如果程序能往下走,就证明接收到数据了
                    //拿到客户端地址、端口号、发送过来的数据
                    InetAddress address = receivedPacket.getAddress();
                    int port = receivedPacket.getPort();
                    byte[] data = receivedPacket.getData();
                    String clientMsg = new String(data, 0, data.length);//把接收到的字节数据转换成String
                    //打印客户端信息和发送过来的数据
                    System.out.println("address = " + address +
                            ", port = " + port + ",(Client's) msg = " + clientMsg);
                      读取Terminal的输入
                      next()也是阻塞的,监听Terminal输入(消息+回车)
                      给客户端返回数据,
                      返回的数据我们希望可以在控制面板Terminal上写,
                      写完按Enter键完成
                    String returnedMsg = mScanner.next();
                    byte[] returnedMsgBytes = returnedMsg.getBytes();//将String转换成byte数组
                    //getSocketAddress中包含getAddress(), getPort(),即包含地址跟数组
                    //下面把需要返回给客户端的数据封装成一个DatagramPacket
                    DatagramPacket sendPacket = new DatagramPacket(returnedMsgBytes,
                            returnedMsgBytes.length, receivedPacket.getSocketAddress());
                    mSocket.send(sendPacket);
                } catch (Exception e) {
                    e.printStackTrace();
        public static void main(String[] args) {
            new UdpServer().start();
    }

    如此便完成了UDP Server的代码的编写, 相对比较简单, 涉及到的API就是以上所说的 DatagramSocket 以及 DatagramPacket 接收、发送数据时候, 都要提前封装一个 DatagramPacket 对象, 接收时的封装传入的参数: 缓存字节数组引用 及其 长度 发送时的封装传入的参数: 缓存字节数组引用 及其 长度 、封装了客户端(发送目的地) ip、port InetAddress对象 然后通过 receive() send() 操作即可;

    创建UDP客户端

    • 先创建java文件,调试完毕之后,再移植到Android上来;
    • udp包下,创建一个 UdpClient

    package com.lwp.sample.udp;
    import java.io.IOException;
    import java.net.DatagramPacket;
    import java.net.DatagramSocket;
    import java.net.InetAddress;
    import java.net.SocketException;
    import java.net.UnknownHostException;
    import java.util.Scanner;
     * <pre>
     *     author : 李蔚蓬(简书_凌川江雪)
     *     time   : 2019/10/28 15:20
     *     desc   :
     * </pre>
    public class UdpClient {
         * 指定Server的 ip 和 port
        private String mServerIp = "***";
        private int mServerPort = 7777;
        private InetAddress mServerAddress;
         * 通信用的Socket
        private DatagramSocket mSocket;
        private Scanner mScanner;
        //构造方法中初始化
        public UdpClient() {
            try {
                    直接实例化一个默认的Socket对象即可,
                    因为我们不需要像服务端那样把别的Client接入过来,
                    不必特别明确指定 自己的ip和port(服务程序),!!!!!!!!!!
                    因为这里是Client,是数据请求获取方,不是数据提供方,!!!!
                    所以只需要一个默认的Socket对象
                    来进行send 和 receive 即可
                mSocket = new DatagramSocket();
                mServerAddress = InetAddress.getByName(mServerIp);
                mScanner = new Scanner(System.in);
                mScanner.useDelimiter("\n");
            } catch (SocketException e) {
                e.printStackTrace();
            } catch (UnknownHostException e) {
                e.printStackTrace();
        public void start() {
            while (true) {
                try {
                        完成向服务端发送数据
                    String clientMsg = mScanner.next();
                    byte[] clientMsgBytes = clientMsg.getBytes();
                   封装数据包,传入数据数组以及服务端地址、端口号
                    DatagramPacket clientPacket = new DatagramPacket(clientMsgBytes,
                            clientMsgBytes.length, mServerAddress, mServerPort);
                    mSocket.send(clientPacket);
                        接收服务端数据
                    byte[] buf = new byte[1024];
                    DatagramPacket serverMsgPacket = new DatagramPacket(buf, buf.length);
                    mSocket.receive(serverMsgPacket);
                    //拿到服务端地址、端口号、发送过来的数据
                    InetAddress address = serverMsgPacket.getAddress();
                    int port = serverMsgPacket.getPort();
                    byte[] data = serverMsgPacket.getData();
                    String serverMsg = new String(data, 0, data.length);//把接收到的字节数据转换成String
                    //打印服务端信息和发送过来的数据
                    System.out.println("(Server's) msg = " + serverMsg);
                } catch (IOException e) {
                    e.printStackTrace();
        public static void main(String[] args) {
            new UdpClient().start();
    }

    private String mServerIp = "***"; 这里,我把代码的内容略去了, 这部分使用的是本机的网络IPV4的ip, 查看本机ip方法传送门 为何用的是 本机ip 呢,因为 UdpServer 中: mInetAddress = InetAddress.getLocalHost(); 处, 设置的 服务端ip 正是 本机ip

    • 开始测试

    注意, 程序运行第二次的时候, 如果第一次运行没有对链接进行关闭, 则第一次运行的端口号会被占用, 导致第二次相关程序运行时 Socket对象 无法 实例化 以致于 Socket对象 空(NULL) 程序报 空指针 的错误! 为了避免这种情况, 可以在不需要 Server 的时候,将 Server 程序暂停; 也可以在更改程序之后,使PC睡眠再重新打开,亦可刷新 port 占用; 或者直接为更改后的程序指定 新的port ,当然这种方法不推荐;

    进入UdpServer.class,右键启动main程序:

    同理启动UdpClient.class的main程序,启动完毕弹出底部界面如下:

    Client端敲一个"nihao I am Client",然后回车:

    切换到UdpServer终端,可以看到接收到信息:

    反复测试:

    移植客户端

    • 将UDP客户端程序移植到Android中; activity_main.xml:
    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
        <Button
            android:id="@+id/id_btn_send"
            android:text="Send"
            android:layout_width="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_height="wrap_content" />
        <EditText
            android:id="@+id/id_et_msg"
            android:layout_toLeftOf="@+id/id_btn_send"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
        <ScrollView
            android:layout_below="@+id/id_et_msg"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <TextView
                android:id="@+id/id_tv_content"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />
        </ScrollView>
    </RelativeLayout>
    • 建一个Package名为biz,用于打包业务代码:
      • 直接copy那UdpClient.class,粘贴在biz包下, 改名为UdpClientBiz:

    编写之:

    public class UdpClientBiz {
         * 指定Server的 ip 和 port
        private String mServerIp = "172.18.1.59";
        private int mServerPort = 7778;
        private InetAddress mServerAddress;
        private Handler mUIHandler = new Handler(Looper.getMainLooper());
         * 通信用的Socket
        private DatagramSocket mSocket;
        //构造方法中初始化
        public UdpClientBiz() {
            try {
                    直接实例化一个默认的Socket对象即可,
                    因为我们不需要像服务端那样把别的Client接入过来,
                    不必特别明确指定 自己的ip和port(服务程序),!!!!!!!!!!
                    因为这里是Client,是数据请求获取方,不是数据提供方,!!!!
                    所以只需要一个默认的Socket对象
                    来进行send 和 receive 即可
                mSocket = new DatagramSocket();
                mServerAddress = InetAddress.getByName(mServerIp);
            } catch (SocketException e) {
                e.printStackTrace();
            } catch (UnknownHostException e) {
                e.printStackTrace();
            需求:客户端接收Server端返回的数据,并展示在控件上
            实现:send 方法绑定一个接口
            ps:这里的回调机制实现其实还有一种写法,
            就是另外单独再起一个setListener方法来绑定Listener ,
            但是这样做不太符合这里具体的场景——每个 服务端 return 回来的数据
            都是跟每个 客户端 send 出去的数据相关联对应的;
            单独使用setListener 的方式,看不到这个关联的逻辑,
            所以这里直接把Listener 作为sendMsg 的必要形参,形成关联逻辑
            以及绑定关系——必须先 sendMsg 之后才能 returnMsg(receiveMsg)
        public interface onMsgReturnedListener{
            void onMsgReturned(String msg);
                Handle Exception
                如果是异步的方法调用:可以把Exception 通过 Listener 给回调出去
                如果是同步的方法调用:尽可能不要在方法中进行try catch,
                最好是将其throw 出去,
                或者catch 之后 封装下错误类型再将其throw 出去,
                即一定要让调用者能知道这个异常;
                这里是异步调用
            void onError(Exception ex);
        public void sendMsg(final String msg, final onMsgReturnedListener listener) {
            new Thread() {
                @Override
                public void run() {
                    try {
                        //信息转型
                        byte[] clientMsgBytes = msg.getBytes();
                   封装数据包,传入数据数组以及服务端地址、端口号
                        DatagramPacket clientPacket = new DatagramPacket(clientMsgBytes,
                                clientMsgBytes.length, mServerAddress, mServerPort);
                        mSocket.send(clientPacket);
                    接收服务端数据
                        byte[] buf = new byte[1024];
                        DatagramPacket serverMsgPacket = new DatagramPacket(buf, buf.length);
                        mSocket.receive(serverMsgPacket);
                        //拿到服务端地址、端口号、发送过来的数据
                        InetAddress address = serverMsgPacket.getAddress();
                        int port = serverMsgPacket.getPort();
                        byte[] data = serverMsgPacket.getData();
                        final String serverMsg = new String(data, 0, data.length);//把接收到的字节数据转换成String
                            以上是信息的发送和接收,写在sendMsg 方法体中,名副其实
                            以下是对接收数据的处理,通过回调处理
                        //这里是子线程,
                        // 但是 Handler 已同 MainLooper 进行绑定,
                        // 则利用这个handle 去更新UI,等同于切回主线程更新UI
                        mUIHandler.post(new Runnable() {
                            @Override
                            public void run() {
                                //数据借助回调外传
                                //“切回了”主线程,在调用的时候,接收数据之后才能更新UI
                                listener.onMsgReturned(serverMsg);
                    } catch (final Exception e) {
                        mUIHandler.post(new Runnable() {
                            @Override
                            public void run() {
                                //异常回调
                                listener.onError(e);
            }.start();
        public void onDestroy() {
            if(mSocket != null){
                mSocket.close();
    }

    MainActivity.java:

    public class MainActivity extends AppCompatActivity {
        private EditText mEtMsg;
        private Button mBtnSend;
        private TextView mTvContent;
        private UdpClientBiz mUdpClientBiz = new UdpClientBiz();
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            initViews();
        private void initViews() {
            mEtMsg = findViewById(R.id.id_et_msg);
            mBtnSend = findViewById(R.id.id_btn_send);
            mTvContent = findViewById(R.id.id_tv_content);
            mBtnSend.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    String msg = mEtMsg.getText().toString();
                    if (TextUtils.isEmpty(msg)) {
                        return;
                    appendMsgToContent("client:" + msg);
                    //发送后清除编辑框文本
                    mEtMsg.setText("");
                    mUdpClientBiz.sendMsg(msg, new UdpClientBiz.onMsgReturnedListener() {
                        @Override
                        public void onMsgReturned(String msg) {
                            //更新UI
                            appendMsgToContent("server:" + msg);
                        @Override
                        public void onError(Exception ex) {
                            ex.printStackTrace();
        private void appendMsgToContent(String msg) {
            mTvContent.append(msg + "\n");
        @Override
        protected void onDestroy() {
            super.onDestroy();
            mUdpClientBiz.onDestroy();
    }

    然后记得添加网络权限:

    <uses-permission android:name="android.permission.INTERNET"/>
    • 测试: 启动UdpServer: 复制UdpActivity一份,原地粘贴,命名为TcpActivity:

      凌川江雪
  • 详解Android 基于TCP和UDP协议的Socket通信

    本来想讲一下基础的网络通信方面的知识点,发现太枯燥乏味了,不过笔试中也经常会问到这方面的问题,所以关于通信方面的知识点,小编会放到面试中去,因为实战中也就面试会...

    砸漏
  • Python基于Socket实现群聊

    套接字(Sockets)是双向通信信道的端点。套接字可以在一个进程内,在同一机器上的进程之间,或者在不同主机的进程之间进行通信,主机可以是任何一台有连接互联网的...

    Python小二
  • 网络编程基础

    在学习Java基础的时候,有一章节就叫《网络编程》,那么何为网络编程呢?在此之前先了解一下何为计算机网络。

    贪挽懒月
  • Socket通信

    OSI是一个理想的模型,一般的网络系统只涉及其中的几层,在七层模型中,每一层都提供一个特殊 的网络功能,从网络功能角度观察:

    小小工匠
  • 简易理解设计模式之:解释器模式——语言和文法

    先理解一些概念

  •