【Java成王之路】EE初阶第八篇:(网络编程) 2

上节回顾

网络初识:

1.网络的基本的一些概念

2.协议

3.协议分层

4.封装和分用(是网络传输数据的具体流程,封装就是在数据中添加一些辅助传输的信息,分用就是就是解析这些信息)

发送数据的时候,上层协议要把数据交给下层协议,由下层协议来添加一些信息

接收数据的时候,下层协议要把数据交给上层协议,由上层协议来进行进一步的解析

socket  

网络编程,通过代码,来控制,让两个主机的进程之间能够进行数据交互.

好比:我使用qq发送一个消息,这个消息就是通过我电脑上的qq客户端进程,先发送给了腾讯服的务器(对应的服务器进程),再由腾讯的服务器进程,把这个消息转发给对方电脑的qq进程.

操作系统就是把网络编程的一些相关操作(相关操作指的是:1.操作系统提供的功能2;访问网络核心的硬件设备,网卡,网卡也是归操作系统来管理的),封装起来了,提供了一组AP供程序猿来调用

操作系统提供的这组API叫做:socket(也可以称为套接字) API

socket的英文原意:叫做插槽 

由于操作系统提供的socket api是C语言风格的接口,咱们在Java中是不能直接使用的.但是没关系,JDK其实也针对C语言这里的socket api  进行了封装.

在标准库中有一组类,这组类就能够让我们完成网络编程.

这组类本质上仍然是调用的操作系统提供的socket API 

Java能调用C语言的函数嘛?

答案是:可以的!

这种调用叫做"跨语言调用"

不同的语言之间,很多都可以互相调用.

要想能够跨语言调用,核心原理在于,了解对应的语言的ABI(二进制编程接口) 

操作系统,提供的API主要有两类(实际上不止这两类,还有其他的)

1.流套接字(底层使用了TCP协议)

2.数据报套接字(底层使用了UDP协议)

简单介绍TCP、UDP

都是传输层协议

socket API 也是属于传输层的东西.

 我们是在应用程序中使用socket API.

socket API也都是传输层协议暴露给上面的应用层的(TCP,UDP)

TCP:

1.有连接

2.可靠传输

3.面向字节流

4.全双工

UDP:

1.无连接

2.不可靠传输

3.面向数据报

4.全双工

有连接:好比就是打电话(连接建立好了才能通电话,如果对面把电话挂了,连接关闭了就没法通电话了)

无连接:好比就是发微信(,回车键一敲,消息就发过去,不管对面看的到还是看不到,想接收还是不想接收,总之消息都能发出去)

可靠传输:发送方能够知道对方是不是收到了

所谓的可靠传输,不是说发送的数据100%就能被对方收到!!(这件事是不可能的),技术再牛逼,你也抵不过拔网线.

不可靠传输:发送方不知道对方是不是收到了.

打电话是否是可靠传输?

是!

发微信是否是可靠传输?

不是!

可靠性 != 安全性

面向字节流:之前介绍过,假设为了发送100个字节,可以一次发送一个字节,重复100次,也可以发送10个字节,重复10次....可以非常灵活的完成这里的发送,接收也是同理.

面向数据报:以一个一个的数据报为基本单位(每个数据报多大,不同的协议里面是有不同的约定的)发送的时候一次至少发一个数据报(如果尝试发一个半,实际只能发出去一个).接收的时候,一次至少接收一个数据报.(如果尝试接收半个,剩下半个就没了)

全双工:双向通信.A和B可以同时向对方发送接收数据

半双工:单向通信.要么A给B发,要么B给A发,不能同时发.

UDP socket 中有两个核心的类:

1.DatagramSocket 描述一个socket 对象.

2.DatagramPacket 描述一个UDP数据报

操作系统提供的网络编程API叫做socket API

socket API中涉及到一个核心概念socket.

socket本质上是一个文件描述符(某个进程被创建出来,进程就会对应一个PCB.PCB中就包含了一个文件描述符表.每次打开一个文件,就会在文件描述符表中分配一个表项.文件描述符表类似于一个数组.数组的下标就是文件描述符,数组的元素是一个内核结构struct file(C语言中的结构体.Linux内核本身就是C语言写的))

一切皆文件:

操作系统在管理硬件设备和一些软件资源的时候.

为了能够风格统一,于是就像用文件的方式来管理

普通文件是文件

键盘 => 标准输入文件

显示器 => 标准输出文件

 网卡也是一个硬件设备.操作系统也是用文件来管理网卡.

此处用来表示网卡设备的文件,就是socket文件.

要想操作网卡,就需要先创建出socket文件.

通过读写这个socket文件的方式来操作网卡了

这个Java标准库中的DatagramSocket对象其实就是在表示一个socket文件.

面向数据报(.DatagramPacket),发送/接收数据,就是以.DatagramPacket对象为单位进行的.

receive()方法:接收数据,如果没有数据过来,receive就会阻塞等待,如果有数据了,receive就能返回一个.DatagramPacket对象.

send()方法:发送数据,以.DatagramPacket为单位进行发送

发送的时候,需要知道发送的目标在哪

接收的时候,也需要知道这个数据从哪来?

ip地址+端口号

在Java里面用InetSocketAddress类表示.

相当于把(ip地址+端口号)这里信息一打包就构成了一个InetSocketAddress类,就可以用这个类表示要发送或者接收具体的一个位置.

UDP的 socket 简单编写一个回显程序.回显客户端 + 回显服务器

 回显:A给B说啥,B就回应啥.(复读机)

回显程序本身没啥意义,但是当前通过这个程序主要是为了能够熟悉Socket API具体的使用

正常的客户端/服务器的通信流程:

服务器代码

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: 灯泡和大白
 * Date: 2022-07-25
 * Time: 13:52
 */
public class UdpEchoServer {
    private DatagramSocket socket = null;

    // port 表示端口号.
    // 服务器在启动的时候, 需要关联(绑定)上一个端口号.
    // 收到数据的时候, 就会根据这个端口号来决定把数据交给哪个进程.
    // 虽然此处 port 写的类型是 int, 但是实际上端口号是一个两个字节的无符号整数.
    // 范围 0-65535
    public UdpEchoServer(int port) throws SocketException {
        socket = new DatagramSocket(port);
    }

    // 通过这个方法来启动服务器.
    public void start() throws IOException {
        System.out.println("服务器启动!");
        // 服务器一般都是持续运行的(7*24)
        while (true) {
            // 1. 读取请求. 当前服务器不知道客户端啥时候发来请求. receive 方法也会阻塞.
            //    如果真的有请求过来了, 此时 receive 就会返回.
            //    参数 DatagramPacket 是一个输出型参数. socket 中读到的数据会设置到这个参数的对象中.
            //    DatagramPacket 在构造的时候, 需要指定一个缓冲区(就是一段内存空间, 通常使用 byte[]).
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
            socket.receive(requestPacket);
            // 把 requestPacket 对象里面的内容取出来, 作为一个字符串.
            String request = new String(requestPacket.getData(), 0, requestPacket.getLength());
            // 2. 根据请求来计算响应.
            String response = process(request);
            // 3. 把响应写回到客户端. 这时候也需要构造一个 DatagramPacket
            //    此处给 DatagramPacket 中设置的长度, 必须是 "字节的个数".
            //    如果直接取 response.length() 此处得到的是, 字符串的长度, 也就是 "字符的个数"
            //    当前的 responsePacket 在构造的时候, 还需要指定这个包要发给谁.
            //    其实发送给的目标, 就是发请求的那一方.
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length,
                    requestPacket.getSocketAddress());
            socket.send(responsePacket);
            // 4. 加上日志打印.
            //    %d 表示要打印一个有符号十进制的整数. %s 表示要打印一个字符串.
            //    如果使用 + 字符串拼接是否可行? 完全可以! 只不过写起来会比较麻烦.
            //    不建议使用字符串拼接.
            String log = String.format("[%s:%d] req: %s; resp: %s",
                    requestPacket.getAddress().toString(),
                    requestPacket.getPort(),
                    request, response, "hello");
            // String log = "[" + requestPacket.getAddress().toString() + ":" + requestPacket.getPort() + "] " .....
            System.out.println(log);
        }
    }

    // 此处的 process 方法负责的功能, 就是根据请求来计算响应.
    // 由于当前是一个 回显服务器 , 就把客户端发的请求直接返回回去即可.
    private String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        UdpEchoServer server = new UdpEchoServer(9090);
        server.start();
    }
}

 客户端代码

import java.io.IOException;
import java.net.*;
import java.util.Scanner;

public class UdpEchoClient {
    private DatagramSocket socket = null;
    private String serverIp;
    private int serverPort;
    //客户端一启动的时候,就需要知道服务器的 ip 和端口
    //但是服务器一启动的时候,是无法知道客户端的 ip 和 端口的.直到客户端的请求到了,服务器才知道对应客户端的ip 和 端口.
    public UdpEchoClient(String sereverIp,int serverPort) throws SocketException {
        this.serverIp = sereverIp;
        this.serverPort = serverPort;
        this.socket = new DatagramSocket();

    }
    public void start() throws IOException {
        Scanner scanner = new Scanner(System.in);
        while (true) {
            //1.从标准输入读入一个数据
            System.out.print("->");
            String request = scanner.nextLine();
            if(request.equals("exit")){
                System.out.println("exit");
                return;
            }
            //2.把字符串构造成一个 UDP 请求,并发送数据
            //这个DatagramPacket中,既要包含具体的数据,又要包含这个数据发给谁
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,
                    InetAddress.getByName(serverIp), serverPort);
            socket.send(requestPacket);
            //3.尝试从服务器这里读取响应
            DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(responsePacket);
            String response = new String(responsePacket.getData(),0,responsePacket.getLength());
            //4.显示这个结果
            String log = String.format("req: %s; resp: %s",request,response);
            System.out.println(log);
        }
    }

    public static void main(String[] args) throws IOException {
        UdpEchoClient client = new UdpEchoClient("127.0.0.1",9090);
        client.start();
    }
}

 

 

 

打印结果:

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

K稳重

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值