JAVA基础学习之UDP网络编程

本文详细介绍了UDP协议的基本概念、特点以及面向无连接的数据传输方式。并通过Java编程示例展示了如何使用DatagramSocket和DatagramPacket类进行UDP通信,包括服务器端监听、客户端发送和接收数据的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

UDP(UserDatagramProtocol)用户数据报包协议

UDP和TCP位于同一层-传输层,但它对于数据包的顺序错误或重发没有TCP来的可靠。因此,UDP不被应用于那些使用虚电路的面向直接服务.
UDP是一种面向无连接的通信协议。UDP向应用程序提供了一种发送封装的原始IP数据包的方法,并且发送时无需建立连接,不保证可靠数据的传输

特点:面向无连接的数据传输,不可靠的,但效率高。
UDP一次发送的数据不能超过64KB.

UDP编程所需要的类:
DatagramSocket 此类表示用来发送和接收数据报包的套接字
DatagramPacket 此类表示数据报包
DatagramPacket(byte[]buf, intlength, InetAddressaddress, intport)
参数:buf -包数据
length -包长度
address -目的地址
port -目的端口号

写法步骤:
服务器端:创建监听套接字,接受数据包,发送数据包,关闭套接字
客户端: 创建套接字,发送数据包,接受数据包,关闭套接字


DatagramSocket类
DatagramSocket(int port)
创建数据报套接字并将其绑定到本地主机上的指定端口。
DatagramSocket(int port, InetAddress laddr)
创建数据报套接字,将其绑定到指定的本地地址。



DatagramPacket类

构造方法摘要

DatagramPacket(byte[] buf, int length)

构造 DatagramPacket,用来接收长度为 length 的数据包。

DatagramPacket(byte[] buf, int length, InetAddress address, int port)

构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。

DatagramPacket(byte[] buf, int offset, int length)

构造 DatagramPacket,用来接收长度为 length 的包,在缓冲区中指定了偏移量。

DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port)

构造数据报包,用来将长度为 length 偏移量为 offset 的包发送到指定主机上的指定端口号。

DatagramPacket(byte[] buf, int offset, int length, SocketAddress address)

构造数据报包,用来将长度为 length 偏移量为 offset 的包发送到指定主机上的指定端口号。

DatagramPacket(byte[] buf, int length, SocketAddress address)

构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。


一般方法

 InetAddress     getAddress()

返回某台机器的 IP 地址,此数据报将要发往该机器或者是从该机器接收到的。

byte[]  getData()

返回数据缓冲区。

int getLength()

返回将要发送或接收到的数据的长度。

int getOffset()

返回将要发送或接收到的数据的偏移量。

int getPort()

返回某台远程主机的端口号,此数据报将要发往该主机或者是从该主机接收到的。

SocketAddress   getSocketAddress()

获取要将此包发送到的或发出此数据报的远程主机的 SocketAddress(通常为 IP 地址 + 端口号)。

void    setAddress(InetAddress iaddr)

设置要将此数据报发往的那台机器的 IP 地址。

void    setData(byte[] buf)

为此包设置数据缓冲区。

void    setData(byte[] buf, int offset, int length)

为此包设置数据缓冲区。

void    setLength(int length)

为此包设置长度。

void    setPort(int iport)

设置要将此数据报发往的远程主机上的端口号。

void    setSocketAddress(SocketAddress address)

设置要将此数据报发往的远程主机的 SocketAddress(通常为 IP 地址 + 端口号)。

示例1:
本例子使用WindowBuilder图形界面进行编写。实现简单的一对一聊天
界面效果:
这里写图片描述

java代码

package com.ql.Receivers;

import java.awt.EventQueue;

import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.UnknownHostException;

import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.JButton;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;

public class Receiver implements ActionListener {

    private JFrame frame;
    private JTextField txtStr;
    private JTextArea txtArea;
    private JButton btnSend, btnEnter;
    private JTextField txtYouAddress;

    private DatagramSocket receiverSocket, sendSocket;//发送和接收套接字
    private JTextField txtMyAddress;

    private String youAddress = "127.0.0.2", myAddress = "127.0.0.1";//己方地址和对方地址

    /**
     * Launch the application.
     */
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    Receiver window = new Receiver();
                    window.frame.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Create the application.
     */
    public Receiver() {
        initialize();
    }

    /**
     * Initialize the contents of the frame.
     */
    private void initialize() {
        frame = new JFrame();
        frame.setBounds(100, 100, 530, 370);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().setLayout(new BorderLayout(0, 0));

        JPanel panel = new JPanel();
        frame.getContentPane().add(panel);
        panel.setLayout(null);

        JPanel panel_1 = new JPanel();
        panel_1.setBounds(0, 33, 514, 256);
        panel.add(panel_1);
        panel_1.setLayout(new BorderLayout(0, 0));

        JScrollPane scrollPane = new JScrollPane();
        panel_1.add(scrollPane);

        txtArea = new JTextArea();
        scrollPane.setViewportView(txtArea);

        JPanel panel_2 = new JPanel();
        panel_2.setBounds(0, 288, 514, 44);
        panel.add(panel_2);
        panel_2.setLayout(null);

        JLabel lblNewLabel = new JLabel("\u8BF7\u8F93\u5165\uFF1A");
        lblNewLabel.setBounds(10, 18, 54, 15);
        panel_2.add(lblNewLabel);

        txtStr = new JTextField();
        txtStr.setBounds(74, 15, 329, 21);
        panel_2.add(txtStr);
        txtStr.setColumns(10);

        btnSend = new JButton("\u53D1\u9001");
        btnSend.setBounds(413, 12, 93, 27);
        btnSend.addActionListener(this);
        panel_2.add(btnSend);

        JPanel panel_3 = new JPanel();
        panel_3.setBounds(0, 0, 514, 31);
        panel.add(panel_3);
        panel_3.setLayout(null);

        JLabel lblNewLabel_1 = new JLabel("\u5BF9\u65B9IP\uFF1A");
        lblNewLabel_1.setBounds(10, 10, 54, 15);
        panel_3.add(lblNewLabel_1);

        txtYouAddress = new JTextField();
        txtYouAddress.setText("127.0.0.1");
        txtYouAddress.setBounds(65, 7, 152, 21);
        panel_3.add(txtYouAddress);
        txtYouAddress.setColumns(10);

        JLabel lblNewLabel_2 = new JLabel("\u60A8\u7684IP\uFF1A");
        lblNewLabel_2.setBounds(234, 10, 54, 15);
        panel_3.add(lblNewLabel_2);

        txtMyAddress = new JTextField();
        txtMyAddress.setText("127.0.0.1");
        txtMyAddress.setBounds(282, 7, 152, 21);
        panel_3.add(txtMyAddress);
        txtMyAddress.setColumns(10);

        btnEnter = new JButton("\u786E\u5B9A");
        btnEnter.setBounds(440, 6, 64, 23);
        btnEnter.addActionListener(this);
        panel_3.add(btnEnter);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        // 按钮处理
        if (e.getSource() == btnSend) { //发送
            String string = txtStr.getText().trim();
            send(string);
        }
        if(e.getSource() == btnEnter){ //连接
            if(!"".equals(txtMyAddress.getText().trim()) && !"".equals(txtYouAddress.getText().trim())){
                myAddress = txtMyAddress.getText().trim();
                youAddress = txtYouAddress.getText().trim();
                connect(myAddress); //封装的连接方法
            }else{
                txtArea.setText(txtArea.getText() + "发送目的地址或自己地址不能为空!");
            }
        }
    }

    /**
     * 封装的连接方法
     * @param address 己方地址,用于接收数据包
     */
    public void connect(String address) {
        SocketAddress mAddress = new InetSocketAddress(address, 8888);

        try {
            receiverSocket = new DatagramSocket(mAddress);//实例化接收套接字
            sendSocket = new DatagramSocket();//实例化发送套接字
        } catch (SocketException e) {
            e.printStackTrace();
        }
        new ReceiverThread(receiverSocket).start();//将接收放入线程中,不断接收数据
    }

    /**
     * 用于接收消息的线程
     * @author qinlang
     *
     */
    class ReceiverThread extends Thread {
        DatagramSocket socket;
        public ReceiverThread(DatagramSocket socket){
            this.socket = socket;
        }

        @Override
        public void run() {
            while(true){
                DatagramPacket packet = new DatagramPacket(new byte[1024], 1024);//定义并实例化接收对象
                try {
                    socket.receive(packet);//接收数据包
                } catch (IOException e) {
                    e.printStackTrace();
                }
                txtArea.setText(txtArea.getText() + "\n" + new String(packet.getData(), 0, packet.getLength()));//显示数据包内容
            }
        }
    }

    /**
     * 用于发送数据包的方法
     * @param str
     */
    public void send(String str){
        InetAddress address = null;//定义发送至的地址
        try {
            address = InetAddress.getByName(youAddress);//实例化地址
        } catch (UnknownHostException e1) {
            e1.printStackTrace();
        }
        DatagramPacket packet = new DatagramPacket(str.getBytes(), str.getBytes().length, address, 8888);//定义并实例化数据包
        try {
            sendSocket.send(packet);//发送数据包
            txtArea.setText(txtArea.getText() + "\n已发送...");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

示例2:
本例子使用WindowBuilder图形界面进行编写。实现简单的多对多聊天

界面效果:
这里写图片描述

java代码:
客户端

package com.ql.talkM2M;

import java.awt.EventQueue;

import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;

import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.JButton;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

public class Sender implements ActionListener{

    private JFrame frame;
    private JTextField txtName;
    private JTextField txtServerAddress;
    private JTextField txtStr;
    private JButton btnConnect, btnSend;
    private JTextArea textArea;

    private DatagramSocket socket;//套接字
    private DatagramPacket packet;//数据包

    private String name; //昵称
    private Thread thread;//用于接收的线程

    /**
     * Launch the application.
     */
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    Sender window = new Sender();
                    window.frame.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Create the application.
     */
    public Sender() {
        initialize();
    }

    /**
     * Initialize the contents of the frame.
     */
    private void initialize() {
        frame = new JFrame();
        frame.addWindowListener(new WindowAdapter() {//程序关闭通知所有在线的用户
            @Override
            public void windowClosing(WindowEvent e) {
                if(thread != null){
                    send("退出了...");
                }
            }
        });
        frame.setBounds(100, 100, 536, 354);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JPanel panel = new JPanel();
        frame.getContentPane().add(panel, BorderLayout.CENTER);
        panel.setLayout(null);

        JPanel panel_1 = new JPanel();
        panel_1.setBounds(0, 0, 520, 37);
        panel.add(panel_1);
        panel_1.setLayout(null);

        JLabel lblNewLabel = new JLabel("\u6635\u79F0\uFF1A");
        lblNewLabel.setBounds(263, 10, 44, 15);
        panel_1.add(lblNewLabel);

        txtName = new JTextField();
        txtName.setBounds(305, 7, 131, 21);
        panel_1.add(txtName);
        txtName.setColumns(10);

        btnConnect = new JButton("连接");
        btnConnect.setBounds(446, 6, 64, 23);
        btnConnect.addActionListener(this);
        panel_1.add(btnConnect);

        JLabel lblNewLabel_1 = new JLabel("\u670D\u52A1\u5668IP\uFF1A");
        lblNewLabel_1.setBounds(10, 10, 64, 15);
        panel_1.add(lblNewLabel_1);

        txtServerAddress = new JTextField();
        txtServerAddress.setText("127.0.0.1");
        txtServerAddress.setBounds(74, 7, 179, 21);
        panel_1.add(txtServerAddress);
        txtServerAddress.setColumns(10);

        JPanel panel_2 = new JPanel();
        panel_2.setBounds(0, 279, 520, 37);
        panel.add(panel_2);
        panel_2.setLayout(null);

        JLabel lblNewLabel_2 = new JLabel("\u8BF7\u8F93\u5165\uFF1A");
        lblNewLabel_2.setBounds(10, 12, 54, 15);
        panel_2.add(lblNewLabel_2);

        txtStr = new JTextField();
        txtStr.setBounds(57, 9, 362, 21);
        panel_2.add(txtStr);
        txtStr.setColumns(10);

        btnSend = new JButton("\u53D1\u9001");
        btnSend.setEnabled(false);
        btnSend.setBounds(427, 8, 83, 23);
        btnSend.addActionListener(this);
        panel_2.add(btnSend);

        JPanel panel_3 = new JPanel();
        panel_3.setBounds(0, 37, 520, 242);
        panel.add(panel_3);
        panel_3.setLayout(new BorderLayout(0, 0));

        JScrollPane scrollPane = new JScrollPane();
        panel_3.add(scrollPane);

        textArea = new JTextArea();
        scrollPane.setViewportView(textArea);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        //按钮按键处理
        if(e.getSource() == btnConnect){ //连接
            if("连接".equals(btnConnect.getText())){//按钮上的文字显示“连接”,表示没有连接,点击连接
                if("".equals(txtServerAddress.getText()) || "".equals(txtName.getText().trim())){
                    textArea.setText(textArea.getText() + "请输入服务器IP或昵称!\n");
                }else{
                    connect();//封装的连接方法
                    btnConnect.setText("断开");//连接按钮改变文字
                    thread = new Thread(new IN(socket));//将接收放入线程中,不断接收数据包
                    thread.start();
                    btnSend.setEnabled(true);//发送按钮使能
                }
            }else if("断开".equals(btnConnect.getText())){//按钮上的文字显示“断开”,表示正在连接,点击断开
                if(e.getSource() == btnConnect){
                    send("退出了...");//断开了连接,表明已下线
                    if(thread != null){
                        thread.stop();//销毁线程
                        btnSend.setEnabled(false);//发送使否
                    }
                    btnConnect.setText("连接");//连接按钮改变文字
                }
            }
        }

        if(e.getSource() == btnSend){ //发送
            String string = txtStr.getText().trim();
            send(string);//封装的发送方法
        }
    }

    /**
     * 连接方法
     */
    public void connect(){
        try {
            socket = new DatagramSocket();//实例化套接字
            name = txtName.getText().trim();
            String str = name + ":上线了...";
            String address = "";
            address = txtServerAddress.getText().trim();//得到服务器ip
            //连接上服务器,发送一次数据,通知服务器上的所有用户
            packet = new DatagramPacket(str.getBytes(),str.getBytes().length, new InetSocketAddress(address, 8888));
            socket.send(packet);
            textArea.setText(textArea.getText() + "\n已连接...");
        } catch (SocketException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 为接收开启单独的线程,不断接收数据
     * @author qinlang
     *
     */
    class IN implements Runnable {
        private DatagramSocket socket;

        public IN(DatagramSocket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            byte[] b = new byte[1024];//一次读取数据大小
            DatagramPacket packet2 = new DatagramPacket(b, b.length);// 用来接收的包裹
            String value = "";
            while (true) {
                try {
                    socket.receive(packet2);//接收数据
                    value = new String(b, 0, packet2.getLength());//将数据分割读取
                    textArea.setText(textArea.getText() + "\n" + value);
                } catch (IOException e) {
                    break;
                }
            }
        }
    }

    /**
     * 发送数据的方法
     * @param data
     */
    public void send(String data){
        try {
            packet.setData((name + ":" + data).getBytes());//设置数据(发送的数据和昵称),转换成字节流
            socket.send(packet);//发送数据
            textArea.setText(textArea.getText() + "\n您:" + data);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

服务器

package com.ql.talkM2M;

import java.io.BufferedReader;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.List;

/**
 * 服务器端,可看做只转发数据的服务器,也可以当客户端发送数据
 * @author qinlang
 *
 */
public class Server {

    /**
     * @param args
     */
    public static void main(String[] args) {
        DatagramSocket socket = null;
        DatagramPacket packet = null;
        List<SocketAddress> addresses;//存放所有用户地址的集合
        try {
            socket = new DatagramSocket(8888);
            packet = new DatagramPacket(new byte[1024], 1024);
            socket.receive(packet);//接收一次自己的数据
        } catch (SocketException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        addresses = new ArrayList<SocketAddress>();
        addresses.add(packet.getSocketAddress()); //将自己的地址加入集合

        new Thread(new INReceiver(socket, addresses)).start();//启动接收线程
        //发送线程没有启动,因为只做转发功能,服务器不需要发送自己的数据
    }

}

/**
 * 发送数据线程,此方法用于服务器对连接在服务器上的用户发数据
 * @author qinlang
 *
 */
class OUTReceiver implements Runnable {
    BufferedReader br;
    DatagramPacket packet;// 数据 地址
    DatagramSocket socket;
    List<SocketAddress> addresses;

    public OUTReceiver(BufferedReader br, DatagramPacket packet,
            DatagramSocket socket, List<SocketAddress> addresses) {
        this.br = br;
        this.packet = packet;
        this.socket = socket;
        this.addresses = addresses;
    }

    @Override
    public void run() {
        while (true) {//死循环,不断发送数据
            try {
                String data = br.readLine();//按行读取数据
                packet.setData((Thread.currentThread().getName() + data).getBytes());
                // 同一个数据,把所有在线的人都发一遍
                for (SocketAddress socketAddress : addresses) {
                    packet.setSocketAddress(socketAddress);
                    socket.send(packet);
                }
                // 建议你不要把服务器关掉
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }
}

/**
 * 接收数据线程
 * @author qinlang
 *
 */
class INReceiver implements Runnable {
    private DatagramSocket socket;
    private List<SocketAddress> addresses;

    public INReceiver(DatagramSocket socket, List<SocketAddress> addresses) {
        this.socket = socket;
        this.addresses = addresses;
    }

    @Override
    public void run() {
        byte[] b = new byte[1024];
        DatagramPacket packet = new DatagramPacket(b, b.length);// 用来接收的包裹
        String value = "";
        while (true) {
            try {
                socket.receive(packet);//接收数据

                SocketAddress myadd = packet.getSocketAddress();// 得到发数据过来的地址
                // 同一个数据,把所有在线的人都发一遍
                for (SocketAddress socketAddress : addresses) {
                    if (socketAddress.equals(myadd))//如果是自身,则跳过
                        continue;
                    packet.setSocketAddress(socketAddress);
                    socket.send(packet);
                }
                // 第一次来的时候,把这个地址添加到地址集合里面去
                if (!addresses.contains(myadd))
                    addresses.add(myadd);

                if (value.equalsIgnoreCase("esc"))//如果退出了,把地址从集合中移除
                    addresses.remove(myadd);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值