小白必懂!使用TCP或者UDP实现在线聊天!

来来来,今天这篇博客,手把手教会你如何写一个在线聊天室,还学不会你来砍我

  • 大家如果有什么疑问,欢迎在评论区讨论!

1、功能设计

  • 实现两个用户之间的在线交流,没有群组功能。

  • 键盘输入

  • 控制台输出

2、技术分析

  • 以下分析UDP和TCP都适用
  1. 首先我们可以很轻易的写出来两个功能Send和Receive,一个监听键盘输入,一个监听连接输入。怎么实现呢?两个功能里分别用上死循环就行了,判断输入是否为结束条件,是就跳出循环,关闭连接,否则就一直监听,这样就可以实现了。这部分代码可以看附件。
  2. 那么我们怎么让用户既能收,又能发呢?有人说了我们可以让一个用户既拥有Send方法,也拥有Receive方法,然后让这个用户分别执行两个方法就行了!NONONONONO!Too Yang Too 森破!事情没有那么简单,因为上面使用了死循环,所以如果先执行了接收,那么必然无法发送,如果先发送,必然无法接收!
  3. 从2的分析中我们得出,必须让收和发同步起来!没错,就是多线程!!!让每个用户开两个线程,不停的收和发,这样就可以建立连接了。那么只要把功能放进run函数里就可以了。

3、具体实现

3.1、UDP的实现

//发送方法实现类
package main.UdpChat;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.*;

public class SendMessage implements Runnable {
    //主要看run方法
    public int port;//自己的端口名
    public int toPort;//发送到哪个端口
    public String toIp;//发送到哪个地址
    public  DatagramSocket socket = null;
    public BufferedReader reader = null;

    public SendMessage(int port, int toPort, String toIp) {
        this.port = port;
        this.toPort = toPort;
        this.toIp = toIp;

        try {
            socket = new DatagramSocket(curport);
        } catch (SocketException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        //发送端
        while(true)//不停的发
        {
            try {
                reader  = new BufferedReader(new InputStreamReader(System.in));
                String data = reader.readLine();//获取输入,注意这里data最后没有\n,键盘中输入的是什么就是什么。 这里键盘输入是阻塞式输入,当没有输入的时候会在这里等待,不会向下执行。
                byte[] datas = data.getBytes();
                DatagramPacket packet = new DatagramPacket(datas,0,datas.length,
                        new InetSocketAddress(toIp,toPort));//打包

                socket.send(packet);//发送 注意哦!这里send的是packet,和Tcp不同!!!
                if(data.equals("bye"))
                    break;
            }catch (Exception e)
            {
                System.out.println(e.getStackTrace());
            }
        }
        socket.close();//关闭
    }
}

//接收方法实现类
package main.UdpChat;

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

public class ReceiveMessage implements Runnable {
    public int port;//自己的端口
    public DatagramSocket socket = null;
    public String who;//谁发给我的?

    public ReceiveMessage(int port,String who) {
        this.port = port;
        this.who = who;
        try {
            socket = new DatagramSocket(port);
        } catch (SocketException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        while (true)
        {
            try {
                byte[] container = new byte[1024];
                DatagramPacket packet = new DatagramPacket(container, 0, container.length);//打包
                socket.receive(packet);//接收数据

                byte[] data = packet.getData();
                String receivedata = new String(data,0,data.length);
                System.out.println(who + " :" +receivedata);//打印数据
                if(receivedata.equals("bye"))
                    break;
            }catch (Exception e)
            {
                System.out.println(e.getStackTrace());
            }
        }
        socket.close();
    }
}

创建完方法类后我们要建立用户了,让用户去使用这两个类中的run方法

//创建用户A
package main.UdpChat;

public class AChat {
    public static void main(String[] args) {
        //新建线程,当cpu有空闲时间的时候就执行
        //新建发送功能,自己是7777端口,发送到localhost的8888端口
        new Thread(new SendMessage(7777,8888,"localhost")).start();
        //新建监听接收功能,监听来自9999端口的信息,同时认定该端口是B连接的地方。
        new Thread(new ReceiveMessage(9999,"B")).start();
    }
}

//创建用户B
package main.UdpChat;

public class BChat {
    public static void main(String[] args) {
        //和A同理。自己的端口是5555,发送到localhost的9999
        new Thread(new SendMessage(5555,9999,"localhost")).start();
        //监听着来自端口8888的信息,并认定该端口连接的是A
        new Thread(new ReceiveMessage(8888,"A")).start();
    }

}

3.2、UDP的实现效果

启动AChat和BChat:

小白必懂!使用TCP或者UDP实现在线聊天!_第1张图片

亮着两个小绿点表示启动完成。

效果:

小白必懂!使用TCP或者UDP实现在线聊天!_第2张图片

小白必懂!使用TCP或者UDP实现在线聊天!_第3张图片

以上就是UDP的实现方式,主要实现思路来源于狂神说!

3.3、TCP的实现

  • 首先实现思路上保持一致,仍然是使用多线程,同时开启收和发的功能。两者之间的区别主要在于构造方法的不同,由于UDP是通过packet发送和接收的,不需要添加换行符,但是TCP需要
  • 其次,为了提高性能,我们对于数组的长度能不固定就不固定,所以使用了BufferedReader类的实现子类。
  • TCP的accept方法是阻塞式的,当接收不到的时候,会一直等待连接。这句话不理解可以先看代码。
//Send方法实现类
package main.Tcp;

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

public class SendMsg implements Runnable{
    private String toIp;//发送到的地址
    private int toPort;//发送到的端口
    private Socket socket;//发送端
    private OutputStream os;//io流

    public SendMsg(String toIp, int toPort) {
        this.toIp = toIp;
        this.toPort = toPort;

        try {
            socket = new Socket(toIp,toPort);
            os = socket.getOutputStream();
            os.write("连接成功\n".getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        while (true)
        {
            try {
                Scanner sc = new Scanner(System.in);
                String data = sc.nextLine() + "\n";//键盘输入,阻塞式,没有输入的时候就会在这里等待。这里必须加换行符,要不然接收端没法从缓存中写出来,因为使用的是BufferedReader类的readline方法,该方法只有读取到\n的时候才会从缓存中输出。
                if(data.equals("bye"))//如果输入bye,那么断开连接。
                    break;
                os.write(data.getBytes());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        try {
            os.close();
            socket.close();
        }catch (Exception e){
            System.out.println(e.getMessage());
        }
    }
}

//Receive 接收端的方法实现类
package main.Tcp;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

public class Receive implements Runnable {
    private int port;//当前端口
    private String from;//从哪里来
    private ServerSocket serverSocket;//服务端
    private Socket socket;//从服务端接收到的客户端
    private BufferedReader br;//流

    public Receive(int port, String from) {
        this.port = port;
        this.from = from;
        try {
            serverSocket = new ServerSocket(port);
            socket = serverSocket.accept();//连接只需要建立一次就行
            br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }

    @Override
    public void run() {
        String data = null;
        while (true) {
            try {
                data = br.readLine();//将缓冲流中的数据读出来 注意!!!这里因为使用了ReadLine,所以只有遇到换行或者终止符的时候才停止写入到缓存中,所以我们在发送端的每一句话最后都加入了换行符。
                System.out.println(from +" :" + data);
                if(data == "bye")
                    break;
            } catch (Exception e) {
                System.out.println(e.getMessage());
            }
        }
        try {
            socket.close();
            serverSocket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

}

//用户A
package main.Tcp;

public class A {
    public static void main(String[] args) {
        //注意要有start方法
        //监听来自8888,用户B的信息
        new Thread(new Receive(8888,"用户B")).start();
        //将信息通过localhost发送到9999端口上
        new Thread(new SendMsg("localhost",9999)).start();
    }
}

//用户B
package main.Tcp;

import java.util.TreeMap;

public class B {
    public static void main(String[] args) {
        new Thread(new SendMsg("127.0.0.1",8888)).start();
        new Thread(new Receive(9999,"用户A")).start();

    }
}

3.4、TCP的实现结果

初始化

小白必懂!使用TCP或者UDP实现在线聊天!_第4张图片

小白必懂!使用TCP或者UDP实现在线聊天!_第5张图片

执行结果

小白必懂!使用TCP或者UDP实现在线聊天!_第6张图片

小白必懂!使用TCP或者UDP实现在线聊天!_第7张图片
以上就是TCP的实现

3.5、附件(两个方法,Send和Receive的实现)

//UDP只能发送
package main.Udp;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.*;
import java.util.Scanner;

public class UdpClient {
    public static void main(String[] args) throws IOException {
        //发送端
        int port = 8888;
        DatagramSocket socket = new DatagramSocket(port);
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

        while(true)
        {
            try {
                String data = reader.readLine();
                byte[] datas = data.getBytes();
                DatagramPacket packet = new DatagramPacket(datas,0,datas.length,
                        new InetSocketAddress("localhost",6666));

                socket.send(packet);
                if(data.equals("bye"))
                    break;
            }catch (Exception e)
            {
                System.out.println(e.getStackTrace());
            }
        }
        socket.close();
    }
}

//UDP只接收
package main.Udp;

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

public class UdpServer {
    public static void main(String[] args) throws IOException {
        int port = 6666;
        DatagramSocket socket = new DatagramSocket(port);
        while (true)
        {
            try {
                byte[] container = new byte[1024];
                DatagramPacket packet = new DatagramPacket(container, 0, container.length);
                socket.receive(packet);

                byte[] data = packet.getData();
                String receivedata = new String(data,0,data.length);
                System.out.println("接收机:" + port+ " :" + receivedata);
                if(receivedata.equals("bye"))
                    break;
            }catch (Exception e)
            {
                System.out.println(e.getStackTrace());
            }
        }
        socket.close();
    }
}

//TCP的发送方法
package main.Tcp;


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;

public class TcpClient {
    public static void main(String[] args) {
        Socket client = null;
        OutputStream outputStream = null;
        try {
                client = new Socket("localhost",9999);
                outputStream = client.getOutputStream();
            while (true)
            {
                String data = new BufferedReader(new InputStreamReader(System.in)).readLine();
                if(data.equals("bye"))
                {
                    outputStream.write(data.getBytes());
                    break;
                }
                outputStream.write((data+"\n").getBytes());
            }
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        try {
            client.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

//TCP接收方法
package main.Tcp;


import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class TcpServer {
    public static void main(String[] args) {
        ServerSocket serverSocket = null;
        Socket socket = null;
        BufferedReader br = null;

        try {
            serverSocket = new ServerSocket(9999);
            socket = serverSocket.accept();//阻塞式结构,只有建立了连接才会继续向下执行

            while (true) {
                br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                String data = null;
                data = br.readLine();
                if (data.equals("bye"))
                {
                    System.out.println("连接断开!");
                    break;
                }
                System.out.println(data);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            br.close();
            socket.close();
            serverSocket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

4、总结

  • 注意点
  1. 使用BufferedReader.readline();的时候只有读取到换行符才会写出。
  2. 端口名要能够匹配的上。
  3. 在功能测试的时候,要先启动服务端,在启动客户端才行哦!

你可能感兴趣的:(网络编程,udp,tcp/ip,java)