软件结构

C/S结构:(Client/Server)客户端和服务器结构,常见QQ
image.png

B/S结构:(Browser/Server)浏览器和服务器结构
image.png

网络通信协议

TCP/IP协议

4层的分层模型

image.png

协议分类

UDP(用户数据报协议)

无连接通信协议
image.png
image.png
特点:

数据被限制在64kb以内,超出这个范围就不能发送了

数据报(Datagram):网络传输的基本单位

TCP(传输控制协议)

面向连接的通信协议
image.png
image.png

可以保证传输数据的安全

网络编程三要素

协议、IP地址、端口号

image.png
image.png

端口号:

逻辑端口,可通过软件查看端口号
网络软件打开时,操作系统会为网络软件分配一个随机的端口号或者网络软件向操作系统要指定的端口号

由两个字节组成:0-65535

1024之前的端口号用户不能使用,已被系统分配给已知网络软件,网络软件端口号不能重复

使用IP地址+端口号,可保证数据准确无误的发送到对方指定软件上

image.png

TCP通信程序

通信步骤:

  1. 服务端先启动
  2. 客户端主动连接服务器端,成功后才能通信;服务端不可主动连接

客户端:
java.net.Socket
创建Socket对象

服务端:
java.net.ServerSocket
创建ServerSocket对象

创建的IO对象为字节流对象

服务器是没有IO流的,但可以获取到请求的客户端对象Socket,使用每个客户端Socket中提供的IO流和客户端进行交互

服务器使用客户端的字节输入流读取客户端发送的数据
服务器使用客户端的字节输出流给客户端回写数据

服务器使用客户端的流和客服端交互,利用方法server.accept

image.png

Socket类

java.net.Socket
实现客户端套接字,套接字是两台机器间通信的端点
套接字:包含了IP地址和端口号的网络单位

构造方法:

Socket(String host, int port)创建一个流套接字并将其连接至指定主机上的指定端口号
参数:
    String host:服务器主机的名称/服务器的IP地址
    int port:服务器的端口号

成员方法

OutputStream getOutputStream()返回套接字的输出流
InputStream getInputStream() 返回套接字的输入流
void close() 关闭套接字

实现步骤:

  1. 创建一个客户端对象Socket,构造方法绑定服务器的IP地址和端口号
  2. 使用Socket对象中getOutputStream方法获取网络字节输出流OutputStream对象
  3. 使用网络字节输出流OutputStream对象中write方法,给服务器发送数据
  4. 使用Socket对象中的getInputStream方法获取网络字节输入流InputStream对象
  5. 使用网络字节输入流InputStream对象中read方法,读取服务器回写的数据
  6. 释放资源(Socket)

注意:

  1. 客户端和服务器端进行交互,必须使用Socket中提供的网络流,禁止使用自己创建的流对象
  2. 创建客户端对象Socket时,请求服务器和客户端经过三次握手建立通路

    1. 服务器未启动,抛出异常
    2. 已启动,进行交互
    public static void main(String[] args) throws IOException {
        //Step1
        Socket socket = new Socket("127.0.0.1", 8888);
        //Step2
        OutputStream os = socket.getOutputStream();
        //Step3
        os.write("你好服务器".getBytes());
        //Step4
        InputStream is = socket.getInputStream();
        //Step5
        byte[] bytes = new byte[1024];
        int len = is.read(bytes);

        System.out.println(new String(bytes, 0, len));

        //Step6
        socket.close();
    }

ServerSocket类

java.net.ServerSocket
实现客户端套接字,套接字是两台机器间通信的端点
套接字:包含了IP地址和端口号的网络单位

构造方法:

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

服务器必须明确是哪个客户端请求的服务器

成员方法

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

实现步骤:

  1. 创建一个服务器对象ServerSocket对象和系统要指定的端口号
  2. 使用ServerSocket对象中accept方法,获取请求的客户端对象Socket
  3. 使用Socket对象中getInputStream方法获取网络字节输入流IntputStream对象
  4. 使用网络字节输出流getInputStream对象中read方法,读取客户端给服务器的数据
  5. 使用Socket对象中的getOutputStream方法获取网络字节输出流OutputStream对象
  6. 使用网络字节输入流getOutputStream对象中write方法,给客户端回写数据
  7. 释放资源(Socket,ServerSocket)
    public static void main(String[] args) throws IOException {
        //Step1
        ServerSocket ss = new ServerSocket(8888);
        //Step2
        Socket socket = ss.accept();
        //Step3
        InputStream is = socket.getInputStream();
        //Step4
        byte[] bytes = new byte[1024];
        int len = is.read(bytes);

        System.out.println(new String(bytes, 0, len));
        //Step5
        OutputStream os = socket.getOutputStream();
        //Step6
        os.write("thanks".getBytes());
        //Step7
        socket.close();
        ss.close();
    }

案例

文件上传案例
image.png

客户端
image.png

    public static void main(String[] args) throws IOException {
        //Step1:本地流读取文件
        FileInputStream fis = new FileInputStream("E:\\A JI\\program\\java\\学习路线.png");
        FileOutputStream fos = new FileOutputStream("E:\\A JI\\program\\java\\学习路线12.png");
        //Step2:创建Socket对象,绑定服务器的端口号和IP地址
        Socket socket = new Socket("127.0.0.1", 8888);
        //Step3:获取网络字节输出流OutputStream对象
        OutputStream os = socket.getOutputStream();
        //Step4:本地字节输入流FileInputStream对象的read,读取本地文件
        int len = 0;
        byte[] bytes = new byte[1024];
        while((len = fis.read(bytes)) != -1) {
            //Step5:使用网络字节输出流OutputStream对象的write方法,上传到服务器
            os.write(bytes, 0, len);
        }
        //Step6:Socket方法getInputStream,获取网络字节输入流InputStream对象
        InputStream is = socket.getInputStream();
        //Step7:网络字节输入流InputStream对象中read方法读取服务器回写数据
        while((len = is.read(bytes)) != -1) {
            System.out.println(new String(bytes, 0, len));
            //从服务器接收的数据放置文件中
            fos.write(bytes);
        }
        //Step8:释放资源
        fis.close();
        socket.close();
    }

服务器端
image.png

    public static void main(String[] args) throws IOException {
        //Step1
        ServerSocket ss = new ServerSocket(8888);
        //Step2
        Socket socket = ss.accept();
        //Step3
        InputStream is = socket.getInputStream();
        //Step4
        File file = new File("E:\\A JI\\program\\java\\test-txt");
        if (!file.exists()) {
            file.mkdir();
        }
        //Step5
        FileOutputStream fos = new FileOutputStream(file + "\\1.jpg");
        //Step6
        int len = 0;
        byte[] bytes = new byte[1024];
        while((len = is.read(bytes)) != -1) {
            //Step7
            fos.write(bytes, 0, len);
        }
        //Step8、Step9
        OutputStream os = socket.getOutputStream();
        os.write("上传成功".getBytes());

        fos.close();
        socket.close();
        ss.close();
    }
两程序都未能停止:

进入阻塞状态,read方法在无输入可用的情况,进入阻塞状态
客户端中read读取文件时,虽然读取到-1停止,但是-1并没有进入while循环,也就没有把结束标记写给服务器
服务器回写也就没有结束标记,客户端接收read就没有结束标记,进入阻塞状态

image.png

image.png

解决方法:
客户端上传完文件,给服务器写一个结束标记
image.png

image.png

    public static void main(String[] args) throws IOException {
        //Step1:本地流读取文件
        FileInputStream fis = new FileInputStream("E:\\A JI\\program\\java\\学习路线.png");
        FileOutputStream fos = new FileOutputStream("E:\\A JI\\program\\java\\学习路线12.png");
        //Step2:创建Socket对象,绑定服务器的端口号和IP地址
        Socket socket = new Socket("127.0.0.1", 8888);
        //Step3:获取网络字节输出流OutputStream对象
        OutputStream os = socket.getOutputStream();
        //Step4:本地字节输入流FileInputStream对象的read,读取本地文件
        int len = 0;
        byte[] bytes = new byte[1024];
        while((len = fis.read(bytes)) != -1) {
            //Step5:使用网络字节输出流OutputStream对象的write方法,上传到服务器
            os.write(bytes, 0, len);
        }

        //防止阻塞状态
        socket.shutdownOutput();
        //Step6:Socket方法getInputStream,获取网络字节输入流InputStream对象
        InputStream is = socket.getInputStream();
        //Step7:网络字节输入流InputStream对象中read方法读取服务器回写数据
        while((len = is.read(bytes)) != -1) {
            System.out.println(new String(bytes, 0, len));
            //从服务器接收的数据放置文件中
            fos.write(bytes);
        }
        //Step8:释放资源
        fis.close();
        socket.close();
    }
命名优化

服务器端
image.png

循环接收

让服务器一直处于监听状态
死循环 accept方法
并且服务器不关闭,ServerSocket.close不执行

    public static void main(String[] args) throws IOException {
        //Step1
        ServerSocket ss = new ServerSocket(8888);
        //Step2
        while (true) {
            Socket socket = ss.accept();
            //Step3
            InputStream is = socket.getInputStream();
            //Step4
            File file = new File("E:\\A JI\\program\\java\\test-txt");
            if (!file.exists()) {
                file.mkdir();
            }
        /*自定义文件命名规则:防止同名的文件被覆盖
        规则:域名+毫秒值+随机数
        */
            String fileName = "itcast" + System.currentTimeMillis() +
                    new Random().nextInt(9999) + ".jpg";

            //Step5
            FileOutputStream fos = new FileOutputStream(file + "\\" + fileName);
            //Step6
            int len = 0;
            byte[] bytes = new byte[1024];
            while((len = is.read(bytes)) != -1) {
                //Step7
                fos.write(bytes, 0, len);
            }
            //Step8、Step9
            OutputStream os = socket.getOutputStream();
            os.write("上传成功".getBytes());

            fos.close();
            socket.close();
        }
        //服务器就不用关闭了
        //ss.close();
    }
多线程效率优化

一个客户端上传文件,开启一个线程,完成文件的上传
注意:

run方法定义时,没有抛出异常,所以重写的时候也不能有,故将文件上传的程序放入try...catch中
public class Test1Server {
    public static void main(String[] args) throws IOException {
        //Step1
        ServerSocket ss = new ServerSocket(8888);
        //Step2
        while (true) {
            Socket socket = ss.accept();

            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        //Step3
                        InputStream is = socket.getInputStream();
                        //Step4
                        File file = new File("E:\\A JI\\program\\java\\test-txt");
                        if (!file.exists()) {
                            file.mkdir();
                        }
                        /*自定义文件命名规则:防止同名的文件被覆盖
                        规则:域名+毫秒值+随机数
                        */
                        String fileName = "itcast" + System.currentTimeMillis() +
                                new Random().nextInt(9999) + ".jpg";

                        //Step5
                        FileOutputStream fos = new FileOutputStream(file + "\\" + fileName);
                        //Step6
                        int len = 0;
                        byte[] bytes = new byte[1024];
                        while ((len = is.read(bytes)) != -1) {
                            //Step7
                            fos.write(bytes, 0, len);
                        }
                        //Step8、Step9
                        OutputStream os = socket.getOutputStream();
                        os.write("上传成功".getBytes());
                        fos.close();
                        socket.close();
                    } catch (IOException e) {
                        System.out.println(e);
                    }
                }
            }).start();
        }
    }
}

waikiki
4 声望2 粉丝