网络-编程

本文介绍了网络编程的基本概念,包括网络通信的两种主要模式:Client-Server 和 Browser/Server。阐述了网络编程的三个关键要素:IP地址、端口及协议,并详细讲解了 TCP 和 UDP 协议的特点与应用场景。
摘要由CSDN通过智能技术生成

网络编程可以让程序与网络上的其他设备中的程序进行数据交互。
常见的通信模式有两种方式:Client-Server(CS)、Browser/Server(BS)

实现网络编程关键的三要素

IP地址:设备在网络中的地址,是唯一的标识。
端口:应用程序在设备中唯一的标识。
协议:数据在网络中传输的规则,常见的协议有UDP协议和TCP协议。

IP地址形式

公网地址、和私有地址(局域网使用)。
192.168.开头的就是常见的局域网地址,范围即为192.168.0.0–192.128.255.255,专门为组织机构内部使用。

IP常用命令

ipconfig:查看本机IP地址,ping IP 地址:检查网络是否连通。
本机IP:127.0.0.1或localhost:称为回送地址也可称本地回环地址,只会寻找当前所在本地。

InetAddress的使用

此类表示Internet协议地址。
public static InetAddress getLocalHost()返回本主机的地址对象
public static InetAddress getByName(String host)得到指定主机的IP地址对象,参数是域名或者IP地址
public static getHostName()获取此IP地址的主机名
public static getHostAddress()返回IP地址字符串
public boolean isReachable(int timeout)在指定毫秒内连通该IP地址对应的主机,连通返回true

 public static void main(String[] args) throws Exception {
        // 1.获取本机地址对象。
        InetAddress ip1 = InetAddress.getLocalHost();
        System.out.println(ip1.getHostName());
        System.out.println(ip1.getHostAddress());

        // 2.获取域名ip对象
        InetAddress ip2 = InetAddress.getByName("www.baidu.com");
        System.out.println(ip2.getHostName());
        System.out.println(ip2.getHostAddress());

        // 3.获取公网IP对象。
        InetAddress ip3 = InetAddress.getByName("112.80.248.76");
        System.out.println(ip3.getHostName());
        System.out.println(ip3.getHostAddress());

        // 4.判断是否能通: ping  5s之前测试是否可通
        System.out.println(ip3.isReachable(5000));
    }
端口号

标识正在计算机设备上运行的进程(程序),被规定为一个16位的二进制,范围是0-65535

端口类型

周知端口:0~1023,被预先定义的知名应用占用(如:HTTP占用80,FTP占用21)
注册端口:1024~49151,分配给用户进程或者某些应用程序(如:TomCat占用8080,MySQL占用3306)
动态端口:49152到65535,之所以称为动态端口,是因为它一般不固定分配某种进程,而是动态分配

我们自己开发的选择注册端口,且一个设备中不能出现两个程序的端口号一样,否则出错

网络通信协议

TCP/IP

在这里插入图片描述

UDP

UDP是一种无连接、不可靠传输的协议
将数据源IP,目的地IP和端口封装成数据包,不需要建立连接
每个数据包的大小限制在64KB内
发送不管对方是否准备好,接收方收到也不确认,故是不可靠的
可以广播发送,发送数据结束时无需释放资源,开销小,速度快

TCP

TCP协议是“面向连接”的可靠通信协议
传输前,采用三次握手方式建立连接,所以是可靠的
在连接中可进行大数据量的传输
连接、发送数据都需要确认,且 传输完毕后,还需要释放已建立的连接,通信效率较低。
TCP协议对使用在对信息安全要求较高的场景,例如,文件下载,金融等数据通信。
在这里插入图片描述

三次握手

三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠。
第一次握手,客户端向服务器端发出连接请求,等待服务器确认。
第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求。
第三次握手,客户端再次向服务器端发送确认信息,确认连接。

四次挥手

客户端向服务器 发出取消连接请求
服务器向客户端返回一个响应,表示收到客户端 取消请求
服务器向客户端发出确认取消信息
客户端再次发送确认消息,连接取消

UDP通信

DatagramPacket:数据包对象
public DatagramPacket(byte[ ] buf,int length,InetAddress address,int port) 创建发送端数据包对象
buf:要发送的内容,字节数组、length:要发送内容的字节长度、address:接收端的IP地址对象、port:接收端的端口号
public DatagramPacket(byte[ ] buf,int length)创建接收端的数据包对象
buf:用来存储接收的内容,length:能够接收内容的长度
DatagramPacket常用方法
public int getLength() 获得实际接收到的字节个数
DatagramSocket:发送端和接收端对象
public DatagramSocket()创建发送端socket对象,系统回随机分配一个端口号
public DatagramSocket(int port)创建接收端的socket对象并指定端口号
DatagramSocket类成员方法
public void send(DatagramPacket do)发送数据包
public void receicve(DatagramPacket p)接收数据包

使用UDP通信实现:发送消息、接收消息

客户端:

public static void main(String[] args) throws Exception{
        System.out.println("=======客户端启动=======");
        //1、创建发送端对象:发送端自带默认的端口
        DatagramSocket socket = new DatagramSocket(6666);
        //2、创建一个数据包对象封装数据
        /*public DatagramPacket(byte buf[],int length,InteAddress address,int port)
        参数一:封装要发送的数据*
        参数二:发送数据的大小
        参数三:服务端的主机IP和地址
        参数四:服务端的端口/
         */
        byte[] buffert = "我是一颗快乐的星星".getBytes();
        DatagramPacket packet = new DatagramPacket(buffert, buffert.length, InetAddress.getLocalHost(), 8888);
        //3、发送数据
        socket.send(packet);

        socket.close();


    }

服务端:

public static void main(String[] args) throws Exception {
        System.out.println("====服务端启动====");
        //1、创建接收端对象:注册端口
        DatagramSocket socket = new DatagramSocket(8888);
        //2、创建一个数据包对象接收数据
        byte[] buffer = new byte[1024 * 64];
        DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
        //3、等待接收数据
        socket.receive(packet);
        //4、取出数据即可,读取多少取出多少
        int len = packet.getLength();
        String rs = new String(buffer, 0, len);
        System.out.println("收到了:" + rs);//收到了:我是一颗快乐的星星
        //获取发送端的ip和端口
        String ip = packet.getSocketAddress().toString();
        System.out.println("对方地址:" + ip);//对方地址:/192.168.137.1:6666
        int port = packet.getPort();
        System.out.println("对方端口:" + port);//对方端口:6666
        socket.close();
    }
使用UDP通信实现:多发多收消息

发送端:多发、多收

public static void main(String[] args) throws Exception {
        System.out.println("=====客户端启动======");

        // 1、创建发送端对象:发送端自带默认的端口号(人)
        DatagramSocket socket = new DatagramSocket(7777);
        

        Scanner sc = new Scanner(System.in);
        while (true) {
            System.out.println("请说:");
            String msg = sc.nextLine();

            if("exit".equals(msg)){
                System.out.println("离线成功!");
                socket.close();
                break;
            }

            // 2、创建一个数据包对象封装数据(韭菜盘子)
            byte[] buffer = msg.getBytes();
            DatagramPacket packet = new DatagramPacket( buffer, buffer.length,
                    InetAddress.getLocalHost() , 8888);

            // 3、发送数据出去
            socket.send(packet);
        }

    }

接收端

public static void main(String[] args) throws Exception {
        System.out.println("=====服务端启动======");
        // 1、创建接收端对象:注册端口(人)
        DatagramSocket socket = new DatagramSocket(8888);

        // 2、创建一个数据包对象接收数据(韭菜盘子)
        byte[] buffer = new byte[1024 * 64];
        DatagramPacket packet = new DatagramPacket(buffer, buffer.length);

        while (true) {
            // 3、等待接收数据。
            socket.receive(packet);
            // 4、取出数据即可
            // 读取多少倒出多少
            int len = packet.getLength();
            String rs = new String(buffer,0, len);
            System.out.println("收到了来自:" + packet.getAddress() +", 对方端口是" + packet.getPort() +"的消息:" + rs);
        }
    }
UDP的三种通信方式

单播:单台主机与单台主机之间的通信
广播:当前主机与所在网络中的所有主机通信
组播:当前主机与选定的一组主机的通信
在这里插入图片描述

TCP通信的客户端

TCP通信的客户端:向服务器发送连接请求,给服务器发送数据,读取服务器回写的数据
表示客户端的类:
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、释放资源
注意:
1、客户端和服务器端进行交互,必须使用Socket中提供的网络流,不能使用自己创建的对象
2、当我们创建客户端对象Socket的时候,就会去请求服务器和服务器经过3次握手建立连接通路
这是如果服务器没有启动,那么就会抛出异常
如果服务器已经启动,那么就可以进行交互了

客户端原版
public class TCPClient {
    public static void main(String[] args) throws Exception {
        //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(StandardCharsets.UTF_8));
        //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.close();
    }
}
客户端改进版
public static void main(String[] args) throws Exception{
        //1、创建Socket通信管道请求有服务端的连接
        //参数一:服务端的IP地址,参数二:服务端的端口
        Socket socket = new Socket("127.0.0.1",7777);
        //2、从socket通信管道中得到一个字节输出流,负责发送数据
        OutputStream os = socket.getOutputStream();
        //3、把低级的字节流包装成打印流
        PrintStream ps = new PrintStream(os);
        //4、发送消息
        ps.println("我是TCP客户端,我已经与你对接");
        ps.flush();

        //关闭资源
        //socket.clost();
    }

TCP通信的服务器端

TCP通信的服务器端:接收客户端的请求,读取客户端发送的数据,给客户端回写数据
表示服务器的类
java.net.ServerSocket:此类实现服务器套接字
构造方法:
ServerSocket(int port)创建绑定到特定端口的服务器套接字
服务器端必须明确一件事,必须得知道是哪个客户端请求的服务器
所以可以使用accept方法获取到请求的客户端对象Socket
成员方法:Socket accept()侦听并接受到此套接字的连接
服务器实现步骤:
1、创建服务器ServerSocket对象和系统要指定的端口号
2、使用ServerSocket对象中的方法accept,获取到请求的客户端对象Socket
3、使用Socket对象中的方法getInputStream()获取网络字节输入流InputStream对象
4、使用网络字节输入流InputStream对象中的方法read,读取客户端发送的数据
5、使用Socket对象的方法getOutputStream()获取网络字节输出流OutputStream对象
6、使用网络字节输出流OutputStream对象中的方法writer,给客户端回写数据
7、释放资源(Socket,ServerSocket)

服务端原版
public class TCPServer {
    public static void main(String[] args) throws IOException {
        //1、创建服务器ServerSocket对象和系统要指定的端口号
        ServerSocket server = new ServerSocket(8888);
        //2、使用ServerSocket对象中的方法accept,获取到请求的客户端对象Socket
        Socket socket = server.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对象中的方法writer,给客户端回写数据
        os.write("收到谢谢".getBytes(StandardCharsets.UTF_8));
        //7、释放资源(Socket,ServerSocket
        socket.close();
        server.close();
    }
}

服务端改进
 public static void main(String[] args) throws Exception {
        System.out.println("===服务端启动成功===");
        //1、注册端口
        ServerSocket serverSocket = new ServerSocket(7777);
        //2、必须调用accept方法,等待接收客户端的Socket连接请求,建立Socket通信管道
        Socket socket = serverSocket.accept();
        //3、从socket通信管道中得到一个字节输入流
        InputStream is = socket.getInputStream();
        //4、把字节输入流包装成缓冲字符输入流进行消息的接收
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        //5、按照行读取消息
        String msg;
        if((msg = br.readLine()) != null){
            System.out.println(socket.getRemoteSocketAddress() + "说了:" + msg);

        }

    }
TCP通信-多发多收消息

客户端

public static void main(String[] args) {
        try {
            System.out.println("====客户端启动===");
            // 1、创建Socket通信管道请求有服务端的连接
            // public Socket(String host, int port)
            // 参数一:服务端的IP地址
            // 参数二:服务端的端口
            Socket socket = new Socket("127.0.0.1", 7777);

            // 2、从socket通信管道中得到一个字节输出流 负责发送数据
            OutputStream os = socket.getOutputStream();

            // 3、把低级的字节流包装成打印流
            PrintStream ps = new PrintStream(os);

            Scanner sc =  new Scanner(System.in);
            while (true) {
                System.out.println("请说:");
                String msg = sc.nextLine();
                // 4、发送消息
                ps.println(msg);
                ps.flush();
            }

            // 关闭资源。
            // socket.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

服务端

 public static void main(String[] args) {
        try {
            System.out.println("===服务端启动成功===");
            // 1、注册端口
            ServerSocket serverSocket = new ServerSocket(7777);
            while (true) {
                // 2、必须调用accept方法:等待接收客户端的Socket连接请求,建立Socket通信管道
                Socket socket = serverSocket.accept();
                // 3、从socket通信管道中得到一个字节输入流
                InputStream is = socket.getInputStream();
                // 4、把字节输入流包装成缓冲字符输入流进行消息的接收
                BufferedReader br = new BufferedReader(new InputStreamReader(is));
                // 5、按照行读取消息
                String msg;
                while ((msg = br.readLine()) != null){
                    System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
TCP通信-同时接受多个客户端消息

之前我们通信不可以同事与多个客户端通信,因为单线程每次只能处理一个客户端的Socket通信。因此我们需要引入多线程来处理多个客户端的通信需求。

客户端

 public static void main(String[] args) {
        try {
            System.out.println("====客户端启动===");
            // 1、创建Socket通信管道请求有服务端的连接
            // public Socket(String host, int port)
            // 参数一:服务端的IP地址
            // 参数二:服务端的端口
            Socket socket = new Socket("127.0.0.1", 7777);

            // 2、从socket通信管道中得到一个字节输出流 负责发送数据
            OutputStream os = socket.getOutputStream();

            // 3、把低级的字节流包装成打印流
            PrintStream ps = new PrintStream(os);

            Scanner sc =  new Scanner(System.in);
            while (true) {
                System.out.println("请说:");
                String msg = sc.nextLine();
                // 4、发送消息
                ps.println(msg);
                ps.flush();
            }

            // 关闭资源。
            // socket.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

服务端
主线程定义了循环负责接收客户端Socket管道连接

public static void main(String[] args) {
        try {
            System.out.println("===服务端启动成功===");
            // 1、注册端口
            ServerSocket serverSocket = new ServerSocket(7777);
            // a.定义一个死循环由主线程负责不断的接收客户端的Socket管道连接。
            while (true) {
                // 2、每接收到一个客户端的Socket管道,交给一个独立的子线程负责读取消息
                Socket socket = serverSocket.accept();
                System.out.println(socket.getRemoteSocketAddress()+ "它来了,上线了!");
                // 3、开始创建独立线程处理socket
                new ServerReaderThread(socket).start();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

使用多线程处理
没接到一个Socket通信管道后分配一个独立的线程负责处理它。


public class ServerReaderThread extends Thread{
    private Socket socket;
    public ServerReaderThread(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            // 3、从socket通信管道中得到一个字节输入流
            InputStream is = socket.getInputStream();
            // 4、把字节输入流包装成缓冲字符输入流进行消息的接收
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            // 5、按照行读取消息
            String msg;
            while ((msg = br.readLine()) != null){
                System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg);
            }
        } catch (Exception e) {
            System.out.println(socket.getRemoteSocketAddress() + "下线了!!!");
        }
    }
}

使用线程池优化

客户端

 public static void main(String[] args) {
        try {
            System.out.println("====客户端启动===");
            // 1、创建Socket通信管道请求有服务端的连接
            // public Socket(String host, int port)
            // 参数一:服务端的IP地址
            // 参数二:服务端的端口
            Socket socket = new Socket("127.0.0.1", 6666);

            // 2、从socket通信管道中得到一个字节输出流 负责发送数据
            OutputStream os = socket.getOutputStream();

            // 3、把低级的字节流包装成打印流
            PrintStream ps = new PrintStream(os);

            Scanner sc =  new Scanner(System.in);
            while (true) {
                System.out.println("请说:");
                String msg = sc.nextLine();
                // 4、发送消息
                ps.println(msg);
                ps.flush();
            }
            // 关闭资源。
            // socket.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

服务端
服务端可以复用线程处理多个客户端,可以避免系统瘫痪
适合客户端通信时长较短的场景

 // 使用静态变量记住一个线程池对象
    private static ExecutorService pool = new ThreadPoolExecutor(300,
            1500, 6, TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(2)
    , Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

    public static void main(String[] args) {
        try {
            System.out.println("===服务端启动成功===");
            // 1、注册端口
            ServerSocket serverSocket = new ServerSocket(6666);
            // a.定义一个死循环由主线程负责不断的接收客户端的Socket管道连接。
            while (true) {
                // 2、每接收到一个客户端的Socket管道,
                Socket socket = serverSocket.accept();
                System.out.println(socket.getRemoteSocketAddress()+ "它来了,上线了!");

                // 任务对象负责读取消息。
                Runnable target = new ServerReaderRunnable(socket);
                pool.execute(target);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

引入线程池

public class ServerReaderRunnable implements Runnable{
    private Socket socket;
    public ServerReaderRunnable(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            // 3、从socket通信管道中得到一个字节输入流
            InputStream is = socket.getInputStream();
            // 4、把字节输入流包装成缓冲字符输入流进行消息的接收
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            // 5、按照行读取消息
            String msg;
            while ((msg = br.readLine()) != null){
                System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg);
            }
        } catch (Exception e) {
            System.out.println(socket.getRemoteSocketAddress() + "下线了!!!");
        }
    }
}

即时通信

即时通信:是指一个客户端的消息发出去,其他客户端可以接收到
之前我们的消息都是发给服务端的。
即时通信需要进行端口转发的设计思想。
服务端需要把在线的Socket管道存储起来,一旦收到一个消息要推送给其他管道。

综合案例

文件上传原理

在这里插入图片描述

客户端

文件上传案例的客户端:读取本地文件,上传到服务器,读取服务器回写的数据
明确:数据源头:D:\java\ceshi\day01
目的地:服务器
实现步骤:1、创建一个本地字节输入流FileInputStream对象,构造方法中绑定要读取的数据源
2、创建一个客户端Socket对象,构造方法中绑定服务器的IP地址和端口号
3、使用Socket中的方法getOutputStream,获取网络字节输出流OutputStream对象
4、使用本地字节输入流FileInputStream对象中的方法read,读取本地文件
5、使用网络字节输出流OutputStream对象中的方法writer,把读取到的文件上传到服务器
6、使用Socket中的方法getInputStream,获取网络字节输入流InputStream对象
7、使用网络字节输入流InputStream对象中的方法read读取服务器回写的数据
8、释放资源(FileInputStream,Socket)

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

服务器端

文件上传案例服务器端:读取客户端上传的文件,保存到服务器的硬盘,给客户端回写“上传成功”
明确:数据源:客户端上传的文件
目的地:服务器的硬盘D:\java\ceshi\day01\aaa\upload\1.jpg
实现步骤:
1、创建一个服务器ServerSocket对象,和系统要指定的端口号
2、使用ServerSocket对象中的方法accept,获取到请求的客户端Socket对象
3、使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
4、判断D:\java\ceshi\day01\aaa\upload文件夹是否存在,不存在则创建
5、创建一个本地字节输出流FileOutputStream对象,构造方法中绑定要输出的目的地
6、使用网络字节输入流InputStream对象中的方法read,读取客户端上传的文件
7、使用本地字节输出流FileOutputStream对象中的方法writer,把读取到的文件保存到服务器的硬盘上
8、使用Socket对象中的方法getOutputStream,获取到网络字节输出流outPutStream对象
9、使用网络字节输出流OutPutStream对象中的方法writer,给客户端回写“上传成功”
10、释放资源(FileOutputStream,Socket,ServerSocket)

public class TCPServer {
    public static void main(String[] args) throws IOException {
        //1、创建一个服务器ServerSocket对象,和系统要指定的端口号
        ServerSocket server = new ServerSocket(8888);
        //2、使用ServerSocket对象中的方法accept,获取到请求的客户端Socket对象
        Socket socket = server.accept();
        // 3、使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
        InputStream is = socket.getInputStream();
        // 4、判断D:\java\ceshi\day01\aaa\upload文件夹是否存在,不存在则创建
        File file = new File("D:\\java\\ceshi\\day01\\aaa\\upload");
        if(!file.exists()){
            file.mkdirs();
        }
        //5、创建一个本地字节输出流FileOutputStream对象,构造方法中绑定要输出的目的地
        FileOutputStream fos = new FileOutputStream(file+"\\1.jpg");
        //6、使用网络字节输入流InputStream对象中的方法read,读取客户端上传的文件
        int len = 0;
        byte[] bytes = new byte[1024];
        while ((len = is.read(bytes))!=-1){
            // 7、使用本地字节输出流FileOutputStream对象中的方法writer,把读取到的文件保存到服务器的硬盘上
            fos.write(bytes,0,len);
        }
        //8、使用Socket对象中的方法getOutputStream,获取到网络字节输出流outPutStream对象
        // 9、使用网络字节输出流OutPutStream对象中的方法writer,给客户端回写“上传成功”
      socket.getOutputStream().write("上传成功".getBytes(StandardCharsets.UTF_8));
        //10、释放资源(FileOutputStream,Socket,ServerSocket)
        fos.close();
        socket.close();
        server.close();
    }
}

阻塞问题

在read方法中输入流读取一个数据字节,如果没有输入可用,则此方法将阻塞。
解决阻塞状态

//解决因为阻塞状态
        //上传完文件,给服务器写一个结束标记
        //void shutdownOutput()禁用此套接字的输出流
        //对于TCP套接字,任何以写入的数据都将被发送,并且后跟TCP的正常终止序列
        socket.shutdownOutput();
文件上传优化

上传文件名称随机
上传过程中服务器不停止
使用多线程技术,提高程序的效率
例如:浏览器解析服务器回写html页面,页面中如果有图片,那么浏览器就会单独的开启一个线程,读取服务器的图片。我们就得让服务器一致处于监听状态,客户端请求一次,服务器就回写一次。

public class TCPServer {
    public static void main(String[] args) throws IOException {
        //1、创建一个服务器ServerSocket对象,和系统要指定的端口号
        ServerSocket server = new ServerSocket(8888);

        //让服务器一直处于监听状态(死循环accept方法)
        //有一个客户端文件上传就保存一个文件
        while(true) {
            //2、使用ServerSocket对象中的方法accept,获取到请求的客户端Socket对象
            Socket socket = server.accept();

            //使用多线程技术,提高程序的效率
            //有一个客户端上传文件,就开启一个线程,完成文件的上传

            new Thread(new Runnable() {
                @Override
                //完成文件的上传
                public void run() {
                    try{
                        // 3、使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
                        InputStream is = socket.getInputStream();

                        File file = new File("D:\\java\\ceshi\\day01\\aaa\\upload");
                        if (!file.exists()) {
                            file.mkdirs();
                        }

                        //自定义一个文件的命名规则,防止同名的文件被覆盖
                        //规则:域名+毫秒值+随机数
                        String fileName = "zgDaren" + System.currentTimeMillis() + new Random().nextInt(999999) + ".jpg";

                        //5、创建一个本地字节输出流FileOutputStream对象,构造方法中绑定要输出的目的地
                        FileOutputStream fos = new FileOutputStream(file + "\\" + fileName);
                        //6、使用网络字节输入流InputStream对象中的方法read,读取客户端上传的文件
                        int len = 0;
                        byte[] bytes = new byte[1024];
                        while ((len = is.read(bytes)) != -1) {
                            // 7、使用本地字节输出流FileOutputStream对象中的方法writer,把读取到的文件保存到服务器的硬盘上
                            fos.write(bytes, 0, len);
                        }
                        //8、使用Socket对象中的方法getOutputStream,获取到网络字节输出流outPutStream对象
                        // 9、使用网络字节输出流OutPutStream对象中的方法writer,给客户端回写“上传成功”
                        socket.getOutputStream().write("上传成功".getBytes(StandardCharsets.UTF_8));
                        //10、释放资源(FileOutputStream,Socket,ServerSocket)
                        fos.close();
                        socket.close();
                    }catch(IOException e){
                        System.out.println(e);
                    }

                }
            }).start();

        }
        //既然服务器不停,就不用释放资源
        //server.close();
    }
}

模拟BS服务器案例

在这里插入图片描述

public class TCPServer {
    public static void main(String[] args) throws IOException {
        //1、创建一个服务器ServerSocket对象,和系统要指定的端口号
        ServerSocket server = new ServerSocket(8888);
        //2、使用ServerSocket对象中的方法accept,获取到请求的客户端Socket对象
        Socket socket = server.accept();
        // 3、使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
        InputStream is = socket.getInputStream();
        //4、使用网络字节输入流InputStream对象中的方法read,读取客户端上传的文件
       /* int len = 0;
        byte[] bytes = new byte[1024];
        while ((len = is.read(bytes))!=-1){
            // 5、使用本地字节输出流FileOutputStream对象中的方法writer,把读取到的文件保存到服务器的硬盘上
            System.out.println(new String(bytes,0,len));
        }*/
        //把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对象
        OutputStream os = socket.getOutputStream();
        //写入HTTP协议响应头固定写法
        os.write("HTTP/1.1 200 OK\r\n".getBytes());
        os.write("Content-Type:txt/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();
        server.close();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zgDaren

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值