概述
上节回顾
网络初识:
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(); } }
打印结果:
最后
以上就是现实棉花糖为你收集整理的【Java成王之路】EE初阶第八篇:(网络编程) 2的全部内容,希望文章能够帮你解决【Java成王之路】EE初阶第八篇:(网络编程) 2所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复