十三、网络编程、UDP、TCP协议

news2024/7/6 19:38:00

JMM(了解)

JMM就是Java内存模型(java memory model)。因为在不同的硬件生产商和不同的操作系统下,内存的访问有一定的差异,所以会造成相同的代码运行在不同的系统上会出现各种问题。所以java内存模型(JMM)屏蔽掉各种硬件和操作系统的内存访问差异,以实现让java程序在各种平台下都能达到一致的并发效果。

Java内存模型规定所有的变量都存储在主内存中,包括实例变量,静态变量,但是不包括局部变量和方法参数。每个线程都有自己的工作内存,线程的工作内存保存了该线程用到的变量和主内存的副本拷贝,线程对变量的操作都在工作内存中进行线程不能直接读写主内存中的变量

不同的线程之间也无法访问对方工作内存中的变量。线程之间变量值的传递均需要通过主内存来完成。

图 直观一点:

在这里插入图片描述

每个线程的工作内存都是独立的,线程操作数据只能在工作内存中进行,然后刷回到主存。这是 Java 内存模型定义的线程基本工作方式。

Volatile

  • 亮亮
package com.heima.tes1;

public class LiangLiang extends Thread {
    public LiangLiang(String name) {
        super(name);
    }

    @Override
    public void run() {
        while (true) {
            if (Money.money != 100000) {
                System.out.println("结婚基金已经不是十万了");
                break;
            }
        }
    }
}

  • 露露
package com.heima.tes1;

public class LULU extends Thread {
    public LULU(String name) {
        super(name);
    }

    @Override
    public void run() {

        try {
            sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Money.money = 90000;
        System.out.println("花了10000");
    }
}

  • 共享变量 money
public class Money {
    public static volatile int money = 100000;
}

  • 测试代码
    • 把共享变量money前的volatile 添加和去掉的区别
public class Demo01 {
    public static void main(String[] args) {
        LiangLiang liangLiang = new LiangLiang("liangliang");
        LULU lulu = new LULU("xiaolu");

        lulu.start();
        liangLiang.start();
    }
}

在这里插入图片描述

给代码加锁也可以解决问题

  • 会在锁释放的时候 抹除变量副本
    在这里插入图片描述

网络编程

网络编程概述【理解】

  • 计算机网络

    是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统

  • 网络编程

    在网络通信协议下,不同计算机上运行的程序,可以进行数据传输

网络编程三要素【理解】

  • IP地址

    要想让网络中的计算机能够互相通信,必须为每台计算机指定一个标识号,通过这个标识号来指定要接收数据的计算机和识别发送的计算机,而IP地址就是这个标识号。也就是设备的标识

  • 端口

    网络的通信,本质上是两个应用程序的通信。每台计算机都有很多的应用程序,那么在网络通信时,如何区分这些应用程序呢?如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的应用程序了。也就是应用程序的标识

  • 协议

    通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规则一样。在计算机网络中,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换。常见的协议有UDP协议和TCP协议

IP地址【理解】

IP地址:是网络中设备的唯一标识

  • IP地址分为两大类
    • IPv4:是给每个连接在网络上的主机分配一个32bit地址。按照TCP/IP规定,IP地址用二进制来表示,每个IP地址长32bit,也就是4个字节。例如一个采用二进制形式的IP地址是“11000000 10101000 00000001 01000010”,这么长的地址,处理起来也太费劲了。为了方便使用,IP地址经常被写成十进制的形式,中间使用符号“.”分隔不同的字节。于是,上面的IP地址可以表示为“192.168.1.66”。IP地址的这种表示法叫做“点分十进制表示法”,这显然比1和0容易记忆得多
    • IPv6:由于互联网的蓬勃发展,IP地址的需求量愈来愈大,但是网络地址资源有限,使得IP的分配越发紧张。为了扩大地址空间,通过IPv6重新定义地址空间,采用128位地址长度,每16个字节一组,分成8组十六进制数,这样就解决了网络地址资源数量不够的问题
  • DOS常用命令:
    • ipconfig:查看本机IP地址
    • ping IP地址:检查网络是否连通
  • 特殊IP地址:
    • 127.0.0.1:是回送地址,可以代表本机地址,一般用来测试使用

InetAddress【应用】

InetAddress:此类表示Internet协议(IP)地址

  • 相关方法

    方法名说明
    static InetAddress getByName(String host)确定主机名称的IP地址。主机名称可以是机器名称,也可以是IP地址
    String getHostName()获取此IP地址的主机名
    String getHostAddress()返回文本显示中的IP地址字符串
  • 代码演示

public class InetAddressDemo {
    public static void main(String[] args) throws UnknownHostException {
		//InetAddress address = InetAddress.getByName("itheima");
        InetAddress address = InetAddress.getByName("192.168.1.66");

        //public String getHostName():获取此IP地址的主机名
        String name = address.getHostName();
        //public String getHostAddress():返回文本显示中的IP地址字符串
        String ip = address.getHostAddress();

        System.out.println("主机名:" + name);
        System.out.println("IP地址:" + ip);
    }
}

端口和协议【理解】

  • 端口

    • 设备上应用程序的唯一标识
  • 端口号

    • 用两个字节表示的整数,它的取值范围是065535。其中,01023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败

协议

  • 计算机网络中,连接和通信的规则被称为网络通信协议

UDP协议

  • 用户数据报协议(User Datagram Protocol)
  • UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。
  • 由于使用UDP协议消耗系统资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输
  • 例如视频会议通常采用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议

TCP协议

  • 传输控制协议 (Transmission Control Protocol)

  • TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。在TCP连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”

  • 三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠

    第一次握手,客户端向服务器端发出连接请求,等待服务器确认

    第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求

    第三次握手,客户端再次向服务器端发送确认信息,确认连接

  • 完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性,TCP协议可以保证传输数据的安全,所以应用十分广泛。例如上传文件、下载文件、浏览网页等

UDP通信程序

UDP发送数据【应用】

  • Java中的UDP通信

    • UDP协议是一种不可靠的网络协议,它在通信的两端各建立一个Socket对象,但是这两个Socket只是发送,接收数据的对象,因此对于基于UDP协议的通信双方而言,没有所谓的客户端和服务器的概念
    • Java提供了DatagramSocket类作为基于UDP协议的Socket
  • 构造方法

    方法名说明
    DatagramSocket()创建数据报套接字并将其绑定到本机地址上的任何可用端
  • DatagramPacket(byte[] buf,int len,InetAddress add,int port) 创建数据包,发送长度为len的数据包到指定主机的指定端口-

  • 相关方法

    方法名说明
    void send(DatagramPacket p)发送数据报包
    void close()关闭数据报套接字
    void receive(DatagramPacket p)从此套接字接受数据报包
  • 发送数据的步骤

    • 1创建发送端的Socket对象(DatagramSocket)
    • 2创建数据,并把数据打包
    • 3调用DatagramSocket对象的方法发送数据
    • 4关闭发送端
  • 代码演示

    public static void main(String[] args) throws IOException {
        //- 1创建发送端的Socket对象(DatagramSocket)
        DatagramSocket ds = new DatagramSocket();

        
        //- 2创建数据,并把数据打包
        String s = "你好";
        //2.1把字符串转为字节数组
        byte[] bytes = s.getBytes("gbk");
        //2.2创建InetAddress对象 里面写入目标地址的ip
        InetAddress addr = InetAddress.getByName("127.0.0.1");

        //2.3 打包数据
        // 参是1 是要发送的数据的字节数组,参2是发送的数组的长度,参3 目标的ip地址的InetAddress对象,参4目标的端口
        DatagramPacket packet = new DatagramPacket(bytes, bytes.length, addr, 10000);

        //- 3调用DatagramSocket对象的方法发送数据
        ds.send(packet);
        //- 4关闭发送端
        ds.close();
    }

UDP接收数据【应用】

  • 接收数据的步骤

    • 1创建接收端的Socket对象(DatagramSocket)
    • 2创建一个数据包,用于接收数据
    • 3调用DatagramSocket对象的方法接收数据
    • 4解析数据包,并把数据在控制台显示
    • 5关闭接收端
  • 构造方法

    方法名说明
    DatagramPacket(byte[] buf, int len)创建一个DatagramPacket用于接收长度为len的数据包
  • 相关方法

    方法名说明
    byte[] getData()返回数据缓冲区
    int getLength()返回要发送的数据的长度或接收的数据的长度
  • 示例代码

        //- 1创建接收端的Socket对象(DatagramSocket)
        //接收的程序 写明端口号 如果不写 每次运行都会自动分配一个不一样的端口
        DatagramSocket ds = new DatagramSocket(10000);


        //- 2创建一个数据包,用于接收数据
        byte[] bytes = new byte[1024];
        //参1是装接收的数据的字节数据,参2表示接收多长的数据装到数组里
        DatagramPacket dp = new DatagramPacket(bytes, bytes.length);

        //- 3调用DatagramSocket对象的方法接收数据f
        System.out.println(1111);
        ds.receive(dp);
        System.out.println(222);

        //- 4解析数据包,并把数据在控制台显示
        //getData获取数据包中的字节数组
        byte[] data = dp.getData();
        //dp.getLength()是接收的数据的长度
        System.out.println(new String(data, 0, dp.getLength(), "gbk"));

        //- 5关闭接收端
        ds.close();

UDP通信程序练习(掌握)

  • 案例需求

    UDP发送数据:数据来自于键盘录入,可以一直发,直到输入的数据是886,发送数据结束

    UDP接收数据:因为接收端不知道发送端什么时候停止发送,故采用死循环接收

  • 代码实现

/*
    UDP发送数据:
        数据来自于键盘录入,直到输入的数据是886,发送数据结束
 */
public class Send {
    public static void main(String[] args) throws IOException {
        //1创建socket对象
        DatagramSocket ds = new DatagramSocket();
        //2键盘录入
        Scanner sc = new Scanner(System.in);

        while (true) {
            //获取发送的数据
            System.out.println("请输入数据");
            String text = sc.next();
            if ("886".equals(text)) {
                break;
            }

            //3 打包数据包
            //数据转为字节数组
            byte[] bytes = text.getBytes();
            //目标ip
            InetAddress addr = InetAddress.getByName("192.168.12.75");
            //创建好数据包
            DatagramPacket packet = new DatagramPacket(bytes, bytes.length, addr, 14000);

            ds.send(packet);
            System.out.println("发送成功");
        }

        ds.close();
    }
}


/*
    UDP接收数据:
        因为接收端不知道发送端什么时候停止发送,故采用死循环接收
 */
public class Receive {
    public static void main(String[] args) throws IOException {
        //1创建socket对象 端口13000
        DatagramSocket ds = new DatagramSocket(14000);

        while (true) {
            //2创建接收的数组和数据包
            byte[] bytes = new byte[1024];
            DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
            System.out.println("等待接收数据");
            //3接收数据
            ds.receive(packet);
            //4打印数据
            System.out.println(new String(packet.getData(), 0, packet.getLength()));
        }
        //ds.close();
    }
}

UDP三种通讯方式【理解】

  • 单播

    单播用于两个主机之间的端对端通信

  • 组播

    组播用于对一组特定的主机进行通信,224.0.0.0~239.255.255.255

  • 广播

    广播用于一个主机对整个局域网上所有主机上的数据通信

UDP组播实现【了解】

  • 组播的ip范围 224.0.0.0~239.255.255.255

  • 实现步骤

    • 发送端
      1. 创建发送端的Socket对象(DatagramSocket)
      2. 创建数据,并把数据打包(DatagramPacket)
      3. 调用DatagramSocket对象的方法发送数据(在单播中,这里是发给指定IP的电脑但是在组播当中,这里是发给组播地址)
      4. 释放资源
    • 接收端
      1. 创建接收端Socket对象(MulticastSocket)
      2. 创建一个箱子,用于接收数据
      3. 把当前计算机绑定一个组播地址
      4. 将数据接收到箱子中
      5. 解析数据包,并打印数据
      6. 释放资源
  • 代码实现

// 发送端
public class ClinetDemo {
    public static void main(String[] args) throws IOException {
        // 1. 创建发送端的Socket对象(DatagramSocket)
        DatagramSocket ds = new DatagramSocket();
        String s = "hello 组播";
        byte[] bytes = s.getBytes();
        InetAddress address = InetAddress.getByName("224.0.1.0");
        int port = 10000;
        // 2. 创建数据,并把数据打包(DatagramPacket)
        DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
        // 3. 调用DatagramSocket对象的方法发送数据(在单播中,这里是发给指定IP的电脑但是在组播当中,这里是发给组播地址)
        ds.send(dp);
        // 4. 释放资源
        ds.close();
    }
}
// 接收端
public class ServerDemo {
    public static void main(String[] args) throws IOException {
        // 1. 创建接收端Socket对象(MulticastSocket)
        MulticastSocket ms = new MulticastSocket(10000);
        // 2. 创建一个箱子,用于接收数据
        DatagramPacket dp = new DatagramPacket(new byte[1024],1024);
        // 3. 把当前计算机绑定一个组播地址,表示添加到这一组中.
        ms.joinGroup(InetAddress.getByName("224.0.1.0"));
        // 4. 将数据接收到箱子中
        ms.receive(dp);
        // 5. 解析数据包,并打印数据
        byte[] data = dp.getData();
        int length = dp.getLength();
        System.out.println(new String(data,0,length));
        // 6. 释放资源
        ms.close();
    }
}

UDP广播实现【理解】

  • 实现步骤

    • 发送端
      1. 创建发送端Socket对象(DatagramSocket)
      2. 创建存储数据的箱子,将广播地址封装进去
      3. 发送数据
      4. 释放资源
    • 接收端
      1. 创建接收端的Socket对象(DatagramSocket)
      2. 创建一个数据包,用于接收数据
      3. 调用DatagramSocket对象的方法接收数据
      4. 解析数据包,并把数据在控制台显示
      5. 关闭接收端
  • 代码实现

// 发送端
public class ClientDemo {
    public static void main(String[] args) throws IOException {
      	// 1. 创建发送端Socket对象(DatagramSocket)
        DatagramSocket ds = new DatagramSocket();
		// 2. 创建存储数据的箱子,将广播地址封装进去
        String s = "广播 hello";
        byte[] bytes = s.getBytes();
        InetAddress address = InetAddress.getByName("255.255.255.255");
        int port = 10000;
        DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
		// 3. 发送数据
        ds.send(dp);
		// 4. 释放资源
        ds.close();
    }
}
// 接收端
public class ServerDemo {
    public static void main(String[] args) throws IOException {
        // 1. 创建接收端的Socket对象(DatagramSocket)
        DatagramSocket ds = new DatagramSocket(10000);
        // 2. 创建一个数据包,用于接收数据
        DatagramPacket dp = new DatagramPacket(new byte[1024],1024);
        // 3. 调用DatagramSocket对象的方法接收数据
        ds.receive(dp);
        // 4. 解析数据包,并把数据在控制台显示
        byte[] data = dp.getData();
        int length = dp.getLength();
        System.out.println(new String(data,0,length));
        // 5. 关闭接收端
        ds.close();
    }
}

TCP通信程序

在这里插入图片描述

一开始就接收的一端:服务器, 要先运行 ,等待客户端连接

一开始就发送的一端:客户端,主动去连接服务器

TCP发送数据

  • Java中的TCP通信

    • Java对基于TCP协议的的网络提供了良好的封装,使用Socket对象来代表两端的通信端口,并通过Socket产生IO流来进行网络通信。
    • Java为客户端提供了Socket类,为服务器端提供了ServerSocket类
  • 构造方法

    方法名说明
    Socket(InetAddress address,int port)创建流套接字并将其连接到指定IP指定端口号
    Socket(String host, int port)创建流套接字并将其连接到指定主机上的指定端口号
  • 相关方法

    方法名说明
    InputStream getInputStream()返回此套接字的输入流
    OutputStream getOutputStream()返回此套接字的输出流
  • 示例代码

public static void main(String[] args) throws IOException {
    //1创建socket对象
    //参数1和2是要连接的服务器的ip和端口
    Socket s = new Socket("192.168.27.58", 13000);
    System.out.println("连接成功");

    //获取发送数据用的字节流对象
    OutputStream os = s.getOutputStream();

    //写入数据
    os.write("hehe".getBytes());

    //os.close();
    //关闭套接字
    s.close();
}

在这里插入图片描述

2TCP接收数据

  • 构造方法

    方法名说明
    ServletSocket(int port)创建绑定到指定端口的服务器套接字
  • 相关方法

    方法名说明
    Socket accept()监听要连接到此的套接字并接受它
  • 注意事项

    1. accept方法是阻塞的,作用就是等待客户端连接
    2. 客户端创建对象并连接服务器,此时是通过三次握手协议,保证跟服务器之间的连接
    3. 针对客户端来讲,一般是先往外写的,所以是输出流
      针对服务器来讲,一般是先往里读的,所以是输入流
    4. read方法也是阻塞的
    5. 客户端在关流的时候,还多了一个往服务器写结束标记的动作
    6. 最后一步断开连接,通过四次挥手协议保证连接终止
  • 三次握手和四次挥手

    • 三次握手

在这里插入图片描述

  • 四次挥手

在这里插入图片描述

  • 模拟场景
阿kun 和 阿花 是情侣,晚上放学后,阿kun回到男生宿舍,阿花回到女生宿舍

三次握手:
阿kun 给 阿花发信息:亲爱的,在忙吗,王者上线不?
阿花 给 阿kun   : 我不忙,确定上线吗?
阿kun 给  阿花: 走起,开黑了。

四次挥手:
阿kun 给 阿花发信息:亲爱的,队友太坑了,我受不了了,我想强退了,休息了,你也休息吧
阿花 给 阿kun :    我马上结束,还差2个人头,马上就0-10了
阿花 给 阿kun :    亲爱的,我搞定了,可以休息了
阿kun 给  阿花:    好的,88 ,明天见
  • 示例代码
public class ServerDemo {
    public static void main(String[] args) throws IOException {
        //创建服务器端的Socket对象(ServerSocket)
        //ServerSocket(int port) 创建绑定到指定端口的服务器套接字
        ServerSocket ss = new ServerSocket(15000);
        

        //这里等待客户端连接 会阻塞,一旦连接会返回一个处理此次连接的Socket对象
        Socket s = ss.accept();
        System.out.println("连接成功"+s.getRemoteSocketAddress());

        //获取输入流,读数据,并把数据显示在控制台
        InputStream is = s.getInputStream();
        byte[] bys = new byte[1024];
        int len = is.read(bys);
        String data = new String(bys,0,len);
        System.out.println("数据是:" + data);

        //释放资源
        s.close();
        
        ss.close();
    }
}

流程图

在这里插入图片描述

3TCP程序练习

  • 案例需求

    客户端:发送数据,接受服务器反馈

    服务器:收到消息后给出反馈

  • 案例分析

    • 客户端创建对象,使用输出流输出数据
    • 服务端创建对象,使用输入流接受数据
    • 服务端使用输出流给出反馈数据
    • 客户端使用输入流接受反馈数据
  • 代码实现

// 客户端
public class ClientDemo {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1",10000);

        OutputStream os = socket.getOutputStream();
        os.write("hello".getBytes());
       // os.close();如果在这里关流,会导致整个socket都无法使用
        socket.shutdownOutput();//仅仅关闭输出流.并写一个结束标记,对socket没有任何影响
        
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String line;
        while((line = br.readLine())!=null){
            System.out.println(line);
        }
        br.close();
        os.close();
        socket.close();
    }
}
// 服务器
public class ServerDemo {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(10000);
        Socket accept = ss.accept();

        InputStream is = accept.getInputStream();
        int b;
        while((b = is.read())!=-1){
            System.out.println((char) b);
        }

        System.out.println("看看我执行了吗?");

        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
        bw.write("你谁啊?");
        bw.newLine();
        bw.flush();

        bw.close();
        is.close();
        accept.close();
        ss.close();
    }
}

流程图

在这里插入图片描述

TCP程序文件上传练习

  • 案例需求

    客户端:数据来自于本地文件,接收服务器反馈

    服务器:接收到的数据写入本地文件,给出反馈

  • 案例分析

    • 创建客户端对象,创建输入流对象指向文件,每读一次数据就给服务器输出一次数据,输出结束后使用shutdownOutput()方法告知服务端传输结束,会给服务器一个结束标记
    • 创建服务器对象,创建输出流对象指向文件,每接受一次数据就使用输出流输出到文件中,传输结束后。使用输出流给客户端反馈信息
    • 客户端接受服务端的回馈信息
  • 相关方法

    名字描述
    void shutdownOutput()禁止用此套接字的输出流
  • 代码实现

// 客户端
public class ClientDemo {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1",10000);

        //是本地的流,用来读取本地文件的.
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("xxx/1.jpg"));

        //写到服务器 --- 网络中的流
        OutputStream os = socket.getOutputStream();
        BufferedOutputStream bos = new BufferedOutputStream(os);

        int b;
        while((b = bis.read())!=-1){
            bos.write(b);//通过网络写到服务器中
        }
        bos.flush();
        //给服务器一个结束标记,告诉服务器文件已经传输完毕
        socket.shutdownOutput();

        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String line;
        while((line = br.readLine()) !=null){
            System.out.println(line);
        }
        bis.close();
        socket.close();
    }
}
// 服务器
public class ServerDemo {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(10000);

        Socket accept = ss.accept();

        //网络中的流,从客户端读取数据的
        BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
        //本地的IO流,把数据写到本地中,实现永久化存储
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("socketmodule\\ServerDir\\copy.jpg"));

        int b;
        while((b = bis.read()) !=-1){
            bos.write(b);
        }

        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
        bw.write("上传成功");
        bw.newLine();
        bw.flush();

        bos.close();
        accept.close();
        ss.close();
    }
}

3.5TCP程序服务器优化

  • 优化方案一

    • 问题:

      服务器目前只能处理一个客户端请求,接收完一个文件之后,服务器就关闭了。

    • 解决方案

      使用循环

    • 代码实现

// 服务器代码如下,客户端代码同上个案例,此处不再给出
public class ServerDemo {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(10000);

        while (true) {
            Socket accept = ss.accept();

            //网络中的流,从客户端读取数据的
            BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
            //本地的IO流,把数据写到本地中,实现永久化存储
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("optimizeserver\\ServerDir\\copy.jpg"));

            int b;
            while((b = bis.read()) !=-1){
                bos.write(b);
            }

            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
            bw.write("上传成功");
            bw.newLine();
            bw.flush();

            bos.close();
            accept.close();
        }
        //ss.close();
        
    }
}
  • 优化方案二

    • 问题:

      第二次上传文件的时候,会把第一次的文件给覆盖。

    • 解决方案

      UUID. randomUUID()方法生成随机的文件名

    • 代码实现

// 服务器代码如下,客户端代码同上个案例,此处不再给出
public class ServerDemo {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(10000);

        while (true) {
            Socket accept = ss.accept();

            //网络中的流,从客户端读取数据的
            BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
            //本地的IO流,把数据写到本地中,实现永久化存储
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("optimizeserver\\ServerDir\\" + UUID.randomUUID().toString() + ".jpg"));

            int b;
            while((b = bis.read()) !=-1){
                bos.write(b);
            }

            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
            bw.write("上传成功");
            bw.newLine();
            bw.flush();

            bos.close();
            accept.close();
        }
        //ss.close();

    }
}
  • 优化方案三

    • 问题

      使用循环虽然可以让服务器处理多个客户端请求。但是还是无法同时跟多个客户端进行通信。

    • 解决方案

      开启多线程处理

    • 代码实现

// 线程任务类
public class ThreadSocket implements Runnable {
    private Socket acceptSocket;

    public ThreadSocket(Socket accept) {
        this.acceptSocket = accept;
    }
  
    @Override
    public void run() {
        BufferedOutputStream bos = null;
        try {
            //网络中的流,从客户端读取数据的
            BufferedInputStream bis = new BufferedInputStream(acceptSocket.getInputStream());
            //本地的IO流,把数据写到本地中,实现永久化存储
            bos = new BufferedOutputStream(new FileOutputStream("optimizeserver\\ServerDir\\" + UUID.randomUUID().toString() + ".jpg"));

            int b;
            while((b = bis.read()) !=-1){
                bos.write(b);
            }
          
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(acceptSocket.getOutputStream()));
            bw.write("上传成功");
            bw.newLine();
            bw.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(bos != null){
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if (acceptSocket != null){
                try {
                    acceptSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
// 服务器代码
public class ServerDemo {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(10000);

        while (true) {
            Socket accept = ss.accept();
            ThreadSocket ts = new ThreadSocket(accept);
            new Thread(ts).start();
        }
        //ss.close();
    }
}
  • 优化方案四

    • 需求

      使用多线程虽然可以让服务器同时处理多个客户端请求。但是资源消耗太大。

    • 解决方案

      加入线程池

    • 代码实现

// 服务器代码如下,线程任务类代码同上,此处不再给出
public class ServerDemo {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(10000);
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                3,//核心线程数量
                10,   //线程池的总数量
                60,   //临时线程空闲时间
                TimeUnit.SECONDS, //临时线程空闲时间的单位
                new ArrayBlockingQueue<>(5),//阻塞队列
                Executors.defaultThreadFactory(),//创建线程的方式
                new ThreadPoolExecutor.AbortPolicy()//任务拒绝策略
        );

        while (true) {
            Socket accept = ss.accept();
            ThreadSocket ts = new ThreadSocket(accept);
            //new Thread(ts).start();
            pool.submit(ts);
        }
        //ss.close();
    }
}

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

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

相关文章

python 办公自动后,第一天:创建excel写入数据

1&#xff0c;python第三方模块&#xff1a;xlwt写入excel文件 #pycharm 中安装pip install xlwt #xlwr 用户写入文件模块 #pip install xlwt # pip list 查看安装的模块 # pip install import xlwt#创建excelwb xlwt.Workbook()# 选择工作簿sh wb.add_sheet(第一个sheet页…

LVGL学习笔记11 - 按钮矩阵Button Matrix

目录 1. 构造矩阵 2. Parts 2.1 LV_PART_MAIN 2.2 LV_PART_ITEMS 3. 样式 3.1 按钮大小 3.2 间距 3.3 控制按钮 3.4 控制map 3.5 Check唯一性 4. 事件 4.1 LV_EVENT_VALUE_CHANGED 4.2 LV_EVENT_LONG_PRESSED 4.3 LV_EVENT_DRAW_PART_BEGIN 按钮矩阵是多个按钮的组…

臻图信息搭建土壤墒情监测应用平台,推进现代农业信息化发展

“十四五”期间&#xff0c;国家高度重视土壤生态环境监测等问题&#xff0c;先后出台了一系列规范性文件&#xff0c;为我国土壤防治领域建立了一套标准、导则、指南和管理等解决对策&#xff0c;使我国土壤修复法律法规和标准化建设呈现出系列化和系统化趋势&#xff0c;有效…

计算机视觉实战-----pytorch官方demo(Lenet网络)实现

系列文章目录 文章目录系列文章目录前言零、环境搭建一、下载CIFAR10数据集二、测试图片三、模型搭建四、开始train五、测试六、tensorboard可视化总结前言 通过一个官方列子&#xff0c;清楚深度学习中图像的训练的整个流程 零、环境搭建 pycharm下载&#xff1a;pycharm官网…

【C语言】算法好题初阶(每日小细节010)

1.存在重复元素 力扣传送门、 这道题目的解题思路就是先排序然后比较相邻元素是不是有相等的&#xff0c;有就是true否者false 排序的算法比较多大&#xff0c;但是我用插入和快排plus版都没有过... 但是非递归的归并过了&#xff0c;对排序算法感兴趣的小伙伴可以去看我的博…

一款统计摸鱼时长的开源项目

对于我们程序员&#xff0c;在工作中一天8小时&#xff0c;不可能完全在写代码了&#xff0c;累了刷刷论坛、群里吹吹牛&#xff0c;这都是非常正常的。虽然一天下来&#xff0c;可能我们都可以按时完成工作&#xff0c;但是我们不知道&#xff0c;时间都花在哪里了&#xff0c…

小米万兆路由器里的Docker安装MySQL8.0

小米2022年12月份发布了万兆路由器&#xff0c;里面可以使用Docker。 今天尝试在小米的万兆路由器里安装MySQL8.0。 准备工作 请参考https://engchina.blog.csdn.net/article/details/128515422的准备工作。 创建存储 请参考https://engchina.blog.csdn.net/article/detail…

Faster RCNN网络源码解读(Ⅹ) --- FastRCNN部分正负样本采样及FastRCNN部分损失计算

目录 一、回顾以及本篇博客内容概述 二、代码解析 2.1 ROIHeads类&#xff08;承接上篇博客的2.5节&#xff09; 2.1.1 初始化函数 __init__回顾 2.1.2 正向传播forward回顾 2.1.3 select_training_samples 2.1.4 add_gt_proposals 2.1.5 assign_targets_to_proposals…

【Git】Git瘦身,清理Git历史提交/.git大文件清理(云效、UI 自动化项目)

目前项目是存在云效(codeup.aliyun.com)上 本地清理后&#xff0c;还需要到云效上清理「存储空间管理」 一、清理/瘦身效果二、到底是什么在占空间&#xff1f;1、先看一下项目里&#xff0c;什么最占空间&#xff1f;2、往下看在/.git里&#xff0c;什么最占空间&#xff1f;三…

车载诊断协议UDS——读取故障服务Service 19

汽车控制器诊断功能,可以通过诊断服务读取车内控制器故障信息。如本文所分享的内容,通过Service 19服务读取车内控制器故障信息。 一、DTC显示类型 在OEM定义的诊断需求规范中,会定义DTC(诊断故障码)与具体控制器具体故障类型相关联(一个DTC故障码对应一个具体故障)…

深度学习目标检测_YOLOV2超详细解读

文章目录YOLO v2概述Batch Normalization&#xff08;批归一化&#xff09;High Resolution Classifier&#xff08;高分辨率预训练分类网络&#xff09;New Network&#xff1a;Darknet-19神经网络中的filter &#xff08;滤波器&#xff09;与kernel&#xff08;内核&#xf…

【Java语言】— 循环结构:while循环、do-while循环

while循环 1.while循环格式与执行流程 while循环格式 初始化语句; while (循环条件){循环体语句(被重复执行的代码);迭代语句; }示例&#xff1a; inti 0; while (i < 3){System.out.println("Hello World");i; }while循环执行流程 什么时候用for循环&#x…

蓝桥杯寒假集训第五天(子串分值和)

没有白走的路&#xff0c;每一步都算数&#x1f388;&#x1f388;&#x1f388; 题目描述&#xff1a; 输入一个字符串&#xff0c;然后计算所有连续子串中没有重复字母的个数 输入描述&#xff1a; 第一行&#xff1a; 一个字符串 输出描述&#xff1a; 所有子串中没有…

软件设计模式---结构型模式

结构型模式 结构型模式概述 结构型模式描述如何将类或者对象结合在一起形成更大的结构&#xff0c;就像搭积木&#xff0c;可以通过简单积木组合形成复杂的、功能更更为强大的结构 结构型模式可以分为类结构型模式和对象结构型模式 类结构型模式关心类的组合&#xff0c;由多…

1、Java多线程技能基础

文章目录第一章 Java多线程技能1.1进程和线程的定义以及多线程的优点1.2 使用多线程1.2.1继承Thread类1.2.2常见的3个命令分析线程的信息方法一\:cmdjsp方法二\:jmc.exe方法三&#xff1a;jvisualcm.exe1.2.3 线程随机性的展现1.2.4 执行start()的顺序不代表执行run()的顺序1…

hcip第五天实验

拓扑图 每台路由器都有两个环回&#xff0c;一个24的环回&#xff0c;一个32的环回&#xff1b;32的环回用于建邻&#xff0c;24的环回用于用户网段&#xff0c;最终所有24的环回可以ping通。 实验步骤 1.配置ip 2.让2,3,4号设备的IGP协议可以通信 3.两两之间建立BGP邻居关…

DR_CAN基尔霍夫电路题解法【自留用】

无目录如图所示电路&#xff0c;输入端电压eie_iei​&#xff0c;输出端电压eoe_oeo​&#xff0c;求二者之间关系。 对其中元件进行标号&#xff0c;并将电流环路标号&#xff0c;指出各元件的压降方向&#xff1a; v值得注意的是&#xff1a; 1&#xff09;电阻R2R_2R2​同时…

rabbitmq消息发送的可靠性:结合mysql来保证消息投递的可靠性

消息从生产者到Broker&#xff0c;则会触发confirmCallBack回调消息从exchange到Queue&#xff0c;投递失败则会调用returnCallBack 用一张表来记录发送到mq的每一条消息&#xff0c;方便发送失败需要重试。status&#xff1a; 1-正常&#xff0c;0-重试&#xff0c;2-失败。发…

【计算机视觉】OpenCV 4高级编程与项目实战(Python版)【1】:图像处理基础

目录 1. OpenCV简介 2. OpenCV开发环境搭建 3. 读取图像 4. 读取png文件出现警告 5. 显示图像 6. 保存图像 7. 获取图像属性 本系列文章会深入讲解OpenCV 4&#xff08;Python版&#xff09;的核心技术&#xff0c;并提供了大量的实战案例。这是本系列文章的第一篇&…

简单了解计算机的工作原理

文章目录一.计算机操作系统二.进程/任务三、进程控制块抽象(PCB)四、进程调度相关属性五、内存管理一.计算机操作系统 概念:操作系统是一组做计算机资源管理的软件的统称. 目前常见的操作系统有&#xff1a;Windows系列、Unix系列、Linux系列、OSX系列、Android系列、iOS系列…