Javaee的网络编程初识
网络初识:
局域网 :把若干个电脑连接到一起,通过路由器,进行组网
广域网 :把很多很多局域网进一步的相连,构成更复杂的网络体系。
IP地址:区分主机
端口号:区分主机上不同的程序
协议:
是一种约定,通信双方,对于通信规则的约定,一定是通信双方都得到认可的。
标准往往是一个认可面非常广泛的,“国家标准”“国际标准”
协议。则不一定
可以是认可面非常广,也可以是认可面不广的。
进行网络通信的时候,一定是需要通信协议的,主要是,两个用来通信的主机设备,不同的硬件,不同的操作系统,不同的应用程序。
进行网络通信的时候,通信协议是非常关键的环节。
协议分层
网络通信,是一个非常复杂的事情,这个过程中涉及到很多的细节问题。
如果使用一个协议来约定上述所有的细节,这个协议就会非常庞大,非常复杂。
然后拆分,但是拆分的协议太多了,就要对这些协议进行分类,甚至,要进行“分层”
协议分层,也就是上述类似的效果,把很多协议,按照功能分成不同的层级,每个层级都有对应的主线任务(目标/要解决的问题)
上层协议会调用下层协议的功能,下层协议会给上层协议提供服务。
协议分层的好处
好处1:封装的效果,某一层协议,不必知道其他层协议的细节,降低学习使用成本。
只要会说汉语,不需要理解电话的工作原理,就能打电话,设计电话的人,可能是外国人,不必懂汉语,也不妨碍他设计开发电话。
好处2:任意层次的协议们都是可以灵活替换的(解耦合)
当前网络的现状,就是有很多的协议,这些协议就是按照一定的分层规则,组织起来的。
OSI七层模型(不是ISO){只存在于教科书上,实际上客观世界不存在}
TCP/IP五层(或四层)模型
可以认为是OSI的简化版本,就是真是世界采取的网络分层模型,现在接触的网络大部分都是TCP/IP模型(电脑上网)
关于五层协议:
1)物理层{硬件层面上的相关约定}
2)数据链路层{关注的是通信过程中,两个相邻节点之间的通信}
3)网络层{关注的是通信中,通信路径的规划,规划出的路径,就决定了,数据要经过哪些节点“点到点的传输”}
4)传输层{关注的是通信双方的“起点”和“终点”,“端到端的传输”}
5)应用层{和具体应用程序直接相关}
协议的层和层之间,是如何配合工作的?
协议的层和层之间,上层协议调用下层协议,下层协议给上层提供服务。
比如:
1.应用层
A通过qq给B发送hello
A给B发送hello.
四个信息,为了组织成字符串,就通过分割各个部分,使用\n结束标志。
qq号/接收人qq号/时间/消息
2.传输层
应用层有了,调用系统api来进行传输。
应用层接下来把数据交给传输层,怎么交给传输层?传输层(操作系统内核)提供了api,让应用程序去调用。
调用这样的api就会把刚才的应用层数据交给输入层
传输层拿到应用层数据包之后,就会把这个数据包进一步的封装,构造成传输层数据包,在传输层,典型协议有两个,TCP/UDP,此处假设使用UDP来作为传输层协议。
报头里放的是一些,udp相关的属性(比如发件人的收件人的端口号,就在UDP中)
传输层构造好数据包之后,就会继续把数据包交给网络层
3.网络层 典型的协议IP协议
ip报头就包含收件人的ip地址和发件人的ip地址。
又进一步的调用数据链路层api,把上述ip数据包,交给数据链路层的协议。
4.数据链路层 典型协议 以太网
此处传输网络数据,也需要介质,就把以太这个词拿过来。
上述数据已经进入到网卡驱动中了。
接下来就要真正发送出去了。
5.物理层
上述的以太网数据帧,本质上还是0101二进制数据。
硬件设备,要把上述二进制数据,转成光信号/电信号/电磁波才会真正进行反射。
上述层层包装数据,不停的加数据报头的过程,称为“封装”
B这边进行转换
网络编程套接字
socket=>操作系统提供的网络编程的api就称为“socket api”
TCP和UDP都是传输层协议,都是给应用层提供服务的
但是由于这两个协议,特点,差异非常大,因此我们就需要搞两套api,来分别表示
特点:TCP:有连接,可靠传输,面向字节流,全双工
UDP:无连接,不可靠传输,面向数据报,全双工
有连接就好比,打电话,打通了才能说话。无连接就好比,发短信,直接上来就发。
可靠传输vs不可靠传输
可靠是尽可能做到数据到达对方,无法100%
不可靠传输则是传输数据的时候,压根不关心,对方是否收到了,发了就完了。
面向字节流 vs 面向数据报
文件操作就是字节流的,比喻成水流一样,读写操作非常灵活,
面向数据报,就不是了,传输数据的基本单位,是一个个的udp数据报,一次读写,只能读写一个完整的udp数据报
全双工vs半双工
全双工:一条链路,能够进行双向通信(tcp,udp都是全双工)
半双工:一条链路,只能进行单向通信。
UDP的socket api重点是两个类
1.DatagramSocket
系统中,本身就有socket这样的概念,DatagramSocket就是对于操作系统的socket概念的封装。
DatagramSocket就可以视为是“操作网卡”的遥控器,针对这个对象进行读写操作,就是针对网卡进行读写操作。
socket也是一种文件,需要close
2.DatagramPacket
针对UDP数据报的一个抽象表示。
一个DatagramPacket对象,就相当于一个UDP数据报,一次发送/一次接受,就是传输了一个DatagramPacket对象。
网络程序,就会有客户端也有服务器。
Echo称为“回显”,他的意思就是请求发了个啥,响应就是啥,这个过程中,没有计算,也没有业务逻辑,最简单的客户端服务器,只是单纯的去认识socket api的用法。
客户端,都是需要通过额外的途径知道服务器ip和端口是啥的。
因此,服务器额ip和端口得是固定的,不能老变。
构造socket对象的时候,没有指定端口号,没指定不代表没有而是操作系统,自动分配了一个空闲的(不和别人冲突)的端口号过来了.这个自动分配的端口号,每次重新启动程序都可能不一样。
为什么服务器要有固定的端口号:
1)服务器要有固定的端口号,是因为,客户端需要主动给服务器发请求。
如果服务器端口号不是固定的(假设是每次都变,此时客户端就不知道请求发给谁了)
2)客户端为啥要系统自动分配,指定固定的端口号行不行?
不行,可能会和客户端所在电脑上的其他程序冲突,一旦端口冲突,程序就启动不了了。
服务器代码:
package netWork;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class UdpEchoServer {
private DatagramSocket socker=null;
public UdpEchoServer(int port) throws SocketException {
socker=new DatagramSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动");
while(true){
//(1)读取请求并解析
DatagramPacket requestPacket=new DatagramPacket(new byte[4096],4096);
socker.receive(requestPacket);
//为了方便在Java代码中处理(尤其是后面进行打印)可以把上述数据报中的二进制数据,拿出来,构成String
String resquest=new String(requestPacket.getData(),0,requestPacket.getLength());
//(2)根据请求计算响应
String response=this.process(resquest);
//(3)把响应写回客户端
DatagramPacket responsePacket=new DatagramPacket(response.getBytes(),0,response.getBytes().length,requestPacket.getSocketAddress());
socker.send(responsePacket);
System.out.printf("[%s:%d] req=%s,resp=%s\n",requestPacket.getAddress(),requestPacket.getPort(),resquest,response);
}
}
//由于当前写的是“回显服务器”
public String process(String resquest) {
return resquest;
}
public static void main(String[] args) throws IOException {
UdpEchoServer server=new UdpEchoServer(9090);
server.start();
}
}
客户端代码:
package netWork;
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class UdpEchoClient {
private DatagramSocket socket=null;
private String serverIp;
private int serverPort;
public UdpEchoClient(String serverIp,int serverPort) throws SocketException {
socket=new DatagramSocket();
this.serverIp=serverIp;
this.serverPort=serverPort;
}
public void start() throws IOException {
Scanner scanner=new Scanner(System.in);
//1.从控制台读取用户输入
String request=scanner.next();
//2.构造请求并发送
//构造请求数据报的时候,不光要有数据,还要有“目标”
DatagramPacket requestPacket=new DatagramPacket(request.getBytes(),0,request.getBytes().length, InetAddress.getByName(serverIp),serverPort);
socket.send(requestPacket);
//3.读取响应数据
DatagramPacket responsePacket=new DatagramPacket(new byte[4096],4096);
socket.receive(responsePacket);
//4.显示响应到控制台上。
String response=new String(responsePacket.getData(),0,responsePacket.getLength());
System.out.println(response);
}
}