JAVA 多线程实现简单UDP一对一聊天

UDP:将数据及源和目的封装成数据包中,不需要建立连接每个数据报的大小在限制在64k内,因无连接,是不可靠协议不需要建立连接,速度快。

本文两端都用的本地ip。

百度解释:tcp/udp区别。

TCP---传输控制协议,提供的是面向连接、可靠的字节流服务。当客户和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据。TCP提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另一端。 
UDP---用户数据报协议,是一个简单的面向数据报的运输层协议。UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去,但是并不能保证它们能到达目的地。由于UDP在传输数据报前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快

一般的聊天程序由于追求快捷的数据传输速度,而又不是比较关注数据的完整性,都是用UDP协议来传递数据,我们聊天的时候就像qq一样,一边打字发消息,一边也在接收消息。

要在程序中实现这种功能,那么就要用到多线程。其中一个线程用来专门接收数据,一个纯种用来专门发送数据。

下面我们就用多线程来实现这个功能:

既然是多线程,那么我们两个主函数里面肯定都有两个线程启动,一个发送,一个接收。

上代码:---------- :

线程类 1 Send 发送数据 :

package com.socket;

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

public class Send implements Runnable {

    private DatagramSocket datagramSocket = null;
    private  int port;
    // 定义构造方法 使其可以传入 DatagramSocket  和要访问的端口号,然后赋值给变量
    Send (DatagramSocket datagramSocket, int port){
        this.datagramSocket = datagramSocket;
        this.port = port;
    }
    // 线程
    @Override
    public void run() {
            // 定义一个字符缓冲输入流
            BufferedReader bufferedReader = null;
        try {
            bufferedReader = new BufferedReader(new InputStreamReader(System.in));
            String string = null;
                //byte[] data1 = "欢迎你 ! ".getBytes();
            while ((string = bufferedReader.readLine()) != null){
                // 把输入的字符放入data里面
                byte[] data = string.getBytes();
                // 把要访问的ip地址,端口号 和 数据放入
                DatagramPacket datagramPacket = new DatagramPacket(data, data.length, InetAddress.getByName("localhost"), port);
                //  发送数据报到指定 ip地址和端口号
                datagramSocket.send(datagramPacket);
                // 这里是 : 如果控制台输入 bye 那么代表 跳出循环 不在发送数据 结束
                if (string.equals("bye"))
                {
                  //  System.out.println("进入if");
                    break;
                }
            }
            //System.out.println("出来了");
        } catch (IOException e) {
            e.printStackTrace();
        }
        finally {
            try {
                if (bufferedReader != null) {
                    bufferedReader.close();  //关闭流
                }
                datagramSocket.close(); // 关闭资源
            } catch (IOException e) {
                e.printStackTrace();
            }
                
        }
    }
}

线程类 2 Receive 接收数据

package com.socket;

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

/**
 *
 *      接收消息线程
 *
 */
public class Receive implements Runnable {
    private DatagramSocket datagramSocket = null;
    // 定义构造方法
    Receive (DatagramSocket datagramSocket){
        this.datagramSocket = datagramSocket;
    }
    // 线程
    @Override
    public void run() {
        try {
            while (true) {
                // 定义一个1024到字节数组,用于存放发送过来到数据
                byte[] data = new byte[1024];
                // new 一个数据报
                DatagramPacket datagramPacket = new DatagramPacket(data, data.length);
                // 阻塞等待数据发送过来
                datagramSocket.receive(datagramPacket);
                // 获取发送过来的数据的IP地址 和 端口号
                String ip = datagramPacket.getAddress().getHostAddress();
                int port = datagramPacket.getPort();
                // 获取传送过来的数据
                String out = new String(datagramPacket.getData(),0, datagramPacket.getLength());
                // 如果传送过来的数据是 bye 那么跳出循环 不在接收消息 结束
                if ("bye".equals(out)){
                    System.out.println("  对方离开了  !  ");
                    break;
                }
                // 用来显示发送过来的 IP地址 端口号 内容
                System.out.println("来自 ip : "+ip+"  端口号 :  "+port + "  内容 :  "+out);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            datagramSocket.close(); // 关闭资源
        }
    }
}


第一个主函数 UDPserver :

package com.socket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class UDPserver
{
    public static void main(String[] args) {
        try {
            DatagramSocket send = new DatagramSocket();
            DatagramSocket receive = new DatagramSocket(8888);  //监听  8888  端口
            new Thread(new Send(send,6666)).start();  // 给 6666  端口 发送数据
            new Thread(new Receive(receive)).start();    // 监听 8888 端口  获取  从其他端口发送过来的数据
        } catch (SocketException e) {
            e.printStackTrace();
        }

    }
}


第二个主函数 UDPclient :

package com.socket;

import java.net.*;

public class UDPclient {
    public static void main(String[] args) {
        try {
            DatagramSocket send = new DatagramSocket();
            DatagramSocket receive = new DatagramSocket(6666);  //监听 6666 端口
            new Thread(new Send(send,8888)).start();  // 给 8888 端口发送数据线程
            new Thread(new Receive(receive)).start();  // 监听 6666 端口  获取  从其他端口发送过来的数据
        } catch (SocketException e) {
            e.printStackTrace();
        }
    }
}

代码敲完,我们运行来看看结果:

这是UDPclient控制台

接着看UDPserver控制台

已经接收到了  UDPclient 输入的信息,那么我们现在从UDPserver回复几句

 看到 UDPclient 收到没ok,收到了。

不想聊了,走了。UDPserver 看到“对方离开了”  回复“bye”


此时线程全部停止,程序运行结束。



此时我们注意到,我们的端口号是  55726 这些,是随机的,原因是:两个程序发送信息的DatagramSocket没有绑定端口,所以发送的信息的端口是随机的。

如果我们在两个主函数分别改为

UDPsever :

        try {
            DatagramSocket send = new DatagramSocket(7777);
            DatagramSocket receive = new DatagramSocket(8888);
            new Thread(new Receive(receive)).start();   //监听  8888  端口
            new Thread(new Send(send,6666)).start();  // 给 6666  端口 发送数据
        } catch (SocketException e) {
            e.printStackTrace();
        }


UDPclient :

        try {
            DatagramSocket send = new DatagramSocket(9999);
            DatagramSocket receive = new DatagramSocket(6666);
            new Thread(new Send(send,8888)).start();
            new Thread(new Receive(receive)).start();
        } catch (SocketException e) {
            e.printStackTrace();
        }

这样我们就会发现端口号不在随机,而是 7777 和 9999 了,图这个就不贴了。


最后,关于bufferedReader.readLine() 说两句 我们看见while循环的条件

string = bufferedReader.readLine() !=null ,为什么没有输入数据的时候还在循环里面呢? 原因在于readLine()是一个阻塞函数,当没有数据读取时,就会一直阻塞在那,而不是返回 null ,所以我们上面想要退出循环结束程序时, 要么关闭数据流  要么就break 出去。 



最后最后,本人也是在校大学生,菜鸟一枚,大神勿喷,大佬有兴趣可以指点指点。

本人第一次写博客,才开始,准备记录自己的学习过程和经历。写的不好,见谅。


:):)

你可能感兴趣的:(编程,java,多线程,udp,聊天)