Java-网络编程

网络通信三要素

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

IP地址

常见的IP分类有IPv4和IPv6

在这里插入图片描述
IPv6: 128位(16个字节)
IPv6分成8个整数,每个整数用四个十六进制位表示,数之间用冒号( : )分开

在这里插入图片描述
IP地址形式:

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

IP常用命令:

  • ipconfig: 查看本机IP地址
  • ping IP地址: 检查网络是否连通

本机IP: 127.0.0.1或者localhost, 称为回送地址也可称本地回环地址, 只会寻找当前所在本机

IP地址操作类-InetAddress
  • 此类表示Internet协议(IP)地址

InetAddress API如下

名称说明
public statis InetAddress getLocalHost()返回本主机的地址对象
public static InetAddress getByName( String host )得到指定主机的IP地址对象,参数是域名或者IP地址
public String getHostName()获取此IP地址的主机名
public String getHostAddress()返回IP地址字符串
public boolean isReachable( int timeout )在指定毫秒内连通该IP地址对应的主机,连通返回true
public static void main(String[] args) throws IOException {
        //获取本机地址对象
        InetAddress localHost = InetAddress.getLocalHost();
        System.out.println(localHost.getHostName());
        System.out.println(localHost.getHostAddress());

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

        //获取公网IP对象
        InetAddress ip1 = InetAddress.getByName("39.156.66.18");
        System.out.println(ip1.getHostName());
        System.out.println(ip1.getHostAddress());

        //判断是否能通:ping 5s之内测试是否可通
        System.out.println(ip1.isReachable(5000));

    }

在这里插入图片描述

端口号

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

端口类型

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

协议

连接和通信数据的规则被称为网络通信协议

TCP协议

特点

  • 必须双方先建立连接,它是一种面向连接的可靠通信协议
  • 传输前,采用"三次握手"方式建立连接,所以是可靠
  • 在连接中可进行大数据变量的传输
  • 连接, 发送数据都需要确认, 且传输完毕后, 还需释放已建立的连接, 通信效率极低

通信场景

  • 对信息安全要求较高的场景,例如: 文件下载, 金融等数据通信

TCP三次握手建立连接

在这里插入图片描述
TCP四次挥手断开连接

在这里插入图片描述

UDP协议

特点

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

通信场景

  • 语音通话, 视频会议等

UDP通信

DatagramPacket: 数据包对象

构造器说明
public DatagramPacket(byte[] buf, int length, InetAddress address, int port)创建发送端数据包对象
public DatagramPacket(byte[] buf, int length)创建接收端的数据包对象

发送端:
buf : 要发送的内容, 字节数组
length : 要发送的内容的字节长度
address : 接收端的IP地址对象
port : 接收端的端口号
接收端:
buf : 用来存储接收的内容
length : 能够接收内容的长度

DatagramSocket: 数据包对象

构造器说明
public DatagramSocket()创建发送端的Socket对象, 系统会随机分配一个端口号
public DatagramSocket(int port)创建接收端的Socket对象并指定端口号

DatagramSocket类成员方法

方法说明
public void send(DatagramPacket dp)发送数据包
public void receive(DatagramPacket p)接收数据包

接收端

public class Server {
    public static void main(String[] args) throws IOException {

        //创建接收端对象
        DatagramSocket socket = new DatagramSocket(8888);

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

        //等待接收数据
        socket.receive(packet);

        //取出数据
        int length = packet.getLength();
        String rs = new String(buffer,0,length);
        System.out.println("收到了:" + rs);

        socket.close();
    }
}

发送端

public class ClientDemo {
    public static void main(String[] args) throws IOException {

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

        //创建数据包对象封装数据
        byte[] buffer = "Hello~".getBytes();
        DatagramPacket packet = new DatagramPacket(buffer,buffer.length, InetAddress.getLocalHost(),8888);

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

        socket.close();
    }
}

TCP通信

一发一收

  • 在java中只要是使用java.net.Socket类实现通信, 底层即是使用了TCP协议

接收端

public class ServerDemo {
    public static void main(String[] args) throws IOException {
        //注册端口
        ServerSocket serverSocket = new ServerSocket(7777);

        //等待接收客户端的连接请求
        Socket socket = serverSocket.accept();

        //从socket通信管道中得到一个字节输入流
        InputStream is = socket.getInputStream();

        //把字节输入流包装成缓冲字符输入流接收消息
        BufferedReader br = new BufferedReader(new InputStreamReader(is));

        String msg;
        if ((msg = br.readLine()) != null){
            System.out.println(socket.getRemoteSocketAddress()+ "-----" + msg);
        }
    }
}

发送端

public class ClientDemo {
    public static void main(String[] args) throws IOException {
        /**
         * 创建Socket通信管道请求有服务端的连接
         * 参数一:服务端的IP地址
         * 参数二:服务端的端口
         */
        Socket socket = new Socket("127.0.0.1",7777);

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

        //把字节流包装成打印流
        PrintStream ps = new PrintStream(os);

        //发送消息
        ps.println("我是TCP的客户端");
        ps.flush();
    }
}

多发多收

  • 可以使用死循环控制服务端收完消息继续等待接收下一个消息
  • 客户端也可以使用死循环等待用户不断输入消息
  • 客户端一旦输入了exit, 则关闭客户端程序并释放资源

服务端

public class ServerDemo {
    public static void main(String[] args) throws IOException {
        //注册端口
        ServerSocket serverSocket = new ServerSocket(7777);

        //等待接收客户端的连接请求
        Socket socket = serverSocket.accept();

        //从socket通信管道中得到一个字节输入流
        InputStream is = socket.getInputStream();

        //把字节输入流包装成缓冲字符输入流接收消息
        BufferedReader br = new BufferedReader(new InputStreamReader(is));

        String msg;
        while ((msg = br.readLine()) != null){
            System.out.println(socket.getRemoteSocketAddress()+ "-----" + msg);
        }
    }
}

客户端

public class ClientDemo {
    public static void main(String[] args) throws IOException {
        /**
         * 创建Socket通信管道请求有服务端的连接
         * 参数一:服务端的IP地址
         * 参数二:服务端的端口
         */
        Socket socket = new Socket("127.0.0.1",7777);

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

        //把字节流包装成打印流
        PrintStream ps = new PrintStream(os);

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

            ps.println(msg);
            ps.flush();
        }
    }
}

本案例实现了多发多收,但是不可以同时接收多个客户端的纤细, 因为服务端现在只有一个线程, 只能与一个客户端进行通信

同时接收多个客户端消息

  • 主线程定义了循环负责接收客户端Socket管道连接
  • 没接受到一个Socket通信管道后分配一个独立的线程负责处理它

客户端同上
服务端

public class ServerDemo {
    public static void main(String[] args) throws IOException {
        //注册端口
        ServerSocket serverSocket = new ServerSocket(7777);

        while (true){
            //每接收到一个客户端的Socket管道,交给一个独立的子线程负责读取信息
            Socket socket = serverSocket.accept();
            System.out.println(socket.getRemoteSocketAddress() + "上线了");
            //开始创建独立线程处理socket
            new ServerReaderThread(socket).start();
        }
    }
}

独立线程

public class ServerReaderThread extends Thread {
    private Socket socket;

    public ServerReaderThread(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try{
            //从socket通信管道中得到一个字节输入流
            InputStream is = socket.getInputStream();
            //把字节输入流包装成缓冲字符输入流接收消息
            BufferedReader br = new BufferedReader(new InputStreamReader(is));

            String msg;
            while ((msg = br.readLine()) != null) {
                System.out.println(socket.getRemoteSocketAddress() + "-----" + msg);
            }
        } catch (Exception e){
            System.out.println(socket.getRemoteSocketAddress() + "下线了");
        }

    }
}
  1. 先启动服务端
  2. 然后启动客户端一
  3. 然后启动客户端二
  4. 客户端一发送消息
  5. 客户端二发送消息
  6. 关闭客户端一
  7. 关闭客户端二

在这里插入图片描述

使用线程池优化
客户端同上
创建Runnable任务交给线程池处理

public class ServerReaderRunnable implements Runnable{
    private Socket socket;

    public ServerReaderRunnable(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try{
            //从socket通信管道中得到一个字节输入流
            InputStream is = socket.getInputStream();
            //把字节输入流包装成缓冲字符输入流接收消息
            BufferedReader br = new BufferedReader(new InputStreamReader(is));

            String msg;
            while ((msg = br.readLine()) != null) {
                System.out.println(socket.getRemoteSocketAddress() + "-----" + msg);
            }
        } catch (Exception e){
            System.out.println(socket.getRemoteSocketAddress() + "下线了");
        }
    }
}

服务端

public class ServerDemo {
    //线程池对象
    private static ExecutorService pool = new ThreadPoolExecutor(3,5,6,
            TimeUnit.SECONDS,new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.AbortPolicy());

    public static void main(String[] args) throws IOException {
        //注册端口
        ServerSocket serverSocket = new ServerSocket(7777);

        while (true){
            //每接收到一个客户端的Socket管道,交给一个独立的子线程负责读取信息
            Socket socket = serverSocket.accept();
            System.out.println(socket.getRemoteSocketAddress() + "上线了");
            pool.execute(new ServerReaderRunnable(socket));
        }
    }
}
  • 服务端可以复用线程处理多个客户端, 可以避免系统瘫痪
  • 适合客户端通信时常较短的场景
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值