用UDP实现聊天室

实现效果:在同一个局域网下,A主机发送的数据,B,C,D,E主机都能看到

因为要实现一个主机能并发的发送消息和接收消息,所以要用多线程实现

方法一:用半双工实现

每台主机用一个端口接收和发送数据

Test1

发送端

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;

//用半双工方式发送和接收数据,所以发送端口和接收端口是同一个
//发送数据的线程和接收数据的线程是并发执行的,不可能同时占用同一个端口,所以端口使用不用互斥操作
//因为发送端口和接收端口是同一个,所以要在既不发数据,也不收数据的时候关闭socket对象。
// 做个约定让接收线程关闭socket
class SendData2 implements Runnable {
    DatagramSocket socket;
    private String toIp;
    private int toPort;

    public SendData2() {
    }

    //socket对象,目的IP,目的端口
    public SendData2(DatagramSocket socket, String toIp, int toPort) {
        this.socket = socket;//用来发送数据的socket对象
        this.toIp = toIp;
        this.toPort = toPort;
    }

    @Override
    public void run() {
        //从键盘写数据并发送出去
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        String data;
        try {
            while ((data = reader.readLine()) != null) {
                byte[] bytes = data.getBytes();//String->byte[]
                //把要发送的数据包装成数据报包
                DatagramPacket packet = new DatagramPacket(bytes, 0, bytes.length, new InetSocketAddress(this.toIp, this.toPort));
                socket.send(packet);//发送数据报包
                if (data.equals("bye")) {//发送bye表示要停止发送数据
                    Test1.sendIsOff = true;
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

接收端

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

class ReceiveData2 implements Runnable {
    DatagramSocket socket;

    public ReceiveData2() {
    }

    public ReceiveData2(DatagramSocket socket) {
        this.socket = socket;//用来接收数据的socket对象
    }

    @Override
    public void run() {
        while (true) {
            //创建充当缓冲区的字节数组
            byte[] bytes = new byte[1024];
            //让数据报包的缓冲区指针指向bytes
            DatagramPacket packet = new DatagramPacket(bytes, 0, bytes.length);
            try {//接收数据报包,并送到缓冲区
                socket.receive(packet);
            } catch (IOException e) {
                e.printStackTrace();
            }
            //解析数据包
            byte[] datas = packet.getData();
            int offset = packet.getOffset();
            int length = packet.getLength();
            String receiveData = new String(datas, offset, length);
            //获得发送方的主机名
            InetAddress fromIP = packet.getAddress();
            System.out.println(fromIP.getHostName() + ":" + receiveData);
            if (receiveData.equals("bye")) {//数据接收结束
                break;
            }
        }
        //1、当代码执行到这里的时候,我们能确定,接收线程不再需要使用DatagramSocket对象
        //2、判断,发送线程是否发出bye
        while (true) {
            try {//没有这个会出现,A发送bye给B,B发送bye给A,A程序结束,B程序一直没结束
                //这里要有休眠,不然会出现接收方先收到bye,然后接收方一直占用CPU去判断sendIsOff是否为true,
                // 导致发送方发了bye后根本没机会把sendIsOff置为true
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (Test1.sendIsOff) {
                break;
            }
        }
        //当发送线程和接收线程都不在使用套接字对象的时候关闭资源
        socket.close();
    }
}

主方法入口

import java.net.DatagramSocket;
import java.net.SocketException;

//Test1和Test2聊天
public class Test1 {
    static boolean sendIsOff=false;//判断数据是否发送完,true是发送完
    public static void main(String[] args) throws SocketException {
        DatagramSocket socket=new DatagramSocket(8888);
        //Test1用端口8888发送消息;目的IP是广播地址,消息会发给本网络的所有主机,我所在的网络号为192.168.5.0
        new Thread(new SendData2(socket, "192.168.5.255", 9999)).start();
        //也用端口8888接收消息
        new Thread(new ReceiveData2(socket)).start();
    }
}

Test2

发送端

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;

public class Send2 implements Runnable {
    DatagramSocket socket;
    private String toIp;
    private int toPort;

    public Send2() {
    }

    //socket对象,目的IP,目的端口
    public Send2(DatagramSocket socket, String toIp, int toPort) {
        this.socket = socket;//用来发送数据的socket对象
        this.toIp = toIp;
        this.toPort = toPort;
    }

    @Override
    public void run() {
        //从键盘写数据并发送出去
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        String data;
        try {
            while ((data = reader.readLine()) != null) {
                byte[] bytes = data.getBytes();//String->byte[]
                //把要发送的数据包装成数据报包
                DatagramPacket packet = new DatagramPacket(bytes, 0, bytes.length, new InetSocketAddress(this.toIp, this.toPort));
                socket.send(packet);//发送数据报包
                if (data.equals("bye")) {//发送bye表示要停止发送数据
                    Test2.sendIsOff = true;
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

接收端

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class Receive2 implements Runnable {
    DatagramSocket socket;

    public Receive2() {
    }

    public Receive2(DatagramSocket socket) {
        this.socket = socket;//用来接收数据的socket对象
    }

    @Override
    public void run() {
        while (true) {
            //创建充当缓冲区的字节数组
            byte[] bytes = new byte[1024];
            //让数据报包的缓冲区指针指向bytes
            DatagramPacket packet = new DatagramPacket(bytes, 0, bytes.length);
            try {//接收数据报包,并送到缓冲区
                socket.receive(packet);
            } catch (IOException e) {
                e.printStackTrace();
            }
            //解析数据包
            byte[] datas = packet.getData();
            int offset = packet.getOffset();
            int length = packet.getLength();
            String receiveData = new String(datas, offset, length);
            //获得发送方的主机名
            InetAddress fromIP = packet.getAddress();
            System.out.println(fromIP.getHostName() + ":" + receiveData);
            if (receiveData.equals("bye")) {//数据接收结束
                break;
            }
        }
        //1、当代码执行到这里的时候,我们能确定,接收线程不再需要使用DatagramSocket对象
        //2、判断,发送线程是否发出bye
        while (true) {
            try {//没有这个会出现,A发送bye给B,B发送bye给A,A程序结束,B程序一直没结束
                //这里要有休眠,不然会出现接收方先收到bye,然后接收方一直占用CPU去判断sendIsOff是否为true,
                // 导致发送方发了bye后根本没机会把sendIsOff置为true
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (Test2.sendIsOff)
                break;

        }
        //当发送线程和接收线程都不在使用套接字对象的时候关闭资源
        socket.close();
    }
}

主方法入口

import java.net.DatagramSocket;
import java.net.SocketException;

//Test1和Test2聊天
public class Test2 {
    static boolean sendIsOff=false;//判断数据是否发送完,true是发送完
    public static void main(String[] args) throws SocketException {
        DatagramSocket socket=new DatagramSocket(9999);
        //Test2用端口9999发送消息;目的IP是广播地址,消息会发给本网络的所有主机,我所在局域网的网络号为192.168.5.0
        new Thread(new Send2(socket, "192.168.5.255", 8888)).start();
        //也用端口9999接收消息
        new Thread(new Receive2(socket)).start();
    }
}

 

 

 

 

 

方法二:用全双工实现

每台主机用一个端口接收数据,一个端口发送数据

发送端

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;

//用全双工方式发送和接收数据,所以发送端口和接收端口不能重复
public class SendData1 implements Runnable {
    DatagramSocket socket = null;
    private int fromPort;
    private String toIp;
    private int toPort;

    public SendData1() {
    }

    public SendData1(int fromPort, String toIp, int toPort) {//源端口,目的IP,目的端口
        this.fromPort = fromPort;
        this.toIp = toIp;
        this.toPort = toPort;
        try {
            socket = new DatagramSocket(fromPort);//用来发送数据的socket对象
        } catch (SocketException e) {
            e.printStackTrace();
        }

    }

    @Override
    public void run() {
        //从键盘写数据并发送出去
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        String data;
        try {
            while ((data = reader.readLine()) != null) {
                byte[] bytes = data.getBytes();//String->byte[]
                //把要发送的数据包装成数据报包
                DatagramPacket packet = new DatagramPacket(bytes, 0, bytes.length, new InetSocketAddress(this.toIp, this.toPort));
                socket.send(packet);//发送数据报包
                if (data.equals("bye")) {//发送bye表示要停止发送数据
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        socket.close();//关闭资源
    }
}

接收端

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

//用全双工方式发送和接收数据,所以发送端口和接收端口不能重复
public class ReceiveData1 implements Runnable {
    DatagramSocket socket = null;
    private int port;

    public ReceiveData1() {
    }

    public ReceiveData1(int port) {//用来接收数据的端口
        this.port = port;
        try {
            socket = new DatagramSocket(port);//用来接收数据的socket对象
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        while (true) {
            //创建充当缓冲区的字节数组
            byte[] bytes = new byte[1024];
            //让数据报包的缓冲区指针指向bytes
            DatagramPacket packet = new DatagramPacket(bytes, 0, bytes.length);
            try {//接收数据报包,并送到缓冲区
                socket.receive(packet);
            } catch (IOException e) {
                e.printStackTrace();
            }
            //解析数据包
            byte[] datas = packet.getData();
            int offset = packet.getOffset();
            int length = packet.getLength();
            String receiveData = new String(datas, offset, length);
            //获得发送方的主机名
            InetAddress fromIP = packet.getAddress();
            System.out.println(fromIP.getHostName() + ":" + receiveData);
            if (receiveData.equals("bye")) {//数据接收结束
                break;
            }
        }
        socket.close();//关闭资源
    }
}

主方法入口

//Test1和Test2聊天
public class Test1 {
    public static void main(String[] args) {
        //Test1用端口7777发送消息;目的IP是广播地址,消息会发给本网络的所有主机,我所在局域网的网络号为192.168.5.0
        new Thread(new SendData1(7777, "192.168.5.255", 9999)).start();
        //用端口8888接收消息
        new Thread(new ReceiveData1(8888)).start();
    }
}


public class Test2 {
    public static void main(String[] args) {
        //Test2用端口5555发送消息;目的IP是广播地址,消息会发给本网络的所有主机
        new Thread(new SendData1(5555, "192.168.5.255", 8888)).start();
        //用端口9999接收消息
        new Thread(new ReceiveData1(9999)).start();
        //System.out.println(InetAddress.getLocalHost());
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值