当前位置: 首页 > article >正文

Java网络通信

前言

1. 基本知识

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

通俗一点就是CS就是要下载应用来访问的,BS是在浏览器上面访问的,不用下载

1.1 IP

在这里插入图片描述
IP地址就是你电脑的主机号,一台设备都有唯一的IP,端口就是程序的唯一标识
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这上面就是我们的ip地址了,有IPv4和IPv6两种
因为IPv4不够用了,所以才用IPv6的

在这里插入图片描述
在这里插入图片描述

IP域名和IP就是一个东西
在这里插入图片描述
在这里插入图片描述
只要有网还有IP地址存在,那么ping就会成功,
127.0.0.1就是我们这个电脑的IP地址,默认是
每个电脑的127.0.0.1都是自己的IP
在这里插入图片描述
在这里插入图片描述

        //1.获取本机IP地址对象
        InetAddress ip1=InetAddress.getLocalHost();
        System.out.println(ip1.getHostName());//获取本机IP地址对象的名字
        System.out.println(ip1.getHostAddress());//获取本机IP地址对象的IP

在这里插入图片描述
在这里插入图片描述
我这个就叫hahaha

        //2.获取指定IP或者域名的IP地址对象
        InetAddress ip2=InetAddress.getByName("www.baidu.com");
        System.out.println(ip2.getHostName());
        System.out.println(ip2.getHostAddress());

在这里插入图片描述
然后我直接在网址输入这个IP
在这里插入图片描述
在这里插入图片描述
就真的变成百度了

        System.out.println(ip2.isReachable(6000));

在这里插入图片描述
这个就是看6秒内,我们本机能否和百度IP建立联系,当然有网就可以了

1.2 端口与通信协议

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
因为视频语音的话,少了几个包最多只是声音变小,卡了一下而已
在这里插入图片描述
在这里插入图片描述
三次握手的意思就是,第一次确认了客户端可以发消息,第二次确认了服务器端可以收消息,和发消息,第三次就是确认客户端可以接受消息,这样两边都可以收发消息了,就没有问题了

在这里插入图片描述
在这里插入图片描述

2. UDP通信

2.1 一发一收

在这里插入图片描述
在这里插入图片描述

public class Server {
    public static void main(String[] args) throws Exception {
        //创建一个服务器对象(创建一个接菜的人),注册端口
        DatagramSocket socket=new DatagramSocket(6666);//这个对象也就是这个进程的端口是6666,可以指定端口的,不然就是随机的
        //2.创建一个数据包对象,用于接收数据,就是创建一个盘子
        byte[] buffer=new byte[1024*64];//因为一个数据包最大发送的内容就是这么大
        DatagramPacket packet=new DatagramPacket(buffer,buffer.length);
        //3.开始正式使用数据包来接收客户端发来的数据
        socket.receive(packet);//只管接收就可以了,只要把IP和端口告诉客户端,只管输入就可以了,这边只管接收
        //接收到的内容在buffer中,然后就是接收到了多少就倒出多少,不可能倒64kb吧
        int len=packet.getLength();
        String rs=new String(buffer,0,len);
        System.out.println(rs);
    }
}

这个是服务端

public class Client {
    public static void main(String[] args) throws Exception {
        //1.创建客户端对象(发菜的人)
        DatagramSocket socket=new DatagramSocket();
        //包装数据包对象分装要发出去的数据(创建盘子)
        byte[] bytes="我是快乐的客户端,我爱你abc".getBytes();
//    public DatagramPacket(byte buf[], int length,
//        InetAddress address, int port) {
//            this(buf, 0, length, address, port);
//        }
        //参数一:分装要发出去的数据,参数二:发送出去的数据大小(字节个数),参数三:服务端的IP地址,参数四:服务端程序的端口
        DatagramPacket packet=new DatagramPacket(bytes,bytes.length, InetAddress.getLocalHost(),6666);//包装菜和盘子

        socket.send(packet);//发出去
        System.out.println("发送完毕");
        socket.close();
    }
}

这个是客户端

应该先启动服务端,因为如果先启动客户端的话,直接就发送了,也不会管你发送成功了没有,还没等服务端接受就没了

在这里插入图片描述
在这里插入图片描述
只要服务端启动了,那么就会停在那个receive那里,等待接受

然后就是服务端接受到了的话,也可以得到客户端的IP和端口,还有就是资源不要忘了关闭,关闭客户端,服务端就可以了


        System.out.println(packet.getAddress().getHostAddress());
        System.out.println(packet.getPort());
        socket.close();

在这里插入图片描述
然后就是其实客户端可以创建一个指定端口的对象

        //1.创建客户端对象(发菜的人)
        DatagramSocket socket=new DatagramSocket(7777);

在这里插入图片描述
在这里插入图片描述

2.2 多发多收

下面我们来实现多个客户端发送的场景

用个while循环就可以了

public class Client {
    public static void main(String[] args) throws Exception {
        DatagramSocket socket=new DatagramSocket(7777);
        Scanner sc=new Scanner(System.in);
        while (true) {
            System.out.println("请说");
            String msg=sc.nextLine();
            if("exit".equals(msg))
            {
                System.out.println("退出成功");
                socket.close();
                break;
            }
            byte[] bytes=msg.getBytes();
            DatagramPacket packet=new DatagramPacket(bytes,bytes.length, InetAddress.getLocalHost(),6666);//包装菜和盘子
            socket.send(packet);//发出去
        }
    }
}

这样就可以了,那么服务端也要循环来接收

public class Server {
    public static void main(String[] args) throws Exception {
        //创建一个服务器对象(创建一个接菜的人),注册端口
        DatagramSocket socket=new DatagramSocket(6666);
        byte[] buffer=new byte[1024*64];
        DatagramPacket packet=new DatagramPacket(buffer,buffer.length);
        while (true) {
            socket.receive(packet);
            int len=packet.getLength();
            String rs=new String(buffer,0,len);
            System.out.println(rs);
            System.out.println("--------------------");
        }
    }
}

因为服务端一般是不会关闭的,所以不用close
现在我们就可以客户端一直输入,服务端一直接受了

在这里插入图片描述
但是如何启动多个客户端呢,我们的编译器默认的是只能启动一个,但是可以改

在这里插入图片描述
点编辑配置
在这里插入图片描述
点允许多个实例

在这里插入图片描述
但是还是会报错,为什么呢,因为我们的客户端的端口指定了为7777
,不可能两个程序用一个端口吧,所以我们还是让系统随机分配端口吧

        DatagramSocket socket=new DatagramSocket();

在这里插入图片描述
这样就可以了

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3. TCP通信

3.1 一发一收

在这里插入图片描述
在这里插入图片描述

public class Client {
    public static void main(String[] args) throws Exception {
        //创建socket对象,并请求与服务端连接
        Socket socket=new Socket("127.0.0.1",8888);//连接的就是IP为127.0.0.1,端口为8888的服务端
        //2.从socket通信管道中得到一个字节输出流,用来发数据给服务端程序
        OutputStream os=socket.getOutputStream();
        //3.把这个低级的字节输出流包装成数据输出流
        DataOutputStream dos=new DataOutputStream(os);
        //4.写数据出去
        dos.writeUTF("在一起毫秒");
        dos.close();
        socket.close();//要释放两个资源,一个网络的,一个IO流的
    }
}

在这里插入图片描述

在这里插入图片描述

public class Server {
    public static void main(String[] args) throws Exception {
        //创建一个ServerSocket的对象,同时为服务端注册端口
        ServerSocket serverSocket=new ServerSocket(8888);
        //使用serverSocket对象调用一个accept方法,等待客户端的连接请求,返回一个socket,与客户端对应,在服务端但是
        Socket socket=serverSocket.accept();
        //从socket通信管道中得到一个字节输入流
        InputStream is=socket.getInputStream();
        //包装成高级的IO流
        DataInputStream dis=new DataInputStream(is);
        //使用数据输入流读取客户端发送过来的消息
        String rs=dis.readUTF();
        System.out.println(rs);
        //打印客户端的IP
        System.out.println(socket.getRemoteSocketAddress());
        dis.close();
        socket.close();
    }
}

在这里插入图片描述
这个服务端会先在accept那里等着连接客户端,成功后,就会在readUTF等待读

在这里插入图片描述

3.2 多发多收

public class Client {
    public static void main(String[] args) throws Exception {
        Socket socket=new Socket("127.0.0.1",8888);
        OutputStream os=socket.getOutputStream();
        DataOutputStream dos=new DataOutputStream(os);
        
        Scanner sc=new Scanner(System.in);
        while(true){
            System.out.println("请说:");
            String msg=sc.nextLine();
            if("exit".equals(msg)){
                System.out.println("退出");
                dos.close();
                socket.close();
                break;
            }
            dos.writeUTF(msg);
            dos.flush();//刷新过去,防止在缓冲区中
        }
    }
}
public class Server {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket=new ServerSocket(8888);
        Socket socket=serverSocket.accept();
        InputStream is=socket.getInputStream();
        DataInputStream dis=new DataInputStream(is);
        while(true){
            String rs=dis.readUTF();
            System.out.println(rs);
        }
    }
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
但是在我们客户端推出的时候,服务端就会异常了,因为一端断开了,read不了了,但是还在等,就会出错,所以要捕获

public class Server {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket=new ServerSocket(8888);
        Socket socket=serverSocket.accept();
        InputStream is=socket.getInputStream();
        DataInputStream dis=new DataInputStream(is);
        while(true){
            try {
                String rs=dis.readUTF();
                System.out.println(rs);
            } catch (Exception e) {
                System.out.println(socket.getRemoteSocketAddress()+"离线了");
                dis.close();
                socket.close();
                break;
            }
        }
    }
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
但是我们这个不能实现服务端与多个用户通信,因为socket始终只有一个,而且又不确定用户端有多少个
所以我们就要用多线程的处理思想了

3.3 与多个用户通信

在这里插入图片描述
我们可以这样,用主线程来接收客户端的连接,连接了一个的话,就把这个socket交给一个子线程来处理

public class ServerReaderThread extends Thread{
    private Socket socket;
    public ServerReaderThread(Socket socket){
        this.socket=socket;
    }
    @Override
    public void run() {

    }
}

先自定义一个线程,因为要把socket交给线程,所以线程的定义要有socket

public class Server {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket=new ServerSocket(8888);
        while(true){
            Socket socket=serverSocket.accept();
            new ServerReaderThread(socket).start();//交给一个新的线程来处理
        }
    }
}
public class ServerReaderThread extends Thread{
    private Socket socket;
    public ServerReaderThread(Socket socket){
        this.socket=socket;
    }
    @Override
    public void run() {
        try {
            InputStream is=socket.getInputStream();
            DataInputStream dis=new DataInputStream(is);
            while (true) {
                try {
                    String rs = dis.readUTF();
                    System.out.println(rs);
                } catch (IOException e) {
                    System.out.println(socket.getRemoteSocketAddress()+"离线了");//结束这个线程
                    dis.close();
                    socket.close();
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

这样的话,主线程就会在accept那里等着,子线程的话,就会在read那里等着

这样我们就可以开启多个客户端了
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

3.4 用户与用户之间通信

其实实现逻辑就是客户端发给服务端,服务端发给其他客户端
在这里插入图片描述
就是一个客户端发送消息,其他客户端都能看到
在这里插入图片描述
socket用集合存着,这个集合只有一个

public class Server {
    public static List<Socket> onLineSockets=new ArrayList<>();
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket=new ServerSocket(8888);
        while(true){
            Socket socket=serverSocket.accept();
            onLineSockets.add(socket);
            System.out.println("有人上线了"+socket.getRemoteSocketAddress());
            new ServerReaderThread(socket).start();
        }
    }
}

接收到一个,就增加一个

对应还有删除,

                    System.out.println(socket.getRemoteSocketAddress()+"离线了");//结束这个线程
                    Server.onLineSockets.remove(socket);//

还要把这个socket发给其他用户
分发给全部客户端就可以了

                    String rs = dis.readUTF();
                    System.out.println(rs);
                    sendRsToAll(rs);
    private void sendRsToAll(String rs) throws Exception {
        for (Socket onLineSocket : Server.onLineSockets) {
            OutputStream os=onLineSocket.getOutputStream();
            DataOutputStream dos=new DataOutputStream(os);
            dos.writeUTF(rs);
            dos.flush();
        }
    }

然后对应每个客户端都要接受来自对应服务端的数据
因为socket是对应的,所以一定会对应回去的

每个客户端都要一直读,所以这也是一个死循环,而且还要一直死循环写,所以又是多线程的逻辑

又要建立一个新的线程
这个的逻辑和服务端的读的线程逻辑是差不多的

public class ClientReaderThread extends Thread{
    Socket socket;
    public ClientReaderThread(Socket socket){
        this.socket=socket;
    }
    @Override
    public void run() {
        try {
            InputStream is=socket.getInputStream();
            DataInputStream dis=new DataInputStream(is);
            while (true) {
                try {
                    String rs = dis.readUTF();
                    System.out.println(socket.getRemoteSocketAddress()+"说"+rs);
                } catch (IOException e) {
                    Server.onLineSockets.remove(socket);//
                    dis.close();
                    socket.close();
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
public class Client {
    public static void main(String[] args) throws Exception {
        Socket socket=new Socket("127.0.0.1",8888);
        new ClientReaderThread(socket).start();//创建一个线程来专门接受读的
        OutputStream os=socket.getOutputStream();
        DataOutputStream dos=new DataOutputStream(os);

        Scanner sc=new Scanner(System.in);
        while(true){
            System.out.println("请说:");
            String msg=sc.nextLine();
            if("exit".equals(msg)){
                System.out.println("退出");
                dos.close();
                socket.close();
                break;
            }
            dos.writeUTF(msg);
            dos.flush();//刷新过去,防止在缓冲区中
        }
    }
}
public class Server {
    public static List<Socket> onLineSockets=new ArrayList<>();
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket=new ServerSocket(8888);
        while(true){
            Socket socket=serverSocket.accept();
            onLineSockets.add(socket);
            System.out.println("有人上线了"+socket.getRemoteSocketAddress());
            new ServerReaderThread(socket).start();
        }
    }
}
public class ServerReaderThread extends Thread{
    private Socket socket;
    public ServerReaderThread(Socket socket){
        this.socket=socket;
    }
    @Override
    public void run() {
        try {
            InputStream is=socket.getInputStream();
            DataInputStream dis=new DataInputStream(is);
            while (true) {
                try {
                    String rs = dis.readUTF();
                    System.out.println(rs);
                    sendRsToAll(rs);
                } catch (IOException e) {
                    System.out.println(socket.getRemoteSocketAddress()+"离线了");//结束这个线程
                    Server.onLineSockets.remove(socket);//
                    dis.close();
                    socket.close();
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void sendRsToAll(String rs) throws Exception {
        for (Socket onLineSocket : Server.onLineSockets) {
            OutputStream os=onLineSocket.getOutputStream();
            DataOutputStream dos=new DataOutputStream(os);
            dos.writeUTF(rs);
            dos.flush();
        }
    }
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.5 实现一个简易版的BS架构

在这里插入图片描述
在这里插入图片描述
我们的服务端只有一个,不同浏览器打开我们的服务端的时候,就会创建不同的子线程

public class Server {
    public static List<Socket> onLineSockets=new ArrayList<>();
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket=new ServerSocket(8888);
        while(true){
            Socket socket=serverSocket.accept();
            onLineSockets.add(socket);
            System.out.println("有人上线了"+socket.getRemoteSocketAddress());
            new ServerReaderThread(socket).start();
        }
    }
}
public class ServerReaderThread extends Thread{
    private Socket socket;
    public ServerReaderThread(Socket socket){
        this.socket=socket;
    }
    @Override
    public void run() {
       //连接到了,就要返回一个‘黑马程序员给浏览器’
        try {
            OutputStream os=socket.getOutputStream();
            DataOutputStream dos=new DataOutputStream(os);
            dos.writeUTF("黑马程序员");
            dos.close();
            socket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在这里插入图片描述
然后我们就可以在浏览器上连接我们的服务端了

在这里插入图片描述
在这里插入图片描述在这里插入图片描述

但是这样不行呢,为什么呢,因为服务器必须给浏览器响应http协议规定的数据格式,不然浏览器不识别返回的数据。
而且浏览器会疯狂去试探,所以有很多上线了,但是没有一个能认识那些数据,所以会出错

在这里插入图片描述
因为换行数据流不好操作,所以我们用打印流
我们返回的数据就要是上面的格式,不然浏览器无法识别,这个就是http协议

public class ServerReaderThread extends Thread{
    private Socket socket;
    public ServerReaderThread(Socket socket){
        this.socket=socket;
    }
    @Override
    public void run() {
       //连接到了,就要返回一个‘黑马程序员给浏览器’
        try {
            OutputStream os=socket.getOutputStream();
            PrintStream ps=new PrintStream(os);
            ps.println("HTTP/1.1 200 OK");
            ps.println("Content-Type:text/html;charset=UTF-8");
            ps.println();//必须换行、、、..后面的就是我们要输入的内容
            ps.println("黑马程序员");
            socket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在这里插入图片描述
在这里插入图片描述
可以美化一下

            ps.println("<div style='color:red;font-size:120px;text-align:center'>黑马程序员");

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
换个浏览器也可以

在这里插入图片描述

拓展一下,我们可以使用线程池

在这里插入图片描述

        //创建一个线程池,,因为这个是IO型任务,所以核心线程数量=CPU核数*2
        ThreadPoolExecutor pool=new ThreadPoolExecutor(16*2,16*2,0, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(8), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

因为为线程池,所以传入的也都是任务对象,就不能是线程对象了

public class ServerReaderRunnable implements Runnable{
public class Server {
    public static List<Socket> onLineSockets=new ArrayList<>();
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket=new ServerSocket(8888);
        //创建一个线程池,,因为这个是IO型任务,所以核心线程数量=CPU核数*2
        ThreadPoolExecutor pool=new ThreadPoolExecutor(16*2,16*2,0, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(8), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
        while(true){
            Socket socket=serverSocket.accept();
            pool.execute(new ServerReaderRunnable(socket));
        }
    }
}
public class ServerReaderRunnable implements Runnable{
    private Socket socket;
    public ServerReaderRunnable(Socket socket){
        this.socket=socket;
    }
    @Override
    public void run() {
        //连接到了,就要返回一个‘黑马程序员给浏览器’
        try {
            OutputStream os=socket.getOutputStream();
            PrintStream ps=new PrintStream(os);
            ps.println("HTTP/1.1 200 OK");
            ps.println("Content-Type:text/html;charset=UTF-8");
            ps.println();//必须换行、、、..后面的就是我们要输入的内容
            ps.println("<div style='color:red;font-size:120px;text-align:center'>黑马程序员");
            ps.close();
            socket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

这样就可以了
在这里插入图片描述

总结

后面还是接着讲Java


http://www.kler.cn/a/376628.html

相关文章:

  • Centos7.6离线安装软件
  • Fooocus图像生成软件本地部署教程:在Windows上快速上手AI创作
  • 手游和应用出海资讯:可灵AI独立APP即将上架;Rollic在英国推出芭比合并解谜手游
  • QT——记事本项目
  • SpringMVC笔记 一万字
  • CytoSPACE·空转和单细胞数据的高分辨率比对
  • 从0开始学python-day17-数据结构2
  • layui 实现 城市联动
  • git cherry-pick用法详解
  • 顺序表和链表(一)
  • jmeter结合ansible分布式压测--准备工作
  • 深入了解嵌入式硬件设计
  • 视频智能分析平台LiteAIServer入侵检测算法平台部署行人入侵检测算法:智能安防的新利器
  • 钉钉内集成第三方免密登录(Vue+.Net)
  • 定制化视频生成新模范!零样本主体驱动,精确运动控制!复旦阿里等发布DreamVideo-2
  • 算法笔记day10
  • CentOS下Redis简洁安装(无坑版)
  • LocalDate 类常用方法详解(日期时间类)
  • 图文深入介绍Oracle DB link(三)
  • Python世界:自动化办公Word之批量替换文本生成副本
  • C++ 实现俄罗斯方块游戏
  • 贪心算法习题其二【力扣】【算法学习day.19】
  • Selenium自动化测试框架(附教程+文档)
  • Rust 力扣 - 2134. 最少交换次数来组合所有的 1 II
  • 游戏光照的专业知识解析
  • 网络学习/复习3序列化与反序列化/HTTP/HTTPS