Java-网络编程(基础篇)

1. 网络编程概念

1.1 软件结构

  • C/S结构(Client/Server):客户端与服务器结构;
  • B/S结构(Browser/Server):浏览器与服务器结构;

1.2 网络编程三要素

1.2.1 IP地址

  • 互联网协议地址(Internet Protocol Address),网络设备的唯一标识
  • IP地址的两种常用的地址形式:
    • IPv4: 32位的二进制数,分为4个字节,格式为a.b.c.d,每个字节范围是0~255;
    • IPv6: 128位的二进制数,每16个字节一组,共八组,格式为a:b:c:d:e:f:g:h,每个字节范围是0~255.

1.2.2 端口号

  • 设备中进程的唯一标识,实际上是一个十进制的整数。
  • 端口号是用两个字节表示的整数,取值范围0~65535。
  • 注意:0~1024一下的端口号是系统保留使用的,程序员要使用1024以上的端口号。

1.2.3 通信协议

  • 规定计算机之间数据传输的格式。

*通信协议中两种常用的协议:

TCP(Transmission Control Prorocol): 传输控制协议。TCP是面向连接的通信协议,在发送端和接收端监理逻辑连接,再传输数据

    • -
  • 特点:保证传输数据的安全

UDP(User Datagram Protocol): 用户数据报包协议。UDP是面向无连接的协议,是不可靠协议,传输速度快,但是容易丢失数据

根据Ip找主机,根据端口号找程序,根据协议确定传输数据的格式

1.3 InetAddress类的概述

  • 一个该类对象代表一个IP地址。

1.3.1常用方法

  • (无构造方法)

  • 静态方法:

    - public static InetAddress getLocalHost()
          获得本地主机IP地址对象(主机名/IP地址字符串);
    - public static InetAddress getName()
          根据主机名或IP地址字符串获得IP地址对象。
  • 成员方法

    - public String getHostName():获得主机名;
    - public String getHostAddress():获得IP地址字符串。

2. UDP通信程序

2.1 概述

  • UDP在数据传输时,数据的发送端和接收端不建立逻辑连接。

  • 特点:

    • 面向无连接协议;

    • 资源消耗少,通信效率高,但不可靠;

    • 只负责发送,发送过程不会确认接收端是否接收到;
    • 基于数据包传输数据:将需发送的数据、源和接收端IP地址、端口号等信息封装成数据包发送;
    • 每个数据包的大小限制在64K内;
  • 使用场景:

    • 即时通讯(如qq)
    • 在线视频(如直播)
    • 网络语音通话(如微信语音聊天)

2.2 UDP通讯使用的类

2.2.1 DatagramPacket类(数据包)

2.2.1.1 概述及作用
  • 用来封装发送端或接收端要发送或接收的数据,是数据包对象
2.2.1.2 构造方法
  • 创建发送端数据包:
- DatagramPacket(byte[] buf, int length, InetAddress address, int port) 
        用于创建发送端数据包对象。

其中:buf:字节数组,封装要发送的数据

​ length:要发送的内容长度,单位:字节

​ address:接收端的ip地址对象

​ port:接收端的端口号

  • 创建接收端数据包:
- DatagramPacket(byte buf[],int length) : 用于创建接收端数据包对象

其中:buf:用于接收数据的数组

​ length: 能够接受内容的长度,即buf数组的长度,单位:字节

2.2.1.2 常用成员方法
- public int getLength():获得发送端实际发送的字节数或接收端实际接收到的字节数;
- public int getPort():获得发送端或接收端端口号

2.2.2 DatagramSocket类(发送数据包)

2.2.2.1 概述及作用
  • DatagramSocket类实例对象可用来发送和接收数据包对象
2.2.2.2 构造方法
- DatagramSocket():创建发送端的发送对象(端口号随机生成)
- DatagramSocket(int port):根据指定端口号创建Socket对象(一般用在接收端)
2.2.2.3 常用成员方法
- public void send(DatagramPacket p):发送数据包
- public void receive(DatagramPacket p):接收数据包
- public void close():关闭资源,释放端口号

2.2.3 示例代码

/*
发送端
*/
public class Notes01_UCPsender {
    public static void main(String[] args) throws Exception{
        //定义字符串,包含要发送的内容
        byte[] content = "你好".getBytes();
        //创建数据包对象
        DatagramPacket dp = new DatagramPacket(content, content.length, InetAddress.getLocalHost(),6666);//假设给本机的端口号为6666的程序发送数据
        /*注意,这里的内容长度不能直接用“你好”.length,因为此时的值是字符的个数而不是字节的个数!*/
        //创建发送对象
        DatagramSocket ds = new DatagramSocket();   //不指定端口号会随机生成端口号
        //使用发送对象发送数据包
        ds.send(dp);
        //关闭socket释放端口号
        ds.close();   
    }
}
/*
接收端
*/
public class Notes01_UCPreceiver {
    public static void main(String[] args) throws IOException {
        //创建接收端的Socket
        DatagramSocket ds = new DatagramSocket(6666);//假设端口是6666
        //创建字节数组:存储接收到的实际内容
        byte[] buf = new byte[1024];
        //创建数据包对象,封装接收到的数据
        DatagramPacket dp = new DatagramPacket(buf, buf.length);
        //接收数据包
        ds.receive(dp);
        /*
        以下的操作是对接收到的数据进行操作,不执行不会影响文件的传输
        接收到的数据都在dp中,所以需要用dp调用方法
         */
        //获得实际接收到的字节个数
        int len = dp.getLength();
        System.out.println("接收到的字节个数为:" + len);
        //将字节数组转换为字符串输出
        System.out.println(new String(buf,0,len));
        //获得发送端的IP地址
        String sendIP = dp.getAddress().getHostAddress();//getAddress()是获得发送端地址信息
        //获得发送端的端口号
        int sendPort = dp.getPort();
        System.out.println("sendIP = " + sendIP);
        System.out.println("sendPort = " + sendPort);
        //关闭socket释放端口号
        ds.close();
    }
}

2.3 UCP通信图解

UCP传输图解

3. TCP通信程序

3.1 TCP协议概述

  • TCP协议是面向连接的通信协议,即在传输数据前先在客户端和服务器端建立逻辑连接,然后再传输数据
  • 特点:
    • 面向连接的协议;
    • 通过三次握手建立连接,形成传输数据的通道;
    • 通过四次挥手断开连接;
    • 效率稍低但是可靠协议;
    • TCP是基于IO流传输数据;
    • 传输数据大小不受限制。
  • 使用场景:
    • 文件传输
    • 发送或接受邮件
    • 远程登录
  • 流程(三次握手):
    • 客户端向服务器段发出连接请求,等待服务器确认;
    • 服务器端向客户端会送一个响应,通知客户端已收到连接请求;
    • 客户端再次向服务器段发送确认信息,确认连接。
    • 然后客户端和服务器可以开始数据传输

3.2 TCP相关的两个类

3.2.1 Socket类(客户端)

3.2.1.1 概念
  • 该类的对象就代表一个客户端程序;

  • 该类实现客户端套接字

套接字指的是两台设备之间通讯的端点。也就是Socket对象

3.2.1.2 常用构造方法
- public Socket(String host, int port)
        创建套接字对象并将其连接到指定逐级上的指定端口号。

其中:host:服务器IP地址(字符串形式)

​ port:服务器端口号

一旦执行该方法,就会立即连接指定的服务器,如果服务器没有开启,则连接失败抛出异常!!!

3.2.1.3 成员方法
- public InputStream getInputStream():返回此套接字的输入流
- public OutputStream getOutputStream():返回此套接字的输出流
  • 如果此Socket具有相关联的通道,则生成的InputStream/OutputStream的所有操作也关联该通道;
  • 关闭生成的InputStream/OutputStream也将关闭相关的Socket。
- public void close() :关闭此套接字。
  • 一旦一个Socket被关闭,它不 可再使用;
  • 关闭此Socket也将关闭相关的InputStream/OutputStream。
- public void shutdownOutput()
        禁用此套接字的输出流。(也就是告知服务器数据已传输完毕)
  • 任何先前写出的数据将被发送,随后终止输出流。
3.2.1.4 TCP客户端代码实现步骤
  1. 创建客户端的Socket对象并指定需要连接的服务器的端口号;
  2. 调用Socket对象的getOutputStream方法获得字节输出流对象;
  3. 调用字节输出流对象的write方法往服务器端输出数据;
  4. 调用Socket对象的getInputStream方法获得字节输入流对象;
  5. 调用字节输入流对象的read方法读取服务器端返回的数据;
  6. 关闭Socket对象断开连接。

3.2.2 ServerSocket类(服务器端)

3.2.2.1 概念
  • 该类的对象就代表一个服务器端程序

  • 该类实现了服务器套接字,相当于开启一个服务器,该对象等待通过网络的请求。

3.2.2.2 构造方法
- public ServerSocket(int port) 
        使用该构造方法在创建ServerSocket对象时,就可以将其绑定到一个指定的端口号上,参数port就是端口号。
3.2.2.3 成员方法
- public Socket accept()
        等待客户端连接,并返回一个与客户端相关的Socket对象,用于和客户端实现通信。
        该方法会一直阻塞直到建立连接。
- public void close() :关闭serverSocket,相当于关闭服务器,一般不会关闭
3.2.2.4 TCP服务器端代码实现步骤
  1. 创建ServerSocket对象并指定端口号;
  2. 调用ServerSocket对象的accept方法等待客户端连接并获得与客户端相关的Socket对象;
  3. 调用Socket对象的getInputStream方法获得字节输入流对象;
  4. 调用字节输入流对象的read方法读取客户端发送的数据;
  5. 调用Socket对象的getOutputStream方法获得字节输出流对象;
  6. 调用字节输出流对象的write方法往客户端返回数据;
  7. 调用close方法关闭Socket和ServerSocket对象。(实际开发不会关闭ServerSocket)

3.2.3 TCP示例代码

/*
客户端实现
*/
public class Notes02_TCPclient {
    public static void main(String[] args) throws Exception{
        //创建客户端Socket对象,相当于开启一个客户端程序
        //假设给本地传输数据,端口假设为8888
        //注意,客户端的端口号要与服务器的对应,否则会无法连接而报错
        Socket socket = new Socket("127.0.0.1",8888);
        //调用Socket对象的getOutputStream方法获得字节输出流对象
        OutputStream os = socket.getOutputStream();
        //调用字节输出流对象的write方法往服务器端输出数据
        os.write("你好吗?".getBytes());
        //注意要用shutdownOutput方法终止客户端传输数据!!!否则服务器有可能会一直处于接收数据阶段
        socket.shutdownOutput();
        /*
        从服务器端接收信息
         */
        //调用Socket对象的getInputStream方法获得字节输入流对象
        InputStream is = socket.getInputStream();
        //调用字节输入流对象的read方法读取服务器端返回的数据
        byte[] content = new byte[1024];
        int len = -1;
        while ((len = is.read(content)) != -1) {
            System.out.println(new String(content,0,len));
        }
        //关闭Socket对象断开连接,自动将相关联的流关闭
        socket.close();
    }
}
/*
服务器端实现
*/
public class Notes02_TCPserver {
    public static void main(String[] args) throws IOException {
        //创建ServerSocket对象并指定端口号
        ServerSocket serverSocket = new ServerSocket(8888);
        //调用ServerSocket对象的accept方法等待客户端连接并获得与客户端相关的Socket对象
        Socket socket = serverSocket.accept();
        //调用Socket对象的getInputStream方法获得字节输入流对象
        InputStream is = socket.getInputStream();
        //调用字节输入流对象的read方法读取客户端发送的数据
        byte[] buf=new byte[1024];
        int len = -1;
        while((len=is.read(buf)) != -1){
            System.out.println(new String(buf,0,len));
        }
        /*
        返回信息给客户端
         */
        //调用Socket对象的getOutputStream方法获取字节输出流对象
        OutputStream os = socket.getOutputStream();
        //调用字节输出流对象的write方法往客户端返回数据
        os.write("很好,已经连上了".getBytes());
        //调用close方法关闭Socket和ServerSocket对象
        socket.close();
        //实际上一般不会关闭服务器
        serverSocket.close();
    }
}

3.3 TCP通信分析

1. 【服务端】启动,创建ServerSocket对象,等待连接。
2. 【客户端】启动,创建Socket对象,请求连接。
3. 【服务端】接收连接,调用accept方法,并返回一个Socket对象。
4. 【客户端】Socket对象,获取OutputStream,向服务端写出数据。
5. 【服务端】Scoket对象,获取InputStream,读取客户端发送的数据。

此时客户端向服务端发送数据成功

此后服务端向客户端回写数据

6. 【服务端】Socket对象,获取OutputStream,向客户端回写数据。
7. 【客户端】Scoket对象,获取InputStream,解析回写数据。
8. 【客户端】释放资源,断开连接。

图解如下:

TCP传输图解

4. 疑问

Q:在传输数据时,什么场景下用shutdownOutput?为何有时不用shutdownOutput终止数据输出?

A:包含但不限于以下的情形,shutdownOutput可以不使用:

  • 数据输出后不需要接受对方放回的数据,直接close的情况。在该情况下socket对象在close时会自动告知对方数据已传输结束。此后这个socket就不能再数据传输;
  • 传输数据时使用字符输出/输入流,在调用wrtie方法输出一行数据后再调用newLine()方法时,newLine()相当于在当行数据末尾加上一个终止的标识,那么对方用readLine()的时候就会识别出该行数据已传输结束。此时不再需要用shutdownOutput关闭输出流来告知对方该行数据已传输结束。

另外要留意:

  • 输出数据时一般需要结束标识告知对方数据已传输完毕,如果没有,则需要调用shutdownOutput来告知。不过一旦调用,该socket就不能输出数据。
  • 一旦调用了shutdownOutput,该socket就不能再使用输出流,再次用getOutputStream也不会再重新打开,只能重新连接(重新获得socket)。
  • 一旦调用close,则整个socket都不能用,包括相关联的输出/输入流

猜你喜欢

转载自blog.csdn.net/KeepStruggling/article/details/81610558