Socket篇(网络通信)

news2024/11/24 12:56:26

目录

一、UDP 通信

1. 简介

2. UDP 编程的两个实现类

DatagramSocket

DatagramPacket

3. 代码示例

示例一:一发/一收

发送端

接收端

示例二:多发/多收

发送端

接收端

示例三:多发/多收

发送端

接收端一

接收端二

示例四:模拟QQ

发送端

接收端

二、TCP 通信

1. 简介

2. TCP通信的基本原理

3. 代码示例

示例一:一发/一收

发送端

接收端

示例二:多发/多收

发送端

接收端

示例三:多发/一收

发送端

接收端

示例四:线程池优化

发送端

接收端

三、综合案例

1. UDP 通信

2. TCP 通信

2.1. TCP通信循环聊天案例

服务器

客户端

2.2. 文件上传综合案例

需求

分析

实现

服务器

客户端

2.3. 模拟B\S服务器

需求

分析

实现


一、UDP 通信

1. 简介

UDP 是 User Datagram Protocol 的简称, 中文名是用户数据报协议,是OSI(Open System

Interconnection,开放式系统互联)参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送

服务。Java 主要提供了两个类来实现基于 UDP 的 Socket 编程。

2. UDP 编程的两个实现类

DatagramSocket

此类表示用来发送和接收数据报包的套接字。

数据报套接字是包投递服务的发送或接收点,每个在数据报套接字上发送或接收的包都是单独编址和路由的。

从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。

在 DatagramSocket 上总是启用 UDP 广播发送。

DatagramPacket

DatagramPacket.class:此类表示数据报包,它用来实现无连接的包投递服务。

DatagramPacket 会根据该包中包含的地址和端口等信息,将报文从一台机器路由到另一台机器。

构造函数

  1. 一个用来接收数据 DatagramPacket(byte buf[], int length) ,用一个字节数组接收 UDP 包,buf 数组在传

递给构造函数时是空的,而 length 值用来设定要读取的字节数。

  1. 另一个用来发送数据 DatagramPacket(byte buf[], int length, InetAddress address, int port),建立将要

传输的 UDP 包,并指定 ip 地址和端口号。

3. 代码示例

示例一:一发/一收

发送端
/**
  发送端  一发 一收
 */
public class ClientDemo1 {
    public static void main(String[] args) throws Exception {
        System.out.println("=====客户端启动======");

        // 1、创建发送端对象:发送端自带默认的端口号(人)
        DatagramSocket socket = new DatagramSocket(666);

        // 2、创建一个数据包对象封装数据(韭菜盘子)
        /**
         public DatagramPacket(byte buf[], int length,
         InetAddress address, int port)
         参数一:封装要发送的数据(韭菜)
         参数二:发送数据的大小
         参数三:服务端的主机IP地址
         参数四:服务端的端口
         */
        byte[] buffer = "我是一颗快乐的韭菜,你愿意吃吗?".getBytes();
        DatagramPacket packet = new DatagramPacket( buffer, buffer.length,
                InetAddress.getLocalHost() , 8888);

        // 3、发送数据出去
        socket.send(packet);

        socket.close();
    }
}
接收端
public class ServerDemo2 {
    public static void main(String[] args) throws Exception {
        System.out.println("=====服务端启动======");
        // 1、创建接收端对象:注册端口(人)
        DatagramSocket socket = new DatagramSocket(8888);

        // 2、创建一个数据包对象接收数据(韭菜盘子)
        byte[] buffer = new byte[1024 * 64];
        DatagramPacket packet = new DatagramPacket(buffer, buffer.length);

        // 3、等待接收数据。
        socket.receive(packet);

        // 4、取出数据即可
        // 读取多少倒出多少
        int len = packet.getLength();
        String rs = new String(buffer,0, len);
        System.out.println("收到了:" + rs);

        // 获取发送端的ip和端口
        String ip  =packet.getSocketAddress().toString();
        System.out.println("对方地址:" + ip);

        int port  = packet.getPort();
        System.out.println("对方端口:" + port);

        socket.close();
    }
}

示例二:多发/多收

发送端
/**
  发送端  多发 多收
 */
public class ClientDemo1 {
    public static void main(String[] args) throws Exception {
        System.out.println("=====客户端启动======");

        // 1、创建发送端对象:发送端自带默认的端口号(人)
        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;
            }

            // 2、创建一个数据包对象封装数据(韭菜盘子)
            byte[] buffer = msg.getBytes();
            DatagramPacket packet = new DatagramPacket( buffer, buffer.length,
                    InetAddress.getLocalHost() , 8888);

            // 3、发送数据出去
            socket.send(packet);
        }

    }
}
接收端
/**
  接收端
 */
public class ServerDemo2 {
    public static void main(String[] args) throws Exception {
        System.out.println("=====服务端启动======");
        // 1、创建接收端对象:注册端口(人)
        DatagramSocket socket = new DatagramSocket(8888);

        // 2、创建一个数据包对象接收数据(韭菜盘子)
        byte[] buffer = new byte[1024 * 64];
        DatagramPacket packet = new DatagramPacket(buffer, buffer.length);

        while (true) {
            // 3、等待接收数据。
            socket.receive(packet);
            // 4、取出数据即可
            // 读取多少倒出多少
            int len = packet.getLength();
            String rs = new String(buffer,0, len);
            System.out.println("收到了来自:" + packet.getAddress() +", 对方端口是" + packet.getPort() +"的消息:" + rs);
        }
    }
}

示例三:多发/多收

发送端
/**
  发送端  多发 多收
 */
public class ClientDemo1 {
    public static void main(String[] args) throws Exception {
        System.out.println("=====客户端启动======");

        // 1、创建发送端对象:发送端自带默认的端口号(人)
        DatagramSocket socket = new DatagramSocket();
        

        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;
            }

            // 2、创建一个数据包对象封装数据(韭菜盘子)
            byte[] buffer = msg.getBytes();
            // 注意:只要目的地IP是 255.255.255.255 这个消息将以广播的形式对外发送
            DatagramPacket packet = new DatagramPacket( buffer, buffer.length,
                    InetAddress.getByName("255.255.255.255") , 8888);

//            DatagramPacket packet = new DatagramPacket( buffer, buffer.length,
//                    InetAddress.getByName("224.0.1.1") , 9898);

                    // 3、发送数据出去
            socket.send(packet);
        }

    }
}
接收端一
/**
  接收端
 */
public class ServerDemo2 {
    public static void main(String[] args) throws Exception {
        System.out.println("=====服务端启动======");
        // 1、创建接收端对象:注册端口(人)
        DatagramSocket socket = new DatagramSocket(8888);

        // 2、创建一个数据包对象接收数据(韭菜盘子)
        byte[] buffer = new byte[1024 * 64];
        DatagramPacket packet = new DatagramPacket(buffer, buffer.length);

        while (true) {
            // 3、等待接收数据。
            socket.receive(packet);
            // 4、取出数据即可
            // 读取多少倒出多少
            int len = packet.getLength();
            String rs = new String(buffer,0, len);
            System.out.println("收到了来自:" + packet.getAddress() +", 对方端口是" + packet.getPort() +"的消息:" + rs);
        }
    }
}
接收端二
/**
  接收端
 */
public class ServerDemo3 {
    public static void main(String[] args) throws Exception {
        System.out.println("=====服务端启动======");
        // 1、创建接收端对象:注册端口(人)
        MulticastSocket socket = new MulticastSocket(9898);

        // 注意:绑定组播地址(加群)
        socket.joinGroup(new InetSocketAddress(InetAddress.getByName("224.0.1.1") , 9898),
                NetworkInterface.getByInetAddress(InetAddress.getLocalHost()));

        // 2、创建一个数据包对象接收数据(韭菜盘子)
        byte[] buffer = new byte[1024 * 64];
        DatagramPacket packet = new DatagramPacket(buffer, buffer.length);


        while (true) {
            // 3、等待接收数据。
            socket.receive(packet);
            // 4、取出数据即可
            // 读取多少倒出多少
            int len = packet.getLength();
            String rs = new String(buffer,0, len);
            System.out.println("收到了来自:" + packet.getAddress() +", 对方端口是" + packet.getPort() +"的消息:" + rs);
        }
    }
}

示例四:模拟QQ

发送端
public class UdpSend {
 
    public static final String QUIT = "quit";
 
    public static void main(String[] args) throws Exception {
        DatagramSocket ds = new DatagramSocket();
        byte[] buf = new byte[1024];
        
        while (true) {
            System.out.println("请输入:");
            Scanner sc = new Scanner(System.in);
            String next = sc.next();
            if (next.equals(QUIT)) {
                // 发送通知退出消息
                DatagramPacket send = new DatagramPacket(next.getBytes(), next.length(),
                        InetAddress.getByName("127.0.0.1"), 3000);
                ds.send(send);
                ds.close();
                System.out.println("程序退出...");
                break;
            }
            // 发送消息数据包
            DatagramPacket send = new DatagramPacket(next.getBytes(), next.length(),
                    InetAddress.getByName("127.0.0.1"), 3000);
            // 发送消息
            ds.send(send);
            System.out.println("消息已经发送...");
 
            // 接收消息数据包
            DatagramPacket recive = new DatagramPacket(buf, 1024);
            // 接收消息
            ds.receive(recive);
            String strRecv = new String(recive.getData(), 0, recive.getLength()) +
                    " from " + send.getAddress().getHostAddress() + ":" + recive.getPort();
            System.out.println(strRecv);
        }
    }
}
接收端
public class UdpRecv {
 
    /**
     * 中断标记
     */
    public static final String QUIT = "quit";
 
    public static void main(String[] args) throws Exception {
        // 创建套接字
        DatagramSocket ds = new DatagramSocket(3000);
        byte[] buf = new byte[1024];
        while (true) {
            // 创建接收消息数据包
            DatagramPacket recive = new DatagramPacket(buf, 1024);
            // 接收消息,如果没有消息,进入阻塞状态
            ds.receive(recive);
            String strRecv = new String(recive.getData(), 0, recive.getLength()) +
                    " from " + recive.getAddress().getHostAddress() + ":" + recive.getPort();
            // 打印接收到的消息
            System.out.println(strRecv);
 
            System.out.println("请输入:");
            Scanner sc = new Scanner(System.in);
            String next = sc.next();
            if (next.equals(QUIT)) {
                ds.close();
                System.out.println("程序退出...");
                break;
            }
 
            // 创建发送消息数据包
            DatagramPacket send = new DatagramPacket(next.getBytes(), next.length(),
                    InetAddress.getByName("127.0.0.1"), recive.getPort());
            // 发送消息
            ds.send(send);
            System.out.println("消息已经发送...");
        }
    }
}

二、TCP 通信

1. 简介

  • TCP是一种面向连接、安全、可靠、的传输数据的协议
  • 传输前,采用“三次握手”方式,点对点通信,是可靠的
  • 在连接中可进行大数据量的传输

如果使用了 WireShark 工具,可以看到一次TCP连接建立时的整个过程。

2. TCP通信的基本原理

  • 客户端怎么发,服务端就怎么收
  • 客户端如果没有消息,服务端会进入阻塞等待
  • Socket一方关闭或者出现异常,对方Socket也会失效或者出错

3. 代码示例

示例一:一发/一收

发送端
/**
   目标:完成Socket网络编程入门案例的客户端开发,实现1发1收。
 */
public class ClientDemo1 {
    public static void main(String[] args) {
        try {
            System.out.println("====客户端启动===");
            // 1、创建Socket通信管道请求有服务端的连接
            // public Socket(String host, int port)
            // 参数一:服务端的IP地址
            // 参数二:服务端的端口
            Socket socket = new Socket("127.0.0.1", 7777);

            // 2、从socket通信管道中得到一个字节输出流 负责发送数据
            OutputStream os = socket.getOutputStream();

            // 3、把低级的字节流包装成打印流
            PrintStream ps = new PrintStream(os);

            // 4、发送消息
            ps.println("我是TCP的客户端,我已经与你对接,并发出邀请:约吗?");
            ps.flush();

            // 关闭资源。
            // socket.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
接收端
/**
   目标:开发Socket网络编程入门代码的服务端,实现接收消息
 */
public class ServerDemo2 {
    public static void main(String[] args) {
        try {
            System.out.println("===服务端启动成功===");
            // 1、注册端口
            ServerSocket serverSocket = new ServerSocket(7777);
            // 2、必须调用accept方法:等待接收客户端的Socket连接请求,建立Socket通信管道
            Socket socket = serverSocket.accept();
            // 3、从socket通信管道中得到一个字节输入流
            InputStream is = socket.getInputStream();
            // 4、把字节输入流包装成缓冲字符输入流进行消息的接收
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            // 5、按照行读取消息
            String msg;
            if ((msg = br.readLine()) != null){
                System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

示例二:多发/多收

发送端
/**
   目标:实现多发和多收
 */
public class ClientDemo1 {
    public static void main(String[] args) {
        try {
            System.out.println("====客户端启动===");
            // 1、创建Socket通信管道请求有服务端的连接
            // public Socket(String host, int port)
            // 参数一:服务端的IP地址
            // 参数二:服务端的端口
            Socket socket = new Socket("127.0.0.1", 7777);

            // 2、从socket通信管道中得到一个字节输出流 负责发送数据
            OutputStream os = socket.getOutputStream();

            // 3、把低级的字节流包装成打印流
            PrintStream ps = new PrintStream(os);

            Scanner sc =  new Scanner(System.in);
            while (true) {
                System.out.println("请说:");
                String msg = sc.nextLine();
                // 4、发送消息
                ps.println(msg);
                ps.flush();
            }

            // 关闭资源。
            // socket.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
接收端
/**
   目标:开发Socket网络编程入门代码的服务端,实现接收消息
 */
public class ServerDemo2 {
    public static void main(String[] args) {
        try {
            System.out.println("===服务端启动成功===");
            // 1、注册端口
            ServerSocket serverSocket = new ServerSocket(7777);
            while (true) {
                // 2、必须调用accept方法:等待接收客户端的Socket连接请求,建立Socket通信管道
                Socket socket = serverSocket.accept();
                // 3、从socket通信管道中得到一个字节输入流
                InputStream is = socket.getInputStream();
                // 4、把字节输入流包装成缓冲字符输入流进行消息的接收
                BufferedReader br = new BufferedReader(new InputStreamReader(is));
                // 5、按照行读取消息
                String msg;
                while ((msg = br.readLine()) != null){
                    System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

示例三:多发/一收

发送端
/**
    目标:实现服务端可以同时处理多个客户端的消息。
 */
public class ClientDemo1 {
    public static void main(String[] args) {
        try {
            System.out.println("====客户端启动===");
            // 1、创建Socket通信管道请求有服务端的连接
            // public Socket(String host, int port)
            // 参数一:服务端的IP地址
            // 参数二:服务端的端口
            Socket socket = new Socket("127.0.0.1", 7777);

            // 2、从socket通信管道中得到一个字节输出流 负责发送数据
            OutputStream os = socket.getOutputStream();

            // 3、把低级的字节流包装成打印流
            PrintStream ps = new PrintStream(os);

            Scanner sc =  new Scanner(System.in);
            while (true) {
                System.out.println("请说:");
                String msg = sc.nextLine();
                // 4、发送消息
                ps.println(msg);
                ps.flush();
            }

            // 关闭资源。
            // socket.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
public class ServerReaderThread extends Thread{
    private Socket socket;
    public ServerReaderThread(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            // 3、从socket通信管道中得到一个字节输入流
            InputStream is = socket.getInputStream();
            // 4、把字节输入流包装成缓冲字符输入流进行消息的接收
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            // 5、按照行读取消息
            String msg;
            while ((msg = br.readLine()) != null){
                System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg);
            }
        } catch (Exception e) {
            System.out.println(socket.getRemoteSocketAddress() + "下线了!!!");
        }
    }
}
接收端
/**
   目标:实现服务端可以同时处理多个客户端的消息。
 */
public class ServerDemo2 {
    public static void main(String[] args) {
        try {
            System.out.println("===服务端启动成功===");
            // 1、注册端口
            ServerSocket serverSocket = new ServerSocket(7777);
            // a.定义一个死循环由主线程负责不断的接收客户端的Socket管道连接。
            while (true) {
                // 2、每接收到一个客户端的Socket管道,交给一个独立的子线程负责读取消息
                Socket socket = serverSocket.accept();
                System.out.println(socket.getRemoteSocketAddress()+ "它来了,上线了!");
                // 3、开始创建独立线程处理socket
                new ServerReaderThread(socket).start();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

示例四:线程池优化

发送端
/**
    拓展:使用线程池优化:实现通信。
 */
public class ClientDemo1 {
    public static void main(String[] args) {
        try {
            System.out.println("====客户端启动===");
            // 1、创建Socket通信管道请求有服务端的连接
            // public Socket(String host, int port)
            // 参数一:服务端的IP地址
            // 参数二:服务端的端口
            Socket socket = new Socket("127.0.0.1", 6666);

            // 2、从socket通信管道中得到一个字节输出流 负责发送数据
            OutputStream os = socket.getOutputStream();

            // 3、把低级的字节流包装成打印流
            PrintStream ps = new PrintStream(os);

            Scanner sc =  new Scanner(System.in);
            while (true) {
                System.out.println("请说:");
                String msg = sc.nextLine();
                // 4、发送消息
                ps.println(msg);
                ps.flush();
            }
            // 关闭资源。
            // socket.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
public class ServerReaderRunnable implements Runnable{
    private Socket socket;
    public ServerReaderRunnable(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            // 3、从socket通信管道中得到一个字节输入流
            InputStream is = socket.getInputStream();
            // 4、把字节输入流包装成缓冲字符输入流进行消息的接收
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            // 5、按照行读取消息
            String msg;
            while ((msg = br.readLine()) != null){
                System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg);
            }
        } catch (Exception e) {
            System.out.println(socket.getRemoteSocketAddress() + "下线了!!!");
        }
    }
}
接收端
/**
   目标:实现服务端可以同时处理多个客户端的消息。
 */
public class ServerDemo2 {

    // 使用静态变量记住一个线程池对象
    private static ExecutorService pool = new ThreadPoolExecutor(300,
            1500, 6, TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(2)
    , Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

    public static void main(String[] args) {
        try {
            System.out.println("===服务端启动成功===");
            // 1、注册端口
            ServerSocket serverSocket = new ServerSocket(6666);
            // a.定义一个死循环由主线程负责不断的接收客户端的Socket管道连接。
            while (true) {
                // 2、每接收到一个客户端的Socket管道,
                Socket socket = serverSocket.accept();
                System.out.println(socket.getRemoteSocketAddress()+ "它来了,上线了!");

                // 任务对象负责读取消息。
                Runnable target = new ServerReaderRunnable(socket);
                pool.execute(target);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

三、综合案例

1. UDP 通信

2. TCP 通信

2.1. TCP通信循环聊天案例

服务器
public class Server {
    public static void main(String[] args) throws Exception{
        // 服务器:
        //1.创建ServerSocket对象,指定端口号 6666
        ServerSocket ss = new ServerSocket(6666);

        //2.调用accept方法接收客户端请求,建立连接,返回Socket对象
        Socket socket = ss.accept(); // 会阻塞,直到客户端连接成功

        // 死循环
        while (true) {
            //3.使用Socket对象获得输入流
            InputStream is = socket.getInputStream();

            //4.读客户端写过来的数据
            byte[] bys = new byte[1024];
            int len = is.read(bys);
            System.out.println("服务器读到客户端的数据是:" + new String(bys, 0, len));

            // 服务器回写数据给客户端
            // 5.使用Socket对象获得输出流
            OutputStream os = socket.getOutputStream();

            // 6.写出数据
            Scanner sc = new Scanner(System.in);
            System.out.println("请输入一个字符串:");
            String msg = sc.nextLine();
            os.write(msg.getBytes());

            // 7.关闭流,释放资源
            // socket.close();
            // ss.close();// 一般服务器是不关
        }
    }
}
客户端
public class Client {
    public static void main(String[] args) throws Exception{
        // 客户端:
        //1.创建Socket对象,指定要连接的服务器的ip地址和端口号
        Socket socket = new Socket("127.0.0.1",6666);

        // 死循环
        while (true) {
            //2.通过Socket对象获得输出流
            OutputStream os = socket.getOutputStream();

            //3.写出字符串数据
            Scanner sc = new Scanner(System.in);
            System.out.println("请输入一个字符串:");
            String msg = sc.nextLine();
            os.write(msg.getBytes());


            // 接收服务器回写的数据
            //4.通过Socket对象获得输入流
            InputStream is = socket.getInputStream();

            //5.读服务器回写的字符串数据
            byte[] bys = new byte[1024];
            int len = is.read(bys);
            System.out.println("客户端读到服务器端回写的数据:"+new String(bys,0,len));

            //6.关闭流,释放资源
            // os.close();
        }
    }
}

2.2. 文件上传综合案例

需求

使用TCP协议, 通过客户端向服务器上传一个文件

分析
  1. 【客户端】输入流,从硬盘读取文件数据到程序中。
  2. 【客户端】输出流,写出文件数据到服务端。
  3. 【服务端】输入流,读取文件数据到服务端程序。
  4. 【服务端】输出流,写出文件数据到服务器硬盘中。
  5. 【服务端】获取输出流,回写数据。
  6. 【客户端】获取输入流,解析回写数据。

实现
服务器
public class Server {
    public static void main(String[] args) throws Exception {
        //服务器:
        //1.创建ServerSocket对象,指定端口号
        ServerSocket ss = new ServerSocket(7777);

        // 循环接收请求,建立连接
        while (true){
            //2.调用accept方法接收请求,建立连接,返回Socket对象
            Socket socket = ss.accept();

            // 建立连接之后,就开启线程上传文件
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        //4.通过Socket对象获得输入流,关联连接通道
                        InputStream is = socket.getInputStream();

                        //5.创建字节输出流,关联目的地文件路径
                        FileOutputStream fos = new FileOutputStream("file\\bbb\\"+System.currentTimeMillis()+".jpg");

                        //6.定义一个byte数组,用来存储读取到的字节数据
                        byte[] bys = new byte[8192];

                        //7.定义一个int变量,用来存储读取到的字节个数
                        int len;

                        System.out.println("服务开始接收文件...");
                        //8.循环读数据--->连接通道
                        while ((len = is.read(bys)) != -1) {// 卡住
                            //9.在循环中,写出数据
                            fos.write(bys, 0, len);
                        }

                        System.out.println("服务器接收文件完毕了!");
                        //10.通过Socket对象获得输出流
                        OutputStream os = socket.getOutputStream();

                        //11.回写数据给客户端
                        os.write("文件上传成功!".getBytes());

                        //12.关闭流,释放资源
                        fos.close();
                        is.close();
                        // ss.close();
                    }catch (Exception e){

                    }
                }
            }).start();

        }
    }
}
客户端
public class Client {
    public static void main(String[] args) throws Exception{
        //客户端:
        //1.创建Socket对象,指定要连接的服务器的ip地址和端口号
        Socket socket = new Socket("127.0.0.1",7777);

        //2.创建字节输入流,关联数据源文件路径
        FileInputStream fis = new FileInputStream("file\\aaa\\mm.jpg");

        //3.通过Socket对象获得输出流,关联连接通道
        OutputStream os = socket.getOutputStream();

        //4.定义一个byte数组,用来存储读取到的字节数据
        byte[] bys = new byte[8192];

        //5.定义一个int变量,用来存储读取到的字节个数
        int len;

        System.out.println("客户端开始上传文件...");
        //6.循环读数据-->源文件
        while ((len = fis.read(bys)) != -1) {
            //7.在循环中,写出数据
            os.write(bys,0,len);// 最后文件中的字节数据
        }
        /*
            结束不了的原因: 服务器不知道客户端不再写数据过来了,所以服务器会一直等到读取数据
            解决办法: 客户端不再写数据了,就需要告诉服务器
         */
        socket.shutdownOutput();// 终止写出数据,但流没有关闭

        System.out.println("客户端上传文件完毕!");

        //8.通过Socket对象获得输入流
        InputStream is = socket.getInputStream();

        //9.读取服务器回写的数据
        int lens = is.read(bys);// 卡住
        System.out.println("服务器回写的数据:"+new String(bys,0,lens));

        //10.关闭流,释放资源
        os.close();
        fis.close();
    }
}

2.3. 模拟B\S服务器

需求

模拟网站服务器,使用浏览器访问自己编写的服务端程序,查看网页效果。

分析
  1. 准备页面数据,web文件夹。
  2. 我们模拟服务器端,ServerSocket类监听端口,使用浏览器访问,查看网页效果
  3. 注意:
// 1.浏览器工作原理是遇到图片会开启一个线程进行单独的访问,因此在服务器端加入线程技术。
// 2. 响应页面的时候需要同时把以下信息响应过去给浏览器
os.write("HTTP/1.1 200 OK\r\n".getBytes());
os.write("Content-Type:text/html\r\n".getBytes());
os.write("\r\n".getBytes());
实现
public class Server {
    public static void main(String[] args) throws Exception {
        //1.创建ServerSocket对象,指定端口号为8888
        ServerSocket ss = new ServerSocket(8888);

        // 浏览器工作原理是遇到图片会开启一个线程进行单独的访问,因此在服务器端加入线程技术。
        while (true){
            //2.调用accept方法接收请求,建立连接,返回Socket对象
            Socket socket = ss.accept();

            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        //3.通过Socket对象获得输入流
                        InputStream is = socket.getInputStream();

                        //4.通过输入流读取连接通道中的请求数据(浏览器发过来)
                        // byte[] bys = new byte[8192];
                        // int len = is.read(bys);
                        // System.out.println(new String(bys,0,len));
                        InputStreamReader isr = new InputStreamReader(is);
                        BufferedReader br = new BufferedReader(isr);
                        String line = br.readLine();
                        System.out.println("line:" + line);

                        //5.通过请求数据筛选出要访问的页面的路径(file\web\index.html)
                        String[] arr = line.split(" ");
                        String path = arr[1].substring(1);
                        System.out.println("path:"+path);

                        //6.创建字节输入流对象,关联需要访问的页面的路径
                        FileInputStream fis = new FileInputStream(path);

                        //7.通过Socket对象获得输出流,关联连接通道
                        OutputStream os = socket.getOutputStream();

                        //8.定义一个byte数组,用来存储读取到的字节数据
                        byte[] bys = new byte[8192];

                        //9.定义一个int变量,用来存储读取到的字节个数
                        int len;

                        //10.循环读取数据
                        // 响应页面的时候需要同时把以下信息响应过去给浏览器
                        os.write("HTTP/1.1 200 OK\r\n".getBytes());
                        os.write("Content-Type:text/html\r\n".getBytes());
                        os.write("\r\n".getBytes());

                        while ((len = fis.read(bys)) != -1) {
                            //11.在循环中,写数据给浏览器
                            os.write(bys,0,len);
                        }
                        //12.关闭流,释放资源
                        os.close();
                        fis.close();
                        //ss.close();
                    }catch (Exception e){

                    }
                }
            }).start();
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2232433.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

江协科技STM32学习- P31 I2C通信协议

&#x1f680;write in front&#x1f680; &#x1f50e;大家好&#xff0c;我是黄桃罐头&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流 &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd;​…

Docker部署Portainer CE结合内网穿透实现容器的可视化管理与远程访问

文章目录 前言1. 本地安装Docker2. 本地部署Portainer CE3. 公网远程访问本地Portainer-CE3.1 内网穿透工具安装3.2 创建远程连接公网地址4. 固定Portainer CE公网地址前言 本篇文章介绍如何在Ubuntu中使用docker本地部署Portainer CE可视化管理工具,并结合cpolar实现公网远程…

数据结构之二叉树--前序,中序,后序详解(含源码)

二叉树 二叉树不能轻易用断言&#xff0c;因为树一定有空 二叉树链式结构的实现 在学习二叉树的基本操作前&#xff0c;需先要创建一棵二叉树&#xff0c;然后才能学习其相关的基本操作。 typedef int BTDataType; typedef struct BinaryTreeNode {BTDataType _data;struct B…

数据库条件查询排查——引号故障

一、错误代码 $where_查询职汇总员[$value头[EmpCode]]$value职员[EmpCode]; 二、正常写法 $where_查询职汇总员[EmpCode]$value职员[EmpCode]; 三、原因 前一个是变量嵌套&#xff0c;这里不需要嵌套

前端用docker部署

1、环境检查 首先需要确认服务器上是否已经安装docker了。 在服务器上执行docker -v 显示对应的版本号说明已经安装好了docker 2、部署 使用Docker部署若依项目的前端服务&#xff0c;我们最终实现的是&#xff1a;启动一个镜像&#xff0c;我们的整个前端就启动了&#xf…

Matlab实现白鲸优化算法(BWO)求解路径规划问题

目录 1.内容介绍 2.部分代码 3.实验结果 4.内容获取 1内容介绍 白鲸优化算法&#xff08;BWO&#xff09;是一种受自然界白鲸捕食行为启发的新型优化算法&#xff0c;它通过模拟白鲸的群体捕猎策略和社会互动来探索问题的最优解。BWO因其强大的全局搜索能力和高效的局部搜索能…

CPU 中央处理器调优

文章目录 1.1 CPU处理方式&#xff1a;1.2 查看CPU一秒钟有多个切换多少次。1.3 调整进程优先级使用更多CPU1.4 CPU亲和力1.5 CPU 性能监控1.6 CPU 利用率比例分配&#xff1a; 1.1 CPU处理方式&#xff1a; 批处理&#xff0c;顺序处理请求。(切换次数少&#xff0c;吞吐量大…

C#:强大而优雅的编程语言

在当今的软件开发领域&#xff0c;C#作为一种广泛应用的编程语言&#xff0c;以其强大的功能、优雅的语法和丰富的生态系统&#xff0c;受到了众多开发者的喜爱。本文将深入探讨 C#的各个方面&#xff0c;展示它的魅力和优势。 一、C#的历史与发展 C#是由微软公司开发的一种面…

信息安全工程师(74)网络安全风险评估技术方法与工具

前言 网络安全风险评估是依据有关信息安全技术和管理标准&#xff0c;对网络系统的保密性、完整性、可控性等安全数据进行科学评价的过程。 一、网络安全风险评估技术方法 风险评估程序 资产评估&#xff1a;确定需要保护的资源。威胁评估&#xff1a;确定可能对资产造成危害的…

【InfluxDB】InfluxDB 2.x基础概念及原理

InfluxDB简介 什么是时序数据库 时序数据库&#xff0c;全称时间序列数据库&#xff08;Time Series Database&#xff0c;TSDB&#xff09;&#xff0c;用于存储大量基于时间的数据。时序数据库支持时序数据的快速写入、持久化&#xff0c;多维度查询、聚合等操作&#xff0…

sparkSQL面试题

一、查询所有数学课程成绩大于语文课程成绩的学生学号 数据 1,yuwen,43 1,shuxue,55 2,yuwen,77 2,shuxue,88 3,yuwen,98 3,shuxue,65 3,yingyu,88 基本步骤&#xff1a; 进行行转列比较语文与数学的成绩 SQL代码&#xff1a; with t1 as(SELECT id,sum(if(name yuwen,sc…

金融标准体系

目录 基本原则 标准体系结构图 标准明细表 金融标准体系下载地址 基本原则 需求引领、顶层设计。 坚持目标导向、问题导向、结果 导向有机统一&#xff0c;构建支撑适用、体系完善、科学合理的金融 标准体系。 全面系统、重点突出。 以金融业运用有效、保护有力、 管理高…

Spring Boot解决 406 错误之返回对象缺少Getter/Setter方法引发的问题

目录 前言1. 问题背景2. 问题分析2.1 检查返回对象 3. 解决方案3.1 确保Controller返回Result类型3.2 测试接口响应 4. 原理探讨5. 常见问题排查与优化建议结语 前言 在Spring Boot开发中&#xff0c;接口请求返回数据是系统交互的重要环节&#xff0c;尤其在开发RESTful风格的…

如何自学机器学习?

自学机器学习可以按照以下步骤进行&#xff1a; 一、基础知识准备 数学基础&#xff1a; 高等数学&#xff1a;学习微积分&#xff08;包括导数、微分、积分等&#xff09;、极限、级数等基本概念。这些知识是后续学习算法和优化方法的基础。 线性代数&#xff1a;掌握矩阵…

SQL,力扣题目1709,访问日期之间最大的空档期

一、力扣链接 LeetCode_1709 二、题目描述 表&#xff1a; UserVisits ------------------- | Column Name | Type | ------------------- | user_id | int | | visit_date | date | ------------------- 该表没有主键&#xff0c;它可能有重复的行 该表包含用户访问…

C# 日志框架 NLog、log4net 和 Serilog对比

文章目录 前言NLog、log4net 和 Serilog 三个框架的详细对比:一、NLog优点:缺点:二、 log4net优点缺点三、Serilog优点缺点四、Serilog使用举例总结前言 NLog、log4net 和 Serilog 三个框架的详细对比: NLog、log4net 和 Serilog 是三个非常流行的 .NET 日志框架,它们各自…

电路设计过程就是波形整形过程

这种说法有一定的道理。在电路设计中&#xff0c;常常需要对输入的电信号波形进行处理和调整&#xff0c;以满足后续电路或系统的要求&#xff0c;这在某种程度上可以理解为波形整形的过程。 例如&#xff0c;在数字电路中&#xff0c;输入的信号可能存在噪声、干扰或者不符合…

系统架构设计师(软考高级)一站式通关课程

系统架构设计师&#xff08;软考高级&#xff09; 一站式通关课程 系统架构设计师&#xff08;软考高级&#xff09;教程&#xff08;三连评论分享链接&#xff01;&#xff09; &#x1f3af; 课程亮点&#xff1a; 全面覆盖&#xff1a; 从基础概念到高级应用&#xff0c;涵盖…

源码阅读心得---如何从零开始阅读一个框架的源码

写在前头&#xff0c;菜鸟作者的一些碎碎念&#xff1a; 回想自己2022年研三第一次去实习的时候&#xff0c;是到了一个数据库小组&#xff0c;是一个做数据库内核的小组&#xff0c;leader分配的目标是先把read/write流程搞明白。第一次出校实习&#xff0c;一来就是直接读内核…