day15 网络编程

day15 网络编程

网络编程的作用:实现跨主机跨进程的通信

网络编程概述

网络编程三要素:IP地址,端口号和传输协议

IP+Port = 唯一进程

image-20220115114702396

java进程之间通信的过程主要看传输层的传输过程,底层已经被实现了

且传输层传输的是字节数据:
image-20220115115309811

udp协议和tcp协议在java语言中的传送过程:

image-20220115115127524

network编程实现

ip地址: 在java语言中一个ip地址,对应的是一个对象InetAddress对象

static InetAddress getByName(String host)
在给定主机名的情况下确定主机的 IP 地址。
主机名可以是机器名(如 "java.sun.com"),也可以是其 IP 地址的文本表示形式

拿到ip地址的方法(在jdk文档中没有说IP地址的构造方法,所以只能通过一些方法获取IP地址的返回值),以下是几种方法:

基于UDP来实现的聊天

版本一:传输一个字符串常量

public class Demo1 {

    public static void main(String[] args) throws UnknownHostException {
        InetAddress ipByLocalHost = InetAddress.getByName("localhost");
        // 127.0.0.1
        System.out.println(ipByLocalHost);

        // 根据计算机名称
        InetAddress shine = InetAddress.getByName("shine");
        System.out.println(shine);

        // 根据ip地址文本表示形式
        InetAddress byIpText = InetAddress.getByName("192.168.4.119");
        System.out.println(byIpText);

        // 根据ip地址文本表示形式
        InetAddress byLoopIp = InetAddress.getByName("127.0.0.1");
        System.out.println(byLoopIp);

    }
}

发送端:

实现基于UDP协议的发送端代码
     1.建立udp的socket对象
       DatagramSocket: 此类表示用来发送和接收数据报包的套接字。

           // 套接字: ip + port
           DatagramSocket(int port)
              创建数据报套接字并将其绑定到本地主机上的指定端口。
     
     2.将要发送的数据封装成数据包
 
     DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port)
     构造数据报包,用来将长度为 length 偏移量为 offset 的包发送到指定主机上的指定端口号。 
     length 参数必须小于等于 buf.length。

     参数:
     buf - 包数据。
     offset - 包数据偏移量。
     length - 包数据长度。
     address - 目的地址。
     port - 目的端口号。

     3.通过udp的socket对象,将数据包发送出

     public void send(DatagramPacket p)
     1)从此套接字发送数据报包。
     2)DatagramPacket 包含的信息指示:将要发送的数据、其长度、远程主机的 IP 地址和远程主机的端口号。


     4. 释放资源

     udp是一种无连接的不可靠的协议:
         不关心数据有没有发送成功,只关心将数据发出去就可以了
         类似于发短信

发送端的代码:(传输一个字符串)

public class Sender {

    public static void main(String[] args) throws IOException {
        // 应用层
            String data = "hello, udp";

        //-----------------------------------------------------------------

        // 传输层
        // DatagramSocket(int port)
        DatagramSocket datagramSocket = new DatagramSocket(10086);

        // 将待发送的数据封装到数据报包
        // DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port)
        byte[] dataBytes = data.getBytes();
        int offset = 0;
        int len = dataBytes.length;

        InetAddress targetIp = InetAddress.getByName("127.0.0.1");
        int port = 10087;

        // 待发送数据的数据报包
        DatagramPacket datagramPacket = new DatagramPacket(dataBytes, offset, len, targetIp, port);

        // 发送数据报包
        datagramSocket.send(datagramPacket);

        // 关闭Socket释放资源
        datagramSocket.close();


    }
}

接收端注意:

接收端:
      1.建立udp的socket对象.
          DatagramSocket: 此类表示用来发送和接收数据报包的套接字。

            // 套接字: ip + port
            DatagramSocket(int port)
               创建数据报套接字并将其绑定到本地主机上的指定端口。

      2.创建用于接收数据的数据报包

      DatagramPacket(byte[] buf, int offset, int length)

        构造 DatagramPacket,用来接收长度为 length 的包,在缓冲区中指定了偏移量。
        参数:
          buf - 保存传入数据报的缓冲区。
          offset - 缓冲区的偏移量
          length - 读取的字节数。



      3.通过socket对象的receive方法接收数据

          public void receive(DatagramPacket p)
             1)从此套接字接收数据报包。
             2)当此方法返回时,DatagramPacket 的缓冲区填充了接收的数据
             3)此方法在接收到数据报前一直阻塞
             4)数据报包也包含发送方的IP地址和发送方机器的端口号
      4.可以对资源进行释放


       通过数据包对象的功能来完成对接收到数据进行解析.

接收端代码:

public class Receiver {

    public static void main(String[] args) throws IOException {
        // 传输层(接收端先写传输层,再写应用层)
        DatagramSocket datagramSocket = new DatagramSocket(10087);

        //创建用于接收数据的数据报包
        //  DatagramPacket(byte[] buf, int offset, int length)
        byte[] byteBuf = new byte[1024];
        int offset = 0;
        int len = byteBuf.length;
        DatagramPacket datagramPacket = new DatagramPacket(byteBuf, offset, len);

        // 接收数据
        datagramSocket.receive(datagramPacket);


        // 关闭套接字对象,释放系统资源
        datagramSocket.close();
        // -----------------------------------------

        // 应用层
        //  从数据报包中,获取存放了接收到的数据的字节数组缓冲区
        byte[] data = datagramPacket.getData();
        // 字节数组缓冲区中,数据填充的起始位置
        int off = datagramPacket.getOffset();
        // 真正读取到的字节个数
        int length = datagramPacket.getLength();

        // 解析
        String s = new String(data, off, len);
        System.out.println(s);
    }
}

运行的结果:

image-20220115142810635

接收端收到了发送端发出的代码

接收端的注意事项
注意事项:
1. DatagramSocket的receive方法是一个阻塞方法
2. 如果让两个相同的有相同端口的进程同时运行,会报错:
	BindException:Address already in use:Sannot bind
	这里的address指的是套接字地址:ip + port
	
	一个端口号就是一个进程的逻辑地址,一个端口号只能绑定一个进程
	但是,一个进程可以绑定多个端口号

从接收端接收发送方的IP地址:

//获得发送方的IP地址:
InetAddress address = datagramPacket.getAddress();
//获得发送方的端口号
int port = datagramPacket.getPort();
//如果在接收端最后加上一句:
System.out.println("ip/port:" + address + "/" + port + "---" + s);

就会得到结果:

image-20220115151149170

版本二:发送键盘录入的字符串

在版本一的基础上,首先是录入单行的字符串,写一份发送端与接收端的代码:

只要new一个BufferedReader对象,读入一行数据即可:

发送端的代码:

public class Sender {

    public static void main(String[] args) throws IOException {

        // 1024 - 65535
        // 创建套接字对象
        DatagramSocket datagramSocket = new DatagramSocket(9999);
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));


        String line = br.readLine();
        // 读取一行数据,发送一行数据
        sendData(datagramSocket, line);
		
         byte[] bytes = line.getBytes();
        InetAddress targetIp = InetAddress.getByName("localhost");
        int port = 9998;
        // 创建用于发送数据的数据报包,并封装待发送的数据
        DatagramPacket sendPacket = new DatagramPacket(bytes, 0, bytes.length, targetIp, port);

        // 发送数据报包
        datagramSocket.send(sendPacket);
        
        // 关闭套接字对象
        datagramSocket.close();

    }
}

接收端代码:

public class Receiver {

    public static boolean flag = true;
    public static void main(String[] args) throws IOException {
        // 传输层
        DatagramSocket datagramSocket = new DatagramSocket(9998);

        // 准备好用于接收数据的数据报包
        byte[] bytes = new byte[1024];
        DatagramPacket receivePacket = new DatagramPacket(bytes, 0, bytes.length);

        // 接收别人发送的数据
        String s = receiveData(datagramSocket, receivePacket);

        
		datagramSocket.receive(receivePacket);
        
        byte[] data = receivePacket.getData();
        int offset = receivePacket.getOffset();
        int length = receivePacket.getLength();
        // 解析
        String s = new String(data, offset, length);
        System.out.println(s);
        return s;

        // 关闭套接字对象
        datagramSocket.close();
    }

}

但是上述思路只能发送1行信息,无法连续发送信息,为了实现连续发送信息,继续对代码进行改进:

  1. 这次改进不再区分传输层和应用层
  2. 将传输层的套接字代码都封装成一个private方法
  3. 在接收端,为了骗过编译器,设置一个flag,用while循环将这个套接字的方法放在方法体中,并将其返回值改为String(改不改都可以)

发送端的代码

public class Sender {

    public static void main(String[] args) throws IOException {

        // 1024 - 65535
        // 创建套接字对象
        DatagramSocket datagramSocket = new DatagramSocket(9999);
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String line;
        while ((line = br.readLine()) != null) {
            // 读取一行数据,发送一行数据
            sendData(datagramSocket, line);
        }
        // 关闭套接字对象
        datagramSocket.close();
    }
    private static void sendData(DatagramSocket datagramSocket, String line) throws IOException {
        byte[] bytes = line.getBytes();
        InetAddress targetIp = InetAddress.getByName("localhost");
        int port = 9998;
        // 创建用于发送数据的数据报包,并封装待发送的数据
        DatagramPacket sendPacket = new DatagramPacket(bytes, 0, bytes.length, targetIp, port);

        // 发送数据报包
        datagramSocket.send(sendPacket);
    }
}

接收端代码:

public class Receiver {

    public static boolean flag = true;
    public static void main(String[] args) throws IOException {
        // 传输层
        DatagramSocket datagramSocket = new DatagramSocket(9998);

        // 准备好用于接收数据的数据报包
        byte[] bytes = new byte[1024];
        DatagramPacket receivePacket = new DatagramPacket(bytes, 0, bytes.length);

        while (flag) {
            // 接收别人发送的数据
            String s = receiveData(datagramSocket, receivePacket);
        }
        // 关闭套接字对象
        datagramSocket.close();

    }

    private static String receiveData(DatagramSocket datagramSocket, DatagramPacket receivePacket) throws IOException {
        datagramSocket.receive(receivePacket);
        byte[] data = receivePacket.getData();
        int offset = receivePacket.getOffset();
        int length = receivePacket.getLength();

        // 解析
        String s = new String(data, offset, length);
        System.out.println(s);
        return s;
    }
}

版本三:模拟两个人的聊天过程

这个版本只能实现发送端发消息,接收端接收消息后就发送回去这个方法才能正常执行

这个版本与上一个版本的区别:
在接收端/发送端的while循环中放入了发送/接收的方法:
发送端代码:

public class Sender {

    public static void main(String[] args) throws IOException {

        // 1024 - 65535
        // 创建套接字对象
        DatagramSocket datagramSocket = new DatagramSocket(9999);
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));


        String line;
        while ((line = br.readLine()) != null) {

            // 读取一行数据,发送一行数据
            sendData(datagramSocket, line);


            // 接收接收端发送给我的数据
            String s = receiveData(datagramSocket);

            if ("886".equals(s)) {
                break;
            }

        }

        // 关闭套接字对象
        datagramSocket.close();

    }

    private static String receiveData(DatagramSocket datagramSocket) throws IOException {
        byte[] bytes = new byte[1024];
        DatagramPacket receivePacket = new DatagramPacket(bytes, 0, bytes.length);

        // 阻塞方法,当没有数据的时候,会阻塞等待
        datagramSocket.receive(receivePacket);

        byte[] data = receivePacket.getData();
        int length = receivePacket.getLength();
        int offset = receivePacket.getOffset();

        String s = new String(data, offset, length);
        System.out.println(s);
        return s;
    }

    private static void sendData(DatagramSocket datagramSocket, String line) throws IOException {
        byte[] bytes = line.getBytes();
        InetAddress targetIp = InetAddress.getByName("localhost");
        int port = 9998;
        // 创建用于发送数据的数据报包,并封装待发送的数据
        DatagramPacket sendPacket = new DatagramPacket(bytes, 0, bytes.length, targetIp, port);

        // 发送数据报包
        datagramSocket.send(sendPacket);
    }
}

接收端代码:

public class Receiver {

    public static boolean flag = true;
    public static void main(String[] args) throws IOException {
        // 传输层
        DatagramSocket datagramSocket = new DatagramSocket(9998);

        // 准备好用于接收数据的数据报包
        byte[] bytes = new byte[1024];
        DatagramPacket receivePacket = new DatagramPacket(bytes, 0, bytes.length);

        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));

        while (flag) {
            // 接收别人发送的数据
            String s = receiveData(datagramSocket, receivePacket);

            sendResponse(datagramSocket, bufferedReader);
            // 依赖自定义协议
            if ("886".equals(s)) {
                break;
            }
        }


        // 关闭套接字对象
        datagramSocket.close();




    }

    private static void sendResponse(DatagramSocket datagramSocket, BufferedReader bufferedReader) throws IOException {
        String sendData = bufferedReader.readLine();

        byte[] dataBytes = sendData.getBytes();
        InetAddress targetIp = InetAddress.getByName("127.0.0.1");
        int port = 9999;
        DatagramPacket sendPacket = new DatagramPacket(dataBytes, 0, dataBytes.length, targetIp, port);


        datagramSocket.send(sendPacket);
    }

    private static String receiveData(DatagramSocket datagramSocket, DatagramPacket receivePacket) throws IOException {
        // 阻塞方法
        datagramSocket.receive(receivePacket);
        byte[] data = receivePacket.getData();
        int offset = receivePacket.getOffset();
        int length = receivePacket.getLength();

        // 解析
        String s = new String(data, offset, length);
        System.out.println(s);
        return s;
    }
}

这里遇见的问题是读入一行数据,被receive方法给阻塞了,所以只能接一句,发一句

运行结果:

image-20220115161006783

image-20220115161025725

receive线程中没有收到最后一句话

版本四:同时接送,同时发送,且可以下线(引入多线程)

两个线程:

  1. SenderTask
  2. ReceiveTask
  3. 主线程OnePerson

建立两个package,在两个包内分别放入3个线程

image-20220115201754287

说明:当两个都人发送886后,两个进程终止了

线程的模型:
image-20220115204653622

这里有问题:当anotherperson中的senderTask先发送886,,ReceiverTask关闭了onePerson中的套接字对象,而onePerson中的SenderTask后发送886,这个时候就会将异常向上报告,所以,何时才能正确的关闭DatagarmSocket对象?(分析)

image-20220115205038388

以oneperson里的class对象来看:

OnePerson对象中的代码:

public class OnePerson {

    // 该变量作为SenderTask是否发出了886的标志位
    public static boolean  senderOffline = false;//修改的部分

    // 创建一个锁对象
    public static Object lockObj = new Object();//修改的部分

    public static void main(String[] args) throws SocketException, UnknownHostException {

        DatagramSocket datagramSocket = new DatagramSocket(8080);

        // 发生任务,接收任务
        String ip = "127.0.0.1";
        int port = 9876;
        SenderTask senderTask = new SenderTask(datagramSocket, ip, port);
        ReceiverTask receiverTask = new ReceiverTask(datagramSocket);

        Thread senderThread = new Thread(senderTask);
        Thread receiveThread = new Thread(receiverTask);

        // 启动发送接收线程
        senderThread.start();
        receiveThread.start();
    }
}

在ReceiverTask中:

public class ReceiverTask implements Runnable{

    private DatagramSocket datagramSocket;//修改的部分

    public ReceiverTask(DatagramSocket datagramSocket) {
        this.datagramSocket = datagramSocket;
    }

    @Override
    public void run() {
        byte[] bytes = new byte[1024];
        DatagramPacket receivePacket = new DatagramPacket(bytes, 0, bytes.length);
        boolean flag = true;
        try {
            while (flag) {
                datagramSocket.receive(receivePacket);

                byte[] data = receivePacket.getData();
                int offset = receivePacket.getOffset();
                int length = receivePacket.getLength();

                String s = new String(data, offset, length);
                System.out.println(s);
                if ("886".equals(s)) {
                    break;
                }
            }


        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 接收到了886,才会执行finally代码块
            // 关闭套接字
            while (true) {

                synchronized (OnePerson.lockObj) {//修改的部分
                  if (OnePerson.senderOffline ) {
                      // 发送线程发出了886
                      break;
                  }
              }

              // 等待一小段时间
                try {
                    Thread.sleep(50);//如果不这样做的话,CPU会一直在while循环中,这样会使得CPU打满,所以我么休眠一段时间
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }

            datagramSocket.close();//关闭套接字进程都在receiverTask中关闭
        }


    }
}

在SendTask线程中:

public class SenderTask implements Runnable {

    // 用于发送数据套接字对象
    private DatagramSocket datagramSocket;

    private InetAddress targetIp;

    int targetPort;

    public SenderTask(DatagramSocket datagramSocket, String targetIp, int targetPort) throws UnknownHostException {
        this.datagramSocket = datagramSocket;
        this.targetIp = InetAddress.getByName(targetIp);
        this.targetPort = targetPort;
    }

    @Override
    public void run() {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        String line;

        try {
            while ((line = br.readLine()) != null) {
                byte[] bytes = line.getBytes();
                DatagramPacket sendPacket = new DatagramPacket(bytes, 0, bytes.length, targetIp, targetPort);
                datagramSocket.send(sendPacket);

                if ("886".equals(line)) {
                    synchronized (OnePerson.lockObj) {//修改的部分!!!
                        OnePerson.senderOffline = true;
                    }
                    break;
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

anotherperson的版本中,只有主线程的端口号的设置不一样:

public class AnotherPerson {

    public static boolean isSenderOffline = false;

    public static Object lockObj = new Object();

    public static void main(String[] args) throws SocketException, UnknownHostException {

        DatagramSocket datagramSocket = new DatagramSocket(9876);

        // 发生任务,接收任务
        String ip = "127.0.0.1";
        int port = 8080;
        SenderTask senderTask = new SenderTask(datagramSocket, ip, port);
        ReceiverTask receiverTask = new ReceiverTask(datagramSocket);

        Thread senderThread = new Thread(senderTask);
        Thread receiveThread = new Thread(receiverTask);

        // 启动发送接收线程
        senderThread.start();
        receiveThread.start();


    }
}

版本五:模拟聊天室程序

一个人发消息,其他人都能看得到

  1. 只要将ip地址改为广播地址即可(查询方式:在cmd窗口中,输入ipconfig命令查询一下本子链接的IPv4地址即可)(加入ip地址为:192.168.4.199,则广播地址为:192.168.4.255)
  2. 将端口号改为所有人共同绑定的端口号,即发送端口号和接收端口号为同一个端口号
  3. 但是有一个弊端:即所有人都不能停止运行进程,即不能发了886以后就进程终止,这样,所有的人都不能再发送信息了:
public class ChatRoom {

    // 该变量作为SenderTask是否发出了886的标志位
    public static boolean  senderOffline = false;

    // 创建一个锁对象
    public static Object lockObj = new Object();

    public static void main(String[] args) throws SocketException, UnknownHostException {

        DatagramSocket datagramSocket = new DatagramSocket(8080);

        // 发生任务,接收任务
        // ip地址改为广播地址
        String ip = "192.168.4.255";
        int port = 8080;
        SenderTask senderTask = new SenderTask(datagramSocket, ip, port);
        ReceiverTask receiverTask = new ReceiverTask(datagramSocket);

        Thread senderThread = new Thread(senderTask);
        Thread receiveThread = new Thread(receiverTask);

        // 启动发送接收线程
        senderThread.start();
        receiveThread.start();


    }
}
public class ReceiverTask implements Runnable{

    private DatagramSocket datagramSocket;

    public ReceiverTask(DatagramSocket datagramSocket) {
        this.datagramSocket = datagramSocket;
    }

    @Override
    public void run() {
        byte[] bytes = new byte[1024];
        DatagramPacket receivePacket = new DatagramPacket(bytes, 0, bytes.length);

        boolean flag = true;

        try {
            while (flag) {
                datagramSocket.receive(receivePacket);

                byte[] data = receivePacket.getData();
                int offset = receivePacket.getOffset();
                int length = receivePacket.getLength();

                String s = new String(data, offset, length);
                InetAddress address = receivePacket.getAddress();
                int port = receivePacket.getPort();
                System.out.println(address + ":"+ port +"---" + s);
            }


        } catch (IOException e) {
            e.printStackTrace();
        }


    }
}
public class SenderTask implements Runnable {

    // 用于发送数据套接字对象
    private DatagramSocket datagramSocket;

    private InetAddress targetIp;

    int targetPort;

    public SenderTask(DatagramSocket datagramSocket, String targetIp, int targetPort) throws UnknownHostException {
        this.datagramSocket = datagramSocket;
        this.targetIp = InetAddress.getByName(targetIp);
        this.targetPort = targetPort;
    }

    @Override
    public void run() {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        String line;

        try {
            while ((line = br.readLine()) != null) {
                byte[] bytes = line.getBytes();
                DatagramPacket sendPacket = new DatagramPacket(bytes, 0, bytes.length, targetIp, targetPort);
                datagramSocket.send(sendPacket);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }



    }
}

TCP协议传送数据

TCP和UDP协议的区别:TCP在传送数据前要先建立可靠链接

接收数据和发送数据的过程(在服务器端和客户端):

image-20220115212427333

实现客户端向服务器端发数据

客户端的注意事项和代码实现
1. 建立客户端的Socket服务,并明确要连接的服务器。
    Socket: 此类实现客户端套接字

  Socket(String host, int port)(jdk中的一种构造方式)
    创建一个流套接字并将其 连接到 指定主机上 的指定端口号
    a. 这里的host, port,要连接的服务器端的ip地址和端口号(这里和udp的不同)
    b. 创建出的Socket对象本身,  默认绑定本机ip  ,绑定一个随机端口
  参数:
    host - 主机名,或者为 null,表示回送地址。
    port - 端口号。

2.如果对象建立成功,就表明已经建立了数据传输的通道.就可以在该通道通过IO进行数据的读取和写入
3.根据需要从socket对象中获取输入,或输出流
4.向流中读取或写入数据
5.释放资源

注意事项:
   虽然我们可以从Socket中获取流对象,但是Socket中流对象,Socket自己会负责关闭

实现代码:

public class Client {

    public static void main(String[] args) throws IOException {

        // 1. 创建Socket对象(在创建对象的时候就直接向目标地址发起连接请求)
        Socket socket = new Socket("127.0.0.1", 9876);

        // 2. 只要Socket对象创建成功,我们客户端就可以认为连接建立好了,获取输出流发送数据
        OutputStream out = socket.getOutputStream();

        //3. 利用流发送数据
        out.write("hello,tcp".getBytes());

        //4. 关闭套接字
        socket.close();

    }
}
服务器端的注意事项和实现代码:
服务器端:
  1.创建Serversocket对象,在指定端口,监听客户端连接请求
     ServerSocket: 此类实现服务器套接字


     ServerSocket(int port)
       创建绑定到特定端口的服务器套接字。
       服务器套接字等待请求通过网络传入

  2.收到客户端连接请求后,建立Socket连接
    public Socket accept()
        1. 侦听并接受到此套接字的连接。
        2. 此方法在连接传入之前一直  阻塞  。

  3.如果连接建立成功,就表明已经建立了数据传输的通道.就可以在该通道通过IO进行数据的读取和写入
   从socket中根据需要获取输入,或输出流
  4.根据需要向流中写入数据或从流中读数据
  5.释放资源

  注意事项:
  1. accept 是一个阻塞方法
  2. BindException: Address already in use, 一个端口号只能绑定一个进程

实现代码:

public class Server {

    public static void main(String[] args) throws IOException {
        // 创建服务器端的套接字对象
        ServerSocket serverSocket = new ServerSocket(9876);

        // 通过accept方法,真正接收处理连接请求,建立连接
        Socket socket = serverSocket.accept();

        // 从服务器端建立连接的Socket对象上,获取流对象,进行数据传输
        InputStream in = socket.getInputStream();

        byte[] bytes = new byte[1024];
        int len = in.read(bytes);

        String s = new String(bytes, 0, len);
        System.out.println(s);

        // 关闭Socket
        socket.close();
        serverSocket.close();

    }
}

要实现从服务器端向客户端发送数据:

即,将输入输出流反转一下即可

练习:

客户端文本文件,服务器端输出文本文件

示意图:

image-20220115213959858

客户端:

/*
        public void shutdownOutput()
        禁用此套接字的输出流。
        对于 TCP 套接字,任何以前写入的数据都将被发送,并且后跟 TCP 的正常连接终止序列
        
        这个方法能够实现关闭客户端的输出流,从而使得服务器端的进程能够不被read方法阻塞,向客户端输出"文件上传完毕"的字样
 */
public class Client {

    public static void main(String[] args) throws IOException {
//        System.out.println(System.getProperty("user.dir"));
//
        // 1. 创建客户端Socket对象
        Socket socket = new Socket("127.0.0.1", 10086);

        // 2. 准备待发送的数据(文件)
        BufferedReader br = new BufferedReader(new FileReader("Demo1.java"));

        OutputStream out = socket.getOutputStream();
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(out));

        // 给服务器端发送文件内容
        String line;
        while ((line = br.readLine()) != null) {
            // 读取一行,发送一行
            bw.write(line);
            bw.newLine();
            bw.flush();//注意这里要刷新缓冲区

            // 向客户端返回文件传输结束的方法1:finish约定为文件结束的标志(不完美)
//            if ("finish".equals(line)) {
//                break;
//            }
        }

        // 方法2:关闭客户端的输出流(完美的解决方案)
        socket.shutdownOutput();

        // 接收服务器端发送的反馈消息
        InputStream in = socket.getInputStream();
        byte[] bytes = new byte[1024];
        
        // Socket的输入流的read方法,是阻塞方法!!!!
        
        int len = in.read(bytes);
        String s = new String(bytes, 0, len);
        System.out.println(s);

        // 关闭套接字
        br.close();
        socket.close();

    }
}

服务器端:

public class Server {

    public static void main(String[] args) throws IOException {
        // 创建服务器端套接字对象
        ServerSocket serverSocket = new ServerSocket(10086);
        Socket socket = serverSocket.accept();

        InputStream in = socket.getInputStream();
        BufferedReader br = new BufferedReader(new InputStreamReader(in));

        FileWriter fileWriter = new FileWriter("copy.java");

        String line;
        
        // Socket的InputStream的read,阻塞方法!!!在这里就是br.readLine()
        
        while ((line = br.readLine()) != null) {
            // 文件结束标志,在文件的末尾加一个finish
//            if ("finish".equals(line)) {
//                break;
//            }
            fileWriter.write(line);
            fileWriter.write(System.lineSeparator());
            fileWriter.flush();
        }

        // 发送文件上传完毕的消息
        OutputStream out = socket.getOutputStream();
        out.write("文件上传完毕".getBytes());

        fileWriter.close();
        socket.close();
        serverSocket.close();

    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值