Java-网络编程基础

目录

一、网络编程

1.网络编程概述

2.电脑之间数据传输需要的条件(网络编程三要素)

(1)IP

(2)端口

(3)协议

①UDP协议

②TCP协议

3.常用命令

(1)IP相关

4.InetAddress类

5.UDP通信程序

(1)发送数据

(2)接收数据

(3)练习

(4)UDP的三种通信方式

(5)组播代码实现

①.组播发送

②组播接收(主要写区别了,大体一致)

(6)UDP广播代码实现

6.TCP通信程序

(1)发送数据

(2)接收数据

(3)三次握手

(4)四次挥手

(5)练习

(6)练习02

(7)服务端优化

①可多次接收客户端

②UUID类

③多线程版

⑤多线程版优化-线程池

(8)问题【待解决】


一、网络编程

1.网络编程概述

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

2.电脑之间数据传输需要的条件(网络编程三要素)

  • 确认接收端在网络中的位置-------------------------------------------------IP地址(设备在网络中的地址,是唯一的标识)

  • 确定接收端中某接收输数据软件的入口----------------------------------端口号(应用程序在设备中唯一的标识)

  • 确定网络中传输数据的规则--------------------------------------------------协议(数据在网络中传输的规则,常见的协议有UDP协议和TCP协议)

(1)IP

全称”互联网协议地址,也称IP地址。是分配给上网设备的数字标签。常见的IP分类为:ipv4和ipv6,

  • ipv4:32bit----------------------------->11000000 10101000 00000001 01000010-------------------------->为了好读使用点分十进制表示法------------------->192.168.1.66

  • ipv6:由于互联网的蓬勃发展,IP地址的需求量愈来愈大,而IPv4的模式下IP的总数是有限的。采用128位地址长度,分成8组------------>位数太多不表示了---------> 为了好读使用冒分十六进制表示法----------------------->2001:0DB8:0000:0023:0008:0800:200C:417A----------------->省略前面的0------------>2001:DB8:0:23:8:800:200C:417A

ipv6特殊情况:如果计算出的16进制表示形式中间有多个连续的0------------------>FF01:0:0:0:0:0:0:1101----------------------->0位压缩表示法---------->FF01::1101

(2)端口

应用程序在设备中唯一的标识

端口号:用两个字节表示的整数,它的取值范围是0~65535,其中0~1023之间的端口号用于一些知名的网络服务或者应用,我们自己使用1024以上的端口号就可以了

注意:一个端口号只能被一个应用程序使用.

(3)协议

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

①UDP协议
  • 用户数据报协议(User Datagram Protocol)

  • 面向无连接通信协议

特点

  • 速度快

  • 有大小限制,一次最多发送64K

  • 不安全,易丢失数据

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

  • TCP协议是面向连接的通信协议。

特点

  • 速度慢

  • 没有大小限制

  • 数据安全

3.常用命令

(1)IP相关
  • ipconfig:查看本机IP地址

  • ping IP地址:检查网络是否通畅

特殊IP地址127.0.0.1

是回送地址也称本地回环地址,可以代表本机的IP地址,一般用来测试使用

4.InetAddress类

为了方便我们对IP地址的获取和操作,Java提供了一个类InetAddress供我们使用

主要方法

方法名说明
static InetAddress getByName(String host)根据提供的主机名和IP地址创建InetAddress
String getHostName()获取此IP地址的主机名
String getHostAddress()返回IP地址字符串
public class InetAddressDemo {
    public static void main(String[] args) throws UnknownHostException {
​
        //获取InetAddress对象,可以传递主机名或IP地址
        InetAddress address = InetAddress.getByName("DESKTOP-03DC35L");
​
        //获得主机名
        final String hostName = address.getHostName();
        //获得IP地址
        final String hostAddress = address.getHostAddress();
        System.out.println(hostName);
        System.out.println(hostAddress);
    }
}

5.UDP通信程序

(1)发送数据
  • 找发送端------------------------创建发送端的DatagramSocket对象

  • 封装信息------------------------创建数据,并把数据打包(DatagramPacket)

  • 发送端发送信息---------------调用DatagramSocket对象的方法发送数据

  • 关闭发送端----------------------释放资源

代码实现:

public class SendingClient {
    public static void main(String[] args) throws IOException {
        //创建发送端
        DatagramSocket datagramSocket = new DatagramSocket();
​
        byte[] bytes = "发送消息".getBytes(StandardCharsets.UTF_8);
        InetAddress inetAddress = InetAddress.getByName("127.0.0.1");
        int port = 10000;
        //打包消息
        DatagramPacket datagramPacket = datagramPacket(bytes, bytes.length, inetAddress, port);
​
        //发送消息
        datagramSocket.send(datagramPacket);
​
        //释放资源
        datagramSocket.close();
​
​
    }
​
    /**
     * 消息打包
     * @param bytes 数据
     * @param length 数据大小,全部则为bytes.length
     * @param inetAddress 接收端
     * @param port 接收端的端口号
     */
    private static DatagramPacket datagramPacket(byte[] bytes, int length, InetAddress inetAddress,int port) {
        //打包消息
        return new DatagramPacket(bytes,length,inetAddress,port);;
    }
}
(2)接收数据
  • 找接收端----------------------------------------------------------------创建接收端的DatagramSocket对象

  • 创建一个容器,用来封装接收的数据--------------------------创建容器,用于接收数据

  • 接收数据,并将数据放入到上一步创建的容器中----------调用DatagramSocket的方法接收数据并将数据放入箱子中

  • 从容器中获取数据---------------------------------------------------解析数据

  • 关闭接收端------------------------------------------------------------释放资源

代码实现:

public class ReceivingClient {
    public static void main(String[] args) throws IOException {
        //创建接收端,传入端口号,
        //无参数则为从随机端口接收数据
        DatagramSocket receive = new DatagramSocket(10000);
        //创建新的容器,用于接收数据
        //DatagramPacket(byte[] buf, int length)
        byte[] bytes = new byte[1024];
        DatagramPacket datagramPacket = getDatagramPacket(bytes);;
​
        //接收数据
        receive.receive(datagramPacket);
​
        //获得数据并解析
        byte[] data = datagramPacket.getData();
        int length = datagramPacket.getLength();
        System.out.println(new String(data,0,length));
​
        //释放资源
        receive.close();
    }
​
    private static DatagramPacket getDatagramPacket(byte[] bytes){
        return new DatagramPacket(bytes,bytes.length);
    }
​
​
}
(3)练习

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

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

发送端:

public class SendClient {
    public static void main(String[] args) throws IOException {
        //固定的数据放上面
        //2.4 接收端ip地址
        InetAddress inetAddress = InetAddress.getByName("127.0.0.1");
        //2.5 接收端端口号
        int port = 10001;
​
        //面向对象化
        //1.创建发送对象
        DatagramSocket send = createSendClient();
​
        //2.数据准备
        //2.1 创建Scanner对象
        Scanner scanner = createScanner();
​
        while (true){
            //2.2 输入要发送的消息
            String message = message(scanner);
            //2.3 数据处理str转byte[]
            byte[] bytes = strToBytes(message);
            //4.封装数据
            //4.1 创建封装对象
            DatagramPacket dataPacket = createDataPacket(bytes, bytes.length, inetAddress, port);
            //5.发送数据
            send.send(dataPacket);
​
            if ("886".equals(message)){
                break;
            }
        }
​
        //6.释放资源
        send.close();
        System.out.println("发送端已关闭");
​
​
​
​
    }
​
    private static DatagramPacket createDataPacket(byte[] bytes,int length,InetAddress inetAddress,int port) {
        return new DatagramPacket(bytes,bytes.length,inetAddress,port);
    }
​
    private static Scanner createScanner() {
        return new Scanner(System.in);
    }
​
    private static DatagramSocket createSendClient() throws SocketException {
        //创建发送端
        DatagramSocket send = new DatagramSocket();
        return send;
    }
​
    private static String message(Scanner scanner){
        System.out.println("请输入要发送的信息");
        String s = scanner.nextLine();
        return s;
    }
​
    private static byte[] strToBytes(String message){
        return message.getBytes();
    }
​
​
}

接收端:

public class ReceivingClient {
    public static void main(String[] args) throws IOException {
        //准备接收端对象,设置端口号
        DatagramSocket receiveClient = createReceivedClient(10001);
​
        //准备新容器,用来接收数据
        //1.接收数据大小
        DatagramPacket dataBox = getDataBox();
​
        while (true){
            //接收数据
            receiveClient.receive(dataBox);
            //从容器中拿数据,输出到控制台
            byte[] data = dataBox.getData();
            //实际接受到的数据大小
            int length = dataBox.getLength();
​
            if ("886".equals(new String(data,0,length))){
                System.out.println("发送端不再发送消息,接收端准备关闭");
                break;
            }else {
                System.out.println("实际接收到的数据大小:"+length);
                System.out.println(new String(data,0,length));
            }
        }
​
​
​
​
        //关闭接收端
        receiveClient.close();
        System.out.println("接收端已关闭");
    }
​
    private static DatagramPacket getDataBox() {
        byte[] bytes = new byte[1024];
        DatagramPacket dataBox = new DatagramPacket(bytes,bytes.length);
        return dataBox;
    }
​
    private static DatagramSocket createReceivedClient(int port) throws SocketException {
        DatagramSocket receiveClient = new DatagramSocket(port);
        return receiveClient;
    }
}
(4)UDP的三种通信方式
  • 单播----------------------------------1对1,单发送单接收

  • 组播(ipv4)----------------------1对多,单发送对一组接收

    多播(ipv6)

  • 广播----------------------------------1对所有,单发送对所有接收

(5)组播代码实现

组播地址:224.0.0.0~239.255.25.255

其中224.0.0.0~224.0.0.255为预留的组播地址

①.组播发送

和上边发送数据步骤基本一致,但发送数据略有不同

在单播中,是发给指定IP的电脑,但是在组播当中,是发给组播地址

public class SendClient {
    public static void main(String[] args) throws IOException {
        //创建对象
        DatagramSocket datagramSocket = new DatagramSocket();
        //数据准备
        String s = "组播测试";
        byte[] bytes = s.getBytes();
        InetAddress inetAddress = InetAddress.getByName("224.0.1.0");
        //封装数据
        DatagramPacket datagramPacket = new DatagramPacket(bytes,bytes.length,inetAddress,10002);
​
        datagramSocket.send(datagramPacket);
​
        datagramSocket.close();
    }
}
②组播接收(主要写区别了,大体一致)
  • 组播接收端创建的是new MulticastSocket(10086);

  • 在创建容器和接收数据之间,添加一步:把当前电脑添加到这个组中

public class ReserveClient {
    public static void main(String[] args) throws IOException {
        MulticastSocket multicastSocket = new MulticastSocket(10002);
​
        byte[] bytes = new byte[1024];
        DatagramPacket packet = new DatagramPacket(bytes,bytes.length);
​
        //添加电脑到组中
        multicastSocket.joinGroup(InetAddress.getByName("224.0.1.0"));
​
        multicastSocket.receive(packet);
​
        byte[] data = packet.getData();
        int length = packet.getLength();
        System.out.println(new String(data,0,length));
​
        multicastSocket.close();
    }
}
(6)UDP广播代码实现

发送

  • 在单播中,是发给指定IP的电脑,但是在广播当中,是发给广播地址

  • 广播地址:255.255.255.255

public class SendClient {
    public static void main(String[] args) throws IOException {
        DatagramSocket datagramSocket = new DatagramSocket();
        String message = "广播测试";
        final byte[] bytes = message.getBytes();
        InetAddress inetAddress = InetAddress.getByName("255.255.255.255");
        DatagramPacket datagramPacket = new DatagramPacket(bytes,bytes.length,inetAddress,10002);
        datagramSocket.send(datagramPacket);
​
        datagramSocket.close();
    }
}

接收

  • 跟接收一模一样,啥都不用改

6.TCP通信程序

TCP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket对象。通信之前要保证连接已经建立

通过Socket产生IO流来进行网络通信

(1)发送数据
  • 创建客户端的socket对象Socket(String host,int port),与指定服务端连接

  • 获得输出流,写数据

  • 释放资源

public class SendClient {
    public static void main(String[] args) throws IOException {
        //创建一个Socket对象,两个参数
        //服务器ip地址
        //端口号
        Socket socket = new Socket("127.0.0.1",10003);
​
        //获取输出流,写数据
        OutputStream outputStream = socket.getOutputStream();
        OutputStreamWriter osw = new OutputStreamWriter(outputStream);
​
        //写数据
        String s = "tcp发送消息";
        osw.write(s);
        //释放资源
        socket.close();
    }
}
(2)接收数据
  • 创建服务端的socket对象ServerSocket(int port)

  • 监听客户端连接,返回一个socket对象,accept()等待连接,死等(阻塞)

  • 获得输入流,读取数据,并把数据显示或解析

  • 释放资源

public class ServiceClient {
    public static void main(String[] args) throws IOException {
        //创建服务端的socket对象ServerSocket
        ServerSocket serverSocket = new ServerSocket(10003);
        //等待客户端连接,连接后会返回一个socket对象
        Socket accept = serverSocket.accept();
​
        //获取输入流,读取数据
        InputStream inputStream = accept.getInputStream();
        InputStreamReader isr = new InputStreamReader(inputStream);
​
        int n;
        while ((n = isr.read()) != -1){
            System.out.print((char) n);
        }
        
        //释放资源
        isr.close();
        serverSocket.close();
​
​
​
    }
}

注意点:

  • accept()方法是阻塞的,作用就是等待客户端连接

  • 客户端创建对象运行后会连接服务器,如果服务器没有启动则会报ConnectException异常

  • 与服务器连接是通过三次握手协议保证的

  • 对客户端来讲,是往外写的,所以是输出流

  • 对服务端来讲,是往里读的,所以是输入流

  • IO流中read()方法也是阻塞的

  • 客户端在关流的时候,还有一个往服务器写结束标记符的动作

  • 最后与服务端断开连接,是通过四次挥手协议保证连接终止的

(3)三次握手
  • 第一次:客户端向服务器发出连接请求,等待服务器确认

  • 第二次:服务器向客户端返回一个响应,告诉客户端收到了请求

  • 第三次:客户端再次发出请求确认信息,连接建立

(4)四次挥手
  • 第一次:客户端向服务端发出取消连接的请求

  • 第二次:服务器向客户端返回一个响应,表示收到客户端请求

  • 第三次:服务器会将最后的数据进行处理,处理完成后服务器会向客户端发出确认取消的消息

  • 第四次:客户端再次发送确认取消连接的消息,服务器取消连接

(5)练习

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

服务器:接收数据,给出反馈

public class SendClient {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1",10005);
​
        OutputStream outputStream = socket.getOutputStream();
        OutputStreamWriter osw = new OutputStreamWriter(outputStream);
​
        osw.write("客户端请求:hello world");
​
        //需要写一个结束标记
        //注意:不能把关流操作放到这,关流完成后会导致整个socket关闭
        //shutdownOutput() 仅仅关闭输出流,并写一个结束标记
        osw.flush();
        socket.shutdownOutput();
​
        InputStreamReader isr = new InputStreamReader(socket.getInputStream());
        int n;
        while ((n = isr.read()) != -1){
            System.out.print((char) n);
        }
​
​
​
        isr.close();
        osw.close();
        socket.close();
​
    }
}
public class ServerClient {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(10005);
​
        Socket accept = serverSocket.accept();
​
        InputStream inputStream = accept.getInputStream();
        InputStreamReader isr = new InputStreamReader(inputStream);
        int n;
        while ((n = isr.read()) != -1){
            System.out.print((char) n);
        }
​
        OutputStream outputStream = accept.getOutputStream();
        outputStream.write("服务端响应:你是谁?".getBytes());
​
​
        outputStream.close();
        isr.close();
        accept.close();
        serverSocket.close();
        
    }
    
}
(6)练习02

客户端:将本地文件上传到服务器。接收服务器的反馈

服务器:接收客户端上传的文件并保存,保存完毕之后给出反馈

public class SendClient {
    public static void main(String[] args) throws IOException {
​
        //创建socket对象
        //传入地址和端口号
        Socket socket = new Socket("127.0.0.1",10005);
​
        //优化输入流的变量
        byte[] bytes = new byte[1024];
        int length;
​
        //创建一个File对象,获取文件名和文件路径
        File file = new File("src\\tcp\\socket_tcp03_2\\music");
        //创建一个ArrayList数组,准备保存文件名
        ArrayList<String> fileNameList = new ArrayList<>();
        //查看该目录下所有文件
        File[] files = file.listFiles();
        if (files.length != 0){
            for (File listFile : files) {
                fileNameList.add(listFile.getName());
            }
        }
​
        //创建一个字节输入流,准备从本地文件中读取一个作为数据写出
        //传入文件路径
        FileInputStream fis = new FileInputStream(new File(file, fileNameList.get(0)));
        //优化输入流
        BufferedInputStream bis = new BufferedInputStream(fis);
        //创建一个集合,用来保存读取到的数据
        ArrayList<byte[]> dataList = new ArrayList<>();
​
​
        while ((length = bis.read(bytes)) != -1){
            dataList.add(Arrays.copyOfRange(bytes,0,length));
        }
​
​
        //获取输出流,准备写出数据
        OutputStream os = socket.getOutputStream();
        BufferedOutputStream bos = new BufferedOutputStream(os);
        for (byte[] bytes1 : dataList) {
            bos.write(bytes1);
            bos.flush();
        }
        //不传输数据后,单独关闭socket的输出流
        socket.shutdownOutput();
​
        //获取输入流,准备接收服务端的反馈
        InputStream is = socket.getInputStream();
        bis = new BufferedInputStream(is);
        while ((length = bis.read(bytes))!= -1){
            System.out.print(new String(bytes,0,length));
        }
        
        socket.close();
​
    }
​
}

Server类,用来实现服务端的具体操作

public class Server {
​
    /**
     * 遍历数组并写出
     * @param listBytes 保存数据的集合
     * @param osw 输出流
     * @throws IOException 会抛出IO异常
     */
    public  void traverseAndWrite(ArrayList<byte[]> listBytes, BufferedOutputStream osw) throws IOException {
        for (byte[] listByte : listBytes) {
            osw.write(listByte);
        }
    }
​
    /**
     * 输出流优化,缓冲字节输出流
     * @param fos 需要优化的字节流
     * @return 缓冲字节输出流对象
     */
    public  BufferedOutputStream getBufferedOutputStream(FileOutputStream fos) {
        return new BufferedOutputStream(fos);
    }
​
    /**
     * 创建输出流
     * @param file 要写出的文件(路径+文件名)
     * @return 返回字节输出流对象
     * @throws FileNotFoundException 抛出FileNotFoundException异常
     */
    public  FileOutputStream getFileOutputStream(File file) throws FileNotFoundException {
        return new FileOutputStream(file);
    }
​
    /**
     * 把内容写入到哪
     * @param folder 目录(目标文件夹)
     * @param fileName 文件名(目标文件名)
     * @param nameSuffix 文件后缀名(格式.jpg/.mp3等)
     * @return 返回File对象
     */
    public  File getFile(File folder, String fileName,String nameSuffix) {
        return new File(folder, fileName +nameSuffix);
    }
​
    /**
     * 获取唯一文件名
     * @return 返回唯一文件名(String)
     */
    public  String getFileName() {
        return UUID.randomUUID().toString().replace("-","");
    }
​
    /**
     * 检查目标文件夹是否存在
     * @param folder 目标文件夹
     */
    public  void inspectFolder(File folder) {
        if (!folder.exists()){
            if (folder.mkdir()){
                System.out.println("文件夹创建成功");
            }
        }
    }
​
    /**
     * 设置写出路径
     * @param path 目标位置
     * @return  返回File对象
     */
    public  File getFolder(String path) {
        return new File(path);
    }
​
    /**
     * 读取数据
     * @param bis 缓冲字节输入流对象
     * @param listBytes 存储数据的集合
     * @throws IOException 会抛出IOException
     */
    public  void readData(BufferedInputStream bis, ArrayList<byte[]> listBytes) throws IOException {
        byte[] bytes = new byte[1024];
        int length;
        while ((length = bis.read(bytes)) != -1){
            listBytes.add(Arrays.copyOfRange(bytes,0,length));
        }
    }
​
    /**
     * 创建集合
     * @return ArrayList<byte[]>
     */
    public  ArrayList<byte[]> getArrayList() {
        return new ArrayList<>();
    }
​
    /**
     * 优化输入流
     * @param inputStream 字节输入流
     * @return 返回缓冲字节输入流对象
     */
    public  BufferedInputStream getBufferedInputStream(InputStream inputStream) {
        return new BufferedInputStream(inputStream);
    }
​
    /**
     * 获取服务端Socket的输入流
     * @param serverSocket 服务端的socket对象
     * @return 返回服务端Socket的字节输入流对象
     * @throws IOException 会抛出IOException
     */
    public  InputStream getInputStream(Socket serverSocket) throws IOException {
        return serverSocket.getInputStream();
    }
​
    /**
     * 获取服务端Socket对象
     * @param ss ServerSocket
     * @return 返回服务端的socket对象
     * @throws IOException 会抛出IOException
     */
    public Socket getServerSocket(ServerSocket ss) throws IOException {
        return ss.accept();
    }
}

服务端:

public class ServerClient {
    public static void main(String[] args) throws IOException {
​
        Server server = new Server();
​
        //创建ServerSocket对象,设置好端口号
        ServerSocket ss = new ServerSocket(10005);
​
​
        //获取服务端的socket对象
        Socket serverSocket = server.getServerSocket(ss);
​
        //获取输入流,准备读取数据
        InputStream inputStream = server.getInputStream(serverSocket);
​
        //输入流优化
        BufferedInputStream bis = server.getBufferedInputStream(inputStream);
​
        //创建一个集合(可选)保存接收到的数据
        ArrayList<byte[]> listBytes = server.getArrayList();
​
        //读取数据并保存到listBytes集合中
        server.readData(bis, listBytes);
​
        //设置写出路径
        String s = "src\\tcp\\socket_tcp03_2\\serverClient_file";
        File folder = server.getFolder(s);
​
        //检查文件夹是否存在
        server.inspectFolder(folder);
​
        //获取唯一文件名
        final String fileName = server.getFileName();
​
        //设置文件后缀名
        String nameSuffix = ".mp3";
​
        //在设置好的路径下创建名为【fileName】的空文件
        File file = server.getFile(folder, fileName,nameSuffix);
​
        //创建输出流,准备写出数据
        FileOutputStream fos = server.getFileOutputStream(file);
        //优化输出流
        BufferedOutputStream osw = server.getBufferedOutputStream(fos);
        //遍历集合并写出数据
        server.traverseAndWrite(listBytes, osw);
​
        //获取输出流,准备给反馈
        OutputStream os = serverSocket.getOutputStream();
        //优化
        BufferedOutputStream bos = new BufferedOutputStream(os);
        //写反馈
        bos.write("保存成功".getBytes());
        bos.flush();
​
​
        //释放资源
        serverSocket.close();
        ss.close();
​
    }
}

(7)服务端优化
①可多次接收客户端

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

while (true){
            //获取服务端的socket对象
            Socket serverSocket = getServerSocket(ss);
​
            //获取输入流,准备读取数据
            InputStream inputStream = getInputStream(serverSocket);
​
            //输入流优化
            BufferedInputStream bis = getBufferedInputStream(inputStream);
​
            //创建一个集合(可选)保存接收到的数据
            ArrayList<byte[]> listBytes = getArrayList();
​
            //读取数据并保存到listBytes集合中
            readData(bis, listBytes);
​
            //设置写出路径
            String s = "src\\tcp\\socket_tcp03\\serverClient_file";
            File folder = getFolder(s);
​
            //检查文件夹是否存在
            inspectFolder(folder);
​
            //获取唯一文件名
            final String fileName = getFileName();
​
            //设置文件后缀名
            String nameSuffix = ".mp3";
​
            //在设置好的路径下创建名为【fileName】的空文件
            File file = getFile(folder, fileName,nameSuffix);
​
            //创建输出流,准备写出数据
            FileOutputStream fos = getFileOutputStream(file);
            //优化输出流
            BufferedOutputStream osw = getBufferedOutputStream(fos);
            //遍历集合并写出数据
            traverseAndWrite(listBytes, osw);
​
            //获取输出流,准备给反馈
            OutputStream os = serverSocket.getOutputStream();
            //优化
            BufferedOutputStream bos = new BufferedOutputStream(os);
            //写反馈
            bos.write("保存成功".getBytes());
​
​
            //释放资源
            serverSocket.close();
        }

②UUID类

主要方法

方法名说明
static UUID randomUUID()静态工厂检索一个类型4《伪随机生成》的UUID。
String toString()返回表示此 UUIDString对象
③多线程版

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

public class ServerClient {
    public static void main(String[] args) throws IOException {
        //创建ServerSocket对象,设置好端口号
        ServerSocket ss = new ServerSocket(10005);
         while (true){
            Socket accept = ss.accept();
            ThreadSocket threadSocket = new ThreadSocket(accept);
            new Thread(threadSocket).start();
        }
    }
}
public class ThreadSocket implements Runnable{
    Socket socket;
​
    public ThreadSocket(Socket socket) {
        this.socket = socket;
    }
​
    @Override
    public void run() {
            //获取输入流,准备读取数据
            InputStream inputStream = null;
            try {
                inputStream = getInputStream(socket);
            } catch (IOException e) {
                e.printStackTrace();
            }
​
            //输入流优化
            BufferedInputStream bis = getBufferedInputStream(inputStream);
​
            //创建一个集合(可选)保存接收到的数据
            ArrayList<byte[]> listBytes = getArrayList();
​
            //读取数据并保存到listBytes集合中
            try {
                readData(bis, listBytes);
            } catch (IOException e) {
                e.printStackTrace();
            }
​
            //设置写出路径
            String s = "src\\tcp\\socket_tcp04\\serverClient_file";
            File folder = getFolder(s);
​
            //检查文件夹是否存在
            inspectFolder(folder);
​
            //获取唯一文件名
            final String fileName = getFileName();
​
            //设置文件后缀名
            String nameSuffix = ".mp3";
​
            //在设置好的路径下创建名为【fileName】的空文件
            File file = getFile(folder, fileName,nameSuffix);
​
            //创建输出流,准备写出数据
            FileOutputStream fos = null;
            try {
                fos = getFileOutputStream(file);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
            //优化输出流
            BufferedOutputStream osw = getBufferedOutputStream(fos);
            //遍历集合并写出数据
            try {
                traverseAndWrite(listBytes, osw);
            } catch (IOException e) {
                e.printStackTrace();
            }
​
            //获取输出流,准备给反馈
            OutputStream os = null;
            try {
                os = socket.getOutputStream();
            } catch (IOException e) {
                e.printStackTrace();
            }
            //优化
            BufferedOutputStream bos = null;
            if (os != null){
                bos = new BufferedOutputStream(os);
            }
​
            //写反馈
            try {
                if (bos!=null){
                    bos.write("保存成功".getBytes());
                    bos.flush();
                }
​
            } catch (IOException e) {
                e.printStackTrace();
            }
​
​
            //释放资源
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
​
    }
​
    /**
     * 遍历数组并写出
     * @param listBytes 保存数据的集合
     * @param osw 输出流
     * @throws IOException 会抛出IO异常
     */
    private  void traverseAndWrite(ArrayList<byte[]> listBytes, BufferedOutputStream osw) throws IOException {
        for (byte[] listByte : listBytes) {
            osw.write(listByte);
            osw.flush();
        }
    }
​
    /**
     * 输出流优化,缓冲字节输出流
     * @param fos 需要优化的字节流
     * @return 缓冲字节输出流对象
     */
    private  BufferedOutputStream getBufferedOutputStream(FileOutputStream fos) {
        return new BufferedOutputStream(fos);
    }
​
    /**
     * 创建输出流
     * @param file 要写出的文件(路径+文件名)
     * @return 返回字节输出流对象
     * @throws FileNotFoundException 抛出FileNotFoundException异常
     */
    private  FileOutputStream getFileOutputStream(File file) throws FileNotFoundException {
        return new FileOutputStream(file);
    }
​
    /**
     * 把内容写入到哪
     * @param folder 目录(目标文件夹)
     * @param fileName 文件名(目标文件名)
     * @param nameSuffix 文件后缀名(格式.jpg/.mp3等)
     * @return 返回File对象
     */
    private  File getFile(File folder, String fileName,String nameSuffix) {
        return new File(folder, fileName +nameSuffix);
    }
​
    /**
     * 获取唯一文件名
     * @return 返回唯一文件名(String)
     */
    private  String getFileName() {
        return UUID.randomUUID().toString().replace("-","");
    }
​
    /**
     * 检查目标文件夹是否存在
     * @param folder 目标文件夹
     */
    private  void inspectFolder(File folder) {
        if (!folder.exists()){
            if (folder.mkdir()){
                System.out.println("文件夹创建成功");
            }
        }
    }
​
    /**
     * 设置写出路径
     * @param path 目标位置
     * @return  返回File对象
     */
    private  File getFolder(String path) {
        return new File(path);
    }
​
    /**
     * 读取数据
     * @param bis 缓冲字节输入流对象
     * @param listBytes 存储数据的集合
     * @throws IOException 会抛出IOException
     */
    private  void readData(BufferedInputStream bis, ArrayList<byte[]> listBytes) throws IOException {
        byte[] bytes = new byte[1024];
        int length;
        while ((length = bis.read(bytes)) != -1){
            listBytes.add(Arrays.copyOfRange(bytes,0,length));
        }
    }
​
    /**
     * 创建集合
     * @return ArrayList<byte[]>
     */
    private  ArrayList<byte[]> getArrayList() {
        return new ArrayList<>();
    }
​
    /**
     * 优化输入流
     * @param inputStream 字节输入流
     * @return 返回缓冲字节输入流对象
     */
    private  BufferedInputStream getBufferedInputStream(InputStream inputStream) {
        return new BufferedInputStream(inputStream);
    }
​
    /**
     * 获取服务端Socket的输入流
     * @param serverSocket 服务端的socket对象
     * @return 返回服务端Socket的字节输入流对象
     * @throws IOException 会抛出IOException
     */
    private  InputStream getInputStream(Socket serverSocket) throws IOException {
        return serverSocket.getInputStream();
    }
​
}
⑤多线程版优化-线程池
public class ServerClient {
    public static void main(String[] args) throws IOException {
        //创建ServerSocket对象,设置好端口号
        ServerSocket ss = new ServerSocket(10005);
        //创建线程池
​
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
                //核心线程数量
                5,
                //线程池的总数量
                10,
                //临时线程空闲时间:多长时间没有任务就销毁
                60,
                //空闲时间单位
                TimeUnit.MINUTES,
                //阻塞队列,允许多少个线程排队
                new ArrayBlockingQueue<>(5),
                //创建线程的方式
                Executors.defaultThreadFactory(),
                //任务拒绝策略
                new ThreadPoolExecutor.AbortPolicy()
        );
​
        while (true){
            Socket accept = ss.accept();
            ThreadSocket threadSocket = new ThreadSocket(accept);
            poolExecutor.submit(threadSocket);
            final int activeCount = poolExecutor.getActiveCount();
            System.out.println(activeCount);
        }
​
    }
​
​
}

public class ThreadSocket implements Runnable{
    Socket socket;
​
    public ThreadSocket(Socket socket) {
        this.socket = socket;
    }
​
    @Override
    public void run() {
            //获取输入流,准备读取数据
            InputStream inputStream = null;
            try {
                inputStream = getInputStream(socket);
            } catch (IOException e) {
                e.printStackTrace();
            }
​
            //输入流优化
            BufferedInputStream bis = getBufferedInputStream(inputStream);
​
            //创建一个集合(可选)保存接收到的数据
            ArrayList<byte[]> listBytes = getArrayList();
​
            //读取数据并保存到listBytes集合中
            try {
                readData(bis, listBytes);
            } catch (IOException e) {
                e.printStackTrace();
            }
​
            //设置写出路径
            String s = "src\\tcp\\socket_tcp04\\serverClient_file";
            File folder = getFolder(s);
​
            //检查文件夹是否存在
            inspectFolder(folder);
​
            //获取唯一文件名
            final String fileName = getFileName();
​
            //设置文件后缀名
            String nameSuffix = ".mp3";
​
            //在设置好的路径下创建名为【fileName】的空文件
            File file = getFile(folder, fileName,nameSuffix);
​
            //创建输出流,准备写出数据
            FileOutputStream fos = null;
            try {
                fos = getFileOutputStream(file);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
            //优化输出流
            BufferedOutputStream osw = getBufferedOutputStream(fos);
            //遍历集合并写出数据
            try {
                traverseAndWrite(listBytes, osw);
            } catch (IOException e) {
                e.printStackTrace();
            }
​
            //获取输出流,准备给反馈
            OutputStream os = null;
            try {
                os = socket.getOutputStream();
            } catch (IOException e) {
                e.printStackTrace();
            }
            //优化
            BufferedOutputStream bos = null;
            if (os != null){
                bos = new BufferedOutputStream(os);
            }
​
            //写反馈
            try {
                if (bos!=null){
                    bos.write("保存成功".getBytes());
                    bos.flush();
                }
​
            } catch (IOException e) {
                e.printStackTrace();
            }
​
​
            //释放资源
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
​
    }
​
    /**
     * 遍历数组并写出
     * @param listBytes 保存数据的集合
     * @param osw 输出流
     * @throws IOException 会抛出IO异常
     */
    private  void traverseAndWrite(ArrayList<byte[]> listBytes, BufferedOutputStream osw) throws IOException {
        for (byte[] listByte : listBytes) {
            osw.write(listByte);
            osw.flush();
        }
    }
​
    /**
     * 输出流优化,缓冲字节输出流
     * @param fos 需要优化的字节流
     * @return 缓冲字节输出流对象
     */
    private  BufferedOutputStream getBufferedOutputStream(FileOutputStream fos) {
        return new BufferedOutputStream(fos);
    }
​
    /**
     * 创建输出流
     * @param file 要写出的文件(路径+文件名)
     * @return 返回字节输出流对象
     * @throws FileNotFoundException 抛出FileNotFoundException异常
     */
    private  FileOutputStream getFileOutputStream(File file) throws FileNotFoundException {
        return new FileOutputStream(file);
    }
​
    /**
     * 把内容写入到哪
     * @param folder 目录(目标文件夹)
     * @param fileName 文件名(目标文件名)
     * @param nameSuffix 文件后缀名(格式.jpg/.mp3等)
     * @return 返回File对象
     */
    private  File getFile(File folder, String fileName,String nameSuffix) {
        return new File(folder, fileName +nameSuffix);
    }
​
    /**
     * 获取唯一文件名
     * @return 返回唯一文件名(String)
     */
    private  String getFileName() {
        return UUID.randomUUID().toString().replace("-","");
    }
​
    /**
     * 检查目标文件夹是否存在
     * @param folder 目标文件夹
     */
    private  void inspectFolder(File folder) {
        if (!folder.exists()){
            if (folder.mkdir()){
                System.out.println("文件夹创建成功");
            }
        }
    }
​
    /**
     * 设置写出路径
     * @param path 目标位置
     * @return  返回File对象
     */
    private  File getFolder(String path) {
        return new File(path);
    }
​
    /**
     * 读取数据
     * @param bis 缓冲字节输入流对象
     * @param listBytes 存储数据的集合
     * @throws IOException 会抛出IOException
     */
    private  void readData(BufferedInputStream bis, ArrayList<byte[]> listBytes) throws IOException {
        byte[] bytes = new byte[1024];
        int length;
        while ((length = bis.read(bytes)) != -1){
            listBytes.add(Arrays.copyOfRange(bytes,0,length));
        }
    }
​
    /**
     * 创建集合
     * @return ArrayList<byte[]>
     */
    private  ArrayList<byte[]> getArrayList() {
        return new ArrayList<>();
    }
​
    /**
     * 优化输入流
     * @param inputStream 字节输入流
     * @return 返回缓冲字节输入流对象
     */
    private  BufferedInputStream getBufferedInputStream(InputStream inputStream) {
        return new BufferedInputStream(inputStream);
    }
​
    /**
     * 获取服务端Socket的输入流
     * @param serverSocket 服务端的socket对象
     * @return 返回服务端Socket的字节输入流对象
     * @throws IOException 会抛出IOException
     */
    private  InputStream getInputStream(Socket serverSocket) throws IOException {
        return serverSocket.getInputStream();
    }
​
}
(8)问题【待解决】

虽然实现了多线程和重复接收的问题,但在测试的时候还有个问题,记录一下

文件写好之后,由于服务端的ServerSocket没有关闭,在服务端读取完数据并写出后,其状态是被java虚拟机使用的,故此文件是无法删除的,但能正常访问~

如图:

我传了三个文件

然后我去文件夹看一下状态

无法删除

但是可以正常听

关闭服务器

文件会正常

  • 18
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值