14 网络编程

网络编程

1、软件架构

  • C/S结构:全称为Client/Server结构,是指客户端和服务器结构。
  • B/S结构 :全称为Browser/Server结构,是指浏览器和服务器结构。

两种结构各有优势,但是无论哪种结构,都离不开网络的支持。网络编程,就是在一定的协议下,实现两台计算机的通信的程序。

  • 网络通信协议:通信协议是对计算机必须遵守的规则,只有遵守这些规则,计算机之间才能进行通信。这就好 比在道路中行驶的汽车一定要遵守交通规则一样,协议中对数据的传输格式、传输速率、传输步骤等做了统一 规定,通信双方必须同时遵守,最终完成数据交换。

  • TCP/IP协议: 传输控制协议/因特网互联协议( Transmission Control Protocol/Internet Protocol),是Internet 最基本、最广泛的协议。它定义了计算机如何连入因特网,以及数据如何在它们之间传输的标准。它的内部包 含一系列的用于处理数据通信的协议,并采用了4层的分层模型,每一层都呼叫它的下一层所提供的协议来完成自己的需求。

通信的协议还是比较复杂的, java.net 包中包含的类和接口,它们提供低层次的通信细节。我们可以直接使用这些 类和接口,来专注于网络程序开发,而不用考虑通信的细节。
java.net 包中提供了两种常见的网络协议的支持:

  • TCP:传输控制协议 (Transmission Control Protocol)。TCP协议是面向连接的通信协议,即传输数据之前,在 发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。 三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠。 第一次握手,客户端向服务器端发出连接请求,等待服务器确认。 第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求。 第三次握手,客户端再次向服务器端发送确认信息,确认连接。

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

  • UDP:用户数据报协议(User Datagram Protocol)。UDP协议是一个面向无连接的协议。传输数据时,不需要 建立连接,不管对方端服务是否启动,直接将数据、数据源和目的地都封装在数据包中,直接发送。每个数据 包的大小限制在64k以内。它是不可靠协议,因为无连接,所以传输速度快,但是容易丢失数据。日常应用中, 例如视频会议、QQ聊天等。 每次发送的数据最大为64kb

2、网络通信三要素

协议

协议:计算机网络通信必须遵守的规则,已经介绍过了,不再赘述。

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 ,号称可以为全世界的每一粒沙子编上一个网址, 这样就解决了网络地址资源数量不够的问题。
  • 特殊的IP地址
    本机IP地址: 127.0.0.1 、 localhost 。

端口号

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

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


  • 利用协议+ IP地址+端口号 三元组合,就可以标识网络中的进程了,那么进程间的通信就可以利用这个标识与其它 进程进行交互。

TCP通信

TCP的客户端

在java中,有一个类,可以表示TCP的客户端,这个类叫做Socket

  • Socket构造方法:
    Socket(String host, int port):
    参数host表示目标服务器的IP地址。
    参数port表示目标服务器程序的端口。

  • Socket的其他方法:
    OutputStream getOutStream():获取输出流,用来发送数据。
    InputStream getInputStream():获取输入流,用来接收数据。
    void close():释放资源。

  • TCP客户端的实现步骤:
    (1)创建Socket对象,并指定要连接的服务器的IP地址以及服务器程序的端口号。
    (2)调用Socket的getOutputStream方法获取输出流,用来发送数据。
    (3)调用输出流的write方法发送数据。
    (4)调用Socket的getInputStream方法获取输入流,用来接收数据。
    (5)调用输入流的方法接收数据。
    (6)释放资源。

public class Demo01Client {
    public static void main(String[] args) throws IOException {
        //1. 创建Socket对象,并指定要连接的服务器的ip地址以及服务器程序的端口号。
        //创建Socket对象的时候,会主动连接服务器,如果连接不成功,就会报错。三次握手就是在这一步发生的。
        Socket socket = new Socket("127.0.0.1", 9527);
        //2. 调用Socket的getOutputStream方法获取输出流,用来发送数据。
        //获取到的输出流目的地是服务器
        OutputStream out = socket.getOutputStream();
        //3. 调用输出流的write方法发送数据。
        out.write("你好".getBytes());
        //4. 调用Socket的getInputStream方法获取输入流, 用来接收数据。
        InputStream in = socket.getInputStream();
        //5. 调用输入流的read方法接收数据。
        byte[] bArr = new byte[1024];
        int len = in.read(bArr);
        System.out.println(new String(bArr, 0, len));
        //6. 释放资源。
        socket.close();
    }

TCP案例的服务器的实现

在java中有一个类叫做ServerSocket,这个类表示服务器,我们可以使用这个类实现服务器程序。

  • ServerSocket的构造方法:
    ServerSocket(int port):
    参数要传递int类型的端口号,该端口号表示服务器程序自己的端口号。

  • ServerSocket的其他方法:
    Socket accept():监听获取客户端Socket(客户端请求)。
    void close();释放资源。

  • 服务器程序的实现步骤:
    (1)创建ServerSocket对象,表示服务器。
    (2)调用服务器的accept方法,监听并获取客户端Socket。
    (3)调用Socket对象的输入流,用来接收数据。
    (4)调用输入流的read方法,去接收客户端发送过来的数据。
    (5)通过Socket对象获取输出流,用来发送数据。
    (6)调用输出流的write方法,给客户端发送数据。
    (7)释放资源。

public class Demo02Server {
    public static void main(String[] args) throws IOException {
        //1. 创建ServerSocket对象,表示服务器。
        ServerSocket serverSocket = new ServerSocket(9527);
        //2. 调用服务器的accept方法,监听并获取客户端Socket。
        Socket socket = serverSocket.accept();
        //3. 调用Socket对象获取输入流,用来接收数据。
        InputStream in = socket.getInputStream();
        //4. 调用输入流的read方法,去接收客户端发送过来的数据。
        byte[] bArr = new byte[1024];
        int len = in.read(bArr);
        System.out.println(new String(bArr, 0, len));
        //5. 通过Socket对象获取输出流,用来发送数据。
        OutputStream out = socket.getOutputStream();
        //6. 调用输出流的write方法,给客户端发送数据。
        out.write("收到!".getBytes());
        //7. 释放资源
        socket.close();
        serverSocket.close();

    }

文件上传案例

上传案例的客户端

对于客户端来说,要做的事情是读取自己电脑文件的字节,将这些字节写给服务器。还需要接收服务器发送过来的消息。

客户端的实现步骤:
1. 创建Socket客户端对象
2. 创建FileInputStream流,用来读取客户端自己电脑的文件。
3. 通过Socket获取OutputStream流, 用来向服务器写数据。
4. 开始读写,一次读写一个字节数组。 每读取到数据,就将读取到的数据写到服务器中。
5. 释放资源。
6. 通过Socket获取InputStream流, 用来读取服务器发送过来的数据。
7. 通过InputStream调用read方法读取数据
8. 释放资源

public class Demo01Client {
    public static void main(String[] args) throws IOException {
        //1. 创建Socket客户端对象
        Socket socket = new Socket("127.0.0.1", 9527);
        //2. 创建FileInputStream流,用来读取客户端自己电脑的文件。
        InputStream is = new FileInputStream("d:\\client\\aa.jpg");
        //3. 通过Socket获取OutputStream流, 用来向服务器写数据。
        OutputStream out = socket.getOutputStream();
        //4. 开始读写,一次读写一个字节数组。 每读取到数据,就将读取到的数据写到服务器中。
        byte[] bArr = new byte[1024];
        int len;
        while ((len = is.read(bArr)) != -1) {
            //如果条件成立,表示读取到了数据,那么就将读取到的数据写到服务器中
            out.write(bArr, 0, len);
        }
        //告诉服务器,我已经操作完了,以后不会再给你写数据了,你也不要等我了
        socket.shutdownOutput();

        //5. 释放资源。
        is.close();
        //6. 通过Socket获取InputStream流, 用来读取服务器发送过来的数据。
        InputStream in = socket.getInputStream();
        //7. 通过InputStream调用read方法读取数据
        len = in.read(bArr);
        System.out.println(new String(bArr, 0, len));
        //8. 释放资源
        socket.close();
    }
}

TCP上传案例的服务器

对于服务器来说,要做的事情是读取客户端发送过来的字节, 然后将这些字节写到自己电脑。 然后给客户端回复(上传成功)

实现步骤:
1. 创建ServerSocket表示服务器。
2. 调用ServerSocket的accept方法,监听获取客户端请求。
3. 调用Socket的getInputStream获取输入流,用来读取客户端发送过来的数据。
4. 创建FileOutputStream字节输出流,用来向服务器自己电脑写数据。
5. 开始读写, 一次读写一个字节数组。 每读取到数据,就将读取到的数据写到自己电脑。
6. 释放资源、
7. 调用Socket的getOutputStream获取输出流,用来给客户端发送数据。
8. 通过该输出流给客户端写数据。
9. 释放资源。

public class Demo02Server {
    public static void main(String[] args) throws IOException {
        //1. 创建ServerSocket表示服务器。
        ServerSocket serverSocket = new ServerSocket(9527);
        //2. 调用ServerSocket的accept方法,监听获取客户端请求。
        Socket socket = serverSocket.accept();
        //3. 调用Socket的getInputStream获取输入流,用来读取客户端发送过来的数据。
        InputStream in = socket.getInputStream();
        //4. 创建FileOutputStream字节输出流,用来向服务器自己电脑写数据。
        //OutputStream os = new FileOutputStream("d:\\server\\aa.jpg");
        //OutputStream os = new FileOutputStream("d:\\server\\" + System.currentTimeMillis() + ".jpg");
        //有一个类叫做UUID,里面有一个方法叫做randomUUID可以获取一个随机不重复的字符序列,然后通过该字符序列调用toString方法可以得到字符串
        OutputStream os = new FileOutputStream("d:\\server\\" + UUID.randomUUID().toString() + ".jpg");


        //5. 开始读写, 一次读写一个字节数组。 每读取到数据,就将读取到的数据写到自己电脑。
        byte[] bArr = new byte[1024];
        int len;
        while ((len = in.read(bArr)) != -1) {
            //如果条件成立,就表示读取到了数据,就将读取到的数据写到服务器自己电脑
            os.write(bArr, 0, len);
        }
        //6. 释放资源、
        os.close();
        //7. 调用Socket的getOutputStream获取输出流,用来给客户端发送数据。
        OutputStream out = socket.getOutputStream();
        //8. 通过该输出流给客户端写数据。
        out.write("上传成功".getBytes());
        //9. 释放资源。
        socket.close();
        serverSocket.close();
    }
}

服务器改进版

  • 之前的服务器接收一次上传请求后服务器就会停止。

  • 改进: 服务器可以一直给客户端执行上传任务,如果第一个客户端上传完了,那么也可以继续给后面的客户端执行上传任务。
    我们可以使用死循环, 在死循环中,让服务器一直监听客户端的请求,每监听到客户端的请求,那么就给该客户端执行上传任务。

  • 死循环版本的服务器存在的问题: 如果有一个客户端上传了非常大的文件,服务器会一直给这个客户端执行上传操作,那么就无法监听下一个客户端的请求了。

  • 问题原因:当程序启动JVM会创建main线程,执行main方法,main线程执行accept方法监听客户端的请求,如果有客户端发来请求了,main线程会向下执行,去该该客户端执行上传任务,如果该上传任务没有执行完,main线程无法向下继续执行, 就无法去监听下一个客户端的请求了。

    • 解决方式: 使用多线程。 使用main线程监听客户的请求,如果有客户端来请求了,那么就创建一个新的线程,使用新的线程给该客户端执行上传操作。 这样新线程执行上传操作的同时不会影响到main线程,main线程可以继续往下执行,去监听下一个客户端的请求。
@SuppressWarnings("all")
public class Demo04ThreadServer {
    public static void main(String[] args) throws IOException {
        //1. 创建ServerSocket表示服务器。
        ServerSocket serverSocket = new ServerSocket(9527);
        //在死循环中,让服务器一直监听客户端的请求,每监听到客户端的请求,那么就给该客户端执行上传任务。
        while (true) {
            //2. 调用ServerSocket的accept方法,监听获取客户端请求。
            Socket socket = serverSocket.accept();
            //创建新的线程,给客户端执行上传任务
            new Thread(() -> {
                try {
                    //3. 调用Socket的getInputStream获取输入流,用来读取客户端发送过来的数据。
                    InputStream in = socket.getInputStream();
                    //4. 创建FileOutputStream字节输出流,用来向服务器自己电脑写数据。
                    OutputStream os = new FileOutputStream("d:\\server\\" + UUID.randomUUID().toString() + ".jpg");
                    //5. 开始读写, 一次读写一个字节数组。 每读取到数据,就将读取到的数据写到自己电脑。
                    byte[] bArr = new byte[1024];
                    int len;
                    while ((len = in.read(bArr)) != -1) {
                        //如果条件成立,就表示读取到了数据,就将读取到的数据写到服务器自己电脑
                        os.write(bArr, 0, len);
                    }
                    //6. 释放资源、
                    os.close();
                    //7. 调用Socket的getOutputStream获取输出流,用来给客户端发送数据。
                    OutputStream out = socket.getOutputStream();
                    //8. 通过该输出流给客户端写数据。
                    out.write("上传成功".getBytes());
                    //9. 释放资源。
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }).start();

        }

    }
}

猜你喜欢

转载自blog.csdn.net/weixin_45507013/article/details/99547124
今日推荐