目录
[UDP的socket api](#UDP的socket api)
一、网络初识
先了解一些核心概念:
局域网:把若干个电脑通过路由器连接在一起。
广域网:把若干个局域网进一步连接在一起,构成更复杂的网络体系。
路由器/交换机:组件网络的基础设备。
IP地址:区分主机。
端口号:区分主机上的不同程序。
二、协议
协议的定义
协议可以理解为是一种约定,是通信双方对于通信规则的约定。听起来和标准的意思相近,但不同的是,标准是认可面很广泛的,而协议可以是认可面非常广也可以是认可面不广只有通信双方认可的。
++通信双方都认可的规则称之为------协议。++
进行网络通信时一定要有通信协议,因为两个用来通信的主机设备往往有不同的硬件不同的操作系统等等,为了保证即使上述不同仍然能进行正确通信。
进行网络通信时,通信协议是非常关键的环节。
协议分层
*网络通信是一个非常复杂的事情,这个过程中涉及很多细节问题,如果使用一个协议约定上述所有内容,那这个协议就会变的非常庞大而且复杂,所以就要把它进行拆分,也就是协议分层。这样就可以把复杂的协议分成功能单一的协议,但拆分出的协议因为数量变多了,所以就要对这些协议进行分类,也就是协议分层,*举个栗子理解一下:
在公司中会进行分组,每个组都有组长,组员把工作汇报给组长,组长再汇报给老板,这样分组的组织结构虽然整体的人很多,但分成组后就不会乱。这样分层的前提是不能越级汇报工作。
协议分层就是上述这样的功能,将协议按照功能分成不同的层级,每个层级有自己的任务,上层协议调用下层协议的功能,下层协议会给上层协议提供服务。
协议分层的好处
好处一:封装的效果,不必知道其他协议的细节,降低学习的成本
比如说两个人在进行打电话时,只需要拨号然后在接通后进行交流即可,通话的两个人不需要理解电话的原理,只要会说汉语就能打电话,
好处二:任意层次的协议都可以灵活替换
意思是电话机和电话机之间有电话机协议,而如果使用无线电的话就会有无线电协议,但使用者不必在意协议的变化,只需要进行通话即可。同理,对于电话机协议,使用者进行汉语交流时使用的汉语协议与使用英语交流时的英语协议没有区别,都是一样进行通信即可。
TCP/IP五层模型
实际开发时采用的网络分层模型。(目前用电脑上网大多数接触到的都是这个网络模型)
五层协议
1、物理层:硬件层面的相关约定,比如网卡,接口之类的
2、数据链路层:关注的是通信时两个相邻节点之间的通信
3、网络层:关注的是通信时对于通信路线的规划,规划出的路径决定了要经过哪些节点,"点到点的传输"
4、传输层:关注的是通信双方的起点与终点,"端到端的传输"
5、应用层:和具体的应用程序相关,传输的数据是做什么的,有什么意义。
协议之间如何配合工作的
首先需要知道:协议的层与层之间,上层协议调用下层协议的功能,下层协议给上层协议提供服务。通过一个栗子来理解层与层之间的配合:
假设A通过vx给B发送一句hello
第一层:A在聊天框输入hello,点击发送,应用程序就要执行上述逻辑,信息是通过网络来执行,首先应用程序将上述要传递的内容组织成应用层数据包,应用程序会有应用层网络协议,协议中约定了数据按照什么样的形式来组织。
++网络上传输的数据本质上二进制的字符串,因此要传输的接收人/发送人/发送时间/消息内容等等就要组织到字符串中,组织的时候就要按照一定的格式来组织。++
不同的协议组织方式是不同的。
假设应用程序的应用层网络协议约定数据的组织格式是这样:
发送人的vx号/接收人的vx号/发送时间/消息正文,具体的栗子就长这样:
这样组织后的数据包是一个结构化的数据,包含了很多字段/属性,要转化成实际的字符串,++将结构化数据转化为二进制字符串的操作------序列化++,反过来将二进制字符串转化为结构化数据的操作为反序列化。
第二步:通过第一层的应用层网络协议得到了应用层数据包,现在应用程序就要调用系统api进行传输,应用层接下来把数据交给传输层,怎么交给传输层?
++传输层提供了api(socket)交给应用程序去调用,调用这样的api就会把数据从应用层交给传输层,进入传输层意味着进入了操作系统内核。++
++传输层拿到了应用层数据包后会进行进一步分装,构造成传输层数据包++,在传输层中有两个典型的协议:UDP/TCP,假设此处使用UDP进行传输协议。
将应用层数据包进一步封装,加上该数据包上加上了UDP报头,报头里放的是一些UDP相关属性(比如发送人和收件人的端口号),原来的应用层数据包为UDP数据包的载荷
传输层构造好传输层数据包后,会把数据包交给网络层,同样的也是传输层调用网络层提供的api
第三层:数据包到网络层后,网络层典型的协议是IP协议,同样的在数据包上加上IP报头,UDP数据包就作为IP数据包的载荷
IP报头里会包括发件人的IP地址和收件人的IP地址。
同样的,加下来网络层调用数据链路层的api(往往是网卡的驱动程序提供),将上述IP数据包交给数据链路层协议
第四层:数据链路层,典型的协议:以太网
以太网拿到iP数据包后进一步封装
上述数据就进入到了网卡驱动中,准备发送了
第五层:物理层。上述的以太网数据帧本质上还是二进制数据,硬件设备要把上述二进制数据转化为光信号/电信号才会真正的发射。
上述层层的包装数据,不停的加数据报头的过程称为封装。
A这边发件人的五层协议执行完,要传输的数据包发送到了B的交换机,数据经过交换机/路由器的一系列转化后最终到达B,
数据到了B后,B执行上述过程的逆过程,B这边:
第一层:物理层,收到光电信号转化为二进制数据,交给数据链路层
第二层数据链路层,按照以太网协议对数据进行解析,解析报头信息,拿到载荷数据交给下一层网络层
第三层网络层,拿到IP数据包后进一步解析,解析报头中的关键信息取出载荷,将载荷传递给传输层
第四层传输层,拿着UDP数据包进一步解析,解析出报头中的关键信息(交给哪个端口号对应的进程)取出载荷交给应用层
第五层应用层,应用程序按照自己的协议解析应用层数据包,得到结构化数据,进行反序列化,将信息显示到界面。
中间的路由器/交换机也要封装分用,也是上述过程,交换机封装分用到数据链路层即可知道下一步转发(工作在数据链路层),路由器分装分用到网络层即可知道下一步转发(工作在网络层)
三、网络编程套接字
操作系统提供的网络编程api为socket api,操作系统提供的socketapi不止一套,包含:(1)流式套接字->给TCP使用 (2)数据包套接字->给UDP使用 (3)Unix域套接字->不能跨主机使用
TCP/UDP都是传输层协议,给应用层提供服务,由于这两个协议的特点/差异非常大,所以我们就需要两套api分别表示。
TCP/UDP特点
++TCP:有连接,可靠传输,面向字节流,全双工++
++UDP:无连接,不可靠传输,面向数据报,全双工++
有连接vs无连接:有连接好比打电话,接通了才能说话,通信双方保存对方的信息;无连接就像发短信,不需要接通可以直接发,通信双方不需要保存对方的信息。
可靠传输vs不可靠传输:
首先要区分可靠与安全的区别,安全是指你传输的数据是否容易被黑客截获,一旦被截获是否会造成严重的影响;可靠是指要传输的数据尽可能的传给对方;UDP是不可靠传输,也就是传输的时候不在意对方是否会收到,只管发。
面向字节流vs面向数据报:
文件操作就是字节流的,读写操作很灵活,TCP和文件操作具有相同的特点;而面向数据报传输数据时的基本单位只能是一个一个的UDP数据报,一次读写只能读一个完整的数据报。
全双工vs半双工:
全双工:一条链路双向通信(TCP/UDP都是,后续代码中创建的socket对象既可以读也可以写)
半双工:一条链路单向通信
UDP回显服务器
UDP的socket api
UDP的socket api重点是两个类:
(1)DatagramSocket:系统中本来就有socket的概念,Datagram是对于操作系统的socket概念的封装;系统中的socket可以理解为是"文件",socket文件可以视为是网卡这种硬件设备的抽象表示形式,针对socket文件的读写操作可以理解为是针对网卡这种硬件设备进行读写。之前学过的普通文件相当于是针对硬盘硬件的设备的抽象,借助文件这个概念(遥控器)来操作硬盘。
++计算机中将遥控器这样的概念称为------句柄(handle)++
此处Datagram就是用来操作网卡的遥控器,针对这个对象的读写操作就是相当于针对网卡进行读写操作。
(2)DatagramPacket:针对UDP数据报的抽象表达,一个DatagramPacket对象就相当于一个UDP数据报,一次发送/接收就是传输了一个DatagramPacket对象。
回显服务器
Echo称为回显,正常服务器发送请求时会返回不同的响应,此处回显服务器就是请求发了什么响应就是什么,这个过程没有计算也没有逻辑,是最简单的客户端服务器,目的是为了学习socket api的用法。
服务器程序一启动就要确认好端口号,客户端是主动的一方,服务器是被动的一方,客户端要知道服务器的端口号才能开始主动
(ip地址:服务器所在的主机ip;port:一个主机上有很多应用程序都要网络通信,就要把哪个程序用哪个端口确认下来,而且还要确保一个端口不能同时被两个或多个进程进程关联,也就是不能重复。实际开发中端口号应该设为多少?在保证端口号合法的前提下,程序员先写个端口号试试,不行再换就可以了)。
也就是要在服务器构造方法中加上参数port,记得抛出异常
java
public class UdpEchoServer {
private DatagramSocket socket = null;
public UdpEchoServer(int port) throws SocketException {
socket = new DatagramSocket(port);
}
}
服务器需要不停的处理客户端的请求不停的返回响应,所以在服务器类的start方法中要写一个while循环不停的处理请求返回响应。
在处理一个请求时,首先读取指令并解析,需要使用socket接受请求,而接受的请求是一个UDP数据报所以要创建一个datagramPocket对象来引用接收的请求。
需要注意参数列表,数据报本质上也是字节,所以创建字节数组来接收,接收的容量假设是数组的最大容量。
为了方便在java代码中处理,可以把上述数据报中的二进制数据转化为字符串,构造成String
java
DatagramPacket requestPacket = new DatagramPacket(new byte[1024],1024);
socket.receive(requestPacket);
String request = new String(requestPacket.getData(),0,requestPacket.getLength());
第二步根据请求返回响应,因为是回响服务器,所以在执行请求时直接返回就好,返回的响应使用String进行接收
Go
String reponse = this.possess(request);
public String possess(String reponse){
return reponse;
}
第三步将响应返回客户端,接收的时候构造一个空的DatapramPacket即可,而发出响应的时候不光要有带数据的对象,还要有数据要发给谁,这里要发送的对象就是谁给你发出请求就是要返回响应的对象,UDPsocket本身没有记录对方的ip端口号等信息,但是datagramPacket里有记录,我们一开始创建出接收客户端请求的对象里会记录发出请求客户端的信息,用这个对象调用getSocketAddress方法可以得到信息
java
DatagramPacket reponsePacket = new DatagramPacket(reponse.getBytes(),0,reponse.getBytes().length,
requestPacket.getSocketAddress());
socket.send(reponsePacket);
这样一个回响服务器就创建好了,在主函数中调用一下即可,完整服务器代码:
java
public class UdpEchoServer {
private DatagramSocket socket = null;
public UdpEchoServer(int port) throws SocketException {
socket = new DatagramSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动");
while(true){
DatagramPacket requestPacket = new DatagramPacket(new byte[1024],1024);
socket.receive(requestPacket);
String request = new String(requestPacket.getData(),0,requestPacket.getLength());
String reponse = this.possess(request);
DatagramPacket reponsePacket = new DatagramPacket(reponse.getBytes(),0,reponse.getBytes().length,
requestPacket.getSocketAddress());
socket.send(reponsePacket);
}
}
public String possess(String reponse){
return reponse;
}
public static void main(String[] args) throws IOException {
UdpEchoServer udpEchoServer = new UdpEchoServer(9090);
udpEchoServer.start();
}
}
回显客户端
首先在客户端类创建DatagramSocket对象,参数列表要有ip地址和端口号,ip地址如果服务器和客户端在一台主机上此时就固定写为127.0.0.1,端口号是服务器在创建socket对象时指定的端口号。
java
public class UdpEchoClient {
private DatagramSocket socket = null;
private String serverPort;
private String serverIP;
public UdpEchoClient(String serverIP,String serverPort) throws SocketException {
socket = new DatagramSocket();
this.serverIP = serverIP;
this.serverPort = serverPort;
}
}
创建socket对象时没有指定端口号,是因为操作系统会自动分配一个空闲的端口号,这个自动的端口号会因为每次重启都会不一样。
++为什么服务器要有固定的端口号而客户端要让系统自动分配?++
(1)服务器要有固定端口号是因为客户端需要主动给服务器发出请求,如果服务器的端口号不是固定的话就需要每次调整。
(2)如果给客户端固定的端口号可能会跟客户端上的应用程序冲突,一旦端口冲突会导致程序不能进行。
在客户端内部的while循环思路与服务器的别无二致,首先向服务器发出请求,然后接收响应,完整的代码为:
java
public class UdpEchoClient {
private DatagramSocket socket = null;
private int serverPort;
private String serverIP;
public UdpEchoClient(String serverIP,int serverPort) throws SocketException {
socket = new DatagramSocket();
this.serverIP = serverIP;
this.serverPort = serverPort;
}
public void start() throws IOException {
System.out.println("客户端启动");
Scanner scanner = new Scanner(System.in);
while(true){
System.out.println("请输入请求: ");
String request = scanner.next();
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),0,request.getBytes().length
, InetAddress.getByName(serverIP),serverPort);
socket.send(requestPacket);
DatagramPacket responsePacket = new DatagramPacket(new byte[1024],1024);
socket.receive(responsePacket);
String response = new String(responsePacket.getData(),0,responsePacket.getLength());
System.out.println(response);
}
}
public static void main(String[] args) throws IOException {
UdpEchoClient udpEchoClient = new UdpEchoClient("127。0.0.1",9090);
udpEchoClient.start();
}
}
UDP模型到这就创建完了。
感谢观看
道阻且长,行则将至。