java基础(十四):网络编程

网络编程 (套接字)

网络编程概述:网络编程即使用套接字来达到进程间通信

基本概念

网络的七层模型 (OSI):物理层、数据链路层、网络层、传输层 (UDP、TCP)、会话层、表示层、应用层 (http\https\ftp)

IP地址:唯一标识网络中的通信实体、

  • IPV4:32位二进制组成;分成4组,每一组的范围0-255之间,每组之间通过.来分隔;例如:192.168.12.22
  • IPV6:128位二进制组成;分成了8组,每一组16位,十六进制;每一组由4个十六进制数字组成;每组之间通过:来分隔;
  • IP不能重复
  • IP本身是动态分配的,静态IP可以实现但是收费高昂;
  • 广播IP地址:255.255.255.255

端口号:

  • 数据发送还是数据接收都需要通过端口号

  • 给端口进行了一个编号:0-65535

  • 0-1024这些端口已经被系统内部或者通用的协议占用了

  • 80:缺省端口

域名:将IP地址通过一个字符串来代替

DNS: 域名解析服务器

  • 提供根据域名查询IP地址的服务
  • 本机hosts文件位置:C盘 —> Windows —> System32 —> drivers —> etc —> hosts文件
  • 首先检查本机的hosts文件中有没有域名的配置,如果有直接连接对应的IP地址,否则在DNS上查找对应的IP地址

代表IP地址和端口的类 — SocketAddress

  • SocketAddress为抽象类

  • 实现类:InetSocketAddress — 实现 IP 套接字地址(IP 地址 + 端口号)

  • 构造方法:

    • InetSocketAddress(String hostname, int port):根据主机名和端口号创建套接字地址。
  • 方法:

    • String getHostName() :获取 hostname。
    • int getPort() :获取端口号。
    public class SocketAddressDemo {
        public static void main(String[] args) {
            // 创建InetSocketAddress对象 表示IP和端口
            // 第一个参数表示IP地址或者主机名,第二个地址表示端口号
            // 127.0.0.1  localhost 永远指向本机
            InetSocketAddress isa = new InetSocketAddress("127.0.0.1",3344);
            // 获取主机名
            // 如果是本机返回主机名;如果是其他机器,则返回传入的ip地址
            System.out.println(isa.getHostName());
            // 获取端口号
            System.out.println(isa.getPort());
        }
    }
    
    

TCP / UDP

UDP

  • 用户数据报协议(User Datagram Protocol)

  • 数据报(Datagram):网络传输的基本单位

  • UDP特点:

    1. 不建立连接

    2. 不可靠

    3. 数据传输效率高

  • 应用场景:对数据可靠性要求不太高,但是对数据传输效率要求比较高;例如:语音通话、视频通话、视频直播

使用UDP进行网络数据传输

  • 底层基于流实现,使用JDK提供的DatagramSocket

DatagramSocket

  • 此类表示用来发送和接收数据报包的套接字;

  • 构造方法:

    • DatagramSocket():构造数据报套接字并将其绑定到本地主机上任何可用的端口
    • DatagramSocket(int port):创建数据报套接字并将其绑定到本地主机上的指定端口。
  • 方法:

    • void send(DatagramPacket p):从此套接字发送数据报包。
    • void receive(DatagramPacket p):从此套接字接收数据报包。
    • void close():用于关闭此数据报套接字

DatagramPacket:

  • 此类表示数据报包

  • 构造方法:

    • 发送端构造方法:DatagramPacket(byte[] buf, int length, SocketAddress address)构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号
    • 接收端构造方法:DatagramPacket(byte[] buf, int length)构造 DatagramPacket,用来接收长度为 length 的数据包。
  • 方法:

    • byte[] getData() :获取数据内容。
    • int getLength() :返回将要发送或接收到的数据的长度
    • SocketAddress getSocketAddress():获取要将此包发送的或发出此包的远程主机的SocketAddress

发送端和接收端的流程

发送端:

  • 指定:IP + 端口号
  1. 创建发送端的对象:DatagramSocket
  2. 准备数据报包:DatagramPacket
  3. 调用方法发送数据
  4. 关流
public class UDPSenderDemo {
    public static void main(String[] args) throws IOException {
        // 发送端
        // 1. 创建发送端的对象:DatagramSocket
        DatagramSocket ds = new DatagramSocket();
        // 2. 准备数据报包:DatagramPacket
        // 2.1 第一个参数: byte[] buf 表示要发送的数据
        String str = "hello world";
        byte[] bytes = str.getBytes();
        // 2.2 第二个参数: int length 发送数据的长度
        // 2.3 第三个参数: InetSocketAddress
        InetSocketAddress isa = new InetSocketAddress("127.0.0.1",3456);
        DatagramPacket dp = new DatagramPacket(bytes,str.length(),isa);
        // 3. 调用方法发送数据
        ds.send(dp);
        // 4. 关流
        ds.close();
    }
}

接收端:

  • 指定:监听端口号
  1. 创建接收端的对象DatagramSocket,并指定端口号
  2. 准备接受数据的数据报包DatagramPacket
  3. 调用接受数据的方法
  4. 关流
  5. 解析数据报包,并打印内容
public class UDPReceiveDemo {
    public static void main(String[] args) throws IOException {
        // 接收端
        // 1. 创建接收端的对象DatagramSocket,并指定端口号
        DatagramSocket ds = new DatagramSocket(3456);
        // 2. 准备接受数据的数据报包DatagramPacket
        // 2.1 第一个参数 byte[] bytes 用于接收数据
        // 2.2 第二个参数 指定数组可以使用的长度,byte数组长度
        DatagramPacket dp = new DatagramPacket(new byte[1024],1024);
        // 3. 调用接受数据的方法
        // 等待发送端发送数据
        ds.receive(dp);
        // 4. 关流
        ds.close();
        // 5. 解析数据报包,并打印内容
        byte[] data = dp.getData();
        System.out.println(new String(data,0,dp.getLength()));
    }
}

注意:先运行接收端在receive()处阻塞,等待接收数据,再运行发送端发送数据

案例演示:使用UDP模拟单人聊天

public class ChatUDPDemo {

    public static void main(String[] args) {
        // 创建对象
        new Thread(new ChatReceiver()).start();
        new Thread(new ChatSender()).start();
    }
}

// 定义发送端的类实现Runnable接口
class ChatSender implements Runnable {

    @Override
    public void run() {
        // 定义UDP发送数据类
        // DatagramSocket ds = null;
        try {
            DatagramSocket ds = new DatagramSocket();
            // 准备Scanner对象用于获取用户输入的数据
            Scanner sc = new Scanner(System.in);
            while (true) {  // 不断发送数据
                // 获取需要发送的字符串
                // 阻塞
                String msg = sc.next();
                // 创建数据报包
                DatagramPacket dp = new DatagramPacket(msg.getBytes(), msg.length(),
                        new InetSocketAddress("127.0.0.1", 3345));
                // 发送数据
                ds.send(dp);
                if ("#".equals(msg)) {  // 结束发送条件
                    break;
                }
            }
            // 关流
            ds.close();
            sc.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

// 定义接收端的类,实现Runnable接口
class ChatReceiver implements Runnable {

    @Override
    public void run() {
        // 定义UDP接收数据类
        // DatagramSocket ds = null;
        try {
            DatagramSocket ds = new DatagramSocket(3345);
            // 端口号必须一致
            while (true) {
                // 准备接收数据的数据报包
                DatagramPacket dp = new DatagramPacket(new byte[200], 200);
                // 接收数据
                // 阻塞
                ds.receive(dp);
                // 解析数据
                String msg = new String(dp.getData(), 0, dp.getLength());
                if ("#".equals(msg)) {
                    System.out.println(dp.getAddress().getHostName() + "已下线");
                    break;
                }
                System.out.println(dp.getAddress().getHostName() + ":" + msg);
            }
            // 关流
            ds.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

TCP

  • 传输控制协议 (Transmission Control Protocol)

  • TCP特点:

    1. 建立连接(三次握手)

    2. 可靠的

    3. 数据传输效率低

    4. 不限制传输数据量

  • 应用场景:数据可靠性要求非常高,对数据传输效率没有特别高的要求;例如:文件传输 QQ文字消息

使用TCP进行网络数据传输

  • TCP底层基于流的

客户端:Socket类

  • 此类实现客户端套接字
  • 构造方法
    • Socket():通过系统默认类型的 SocketImpl 创建未连接套接字
    • Socket(String host, int port):创建一个流套接字并将其连接到指定主机上的指定端口号。
  • 方法:
    • void connect(SocketAddress endpoint) :将此套接字连接到服务器
    • InputStream getInputStream(): 返回此套接字的输入流
    • OutputStream getOutputStream() : 返回此套接字的输出流
    • void shutdownInput(): 读取完成
    • void shutdownOutput(): 输出完成。

服务器端:ServerSocket

  • 此类实现服务器套接字
  • 构造方法
    • ServerSocket(int port):创建绑定到特定端口的服务器套接字。
  • 方法
    • Socket accept(): 接受客户端的连接请求。 返回一个代表当前正在连接的客户端对象
    • void close():关流

TCP实现网络数据传输

  • 客户端
    1. 创建客户端对象 — Socket
    2. 发起连接服务器端的请求 — connect()
    3. 获取输出流 — getOutputStream()
    4. 写出数据 — write()
    5. 通知服务器端数据写出完成 — shutdownOutput()
    6. 关流 — close()
public class TCPClientDemo {
    public static void main(String[] args) throws IOException {
        // 1. 创建客户端对象
        Socket socket = new Socket();
        // 2. 发起连接服务器端的请求
        // 需要一个参数指定服务器端的ip地址和端口号
        socket.connect(new InetSocketAddress("127.0.0.1",8888));
        // 3. 获取输出流
        OutputStream out = socket.getOutputStream();
        // 4. 写出数据
        out.write("hello".getBytes());
        // 5. 通知服务器已经写出完成
        socket.shutdownOutput();
        // 6. 关闭客户端
        socket.close();
    }
}
  • 服务器端:
    1. 创建服务器端对象,监听端口 — ServerSocket
    2. 接受客户端的请求 — accept()
    3. 获取一个输入流 — getInputStream()
    4. 读取数据 — read()
    5. 通知客户端读取完成 — shutdownInput()
    6. 关流 — close()
public class TCPServerDemo {

    public static void main(String[] args) throws IOException {
        // 1. 创建服务器端对象 ServerSocket,绑定端口号
        ServerSocket ss = new ServerSocket(8888);
        // 2. 接受客户端请求
        // accept() --- 阻塞
        Socket socket = ss.accept();
        // 3. 获取一个输入流
        InputStream in = socket.getInputStream();
        // 4. 读取数据
        byte[] bytes = new byte[1024];
        int len;
        while ((len = in.read(bytes)) != -1) {
            // 打印数据
            System.out.println(new String(bytes, 0, len));
        }
        // 5. 通知客户端,数据读取完成
        socket.shutdownInput();
        // 6. 关闭流
        ss.close();
    }
}

案例演示:使用TCP通信模拟文件传输流程

import java.io.*;
import java.net.*;


public class FileUploadDemo {
    public static void main(String[] args) {
        Server server = new Server();
        new Thread(server).start();
        Client client = new Client();
        new Thread(client).start();

    }
}

class Client implements Runnable{

    @Override
    public void run() {
        // 1. 创建客户端对象
        Socket socket = new Socket();

        try {
            // 2. 连接服务器端
            socket.connect(new InetSocketAddress("127.0.0.1",8888));
            // 3. 输入流,读取本地文件
            File file = new File("D:\\Javase\\Day21\\src\\test.txt");
            InputStream in = new FileInputStream(file);
            // 获取文件名
            String fileName = file.getName();

            // 4. 获取一个输出流
            OutputStream out = socket.getOutputStream();
            // 5. 读取本地文件,发送给服务器端
            // 5.1 发送文件名(长度 + 名称)
            out.write(fileName.length());
            out.write(fileName.getBytes());
            // 5.2 发送内容
            byte[] bytes = new byte[1024];
            int len;
            while ((len = in.read(bytes)) !=-1){
                out.write(bytes,0,len);
            }
            // 6. 通知服务器端文件发送完成
            socket.shutdownOutput();

            // 新增功能 获取服务器端的回复数据并打印
            InputStream in1 = socket.getInputStream();
            byte[] bytes1 = new byte[1024];
            int len1;
            while ((len1 = in1.read(bytes1)) !=-1){
                System.out.println(new String(bytes1,0,len1));
            }
            // 通知服务器端数据读取完成
            socket.shutdownInput();

            // 7. 关闭所有流
            socket.close();
            in.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class Server implements Runnable{

    @Override
    public void run() {
        // 1. 创建服务器对象
        try {
            ServerSocket ss = new ServerSocket(8888);
            // 2. 接受客户端连接请求
            Socket socket = ss.accept();
            // 3. 获取一个输入流
            InputStream in = socket.getInputStream();
            // 4. 准备一个输出流 将数据写出到e
            // 4.1 读取文件名长度
            int fileNameLen = in.read();
            // 4.2 读取文件名称
            byte[] b = new byte[fileNameLen];
            in.read(b);
            String fileName = new String(b);
            OutputStream out = new FileOutputStream("D:\\Javase\\Day21\\src\\testFile\\" + fileName);
            // 5. 一边通过输入流读取客户端发送的数据,一边写出
            //  读取内容
            int len;
            byte[] bytes = new byte[1024];
            while ((len = in.read(bytes)) != -1){
                // 当前已经接收了客户端发送的一部分数据
                // 将这部分数据写出
                out.write(bytes,0,len);
            }
            // 6. 通知客户端文件接收完成
            socket.shutdownInput();

            // 新增功能:当文件接受完成,给客户端一个回复
            OutputStream out1 = socket.getOutputStream();
            out1.write("文件传输完成".getBytes());
            // 通知客户端文件写出完成
            socket.shutdownOutput();

            // 7. 关闭所有流
            socket.close();
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值