Socket篇(网络通信)

目录

一、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();
        }
    }
}
上一篇:ssm校园二手交易管理系统+vue


下一篇:ElementUI中el-table双击单元格显示输入框-实现