Java【网络编程】

本文介绍了Java网络编程的基础知识,包括C/S和B/S架构、网络通信协议如TCP/IP,以及Java中的网络编程元素。详细讲解了TCP通信的Socket和ServerSocket类,并给出了简单的TCP客户端和服务器程序示例。此外,还探讨了文件上传的TCP实现和模拟B/S服务器的案例,强调了服务器优化和多线程处理的重要性。
摘要由CSDN通过智能技术生成

Java【网络编程】


一、网络编程入门

1.1 软件结构
  • C/S结构:全称为Client/Server结构,指客户端和服务结构,常见程序有QQ、迅雷等软件。
    在这里插入图片描述

  • B/S结构:全称为Browser/Server结构,是指浏览器和服务器的结构。常见的浏览器有谷歌、火狐等。
    在这里插入图片描述
    两种架构都离不开网络的支持。网络编程,就是在一定的协议下,实现两台计算机的通信的程序。

1.2 网络通信协议
  • 网络通信协议:计算机连接和通信需要遵守的规则。

  • TCP/IP协议:传输控制协议/因特网互联协议(Transmission Control Protocol/Internet Protocol)。

    采用了4层的分层模型,TCP/IP协议中的四层分别是应用层、传输层、网络层和链路层。

1.3 协议分类

java.net包中包含的类和接口,用来专注于网络程序开发。

java.net包中提供了两种常见的网络协议的支持:

  • UDP:用户数据报协议(User Datagram Protocol),无连接通信协议,特点是数据被限制在64kb以内,超出这个范围就不能发送了。数据报:网络传输的基本单位。
  • TCP:传输控制协议(Transmission Control Protocol),面向连接的通信协议,每次连接的创建都要经过**”三次握手“**。
1.4 网络编程三要素

协议:计算机实现网络通信必须遵守的规则

IP地址:互联网协议地址(Internet Protocol Address)

  • IPv4:一个32位的二进制数,通常被分为4个字节,表示为a.b.c.d的形式。最多42亿个。
  • IPv6:128位二进制数,每16个字节一组,分为八组十六进制数,号称可以为全世界每一粒沙子编一个地址,解决了网络地址资源紧张的问题。

端口号:端口号标识了一个主机上进行通信的不同的应用程序。常见的端口号:

  • 网络端口:80
  • 数据库:mysql3306 oracle1521
  • Tomcat服务器:8080
二、TCP通信程序

2.1 概述

TCP通信的客户端:向服务器发送连接请求,给服务器发送数据,读取服务器回写的数据。表示客户端的类:java.net.Socket

TCP通信的服务器端:接受客户端的请求,读取客户端发送的数据,给客户端回写数据。表示服务器的类:

java.net.ServerSocket

2.2 Socket类

java.net.Socket:此类实现客户端套接字(也可以叫”套接字“),套接字是两台机器间通信的端点。

套接字:包含了IP地址和端口号的网络单位。

  • 构造方法

    Socket(String host, int port) 创建一个套接字并将其连接到指定主机上的指定端口号

  • 成员方法

OutputStream getOutputStream() 返回此套接字的输出流

InputStream getInputStream() 返回此套接字的输入流

void close() 关闭此套接字

  • 注意

    1)客户端和服务端进行交互,必须使用Socket中提供的网络流,不能使用自己创建的流对象

    2)当我们创建客户端对象Socket的时候,就会去请求服务器和服务器经过3次握手建立连接通路。如果服务器没有启动,就会抛出异常。

2.3 SocketServer类

java.net.ServerSocket:此类实现服务器套接字。

  • 构造方法

    ServerSocket(int port) 创建绑定到特点端口的服务器套接字

  • 成员方法

    Socket accept() 监听并接受到此套接字的连接

2.3 简单的TCP网络程序

TCPClient

public class TCPClient {
    public static void main(String[] args) throws IOException {
        //1、创建一个客户端对象Socket,构造方法绑定服务器的IP地址和端口号
        Socket socket = new Socket("127.0.0.1",8888);
        //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();
    }
}

TCPServer

public class TCPServer {
    public static void main(String[] args) throws IOException {
        //1、创建服务器ServerSocket对象和系统要指定的端口号
        ServerSocket ss = new ServerSocket(8888);
        //2、使用ServerSocket对象中的方法accept,获取到请求的客户端对象Socket
        Socket socket = ss.accept();
        //3、使用Socket对象中的方法getInputStream(), 获取网络字节输入流InputStream对象
        InputStream is = socket.getInputStream();
        //4、使用网络字节输入流InputStream对象中的方法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、使用网络字节输出流OutputStream对象中的方法write(),给客户端回写数据
        os.write("你好客户端,服务器收到".getBytes());
        //7、释放资源(Socket,ServerSocket)
        ss.close();
    }
}
三、综合案例

3.1 文件上传案例
  • 分析

明确数据源和目的地数据源:/Users/yanzhuang/Desktop/3.jpg 目的地:服务器

【客户端】输入流,从硬盘读取数据到程序中。

【客户端】输出流,写出文件数据到服务端。

【服务端】输入流,读取文件数据到服务端程序。

【服务端】输出流,写出文件数据到服务器硬盘中。

TCPClient

public class TCPClient {
    public static void main(String[] args) throws IOException {
        //1、创建一个本地字节输入流FileInputStream对象,构造方法中绑定要读取的数据源
        FileInputStream fis = new FileInputStream("/Users/yanzhuang/Desktop/3.jpg");
        //2、创建一个客户端Socket对象,绑定服务器的IP地址和端口号
        Socket socket = new Socket("127.0.0.1",8888);
        //3、使用Socket中的方法getOutputStream()获取网络字节输出流对象
        OutputStream os = socket.getOutputStream();
        //4、使用本地字节输入流对象中的read(),读取本地文件
        byte[] bytes = new byte[1024];
        int len = 0;
        while((len = fis.read(bytes)) != -1) {
            //5、使用网络字节输出流对象中的write(),把读取的文件上传到服务器
            // 结束标记不会传到服务器中,服务器会陷入死循环
            os.write(bytes);
        }
        /*
            public void shutdownOutput() throws IOException
            使用socket的shutdownOutput方法结束输入流,将结束标记发送给服务器
         */
        socket.shutdownOutput();
        //6、使用Socket中的方法getInputStream,获取网络字节输入流对象
        InputStream is = socket.getInputStream();
        //7、使用网络字节输入流对象中的read()读取服务器回写的数据
        while ((len = is.read(bytes)) != -1){
            System.out.println(new String(bytes,0,len));
        }
        //8、释放资源
        fis.close();
        socket.close();
    }
}

TCPServer

public class TCPServer {
    public static void main(String[] args) throws IOException {
        //1、创建一个服务器SocketServer对象,和系统要制定的端口号
        ServerSocket ss = new ServerSocket(8888);
        //2、使用SocketServer对象中的accept方法,获取请求的客户端socket对象
        Socket socket = ss.accept();
        //3、使用Socket对象的getInputStream(),获取网络字节输入流对象
        InputStream is = socket.getInputStream();
        //4、判断/Users/yanzhang/Desktop/upload是否存在,不存在即创建目录
        File file = new File("/Users/yanzhuang/Desktop/upload");
        if(!file.exists()){
            file.mkdirs();
        }
        //5、创建一个本地字节输出流FileOutputStream对象,构造方法中绑定需要输出的目的地
        FileOutputStream fos = new FileOutputStream(file+"/a.jpg");
        //6、使用网络字节输入流对象的read()方法,读取客户端上传的文件
        int len = 0;
        byte[] bytes = new byte[1024];
        while((len = is.read(bytes)) != -1) {
            System.out.println(new String(bytes,0,len));
            //7、使用本地字节输出流对象的write()方法,把读到的文件保存到服务器的硬盘上
            fos.write(bytes,0,len);
        }
        //8、使用Socket对象的getOutputStream(),获取网络字节输出流对象
        OutputStream os = socket.getOutputStream();
        //9、使用网络字节输出流对象的write()方法,回写给客户端"上传成功"
        os.write("上传成功".getBytes());
        //10、释放资源
        fos.close();
        socket.close();
        ss.close();
    }
}
  • 服务器程序的优化

自定义一个命名规则:放置同名的文件被覆盖

使服务器始终处于监听状态:有一个客户端上传文件,就保存一个文件

多线程提高效率:使用多线程处理多个客户端套接字的请求

优化后服务器端代码:

public class TCPServer {
    public static void main(String[] args) throws IOException {
        //1、创建一个服务器SocketServer对象,和系统要制定的端口号
        ServerSocket ss = new ServerSocket(8888);
        /*
            优化2:
            使用死循环让服务器一直处于监听状态,有一个客户端上传文件,就保存一个文件
         */
        while(true) {
            Socket socket = ss.accept();
            /*
                优化3:
                进一步提高效率,使用多线程处理多个客户端套接字的请求
             */
            new Thread(new Runnable() {
                @Override
                public void run() {
                    // 提升变量的作用域
                    FileOutputStream fos = null;
                    try{
                        //3、使用Socket对象的getInputStream(),获取网络字节输入流对象
                        InputStream is = socket.getInputStream();
                        //4、判断/Users/yanzhang/Desktop/upload是否存在,不存在即创建目录
                        File file = new File("/Users/yanzhuang/Desktop/upload");
                        if(!file.exists()){
                            file.mkdirs();
                        }
                    /*
                        优化1:
                        自定义一个命名规则,防止同名的文件被覆盖
                        规则:域名 + 毫秒值 + 随机数
                     */
                        String fileName = "zx" + System.currentTimeMillis() + new Random().nextInt(999999) + ".jpg";
                        //5、创建一个本地字节输出流FileOutputStream对象,构造方法中绑定需要输出的目的地
                        fos = new FileOutputStream(file+"/" + fileName);
                        //6、使用网络字节输入流对象的read()方法,读取客户端上传的文件
                        int len = 0;
                        byte[] bytes = new byte[1024];
                        while((len = is.read(bytes)) != -1) {
                            //7、使用本地字节输出流对象的write()方法,把读到的文件保存到服务器的硬盘上
                            fos.write(bytes,0,len);
                        }
                        //8、使用Socket对象的getOutputStream(),获取网络字节输出流对象
                        OutputStream os = socket.getOutputStream();
                        //9、使用网络字节输出流对象的write()方法,回写给客户端"上传成功"
                        os.write("上传成功".getBytes());
                    } catch (IOException e) {
                        e.printStackTrace();
                    } finally {
                        //10、释放资源
                        try {
                            fos.close();
                            socket.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        }
        // 服务器需要一直处于启动状态,进行监听
        //ss.close();
    }
}
3.2 模拟B/S服务器

浏览器服务器交互方式的服务器程序:

public class TCPServer {
    public static void main(String[] args) throws IOException {
        //创建一个服务器SocketServer对象,和系统要制定的端口号
        ServerSocket ss = new ServerSocket(8080);
        /*
            浏览器解析服务器回写的html页面,如果页面中有图片,那么浏览器就会单独的开一个线程,读取服务器的图片
            我们就让服务器一直处在监听状态,客户端请求一次,服务器就回写一次
         */
        while (true) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try{
                        //使用SocketServer对象中的accept方法,获取请求的客户端socket对象(浏览器)
                        Socket socket = ss.accept();
                        //使用Socket对象的getInputStream(),获取网络字节输入流对象
                        InputStream is = socket.getInputStream();

                        //把is网络字节输入流转换为字符缓冲输入流
                        BufferedReader br = new BufferedReader(new InputStreamReader(is));
                        //把客户端请求信息的第一行读取出来
                        String line = br.readLine();
                        //对字符串进行切割,获取需要的部分
                        String[] arr = line.split(" ");
                        //把前面的路径/ 去掉,截取
                        String htmlPath = arr[1].substring(1);

                        //创建一个本地字节输入流,构造方法中绑定要读取的html路径
                        FileInputStream fis = new FileInputStream(htmlPath);
                        //使用Socket中的方法getOutputStream获取网络字节输出流
                        OutputStream os = socket.getOutputStream();
                        //写入HTTP协议的响应头,固定写法
                        os.write("HTTP/1.1 200 OK\r\n".getBytes());
                        os.write("Content-Type:text/html\r\n".getBytes());
                        // 必须要写入空行,否则浏览器不解析
                        os.write("\r\n".getBytes());

                        //一读一写复制文件,把服务器读取的html文件回写到客户端(网页)
                        int len = 0;
                        byte[] bytes = new byte[1024];
                        while ((len = fis.read(bytes)) != -1){
                            os.write(bytes, 0, len);
                        }

                        //释放资源
                        fis.close();
                        socket.close();
                        ss.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }


    }
}

注意:浏览器解析服务器回写的html页面,如果页面中有图片,那么浏览器就会单独的开一个线程,读取服务器的图片,我们可以使用循环让服务器一直处在监听状态,客户端请求一次,服务器就回写一次。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值