【Java】韩顺平Java学习笔记 第21章 网络编程

网络相关概念

网络

  • 网络通信
  • 网络

IP地址

  • 概念
  • 表示:点分十进制
  • 分类

域名

  • 将IP地址映射成域名,方便记忆(怎么映射:HTTP协议)

端口

  • 用于标识计算机上某个特定的网络程序
  • 主机需要通过IP和端口访问到另一个主机的服务
  • 表示形式:数字 0~65535(216-1)
  • 0~1024 已被占用(网络开发中不要使用,一般被知名程序占用) ssh 22, ftp 21, smtp 25,http 80
  • 常见网络程序端口号:
    • tomcat:8080
    • mysql:3306
    • oracle:1521
    • sqlserver:1433

网络协议

  • 协议:数据的组织形式
  • TCP/IP 协议(传输控制协议)

TCP和UDP

TCP

  • 传输控制协议
  • 传输前建立连接,是可靠的
  • 进行通信的两个应用进程:客户端、服务端
  • 连接中大数据量传输
  • 效率低
  • 发送完断开连接

UDP

  • 用户数据协议
  • 不需要链接,不可靠
  • 每个数据报再64K内,不适合传输大量数据
  • 效率高
  • 发送结束无需释放资源

InetAddress 类的常用方法

  • 实现Serializable接口,可序列化
  • getLocakHost 获取本机的InetAddress对象(计算机名称、IP地址)
  • getByname 根据指定的主机名域名获取对象
  • getHostAddress 通过对象获取对应的地址
  • getHostName 通过对象获取对应的主机名或域名

Socket(套接字)

  • 两台机器间通信的端点
  • 发起连接的一方:客户端
  • 接受(请求)连接的一方:服务端
  • 作用:当需要通讯(读写数据)时,用Socket里的getOutputStream发送数据,getInputStream接受数据
  • 两种编程方式:TCP、UDP
  • 使用完之后一定要关闭,因为连接数是有限的

TCP网络编程

  • 难点:有客户端和服务端两头,都要考虑

  • new ServerSocket(9999) 服务端在9999端口进行监听(ServerSocket可以对应多个Socket,accept到一个就返回一个Socket——多个客户端连接服务器的并发)

  • accept() :当没有客户端连接时,阻塞;当有时,返回socket对象

  • new Socket(InetAddress.getLocalHost(),9999【端口号】) 连接本机(客户端)的9999端口,连接成功返回socket对象

  • 当客户端和服务端连接时,实际上客户端也是通过一个端口连接的,这个在客户端上的端口是随机的,由 TCP/IP 分配

  • socket.getOutputStream() 得到和socket关联的输出流,输入流同理(没有得到内容的话,会阻塞) 这里用的是字节流,用字符流也可以(但是常和转换流结合)

  • 注:如果用字符流,需要手动刷新(用flush),否则不会写入(输出)。如果用带缓冲区的输出流(BufferedOutputStream),也需要手动刷新,否则不会输出

    当你使用BufferedOutputStream时,数据实际上并不是立即写入到最终的目标输出流(如文件或网络连接),而是先写入到了缓冲区中。这样设计的目的是为了减少实际的I/O操作次数,提高效率。因此,如果你不手动调用flush()方法,缓冲区中的数据会一直积攒直到缓冲区满或者你显式地调用flush()来清空缓冲区并确保数据被写入到目标输出流。

    不手动刷新直接shutdownOutput()

    如果你使用的是Socket编程中的Socket.shutdownOutput()方法,这个方法会关闭输出流,它会导致尚未写出的缓冲数据被丢弃,且后续的写操作将抛出异常。因此,直接调用shutdownOutput()而不先调用flush()来确保缓冲区的数据被写出,可能会导致数据丢失。

    直接close()

    如果你直接调用BufferedOutputStreamclose()方法,实际上它内部会自动调用flush()方法来确保缓冲区中的数据被写出到目标输出流,然后再关闭流并释放相关资源。因此,直接调用close()是一种安全的做法,它既保证了数据完整性,又正确地关闭了资源。这是推荐的做法,尤其是在不需要继续使用该流的场景下。

    总结来说,虽然可以不手动调用flush()而直接调用close(),这是因为在关闭流时,close()会帮你完成刷新缓冲区的操作,确保数据安全写入。但如果是使用shutdownOutput(),则需要先手动flush(),否则缓冲区中的数据可能会丢失。

  • close() 关闭socket

  • 设置写入结束标记,防止通信通道阻塞:socket.shutdownOutput()

    writer.newLine()(用字符流时),但要求对方用readLine()的方式读取(newLine())相当于换行符

    注意:写入结束标记写在**输出端(Output)**的后面,如果用bufferedOutputStream输出数据后shutdown,再关闭bufferedOutputStream一次会报错

    最好是shutdown之后就不再关闭这个输出流了

    GPT: socket.shutdownOutput() 方法实际上不会直接关闭 BufferedOutputStream,但它会关闭底层 Socket 输出流的写入端,从而导致任何进一步的写入操作都可能失败,并抛出 IOException。当你试图在调用 shutdownOutput() 之后关闭 BufferedOutputStream 时,BufferedOutputStream 可能会尝试刷新缓冲区中的数据,而这会因为底层输出流已经被关闭而失败。

    为了处理这种情况,可以采取以下步骤:

    1. 在调用 shutdownOutput() 之前关闭(存疑??)或刷新 BufferedOutputStream:确保所有数据已经被写入。
    2. 顺序正确:在关闭 BufferedOutputStream 之后再调用 shutdownOutput()

    或者不使用带缓冲区的输出流,如直接使用 FileOutputStream ,则不会报错

  • 先关闭流,再关闭socket,否则会报错

注意的坑点

  • 如果要接受来自客户端的字符串,直接用socket.getInputStream得到的inputStream读取即可,在while循环中读取,用自增的方法(为了怕文件名长度超过1024字节)将读取的内容加到创建好的空字符串中,如:

    byte[] rec = new byte[1024];
            int readlen = 0;
            String s = "";
            while((readlen = socketInputStream.read(rec)) != -1){
                s += new String(rec,0,readlen);
            }
    

    不用自增的方法:

            byte[] rec = new byte[1024];
            int readlen = 0;
            readlen = socketInputStream.read(rec);
            String s = new String(rec,0,readlen);
    

netstat 指令

  • netstat -an : 查看当前主机网络情况,包括端口监听网络连接的情况
  • netstat -an | more 可以分页显示
  • netstat -and 查看哪些程序在监听这个端口
  • 在 dos 控制台下执行
  • Listening 表示某个端口在监听
  • 如果有一个外部程序连接到端口,则显示一条连接信息

UDP网络编程

  • 通过 DatagramSocket 类和 DatagramPacket[数据报] 类来实现
  • 数据报里有客户端和服务端的完整IP和端口,故不需要建立连接
  • 没有明确的服务端和客户端,故演变成数据的发送端接收端(地位相等)
  • 接受和发送数据通过DatagramSocket 对象完成
  • 将数据封装到 DatagramPacket[数据报] 对象(装包),接收到以后,需要进行拆包取出数据
  • DatagramSocket可以指定在哪个端口接受数据

基本流程

  • 建立发送接收端
  • 建立数据报
  • 调用DatagramSocket的发送、接收方法
  • 关闭DatagramSocket

具体操作

  • socket.receive(packet) 接收方法,有数据则接收到数据报packet,否则阻塞等待

  • socket.send(packet) 发送方法

  • 创建 DatagramSocket 对象

    DatagramSocket socket = new DatagramSocket(9999)

  • 创建 DatagramPacket 对象存放数据,第一种是准备接受数据的创建形式,第二种是准备发送数据的创建形式

    DatagramPacket packet = new DatagramPacket(buf,buf.length)(buf是字节数组)

    DatagramPacket packet = new DatagramPacket(buf,buf.length,InetAddress.getByName(192.168.10.1)[主机],9999[端口])

  • packet.getLength() 获取接受到的数据长度

  • packet.getData() 接受数据,返回给一个字节数组

  • socket.close() 关闭套接字

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值