关于Java的网络编程

网络的一些了解

网络通信协议

TCP/IP网络模型

  • 链路层:链路层是用于定义物理传输通道,通常是对某些网络连接设备的驱动协议,例如针对光纤、网线提供的驱动。
  • 网络层:网络层是整个TCP/IP协议的核心,它主要用于将传输的数据进行分组,将分组数据发送到目标计算机或者网络。
  • 传输层:主要使网络程序进行通信,在进行网络通信时,可以采用TCP协议,也可以采用UDP协议。
  • 应用层:主要负责应用程序的协议,例如HTTP协议、FTP协议等。

IP地址和端口

要想使网络中的计算机能够进行通信,必须为每台计算机指定一个标识号,通过这个标识号来指定接受数据的计算机或者发送数据的计算机。

在TCP/IP协议中,这个标识号就是IP地址,它可以唯一标识一台计算机,目前,IP地址广泛使用的版本是IPv4,它是由4个字节大小的二进制数来表示,如:00001010000000000000000000000001。由于二进制形式表示的IP地址非常不便记忆和处理,因此通常会将IP地址写成十进制的形式,每个字节用一个十进制数字(0-255)表示,数字间用符号“.”分开,如 “192.168.1.100”,但是有一个地址比较特别,那就是“127.0.0.1”,这个回环地址,代表本机(也就是自己本身)。

随着计算机网络规模的不断扩大,对IP地址的需求也越来越多,IPV4这种用4个字节表示的IP地址面临枯竭,因此IPv6 便应运而生了,IPv6使用16个字节表示IP地址,它所拥有的地址容量约是IPv4的8×1028倍,达到2128个(算上全零的),这样就解决了网络地址资源数量不够的问题。

通过IP地址可以连接到指定计算机,但如果想访问目标计算机中的某个应用程序,还需要指定端口号。在计算机中,不同的应用程序是通过端口号区分的。端口号是用两个字节(16位的二进制数)表示的,它的取值范围是0-65535,其中,[font color=“red”]0-1023之间的端口号用于一些知名的网络服务和应用,用户的普通应用程序需要使用1024以上的端口号[/font],从而避免端口号被另外一个应用或服务所占用。

接下来通过一个图例来描述IP地址和端口号的作用,如下图所示。

关于IP地址和端口的作用应用

从上图中可以清楚地看到,位于网络中一台计算机可以通过IP地址去访问另一台计算机,并通过端口号访问目标计算机中的某个应用程序。

InetAddress类

了解了IP地址的作用,我们看学习下JDK中提供了一个InetAdderss类,该类用于封装一个IP地址,并提供了一系列与IP地址相关的方法,下表中列出了InetAddress类的一些常用方法。

InetAddress API说明

上图中,列举了InetAddress的四个常用方法。其中,前两个方法用于获得该类的实例对象,第一个方法用于获得表示指定主机的InetAddress对象,第二个方法用于获得表示本地的InetAddress对象。通过InetAddress对象便可获取指定主机名,IP地址等,接下来通过一个案例来演示InetAddress的常用方法,如下所示。

import java.io.IOException;
import java.net.InetAddress;

/**
 * 表示互联网中的IP地址
 *  java.net.InetAddress
 * 静态方法
 *  static InetAddress getLocalHost() 获取本地主机IP地址和主机名,返回值InetAddress对象
 *  static InetAddress getByName(String hostName) 传递主机名,获取IP地址对象
 * 非静态方法
 *  String getHostName() 获取主机IP地址
 *  String getHostAddress() 获取主机名
 */
public class InetAddressDome {
    public static void main(String[] args) throws IOException {
        InetAddress local = InetAddress.getLocalHost();
        InetAddress remote = InetAddress.getByName("if010.com");
        System.out.println("本机的主机名:" + local.getHostName());
        System.out.println("本机的IP地址:" + local.getHostAddress());
        System.out.println("IF010的主机名为:" + remote.getHostName());
        System.out.println("IF010的IP地址:" + remote.getHostAddress());
    }
}

---------- 输出结果 ----------
本机的主机名:Kims-MacBook.local
本机的IP地址:127.0.0.1
IF010的主机名为:if010.com
IF010IP地址:27.152.185.64

UDP、TCP协议

在介绍TCP/IP结构时,提到传输层的两个重要的高级协议,分别是UDP和TCP,其中UDP是User Datagram Protocol的简称,称为用户数据报协议,TCP是Transmission Control Protocol的简称,称为传输控制协议。

UDP协议

UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。

由于使用UDP协议消耗资源小,通信效率高,且数据传输一般限制在64KB内,所以通常都会用于音频、视频和普通数据的传输例如视频会议都使用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。

但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议。UDP的交换过程如下图所示。

UDP交换过程图

TCP协议

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

  • 第一次握手,客户端向服务器端发出连接请求,等待服务器确认
  • 第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求
  • 第三次握手,客户端再次向服务器端发送确认信息,确认连接。

整个交互过程如下图所示。

TCP交换过程图(三次握手)

由于TCP协议的面向连接特性,它可以保证传输数据的安全性,所以是一个被广泛采用的协议,例如在下载文件时,如果数据接收不完整,将会导致文件数据丢失而不能被打开,因此,下载文件时必须采用TCP协议。

UDP通信

DatagramPacket

前面介绍了UDP是一种面向无连接的协议,因此,在通信时发送端和接收端不用建立连接。UDP通信的过程就像是货运公司在两个码头间发送货物一样。在码头发送和接收货物时都需要使用集装箱来装载货物,UDP通信也是一样,发送和接收的数据也需要使用“集装箱”进行打包,为此JDK中提供了一个DatagramPacket类,该类的实例对象就相当于一个集装箱,用于封装UDP通信中发送或者接收的数据。

想要创建一个DatagramPacket对象,首先需要了解一下它的构造方法。在创建发送端和接收端的DatagramPacket对象时,使用的构造方法有所不同,接收端的构造方法只需要接收一个字节数组来存放接收到的数据,而发送端的构造方法不但要接收存放了发送数据的字节数组,还需要指定发送端IP地址和端口号。

接下来根据API文档的内容,对DatagramPacket的构造方法进行逐一详细地讲解。

DatagramPacket API说明_01

使用该构造方法在创建DatagramPacket对象时,指定了封装数据的字节数组和数据的大小,没有指定IP地址和端口号。很明显,这样的对象只能用于接收端,不能用于发送端。因为发送端一定要明确指出数据的目的地(ip地址和端口号),而[font color=“red”]接收端不需要明确知道数据的来源,只需要接收到数据即可[/font]。

DatagramPacket API说明_02

使用该构造方法在创建DatagramPacket对象时,不仅指定了封装数据的字节数组和数据的大小,还指定了数据包的目标IP地址(addr)和端口号(port)。该对象通常用于发送端,因为在[font color=“red”]发送数据时必须指定接收端的IP地址和端口号[/font],就好像发送货物的集装箱上面必须标明接收人的地址一样。

上面我们讲解了DatagramPacket的构造方法,接下来对DatagramPacket类中的常用方法进行详细地讲解,如下表所示。

DatagramPacket API说明_03

DatagramSocket

DatagramPacket数据包的作用就如同是“集装箱”,可以将发送端或者接收端的数据封装起来。然而运输货物只有“集装箱”是不够的,还需要有码头。在程序中需要实现通信只有DatagramPacket数据包也同样不行,为此JDK中提供的一个DatagramSocket类。DatagramSocket类的作用就类似于码头,使用这个类的实例对象就可以发送和接收DatagramPacket数据包,发送数据的过程如下图所示。

发送数据过程

在创建发送端和接收端的DatagramSocket对象时,使用的构造方法也有所不同,下面对DatagramSocket类中常用的构造方法进行讲解。

DatagramSocket API说明_01

该构造方法用于创建发送端的DatagramSocket对象,在创建DatagramSocket对象时,并没有指定端口号,此时,系统会分配一个没有被其它网络程序所使用的端口号。

DatagramSocket API说明_02

该构造方法既可用于创建接收端的DatagramSocket对象,又可以创建发送端的DatagramSocket对象,在创建接收端的DatagramSocket对象时,必须要指定一个端口号,这样就可以监听指定的端口。

上面我们讲解了DatagramSocket的构造方法,接下来对DatagramSocket类中的常用方法进行详细地讲解。

DatagramSocket API说明_03

UDP通信设计

讲解了DatagramPacket和DatagramSocket的作用,接下来通过一个案例来学习一下它们在程序中的具体用法。

UDP发送端与接收端交互图解

要实现UDP通信需要创建一个发送端程序和一个接收端程序,很明显,在通信时只有接收端程序先运行,才能避免因发送端发送的数据无法接收,而造成数据丢失。因此,首先需要来完成接收端程序的编写。

UDP发送端

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

/**
 * 实现UDP协议的发送端
 *  实现封装数据的类 java.net.DatagramPacket 将你的数据包装
 *  实现数据传输的类 java.net.DatagramSocket 将你的数据包发送出去
 *
 * 实现步骤:
 *  1.创建 DatagramPacket 对象,封装数据、接收端的IP地址、接收端的端口
 *  2.创建 DatagramSocket 对象
 *  3.调用 DatagramSocket 类的方法 send(DatagramPacket dp),发送数据包
 *  4.关闭资源
 *
 * DatagramPacket构造方法:
 *  DatagramPacket(byte[] buf, int length, InetAddress address, int port)
 *
 * DatagramSocket构造方法:
 *  DatagramSocket() 空参数
 *   方法: send(DatagramPacket d)
 */
public class UDPSend {
    public static void main(String[] args) throws IOException {
        //创建数据包对象,封装要发送的数据,接收端IP,端口
        byte[] date = "你好,我是一个UDP数据包".getBytes();
        //创建InetAddress对象,封装接收端的IP地址
        InetAddress inet = InetAddress.getByName("127.0.0.1");
        DatagramPacket dp = new DatagramPacket(date, date.length, inet,6000);
        //创建DatagramSocket对象,数据包的发送和接收对象
        DatagramSocket ds = new DatagramSocket();
        //调用ds对象的方法send,发送数据包
        ds.send(dp);
        //关闭资源
        ds.close();
    }
}

UDP接收端

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

/**
 * 实现UDP协议的接收端
 *  实现封装数据的类 java.net.DatagramPacket 将数据包拆封
 *  实现数据传输的类 java.net.DatagramSocket 接收数据包
 *
 * 实现步骤:
 *  1.创建 DatagramSocket 对象,绑定端口号,要和发送端端口号一致
 *  2.创建字节数组,接收发来的数据
 *  3.创建数据包对象 DatagramPacket
 *  4.调用 DatagramSocket 类的方法 receive(DatagramPacket dp) 接收数据,数据放在数据包中
 *  5.拆包
 *      发送的IP地址
 *          数据包对象DatagramPacket方法getAddress()获取的是发送端的IP地址对象
 *          返回值是InetAddress对象
 *      接收到的字节个数
 *          数据包对象DatagramPacket方法 getLength()
 *      发送方的端口号
 *          数据包对象DatagramPacket方法 getPort()发送端口
 *  6.关闭资源
 */
public class UDPReceive {
    public static void main(String[] args) throws IOException {
        //创建数据包传输对象 DatagramSocket 绑定端口号
        DatagramSocket ds = new DatagramSocket(6000);
        //创建字节数组,最大64KB
        byte[] data = new byte[1024*64];
        //创建数据包对象,传递字节数组
        DatagramPacket dp = new DatagramPacket(data, data.length);
        //调用ds对象的方法receive传递数据包
        ds.receive(dp);

        //获取发送端的IP地址对象
        String ip = dp.getAddress().getHostAddress();
        //获取发送的端口号
        int port = dp.getPort();
        //获取接收到的字节个数
        int length = dp.getLength();
  
        System.out.println(new String(data,0,length)+"..."+ip+":"+port);
  
        //关闭资源
        ds.close();
    }
}

---------- 输出结果 ----------
你好,我是一个UDP数据包...127.0.0.1:56092

模拟简单聊天室案例

聊天室客户端

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;

/**
 * 实现键盘输入聊天客户端,使用UPD协议发送消息
 */
public class ChatClient {
    public static void main(String[] args) throws IOException {
        Scanner sc = new Scanner(System.in);
        InetAddress inet = InetAddress.getByName("127.0.0.1");
        DatagramSocket ds = new DatagramSocket();

        while (true) {
            System.out.println("请输入你要发送的消息:");
            String message = sc.nextLine();
            byte[] date = message.getBytes();
            DatagramPacket dp = new DatagramPacket(date, date.length, inet, 6000);
            ds.send(dp);
        }
    }
}

---------- 输出结果 ----------
请输入你要发送的消息:
你好

聊天室服务端

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

/**
 * 实现键盘输入聊天服务端,使用UPD协议接收消息
 * 保持持续接收
 */
public class ChatService {
    public static void main(String[] args) throws IOException {
        DatagramSocket ds = new DatagramSocket(6000);
        byte[] data = new byte[1024*64];
        while (true) {
            DatagramPacket dp = new DatagramPacket(data, data.length);
            ds.receive(dp);

            String ip = dp.getAddress().getHostAddress();
            int port = dp.getPort();
            int length = dp.getLength();

            System.out.println("接收到来自:" + ip + ":" + port);
            System.out.println(new String(data, 0, length));
        }
    }
}

---------- 输出结果 ----------
接收到来自:127.0.0.1:52285
你好

TCP通信

TCP通信同UDP通信一样,都能实现两台计算机之间的通信,通信的两端都需要创建socket对象。

区别在于,UDP中只有发送端和接收端,不区分客户端与服务器端,计算机之间可以任意地发送数据。

而TCP通信是严格区分客户端与服务器端的,在通信时,必须先由客户端去连接服务器端才能实现通信,服务器端不可以主动连接客户端,并且服务器端程序需要事先启动,等待客户端的连接。

在JDK中提供了两个类用于实现TCP程序,[font color=“red”]一个是ServerSocket类,用于表示服务器端,一个是Socket类,用于表示客户端[/font]。

通信时,首先创建代表服务器端的ServerSocket对象,该对象相当于开启一个服务,并等待客户端的连接,然后创建代表客户端的Socket对象向服务器端发出连接请求,服务器端响应请求,两者建立连接开始通信。

客户端和服务端

ServerSocket

通过前面的学习知道,在开发TCP程序时,首先需要创建服务器端程序。JDK的java.net包中提供了一个ServerSocket类,该类的实例对象可以实现一个服务器段的程序。通过查阅API文档可知,ServerSocket类提供了多种构造方法,接下来就对ServerSocket的构造方法进行逐一地讲解。

ServerSocket API_01

使用该构造方法在创建ServerSocket对象时,就可以将其绑定到一个指定的端口号上(参数port就是端口号)。

接下来学习一下ServerSocket的常用方法,如表所示。

ServerSocket API_02

ServerSocket对象负责监听某台计算机的某个端口号,在创建ServerSocket对象后,需要继续调用该对象的accept()方法,接收来自客户端的请求。当执行了accept()方法之后,服务器端程序会发生阻塞,直到客户端发出连接请求,accept()方法才会返回一个Scoket对象用于和客户端实现通信,程序才能继续向下执行。

Socket

讲解了ServerSocket对象可以实现服务端程序,但只实现服务器端程序还不能完成通信,此时还需要一个客户端程序与之交互,为此JDK提供了一个Socket类,用于实现TCP客户端程序。

通过查阅API文档可知Socket类同样提供了多种构造方法,接下来就对Socket的常用构造方法进行详细讲解。

Socket API_01

使用该构造方法在创建Socket对象时,会根据参数去连接在指定地址和端口上运行的服务器程序,其中参数host接收的是一个字符串类型的IP地址。

Socket API_02

该方法在使用上与第二个构造方法类似,参数address用于接收一个InetAddress类型的对象,该对象用于封装一个IP地址。

在以上Socket的构造方法中,最常用的是第一个构造方法。

接下来学习一下Socket的常用方法,如表所示。

方法声明功能描述
int getPort()该方法返回一个int类型对象,该对象是Socket对象与服务器端连接的端口号
InetAddress getLocalAddress()该方法用于获取Socket对象绑定的本地IP地址,并将IP地址封装成InetAddress类型的对象返回
void close()该方法用于关闭Socket连接,结束本次通信。在关闭socket之前,应将与socket相关的所有的输入/输出流全部关闭,这是因为一个良好的程序应该在执行完毕时释放所有的资源
InputStream getInputStream()该方法返回一个InputStream类型的输入流对象,如果该对象是由服务器端的Socket返回,就用于读取客户端发送的数据,反之,用于读取服务器端发送的数据
OutputStream getOutputStream()该方法返回一个OutputStream类型的输出流对象,如果该对象是由服务器端的Socket返回,就用于向客户端发送数据,反之,用于向服务器端发送数据

在Socket类的常用方法中,getInputStream()和getOutStream()方法分别用于获取输入流和输出流。当客户端和服务端建立连接后,数据是以IO流的形式进行交互的,从而实现通信。

接下来通过一张图来描述服务器端和客户端的数据传输,如下图所示

数据流向图

TCP通讯设计

简单的TCP网络程序设计

简单的TCP网络程序

TCP的服务端

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 实现TCP服务端,接收客户端数据
 * 实现TCP服务端程序的类 java.net.ServerSocket
 *
 * 构造方法:
 *  ServerSocket(int port) 传递端口号
 *
 *  Socket accept()
 *      必须要获得客户端的套接字Socket,才能读取流信息!!!
 */
public class TCPService {
    public static void main(String[] args) throws IOException {
        //创建ServerSocket对象,监听6666端口
        ServerSocket serverSocket = new ServerSocket(6666);
        //调用ServerSocket对象的方法accept(),用于获取客户端Socket对象
        Socket clientSocket = serverSocket.accept();
        //通过客户端Socket对象,获取字节输入流(客户端发来的数据)
        InputStream inputStream = clientSocket.getInputStream();
        byte[] data = new byte[1024];
        int len = inputStream.read(data);
        System.out.println(new String(data,0,len));

        //向客户端进行数据回复,字节输出流,通过客户端Socket对象获取字节输出流
        OutputStream outputStream = clientSocket.getOutputStream();
        outputStream.write("Server is OK!".getBytes());

        //关闭资源
        clientSocket.close();
        serverSocket.close();
    }
}

---------- 输出结果 ----------
Server, Are you ok?

TCP的客户端

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

/**
 * 实现TCP客户端,连接到服务器,并进行数据交换
 * 实现TCP客户端程序的类 java.net.Socket
 *
 * 构造方法:
 *  Socket(String host, int port) 传递服务器IP和端口号
 *      注意:构造方法只要运行,就会和服务器进行连接,连接失败则会抛出异常,所以需要保证服务端是否处于监听
 *
 *  OutputStream getOutputStream() 返回套接字的输出流
 *      作用:将数据输出,输出到服务器
 *
 *  InputStream getInputStream() 返回套接字的输入流
 *      作用:从服务端读取数据
 *
 * !!!客户端与服务端进行数据交换必须使用套接字对象Socket中获取IO流,自己new流是不行的!!!
 */
public class TCPClient {
    public static void main(String[] args) throws IOException {
        //创建Socket对象,连接服务器
        Socket socket = new Socket("127.0.0.1",6666);
        //通过客户端的套接字对象Socket方法,获取字节输出流,将数据写向服务器
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write("Server, Are you ok?".getBytes());

        //等待服务器回复数据,使用Socket对象中的字节输入流
        byte[] data = new byte[1024];
        InputStream inputStream = socket.getInputStream();
        int len = inputStream.read(data);
        System.out.println(new String(data,0,len));

        socket.close();
    }
}

---------- 输出结果 ----------
Server is OK!

TCP文件上传案例

TCP文件上传案例分析

目前大多数服务器都会提供文件上传的功能,由于文件上传需要数据的安全性和完整性,很明显需要使用TCP协议来实现。接下来通过一个案例来实现文件上传的功能。如下图所示。

文件上传

代码实现

TCP文件上传的服务端

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Random;

/**
 * 实现TCP协议进行文件上传的服务端
 * 实现步骤:
 *  1、创建ServerSocket套接字对象,监听6666端口
 *  2、使用ServerSocket套接字对象的accrpt()方法获取客户端的连接Socket对象
 *  3、客户端的连接Socket对象获取字节输入流,读取客户端发送的文件
 *  4、创建File对象,绑定上传文件夹
 *      判断文件夹是否存在,不存在则自动创建
 *  5、创建字节输出流,数据目的为File对象所在的文件夹
 *  6、字节流读取文件,字节流将文件写入到文件夹当中
 *  7、将上传成功的消息写会客户端
 *  8、关闭资源
 */
public class TCPService {
    public static void main(String[] args) throws IOException {
        //创建ServerSocket套接字对象,监听6666端口
        ServerSocket serverSocket = new ServerSocket(6666);
        //获取客户端的连接Socket对象
        Socket clentSocket = serverSocket.accept();
        System.out.println(
                "收到来自:" + clentSocket.getLocalAddress() + ":" + clentSocket.getPort()
        );

        //通过客户端连接对象,获取字节输入流,以便读取客户端发来的文件
        InputStream inputStream = clentSocket.getInputStream();

        //创建File对象,将文件夹封装,并判断文件夹是否存在
        File uploadDir = new File("./upload");
        if (!uploadDir.exists()){
            uploadDir.mkdir();
        }

        //防止文件名重名问题,自定义规则:毫秒值+6位随机数
        String filename = System.currentTimeMillis() + new Random().nextInt(999999)+".jpg";

        //创建字节输出流,将文件写入到目的文件夹当中
        FileOutputStream fos = new FileOutputStream(uploadDir+File.separator+filename);

        //读写字节数组
        int len = 0;
        byte[] data = new byte[1024*1024];
        while ((len = inputStream.read(data)) != -1){
            fos.write(data,0,len);
        }

        //通过客户端的连接Socket对象获取字节输出流,发回上传成功消息
        clentSocket.getOutputStream().write("上传成功".getBytes());

        //关闭资源
        fos.close();
        clentSocket.close();
        serverSocket.close();
    }
}

TCP文件上传的客户端

package com.if010.uploadfile;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

/**
 * 实现TCP协议进行文件上传的客户端
 * 实现步骤:
 *  1、创建Socket套接字连接服务器
 *  2、通过Socket获取字节输出流,写入文件
 *  3、使用自己的流对象,读取文件数据源
 *      FileInputStream
 *  4、读取文件,使用字节输出流将文件写入服务器
 *  5、通过Socket套接字获取字节输入流,读取服务器返回的结果消息
 *  6、关闭资源
 */
public class TCPClient {
    public static void main(String[] args) throws IOException {
        //使用Socket对象,连接服务器
        Socket socket = new Socket("127.0.0.1",6666);
        //获取字节输出流,文件写到服务器
        OutputStream outputStream = socket.getOutputStream();

        //创建字节输入流,读取本机的文件数据
        FileInputStream fileInputStream = new FileInputStream("/Users/kim/Documents/Code/resource/Avatar.jpg");
        //开始读写字节数组
        int len = 0;
        byte[] data = new byte[1024*1024];
        while ((len = fileInputStream.read(data)) != -1){
            outputStream.write(data,0,len);
        }

        //给服务器写终止序列,否则服务端会一直等待消息发送
        socket.shutdownOutput();

        //获取字节输入流,读取服务器返回的结果消息
        InputStream inputStream = socket.getInputStream();
        len = inputStream.read(data);
        System.out.println(new String(data,0,len));

        //关闭资源
        fileInputStream.close();
        socket.close();
    }
}

改良实现多线程上传

多线程文件上传

创建Upload类方法,将上面的TCPServer实现方法写入run中,然后再创建多线程版本的TCPThreadServer类,只要收到新的链接就掉一次run方法实现多线程。

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.util.Random;

public class Upload implements Runnable {
    private Socket clentSocket;

    public Upload(Socket socket) {
        this.clentSocket = socket;
    }

    @Override
    public void run() {
        try {
            System.out.println(
                    "收到来自:" + clentSocket.getLocalAddress() + ":" + clentSocket.getPort()
            );

            //通过客户端连接对象,获取字节输入流,以便读取客户端发来的文件
            InputStream inputStream = clentSocket.getInputStream();

            //创建File对象,将文件夹封装,并判断文件夹是否存在
            File uploadDir = new File("./upload");
            if (!uploadDir.exists()){
                uploadDir.mkdir();
            }

            //防止文件名重名问题,自定义规则:毫秒值+6位随机数
            String filename = System.currentTimeMillis() + new Random().nextInt(999999)+".jpg";

            //创建字节输出流,将文件写入到目的文件夹当中
            FileOutputStream fos = new FileOutputStream(uploadDir+File.separator+filename);

            //读写字节数组
            int len = 0;
            byte[] data = new byte[1024*1024];
            while ((len = inputStream.read(data)) != -1){
                fos.write(data,0,len);
            }

            //通过客户端的连接Socket对象获取字节输出流,发回上传成功消息
            clentSocket.getOutputStream().write("上传成功".getBytes());

            //关闭资源
            fos.close();
            clentSocket.close();
        } catch (IOException e) {

        }
    }
}
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class TCPThreadServer {
    public static void main(String[] args) throws IOException {
        //创建ServerSocket套接字对象,监听6666端口
        ServerSocket serverSocket = new ServerSocket(6666);
  
        //循环获取客户端的连接Socket对象,只要获取到就跑Upload的run上传方法
        while (true) {
            Socket clentSocket = serverSocket.accept();
            new Thread(new Upload(clentSocket)).start();
        }
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

你的隐私被泄漏了吗

近日,某高校毕业生在校期间窃取学校内网数据,收集全校学生个人隐私信息的新闻引发了人们对互联网生活中个人信息安全问题的再度关注。在大数据时代,算法分发带来了隐私侵犯,在享受消费生活等便捷权利的同时,似乎又有不…

按关键词全网采集

简数采集器支持按关键词全网采集,只需输入对应关键词,即可在全网采集相关数据,类似搜索引擎,无需用户配置采集规则。 简数采集器按关键词泛采集可用于舆情监控、市场研究分析等。 使用方法如下: 目录 1. 创建关键词…

MySQL为什么采用B+树作为索引底层数据结构?

索引就像一本书的目录,通过索引可以快速找到我们想要找的内容。那么什么样的数据结构可以用来实现索引呢?我们可能会想到:二叉查找树,平衡搜索树,或者是B树等等一系列的数据结构,那么为什么MySQL最终选择了…

【框架篇】对象注入的三种实现方式

对象注入的实现 一,实现方式的使用 对象注入也可被称为对象装配,是把Bean对象获取出来放到某个类中。 对象注入的实现方式有3种,分别为属性注入,Setter注入和构造方法注入。 为了更好地理解对象注入的实现方式,搞个…

Spring管理事务知识

目录 1.什么是事务 2.事务的特性ACID 3.Spring 管理事务的方式 4.Spring管理事务的体现:JDBCTemplate 5.声明式事务的属性有哪些 6.声明式事务属性---只读 7.声明式事务属性---超时 8.声明式事务属性---回滚策略 9.声明式事务属性---事务隔离级别 10.声明…

1、Kubernetes 概述和架构

目录 一、基本介绍 二、kubernetes功能和架构 2.1、 概述 2.2 、功能 (1)自动装箱 (2)自我修复(自愈能力) (3)水平扩展 (4)服务发现 (5)滚动更新 &a…

【Vue】给 elementUI 中的 this.$confirm、this.$alert、 this.$prompt添加按钮的加载效果

文章目录 主要使用 beforeClose 方法实现 loading 的效果beforeClose MessageBox 关闭前的回调,会暂停实例的关闭 function(action, instance, done)1. action 的值为confirm, cancel或close。 2. instance 为 MessageBox 实例,可以通过它访问实例上的属…

C语言中定义和声明的区别

声明(declaration)与定义(definition) 为了使不同的文件都可以访问同一个变量,C会区 分变量的声明和定义。 变量的定义会为这个变量分配存储空间,并且 可能 会为其指定一个初始化的值, 一个变量的定义有且 仅有一处。 定义实际上是一种特殊…

【网络】HTTPS协议原理

目录 “加密”相关概念 为什么要加密 常见加密方式 对称加密 非对称加密 HTTPS工作过程探究 方案1-只使用对称加密 方案2-只使用非对称加密 方案3-客户端和服务端双方都使用非对称加密 方案4-非对称加密 对称加密 上述方案问题分析 方案5-证书认证 非对称加密对…

Kafka传输数据到Spark Streaming通过编写程序java、scala程序实现操作

一、案例说明 现有一电商网站数据文件,名为buyer_favorite1,记录了用户对商品的收藏数据,数据以“\t”键分割,数据内容及数据格式如下: 二、前置准备工作 项目环境说明 Linux Ubuntu 16.04jdk-7u75-linux-x64scal…

C++的switch函数用法

一个 switch 语句允许测试一个变量等于多个值时的情况。每个值称为一个 case,且被测试的变量会对每个 switch case 进行检查。 语法 C 中 switch 语句的语法: switch(expression){ case constant-expression : statement(s); break; // 可选的 case c…

解决MAC IDEA终端每次都要source ~/.zshrc

安装nvm之后,发现每隔一段时间(不清楚是新打开一个终端还是会定时刷新)就要重新执行source ~/zshrc,才能执行nvm命令。找了一圈发现idea默认使用的shell是bash,将默认的shell改成zsh就可以,更改位置&#x…

多模态系列论文--CoCa 详细解析

论文地址:CoCa: Contrastive Captioners are Image-Text Foundation Models 代码地址:CoCa CoCa 1 摘要2 网络结构3 损失函数4 实验结果5 总结 1 摘要 CoCa代表Contrastive Captioners的缩写,代表模型用两个目标函数训练出来的,一…

selenium怎么使用代理IP

什么是selenium Selenium 是一个自动化测试框架,用于测试 Web 应用程序的功能性。它支持多个编程语言(如Java,Python,C#等)并且可以在操作系统和不同浏览器上运行测试。Selenium 可以模拟用户在浏览器中的操作&#x…

PyTorch从零开始实现Transformer

文章目录 自注意力Transformer块编码器解码器块解码器整个Transformer参考来源全部代码(可直接运行) 自注意力 计算公式 代码实现 class SelfAttention(nn.Module):def __init__(self, embed_size, heads):super(SelfAttention, self).__init__()self.e…

RDS-Tools RDS-Knight Crack

RDS 高级安全性 利用全面的网络安全工具箱中有史以来最强大的安全功能集来保护您的 RDS 基础架构。 全方位 360 保护 无与伦比的功能集 无与伦比的物有所值 企业远程桌面安全。现代工作空间的智能解决方案。 办公室正在权力下放。远程办公室和移动员工数量创历史新高。随…

机器学习技术(四)——特征工程与模型评估

机器学习技术(四)——特征工程与模型评估(1️⃣) 文章目录 机器学习技术(四)——特征工程与模型评估(:one:)一、特征工程1、标准化2、特征缩放3、缩放有离群值的数据4、非线性转换5、样本归一化6、特征二值化7、标称特征编码(one-…

设计模式——命令模式

命令模式 定义 将一个请求封装成一个对象,从而让你使用不同的请求吧客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。 命令模式是一个高内聚的模式。 优缺点、应用场景 优点 类间解耦。调用者与接收者之间没有任…

Linux系统使用(超详细)

目录 Linux操作系统简介 Linux和windows区别 Linux常见命令 Linux目录结构 Linux命令提示符 常用命令 ls cd pwd touch cat echo mkdir rm cp mv vim vim的基本使用 grep netstat Linux面试题 Linux操作系统简介 Linux操作系统是和windows操作系统是并列…

Github Pages使用自定义域名

Github Pages使用自定义域名 部署好网站后默认访问地址是xxx.github.io,我们想要自定义为自己的域名 1.DNS解析 这里我使用的是腾讯云,DNS解析DNSPod 添加两条解析记录: 第一个解析记录的记录类型为A,主机记录为,记录值为ping 你的github用户名.githu…
最新文章