Java学习笔记——Java语言基础(二十四)(网络编程、Socket通信、UDP、TCP协议下进行计算机的通信)

一、网络编程

1.1 软件的结构
  • C/S结构 :全称为Client/Server结构,是指客户端和服务器结构。常见程序有QQ、迅雷等软件。
  • B/S结构 :全称为Browser/Server结构,是指浏览器和服务器结构。常见浏览器有谷歌、火狐等。
    两种架构各有优势,但是无论哪种架构,都离不开网络的支持。网络编程,就是在一定的协议下,实现两台计算机
    的通信的程序。
1.2 网络模型的概述

1.网络模型概述:计算机网络之间的通信规则。
网络模型一般是指OSI(Open System Interconnection开放系统互连)七层参考模型
TCP/IP四层参考模型:
主机至网络层(物理层 , 数据链路层) , 网际层 , 传输层 , 应用层(应用层 , 表示层 , 会话层)
网络模型的七层以及TCP/IP协议的 四层模型概述:
在这里插入图片描述

1.物理层:主要定义物理设备标准,如网线的接口类型、光纤的接口类型、各种传输介质的传输速率等。它的主要作用是传输比特流(就是由1、0转化为电流强弱来进行传输,到达目的地后在转化为1、0,也就是我们常说的数模转换与模数转换),这一层的数据叫做比特。

2.数据链路层:主要将从物理层接收的数据进行MAC地址(网卡的地址)的封装与解封装。常把这一层的数据叫做帧。在这一层工作的设备是交换机,数据通过交换机来传输。 

3.网络层:在位于不同地理位置的网络中的两个主机系统之间提供连接和路径选择,Internet的发展使得从世界各站点访问信息的用户数大大增加,而网络层正是管理这种连接的层。主要将从下层接收到的数据进行IP地址(例192.168.0.1)的封装与解封装。在这一层工作的设备是路由器,常把这一层的数据叫做数据包。 

4.传输层:定义了一些传输数据的协议和端口号(WWW端口80等),如:TCP(传输控制协议,传输效率低,可靠性强,用于传输可靠性要求高,数据量大的数据),UDP(用户数据报协议,与TCP特性恰恰相反,用于传输可靠性要求不高,数据量小的数据,如QQ聊天数据就是通过这种方式传输的), 主要是将从下层接收的数据进行分段和传输,到达目的地址后再进行重组,常常把这一层数据叫做段。

5.会话层:通过传输层(端口号:传输端口与接收端口)建立数据传输的通路,主要在你的系统之间发起会话或者接受会话请求(设备之间需要互相认识可以是IP也可以是MAC或者是主机名)。

6.表示层:可确保一个系统的应用层所发送的信息可以被另一个系统的应用层读取。主要是进行对接收的数据进行解释、加密与解密、压缩与解压缩等(也就是把计算机能够识别的东西转换成人能够能识别的东西(如图片、声音等)。

7.应用层:是最靠近用户的OSI层,这一层为用户的应用程序(例如电子邮件、文件传输和终端仿真)提供网络服务。
主要是一些终端的应用,比如说FTP(各种文件下载),WEB(IE浏览),QQ之类的(可以把它理解成我们在电脑屏幕上可以看到的东西.就是终端应用)。
1.3 网络编程的三要素

协议

协议:计算机网络通信必须遵守的规则。
网络通讯协议:通信协议是对计算机必须遵守的规则,只有遵守这些规则,计算机之间才能进行通信。协议中对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守,最终完成数据交换。
TCP/IP协议:传输控制协议/因特网互联协议( Transmission Control Protocol/Internet Protocol),是Internet最基本、最广泛的协议。它定义了计算机如何连入因特网,以及数据如何在它们之间传输的标准。它的内部包含一系列的用于处理数据通信的协议,并采用了4层的分层模型(见上图),每一层都呼叫它的下一层所提供的协议来完成自己的需求。

协议的分类

通信的协议是比较复杂的,Java.net 包中包含的类和接口,它们提供低层次的通信细节。我们可以直接使用
java.net 包中提供了两种常见的网络协议的支持:
1.TCP:传输控制协议 (Transmission Control Protocol)。TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。
2.UDP:用户数据报协议(User Datagram Protocol)。UDP协议是一个面向无连接的协议。传输数据时,不需要建立连接,不管对方端服务是否启动,直接将数据、数据源和目的地都封装在数据包中,直接发送。每个数据包的大小限制在64k以内。它是不可靠协议,因为无连接,所以传输速度快,但是容易丢失数据。

IP地址

IP地址:指互联网协议地址(Internet Protocol Address),俗称IP。IP地址用来给一个网络中的计算机设备做唯一的编号。
IP地址的分类

IPv4:是一个32位的二进制数,通常被分为4个字节,表示成 a.b.c.d 的形式,例如 192.168.65.100 。其中a、b、c、d都是0~255之间的十进制整数,那么最多可以表示42亿个。
IPv6:由于互联网的蓬勃发展,IP地址的需求量愈来愈大,但是网络地址资源有限,使得IP的分配越发紧张。有资料显示,全球IPv4地址在2011年2月分配完毕。为了扩大地址空间,拟通过IPv6重新定义地址空间,采用128位地址长度,每16个字节一组,分成8组十六进制数,
表示ABCD:EF01:2345:6789:ABCD:EF01:2345:6789 ,号称可以为全世界的每一粒沙子编上一个网址,这样就解决了网络地址资源数量不够的问题。

常用的CMD命令:
查看本机IP地址,控制台输入:ipconfig
本机IP地址:127.0.0.1、localhost
查看网络是否连通,控制台输入:ping+空格+IP地址

端口号

端口号:用两个字节表示的整数,它的取值范围是065535。其中,01023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败。
端口号可以唯一标识设备中的进程(应用程序)了。IP地址可以唯一标识网络中的设备。
利用 协议 + IP地址 + 端口号 三元组合,就可以标识网络中的进程了,那么进程间的通信就可以利用这个标识与其它进程进行交互

二、Socket通信

Socket=IP+端口号
Socket类实现客户端套接字,套接字指的是两台设备之间通讯的端点。
Socket套接字概述:
网络上具有唯一标识的IP地址和端口号组合在一起才能构成唯一能识别的标识符套接字
Socket原理机制:

通信的两端都有Socket
网络通信其实就是Socket之间的通信
数据在两个Socket间通过IO传输
2.1 IP(InetAddress类)

InetAddress:Java为了方便我们对于IP地址的获取与操作,提供了InetAddress来表示互联网协议(IP)地址。
常用方法

public static InetAddress getByName(String host)
public String getHostAddress()//获取IP
public String getHostName()//获取主机名
getLocalHost();
public class Test {
    public static void main(String[] args) throws UnknownHostException {
        //参数传递主机名或者主机IP地址
        InetAddress ipaddress = InetAddress.getByName("DESKTOP-IVJSGR2");
        //通过getHostAddress方法获取主机IP
        String hostAddress = ipaddress.getHostAddress();
        //通过getHostName方法获取主机名
        String hostName = ipaddress.getHostName();
        System.out.println(hostAddress);
        System.out.println(hostName);
        //参数传递主机IP地址
        InetAddress address = InetAddress.getByName("192.168.2.213");
        System.out.println(address.getHostAddress());
        System.out.println(address.getHostName());
        //getLocalHost方法获取主机的地址
        InetAddress localHost = InetAddress.getLocalHost();
        //通过getHostName方法获取主机名
        System.out.println(localHost.getHostName());
        //通过getHostAddress方法获取主机IP
        System.out.println(localHost.getHostAddress());
    }
}
2.2 UDP协议

UDP协议:为应用程序提供了一种无需建立连接就可以发送封装的 IP 数据报的方法。无需连接,它除了给应用程序发送数据包功能并允许它们在所需的层次上架构自己的协议之外,几乎没有做什么特别的的事情。
特点:

1.将数据源和目的封装成数据包中,不需要建立连接
2.每个数据报的大小有一定的限制,不得超过64k
3.由于UDP不需要连接,是不可靠协议
4.由于不需要建立连接,速度快

Java针对这种协议提供了相应的Socket进行数据的传输。DatagramSocket 此类表示用来发送和接收数据报包的套接字。
客户端使用步骤:

  • 创建UDP协议对应的Socket,DatagramSocket类的对象。
  • 创建数据报包封装数据DatagramPacket
  • 发送数据DatagramSocket类的对象调用send方法
  • 释放资源
public class UDPClient {
    public static void main(String[] args) throws IOException {
        //构造DatagramSocket的对象
        DatagramSocket ds = new DatagramSocket();
        //写入的数据
        byte[] bytes = "你好,UDP连接".getBytes();
        //创建数据报包 用来封装数据
        //第三个参数是服务器的ip地址 第四个参数传递服务端的端口号
        DatagramPacket dp = new DatagramPacket(bytes, bytes.length, InetAddress.getByName("DESKTOP-IVJSGR2"), 9999);
        //发送数据
        ds.send(dp);
        //释放资源
        ds.close();
    }
}

服务端:

public class UDPServer {
    public static void main(String[] args) throws IOException {
        //构造服务端的对象   参数传递 端口号
        DatagramSocket ds = new DatagramSocket(9999);
        //创建数据报包接受数据
        byte[] bytes=new byte[1024];
        DatagramPacket dp = new DatagramPacket(bytes, bytes.length);
        System.out.println("服务端开启");
        //阻塞方法 开启服务端之后 程序会停止在receive方法处
        ds.receive(dp);
        //获取收到的信息
        byte[] data = dp.getData();
        //实际长度
        int length = dp.getLength();
        //获取客户端IP地址
        String address = dp.getAddress().getHostAddress();
        String s = new String(data, 0, length);
        System.out.println(address+"发来消息"+s);
    }
}

receive方法作为接受数据报包,当此方法返回时,DatagramPacket 的缓冲区填充了接收的数据。该数据报包中存在发送方的IP地址和发送方的计算机的端口号。该方法会在接受到数据之前处于阻塞状态。

服务端获取键盘录入的数据

客户端:

public class UDPClient {
    public static void main(String[] args) throws IOException {
        //UDP协议下 创建客户端的socket
        DatagramSocket datagramSocket = new DatagramSocket();
        //创建键盘录入
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        while (true){
            String s = br.readLine();
            if (s.equals("停止输入")){
                break;
            }
            //获取键盘录入的字节数组
            byte[] bytes = s.getBytes();
            //创建数据报包  这里我使用的是自己的IP地址
            DatagramPacket dp = new DatagramPacket(bytes, bytes.length, InetAddress.getByName("192.168.2.213"), 9999);
            //发送数据
            datagramSocket.send(dp);
        }
        //释放资源
        datagramSocket.close();
    }
}

客户端:

public class UDPServer {
    public static void main(String[] args) throws IOException {
        //UDP协议 创建服务端的Socket  参数传递端口号
        DatagramSocket ds = new DatagramSocket(9999);
        System.out.println("开启服务器,等待接受数据");
        byte[] bytes = new byte[1024];
        DatagramPacket dp = new DatagramPacket(bytes, bytes.length);
        ds.receive(dp);
        //获取传入的数据
        byte[] data = dp.getData();
        int length = dp.getLength();
        String ip = dp.getAddress().getHostAddress();
        System.out.println(ip+"发来数据--->"+new String(data,0,length));
    }
}

在这里插入图片描述
当客户端输入停止信息时,客户端断开连接。而服务端依旧处于开启状态。

多线程实现聊天室程序

主方法开启子线程用来接受数据,将发生数据抽取一个方法。
用户1

public class User1 {
    public static void main(String[] args) {
        //用户1创建子线程开启服务器,接受数据
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    DatagramSocket ds = new DatagramSocket(9999);
                    System.out.println("用户1的服务器已经开启,等待接受数据");
                    while (true){
                        //创建数据报包,接受数据
                        byte[] bytes = new byte[1024];
                        DatagramPacket dp = new DatagramPacket(bytes, bytes.length);
                        ds.receive(dp);
                        //取出数据
                        byte[] data = dp.getData();
                        int length = dp.getLength();
                        String ip = dp.getAddress().getHostAddress();
                        System.out.println(ip+"发来数据--->"+new String(data,0,length));
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        //主线程用来发送消息,我们可以将发消息的代码抽一个方法
        sendMessage();
    }

    private static void sendMessage() {
        //发送消息 键盘录入
        try {
            DatagramSocket ds = new DatagramSocket();
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            while (true){
                System.out.println("请输入发送给用户2的消息");
                String s = br.readLine();
                if (s.equals("结束聊天")){
                    break;
                }
                byte[] bytes = s.getBytes();
                //参数4为用户2的端口号
                DatagramPacket dp = new DatagramPacket(bytes, bytes.length, InetAddress.getByName("192.168.2.213"), 8888);
                ds.send(dp);
            }
            ds.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

用户2:

public class User2 {
    public static void main(String[] args) {
        //用户2创建子线程开启服务器,接受数据
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    DatagramSocket ds = new DatagramSocket(8888);
                    System.out.println("用户2的服务器已经开启,等待接受数据");
                    while (true){
                        //创建数据报包,接受数据
                        byte[] bytes = new byte[1024];
                        DatagramPacket dp = new DatagramPacket(bytes, bytes.length);
                        ds.receive(dp);
                        //取出数据
                        byte[] data = dp.getData();
                        int length = dp.getLength();
                        String ip = dp.getAddress().getHostAddress();
                        System.out.println(ip+"发来数据--->"+new String(data,0,length));
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        //主线程用来发送消息,我们可以将发消息的代码抽一个方法
        sendMessage();
    }

    private static void sendMessage() {
        //发送消息 键盘录入
        try {
            DatagramSocket ds = new DatagramSocket();
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            while (true){
                System.out.println("请输入发送给用户1的消息");
                String s = br.readLine();
                if (s.equals("结束聊天")){
                    break;
                }
                byte[] bytes = s.getBytes();
                //参数4为用户1的端口号
                DatagramPacket dp = new DatagramPacket(bytes, bytes.length, InetAddress.getByName("192.168.2.213"), 9999);
                ds.send(dp);
            }
            ds.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

用户1的运行界面
在这里插入图片描述
用户2的运行界面
在这里插入图片描述

2.2 TCP协议

TCP通信能实现两台计算机之间的数据交互,通信的两端,要严格区分为客户端(Client)与服务端(Server)。
两端通信的步骤:
1.服务端程序,需要先启动,等待客户端的连接
2.客户端主动连接服务器端,连接成功才能进行通信。服务端不可以主动连接客户端
在Java中,提供了两个类用于实现TCP通信程序
1.客户端:Socket类。创建 Socket 对象,向服务端发出连接请求,服务端响应请求,两者建立连接开始通信。
2.服务端:ServerSocket类。创建 ServerSocket 对象,相当于开启一个服务,并等待客户端的连接。

2.2.1 Socket类

Socket 类:该类实现客户端套接字,套接字指的是两台设备之间通讯的端点。
构造方法

public Socket(String host, int port) :创建套接字对象并将其连接到指定主机上的指定端口号。如果指定的host是null ,则相当于指定地址为回送地址。
构造方法举例:参数为服务端的ip地址和端口号
Socket client = new Socket("127.0.0.1", 6666);

成员方法

public InputStream getInputStream() : 返回此套接字的输入流。如果此Scoket具有相关联的通道,则生成的InputStream 的所有操作也关联该通道。
关闭生成的InputStream也将关闭相关的Socket。
public OutputStream getOutputStream() : 返回此套接字的输出流。如果此Scoket具有相关联的通道,则生成的OutputStream 的所有操作也关联该通道。
关闭生成的OutputStream也将关闭相关的Socket。
public void close() :关闭此套接字。一旦一个socket被关闭,它不可再使用。关闭此socket也将关闭相关的InputStream和OutputStream 。
public void shutdownOutput() : 禁用此套接字的输出流。任何先前写出的数据将被发送,随后终止输出流。
2.2.2 ServerSocket类

ServerSocket 类:这个类实现了服务器套接字,该对象等待通过网络的请求。
构造方法

public ServerSocket(int port) :使用该构造方法在创建ServerSocket对象时,就可以将其绑定到一个指定的端口号上,参数port就是端口号。
构造方法举例:参数为端口号
ServerSocket server = new ServerSocket(6666);

成员方法:

public Socket accept() :侦听并接受连接,返回一个新的Socket对象,用于和客户端实现通信。该方法会一直阻塞直到建立连接。
2.2.3 建立TCP的连接

TCP的通信分析:

1. 【服务端】启动,创建ServerSocket对象,等待连接。
2. 【客户端】启动,创建Socket对象,请求连接。
3. 【服务端】接收连接,调用accept方法,并返回一个Socket对象。
4. 【客户端】Socket对象,获取OutputStream,向服务端写出数据。
5. 【服务端】Scoket对象,获取InputStream,读取客户端发送的数据。
这个时候,服务端获取客户端发送的数据
服务端回写客户端数据
6. 【服务端】Socket对象,获取OutputStream,向客户端回写数据。
7. 【客户端】Scoket对象,获取InputStream,解析回写数据。
8. 【客户端】释放资源,断开连接。

客户端向服务端发送数据

public class TcpClient {
  
    public static void main(String[] args) throws IOException {
        //1.创建一个客户端对象Socket,构造方法绑定服务器的IP地址和端口号
        Socket socket=new Socket("127.0.0.1",8868);
        //2.使用Socket中getOutputStream()方法获取网络字节输出流OutputStream对象
        OutputStream os = socket.getOutputStream();
        //3.使用网络字节输出流OutputStream对象中的write()方法,给服务器发送数据
        os.write("你好服务器".getBytes());
        // 4.使用Socket中getInputStream()方法获取网络字节输入流InputStream对象
        InputStream is = socket.getInputStream();
        //5.使用网络字节输入流InputStream对象中的read()方法,读取服务器回写的数据
        byte[] bytes=new byte[1024];
        int len=is.read(bytes);
        System.out.println(new String(bytes,0,len));
        //6.释放资源(Socket)
        socket.close();
    }
}

服务端接受并且回写客户端数据

public class TcpServer {
    public static void main(String[] args) throws IOException {
        //1.构造方法传入指定的端口号
        ServerSocket serverSocket=new ServerSocket(8868);
        //2.accept方法,获取到请求的客户端对象Socket
        Socket socket = serverSocket.accept();
        //3.Socket中getInputStream()方法获取网络字节输入流InputStream对象
        InputStream is = socket.getInputStream();
        //4.read()方法,读取客户端发送的数据
        byte[] bytes=new byte[1024];
        int len=is.read(bytes);
        System.out.println(new String(bytes,0,len));
        //5.使用Socket中getOutputStream()方法获取网络字节输出流OutputStream对象
        OutputStream os = socket.getOutputStream();
        //6.write()方法,给客户端回写数据
        os.write("收到,谢谢".getBytes());
        //7.释放资源
        socket.close();
        serverSocket.close();
    }
}

三、TCP协议的文件上传

3.1 客户端键盘录入服务器控制台输出

客户端

public class UDPClient {
    public static void main(String[] args) throws IOException {
        //实现客户端键盘录入,服务端控制台打印
        //创建一个客户端对象Socket,构造方法绑定服务器的IP地址和端口号
        Socket socket = new Socket("192.168.2.213", 8989);
        //构造键盘录入字符流
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        while (true){
            String msg = br.readLine();
            if (msg.equals("断开连接")){
                break;
            }
            //Socket中getOutputStream()方法获取网络字节输出流OutputStream对象
            OutputStream os = socket.getOutputStream();
            os.write(msg.getBytes());
        }
        socket.close();
    }
}

服务端:

public class UDPServer {
    public static void main(String[] args) throws IOException {
        //构造方法传入指定的端口号
        ServerSocket serverSocket = new ServerSocket(8989);
        //accept方法,获取客户端对象Socket
        Socket socket = serverSocket.accept();
        //读取客户端发来的信息
        while (true){
            InputStream is = socket.getInputStream();
            byte[] bytes = new byte[1024];
            int len = is.read(bytes);
            String msg = new String(bytes, 0, len);
            if (msg.equals("断开连接")){
                    break;
            }
            //获取客户端的ip
            String ip = socket.getInetAddress().getHostAddress();
            System.out.println(ip+"发来消息"+msg);
        }
           serverSocket.close();
    }
}
3.2 客户端键盘录入服务器写到文本文件

客户端:

public class TCPClient {
    public static void main(String[] args) throws IOException {
        //客户端键盘录入服务器写到文本文件
        Socket socket = new Socket("192.168.2.213", 6666);
        BufferedReader bfr = new BufferedReader(new InputStreamReader(System.in));
        String line=null;
        //  //可以把通道中的字节流,包装成字符流
        OutputStream out = socket.getOutputStream();
        BufferedWriter bfw = new BufferedWriter(new OutputStreamWriter(out));
        while ((line=bfr.readLine())!=null){
            System.out.println("请输入消息");
            bfw.write(line);
            bfw.newLine();
            bfw.flush();
            if("断开连接".equals(line)){
                break;
            }
        }
        socket.close();
    }
}

服务端:

public class TCPServer {
    public static void main(String[] args) throws IOException {
        //服务器接收客户端发来的消息,把数据保存到文本文件中
        ServerSocket serverSocket = new ServerSocket(6666);
        System.out.println("服务端已经开启,等待连接。。。");
        Socket socket = serverSocket.accept();
        //把网络字节输入流包装成字符流
        InputStream in = socket.getInputStream();
        BufferedReader bfr = new BufferedReader(new InputStreamReader(in));
        String line = null;
        BufferedWriter bfw = new BufferedWriter(new FileWriter("msg.txt"));
        while ((line = bfr.readLine()) != null) {
            if("断开连接".equals(line)){
                break;
            }
            //写入文本文件
            bfw.write(line);
            bfw.newLine();
            bfw.flush();
            System.out.println(line);
        }
        serverSocket.close();
    }
}
3.3 文件上传案例

客户端:

public class TcpClient {
    public static void main(String[] args) throws IOException {
        //创建一个FileInPutStream本地输入流对象,构造方法传入数据源 
        FileInputStream fis=new FileInputStream("D:\\james\\6944620.jpg");
        Socket socket=new Socket("127.0.0.1",6666);
        OutputStream os = socket.getOutputStream();
        //使用FileInPutStream本地输入流对象的read方法,读取本地文件
        byte[] bytes=new byte[1024];
        int len=0;
        while ((len=fis.read(bytes))!=-1){
           // System.out.println(new String(bytes,0,len));
            os.write(bytes,0,len);
        }
        //While读取服务器返回的数据前
        System.out.println("while循环之前输出 可以打印3333333333");
        InputStream is = socket.getInputStream();
        while ((len=is.read(bytes))!=-1){
            System.out.println(new String(bytes,0,len));
        }
        //While读取服务器返回的数据后
        System.out.println("while循环之后输出 不可以打印444444444");
        //8.释放资源(FileInPutStream  Socket)
        fis.close();
        socket.close();
    }
}

服务端:

public class TcpServer {
    public static void main(String[] args) throws IOException {
		ServerSocket serverSocket=new ServerSocket(6666);
        Socket socket = serverSocket.accept();
        InputStream is = socket.getInputStream();

        //判断上传的文件夹是否存在,不存在需要创建一个文件夹
        //创建一个本地字节输出流FileOutPutStream对象,构造方法传入上传的目的地  
        File file=new File("D:\\upload");
        if (file.exists()){
            file.mkdir();
        }
        FileOutputStream fos=new FileOutputStream(file+"\\6944620.jpg");
 		//在while循环前输出一条语句
        System.out.println("while循环前:11111111111 可以打印");
        int len=0;
        byte[] bytes=new byte[1024];
        while ((len=is.read(bytes))!=-1){
            fos.write(bytes,0,len);
        }
        //在while循环后输出一条语句
        System.out.println("while循环后的语句:22222222  while死循环打印不到");
        socket.getOutputStream().write("上传成功".getBytes());
        socket.close();
        serverSocket.close();
    }
}

通过程序运行结果来看:
服务端的读取文件前的语句可以打印,但是读取文件后的语句不能够打印。
客户端的读取服务端回写的数据前可以打印,但是读取文件后的语句不能够打印。
原因在于:
客户端在读取文件时候不能够读取到数据的结束标记,当服务端接受客户端上传的文件进行读取的时候,导致服务器端读取数据时不能够停止。while循环读取不到结束标记,就会是一个死循环。导致服务端回写的数据也不能够被执行
解决方法:当客户端上传文件结束之后,写一个结束标记
在客户端上产文件结束之后,加入一行代码:

 socket.shutdownOutput();  对于 TCP 套接字,任何以前写入的数据都将被发送,并且后跟 TCP 的正常连接终止序列

优化代码:
1.循环接受的问题:
服务端,上述代码接受一个文件就关闭了,之后的用户无法进行上传,可以使用循环改进,使服务端接受不同用户的文件。
2.文件名称写死的问题:
服务端,保存文件的名称被写死,会导致服务器中只能够保留一个文件。
3.效率问题
服务端在接受大数据文件时,可能会消耗一定的时间,此时不能够接受其他用户的上传,所以我们可以使用多线程优化。
针对上面三个问题,设计一个优化的代码:
修改服务端的代码:

public class TcpServer {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(6666);
        //让服务器一直处于监听状态,死循环accept方法 .一个客户端上传文件,就让保存一个文件
        while (true) {
            Socket socket = serverSocket.accept();
            /*
            使用多线程提高程序的效率
            有一个客户端上传文件,开启一个线程,完成文件的上传
             */
            new Thread(()-> {
                //完成文件的上传
                    try {
                        InputStream is = socket.getInputStream();
        
                       //判断上传的文件夹是否存在,不存在需要创建一个文件夹
        
                        File file = new File("D:\\upload");
                        if (file.exists()) {
                            file.mkdir();
                        }
                        //自定义一个文件的命名规则:防止同名的文件被覆盖
                        String fileName = "itcast" + System.currentTimeMillis() + new Random().nextInt(999999) + "jpg";
                        FileOutputStream fos = new FileOutputStream(file + "\\" + fileName);
                        int len = 0;
                        byte[] bytes = new byte[1024];
                        while ((len = is.read(bytes)) != -1) {
                            fos.write(bytes, 0, len);
                        }
                        socket.getOutputStream().write("上传成功".getBytes());
                    } catch (IOException e) {
                    }
                }
            ).start();

        }
        //服务器不用关闭 一直接受客户端的文件上传,不要关闭
        //serverSocket.close();
    }
}

线程的方法体中存在异常。原方法没有抛出异常,实现时也不能够抛出异常,需要使用try…catch方法解决异常。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值