java之网络编程篇

前言

网络编程就是计算机和计算机之间通过网络进行数据传输,下面介绍一些概念和如何实现UDP和TCP两种模式的传输。

一、常见的软件架构C/S和B/S

C/S架构需要一个客户端软件程序+服务器

B/S只需要打开网页+服务器

 C/S架构的优缺点和应用场景

优点:画面可以做的非常精美,用户体验好

缺点:需要开发客户端,也需要开发服务端

           用户需要下载和更新的时候太麻烦

应用场景:适合定制专业化的办公类软件如:IDEA、网游

 B/S架构的优缺点和应用场景

优点:不需要开发客户端,只需要开发服务端

           用户不需要下载,打开浏览器就能使用

缺点:如果应用过大,用户体验受到影响

应用场景:适合移动互联网应用,可以在任何地方随时访问的系统。

二、网络编程三要素(IP、端口号、协议)

1.概述

IP               设备在网络中的地址,是唯一的标识。

端口号        应用程序在设备中唯一的标识。

协议           数据在网络中传输的规则,常见的协议有UDP、TCP、http、https、ftp。

2.详说

IP

全称:Internet Protocol,是互联网协议地址,也称IP地址。

是分配给上网设备的数字标签。

 通俗理解就是,上网设备在网络中的地址,是唯一的

常见的IP分类为 IPv4、IPv6

为解决IPv4的IP不够用的问题,出现了IPv6

IPv4的细节

分类:公网地址(万维网使用)和私有地址(局域网使用)。

           192.168.开头的就是私有址址,范围即为192.168.0.0--192.168.255.255,专门为组织机构内部使用,以此节省IP

三个问

1.IPv6还未普及,现在如何解决IPv4不够的问题?

利用局域网IP解决IP不够的问题

2.特殊的IP是什么?

127.0.0.1(永远表示本机)

3.常见的两个CMD命令

ipconfig:查看本机IP地址

ping:检查网络是否连通

 InetAddress类

 InetAddress类可以获取IP对象,主机名,IP

public class InetAddressTest {
    public static void main(String[] args) throws UnknownHostException {
        //传递电脑的名字或网址都可以获得对应的IP地址
        InetAddress address = InetAddress.getByName("LAPTOP-8DV5B4U6");
        System.out.println(address);

        //获得本机的主机名
        String hostName = address.getHostName();
        System.out.println(hostName);

        //获得本机的IP地址
        String hostAddress = address.getHostAddress();
        System.out.println(hostAddress);
    }
}

 端口号

就是,应用程序在设备中唯一的标识。

端口号:由两个字节表示的整数,取值范围:0~65535

                其中0~1023之间的端口号用于一些知名的网络服务或者应用。

                我们自己使用1024以上的端口号就可以了。

注意:一个端口号只能被一个应用程序使用。

传输的数据只能由电脑绑定的端口号的端口发出和接收

协议

计算机网络中,连接和通信的规则被称为网络通信协议。

下面是传输层的两个协议UDP和TCP

三、UDP 

1.发送数据

步骤

(1)创建发送端的DatagramSocket对象

(2)数据打包(DatagramPacket)

(3)发送数据

(4)释放资源

示例代码

public class SendDataTest {
    public static void main(String[] args) throws IOException {
        //1.创建UDP  socket 即new 一个 DatagramSocket对象
        //细节:
        //绑定端口,以后我们就是通过这个端口往外发送
        //空参:所有可用的端口中随机一个进行使用
        //带参:指定端口进行绑定
        DatagramSocket socket = new DatagramSocket();

        //2.打包数据
        byte[] data = "你好厉害u".getBytes();
        InetAddress address = InetAddress.getByName("127.0.0.1");
        int port = 10086;

        DatagramPacket packet = new DatagramPacket(data, data.length, address, port);

        //3.发送数据
        socket.send(packet);

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

    }
}

 2.接收数据

步骤

(1)创建接收端的DatagramSocket对象

(2)接收打包好的数据(DatagramPacket)

(3)解析数据包

(4)释放资源

 示例代码

public class ReceiveDataType {
    public static void main(String[] args) throws IOException {
        // 1.创建一个DatagramSocket对象
        //细节:
        //在接收的时候,一定要绑定端口
        //而且绑定的端口一定要跟发送的端口保持一致
        DatagramSocket socket = new DatagramSocket(10086);

        // 2.创建一个DatagramPacket对象接收数据
        byte[] bytes = new byte[1024];
        DatagramPacket packet = new DatagramPacket(bytes,bytes.length);
        //该方法是阻塞的
        //程序执行到这一步的时候,会在这里死等
        //等发送端发送消息
        socket.receive(packet);

        //3.解析数据
        byte[] data = packet.getData();
        int len = packet.getLength();
        String str = new String(data,0,len);
        InetAddress address = packet.getAddress();
        int port = packet.getPort();

        System.out.println("接收到的数据:"+str);
        System.out.println("这数据从"+address+"这台电脑中的"+port+"端口发出");

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

3.聊天室

按照下面的要求实现程序
UDP发送数据:娄数据来自于键盘录入,直到输入的数据是886,发送数据结束
UDP接收数据:因为接收端不知道发送端什么时候停止发送,故采用死循环接收 

发送端代码

public class SendSide {
    public static void main(String[] args) throws IOException {
        DatagramSocket socket = new DatagramSocket();

        Scanner scanner = new Scanner(System.in);
        while (true) {
            System.out.println("请输入要发送的信息:");
            String s = scanner.nextLine();
            if ("886".equals(s)){
                break;
            }
            byte[] data = s.getBytes();
            InetAddress address = InetAddress.getByName("127.0.0.1");
            int port = 10086;
            DatagramPacket packet = new DatagramPacket(data, data.length, address, port);

            socket.send(packet);
        }
        socket.close();
    }
}

 idea里面修改发送代码的运行配置,改为允许多个实例跑

接收端代码

public class ReceiveSide {
    public static void main(String[] args) throws IOException {
        DatagramSocket socket = new DatagramSocket(10086);

        byte[] data = new byte[1024];
        DatagramPacket packet = new DatagramPacket(data, data.length);
        while (true) {
            socket.receive(packet);

            byte[] data1 = packet.getData();
            int length = packet.getLength();
            String str = new String(data1, 0, length);
            SocketAddress socketAddress = packet.getSocketAddress();
            System.out.println("来自:" + socketAddress+"发出的一条信息:" + str );
        }
    }
}

4.三种通信方式(单播、组播、广播) 

单播     上面的代码就是单薄

组播      组播地址:224.0.0.0~239.255.255.255      发送到的数据一组的主机都能收到
                其中224.0.0.0~224.0.0.255为预留的组播地址

广播      广播地址:255.255.255.255                        发送到的数据所有的主机都能收到

(1)组播

发送数据步骤

1.创建MulticastSocket对象

2.创建DatagramPacket对象(这里的目的ip对象要指定组播地址比如224.0.0.1)

3.调用MulticastSocket发送数据方法发送数据

4.释放资源

接收数据步骤

1.创建MulticastSocket对象

2.将当前本机,添加到224.0.0.1的这一组当中

3.创建DatagramPacket对象

4.接收数据

5.解析数据

6.释放资源

实例代码

1.发送端代码

public class SendSide {
    public static void main(String[] args) throws IOException {
        //1.创建UDP  socket 即new 一个 MulticastSocket对象
        MulticastSocket socket = new MulticastSocket();

        //2.打包数据
        byte[] data = "你好厉害u".getBytes();
        //指定接收端的组播ip
        InetAddress address = InetAddress.getByName("224.0.0.1");
        //指定接收端的端口
        int port = 10086;

        DatagramPacket packet = new DatagramPacket(data, data.length, address, port);

        //3.发送数据
        socket.send(packet);

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

2.接收端代码 

public class ReceiveSide {
    public static void main(String[] args) throws IOException {
        // 1.创建一个MulticastSocket对象
        //细节:
        //在接收的时候,一定要绑定端口
        //而且绑定的端口一定要跟发送的端口保持一致
        MulticastSocket socket = new MulticastSocket(10086);

        //2..将当前本机,添加到224.0.0.2的这一组当中
        InetAddress address = InetAddress.getByName("224.0.0.2");
        socket.joinGroup(address);

        // 3.创建一个DatagramPacket对象接收数据
        byte[] bytes = new byte[1024];
        DatagramPacket packet = new DatagramPacket(bytes,bytes.length);
        
        //4.接收数据
        //该方法是阻塞的
        //程序执行到这一步的时候,会在这里死等
        //等发送端发送消息
        socket.receive(packet);

        //5.解析数据
        byte[] data = packet.getData();
        int len = packet.getLength();
        String str = new String(data,0,len);
        InetAddress src = packet.getAddress();
        int port = packet.getPort();

        System.out.println("接收到的数据:"+str);
        System.out.println("这数据从"+src+"这台电脑中的"+port+"端口发出");

        //6.关闭资源
        socket.close();
    }
}

设置接收端的代码的运行设置为允许多个实例运行。 

多次运行接收端的代码,控制台多几个接收端的实例,几个接收实例创建就加入224.0.0.2的一个组播地址的组。只要发送端一发送消息,几个接收端都能收到。 

 (2)广播

这个广播只需要在单播的基础上,修改发送端发送包参数指定的目的ip地址。

修改后的发送端代码如下

发送后,无论是多个单播或组播的接收端都是能接收到的。但是端口号还是要对应。

四、TCP

 1.客户端和服务端的步骤

 

2.实例代码

服务端代码

public class ServerType {
    public static void main(String[] args) throws IOException {
        //1.创建一个服务器ServerSocket
        ServerSocket serverSocket = new ServerSocket(10086);
        //2.监听客户端的连接
        //没有客户端连接,会阻塞在这里
        Socket socket = serverSocket.accept();
        //3.获取输入流
        InputStream inputStream = socket.getInputStream();
        //4.读数据
        int b;
        while ((b = inputStream.read()) != -1) {
            System.out.print((char) b);
        }
        //5.关闭流
        inputStream.close();
        socket.close();
    }
}

客户端代码

public class CilentType {
    public static void main(String[] args) throws IOException {
        //1.创建客户端socket
        Socket socket = new Socket("127.0.0.1", 10086);
        //2.获取输出流
        OutputStream outputStream = socket.getOutputStream();
        //3.写数据
        outputStream.write("我是客户端".getBytes());
        //4.关闭资源
        outputStream.close();
        socket.close();
    }
}

 注意:上面的代码是使用字节流传输数据的,中文的话会乱码

因此需要将字节输入流转换为字符输入流,要修改服务端的代码

修改后的服务端的代码

public class ServerType {
    public static void main(String[] args) throws IOException {
        //1.创建一个服务器ServerSocket
        ServerSocket serverSocket = new ServerSocket(10086);
        //2.监听客户端的连接
        //没有客户端连接,会阻塞在这里
        Socket socket = serverSocket.accept();
        //3.获取输入流
        InputStream inputStream = socket.getInputStream();
        //转换为字符流,避免出现中文乱码
        InputStreamReader isr = new InputStreamReader(inputStream);
        //4.读数据
        int b;
        while ((b = isr.read()) != -1) {
            System.out.print((char) b);
        }
        //5.关闭流
        inputStream.close();
        socket.close();
    }
}

运行结果正常 

3.三次握手和四次挥手

(1)三次握手

(2)四次挥手

五、Demo

1.Demo1 多发多收

 客户端代码

public class Client {
    public static void main(String[] args) throws IOException {
        //1.创建Socket对象并连接服务端
        Socket socket = new Socket("127.0.0.1", 10086);

        Scanner sr = new Scanner(System.in);
        //2.获取输出流,发送数据
        OutputStream outputStream = socket.getOutputStream();
        while (true) {
            System.out.println("请输入要发送的数据:");
            String str = sr.nextLine()+"\r\n";
            if ("886".equals(str)){
                break;
            }
            outputStream.write(str.getBytes());
        }
        //3.释放资源
        outputStream.close();
        socket.close();
    }
}

服务端代码

public class Server {
    public static void main(String[] args) throws IOException {
        //1.创建一个服务器ServerSocket
        ServerSocket serverSocket = new ServerSocket(10086);

        //2.调用accept()方法,获取到请求的客户端Socket
        Socket socket = serverSocket.accept();

        //3.获取输入流,读取客户端发送的数据
        InputStreamReader isr = new InputStreamReader(socket.getInputStream());
        int b;
        while((b = isr.read()) != -1) {
            System.out.print((char)b);
        }
        //4.关闭流和socket
        isr.close();
        socket.close();
    }
}

2.Demo2 接收和反馈

 客户端代码

public class CilentType {
    public static void main(String[] args) throws IOException {
        //1.创建客户端socket
        Socket socket = new Socket("127.0.0.1", 10086);
        //2.获取输出流
        OutputStream outputStream = socket.getOutputStream();
        //3.写数据
        outputStream.write("我是客户端".getBytes());

        //在此写一个结束标记
        socket.shutdownOutput();
        //4.接收服务端的回复
        InputStreamReader isr = new InputStreamReader(socket.getInputStream());
        int b;
        while ((b = isr.read()) != -1) {
            System.out.print((char) b);
        }
        //关闭资源
        outputStream.close();
        isr.close();
        socket.close();
    }
}

服务端代码:

public class ServerType {
    public static void main(String[] args) throws IOException {
        //1.创建一个服务器ServerSocket
        ServerSocket serverSocket = new ServerSocket(10086);
        //2.监听客户端的连接
        //没有客户端连接,会阻塞在这里
        Socket socket = serverSocket.accept();
        //3.获取输入流
        InputStream inputStream = socket.getInputStream();
        //转换为字符流,避免出现中文乱码
        InputStreamReader isr = new InputStreamReader(inputStream);
        //4.读数据
        int b;
        //细节:
        //read方法会从连接通道中读取数据
        //如果客户端没有发送数据,会阻塞在这里
        //需要一个结束标记,此处循环才会停止。
        while ((b = isr.read()) != -1) {
            System.out.print((char) b);
        }
        //5.写回数据
        String str = "我是服务端";
        socket.getOutputStream().write(str.getBytes());
        //关闭流
        inputStream.close();
        socket.close();
    }
}

 注意:

read方法会从连接通道中读取数据
        如果客户端没有发送数据,会阻塞在循环那里
        需要一个结束标记,循环才会停止。

3.Demo3 上传文件

客户端代码

public class Client {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1", 10086);
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("files\\xjj.jpg"));
        BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
        byte[] buffer = new byte[1024];
        int b;
        while ((b = bis.read(buffer)) != -1) {
            bos.write(buffer, 0, b);
        }
        bos.flush();
        socket.shutdownOutput();
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String line = br.readLine();
        System.out.println(line);
        socket.close();
    }
}

服务端代码 

public class Server {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(10086);
        Socket socket = serverSocket.accept();
        BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy\\a.jpg"));
        int b;
        byte[] buf = new byte[1024];
        while ((b = bis.read(buf)) != -1) {
            bos.write(buf, 0, b);
        }
        bos.flush();
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        bw.write("服务端接收到了数据!");
        bw.newLine();
        bw.flush();
        socket.close();
        serverSocket.close();
    }
}

 注意注意注意:1.缓冲字节流是需要刷新的  2.流不要随意的关闭,不然就导致socket关闭了

4.Demo4 文件名重复问题

这道代码只需要在上一题的基础上,修改服务端的代码,保存文件的名字用下面的UUID类生成唯一的标识码。 

 

5.Demo5 上传文件多线程版本

使用循环+多线程

服务端代码

public class Server {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(10086);
        while (true) {
            Socket socket = serverSocket.accept();
            MyRunnable mr = new MyRunnable(socket);
            new Thread(mr).start();
        }
    }
}

 线程类代码

public class MyRunnable implements Runnable{
    Socket socket;

    public MyRunnable(Socket socket) {
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
            String name = UUID.randomUUID().toString().replace("-", "")+".jpg";
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy\\"+name));
            int b;
            byte[] buf = new byte[1024];
            while ((b = bis.read(buf)) != -1) {
                bos.write(buf, 0, b);
            }
            bos.flush();
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            bw.write("服务端接收到了数据!");
            bw.newLine();
            bw.flush();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                socket.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

客户端代码(和上面的一样)

public class Client {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1", 10086);
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("files\\xjj.jpg"));
        BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
        byte[] buffer = new byte[1024];
        int b;
        while ((b = bis.read(buffer)) != -1) {
            bos.write(buffer, 0, b);
        }
        bos.flush();
        socket.shutdownOutput();
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String line = br.readLine();
        System.out.println(line);
        socket.close();
    }
}

6.Demo6  线程池优化版本

在上一道题的代码的基础上,修改服务端的代码,增加一个线程池管理线程

修改后的服务端的代码

public class Server {
    public static void main(String[] args) throws IOException {
        //创建自定义线程池
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                3,//核心线程数
                10,//最大线程数
                60,//空闲时间
                TimeUnit.SECONDS,//时间单位
                new ArrayBlockingQueue<>(2),//阻塞队列
                Executors.defaultThreadFactory(),//线程工厂
                new ThreadPoolExecutor.AbortPolicy()//拒绝策略
        );
        ServerSocket serverSocket = new ServerSocket(10086);
        while (true) {
            Socket socket = serverSocket.accept();
            MyRunnable mr = new MyRunnable(socket);
            threadPoolExecutor.submit(mr);
        }
    }
}

7.Demo7 接收浏览器的信息并打印

 

 这道题只需要服务端的代码,然后用浏览器去访问服务端

给个Demo1的服务端代码

public class Server {
    public static void main(String[] args) throws IOException {
        //1.创建一个服务器ServerSocket
        ServerSocket serverSocket = new ServerSocket(10086);

        //2.调用accept()方法,获取到请求的客户端Socket
        Socket socket = serverSocket.accept();

        //3.获取输入流,读取客户端发送的数据
        InputStreamReader isr = new InputStreamReader(socket.getInputStream());
        int b;
        while((b = isr.read()) != -1) {
            System.out.print((char)b);
        }
        //4.关闭流和socket
        isr.close();
        socket.close();
    }
}

先运行服务端的代码,再打开浏览器,输入127.0.0.1:10086访问服务端

服务端在控制台输出

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值