哈哈哈,之前的保存成草稿忘了发
目录
一 . 先回顾一下网络初始中的相关概念:
1.网络通信:
2.局域网:
3.广域网:
4.IP地址:
5.端口:
概念
格式
6.协议:
7.五元组:
8.分层协议:
9.封装和分用
10.网络设备分层uuuj
二 . socket
分类
流套接字:使用传输层TCP协议
数据报套接字:使用传输层UDP协议
udp的两个核心类——DatagramSocket和Datagrampacket
UDP的回显服务器
客户端:
服务器端
一 . 先回顾一下网络初始中的相关概念:
1.网络通信:
数据共享本质是 网络数据传输 ,即计算机之间通过网络来传输数据,也称为 网络通信 。2.局域网:
即 Local Area Network,简称LAN。
局域网内的主机之间能方便的进行网络通信,又称为内网;局域网和局域网之间在没有连接的情况下,是无法通信的。3.广域网:
即 Wide Area Network,简称WAN。
通过路由器,将多个局域网连接起来,在物理上组成很大范围的网络,就形成了广域网。广域网内部的 局域网都属于其子网。4.IP地址:
用于定位主机 的网络地址。格式IP 地址是一个 32 位的二进制数,通常被分割为 4 个 “8 位二进制数 ” (也就是 4 个字节),如:01100100.00000100.00000101.00000110 。通常用 “ 点分十进制 ” 的方式来表示,即 a.b.c.d 的形式( a,b,c,d 都是 0~255 之间的十进制整数)。如: 100.4.5.6。特殊 IP127.* 的 IP 地址用于本机环回 (loop back) 测试,通常是 127.0.0.1本机环回主要用于本机到本机的网络通信(系统内部为了性能,不会走网络的方式传输),对于开发网络通信的程序(即网络编程)而言,常见的开发方式都是本机到本机的网络通信。IP 地址解决了网络通信时,定位网络主机的问题,但是还存在一个问题,传输到目的主机后, 由哪个进程来接收这个数据呢?这就需要端口号来标识 。5.端口:
概念
在网络通信中, IP 地址用于标识主机网络地址,端口号可以标识主机中发送数据、接收数据的进程。简 单说:端口号用于定位主机中的进程。一个网络程序运行的时候就需要绑定一个或者多个端口,后续的通信过程都需要通过端口来进行保证类似发送快递时,不光需要指定收货地址(IP地址),还需要指定收货人(端口号)。两个不同的进程,不能绑定同一个端口号,但一个进程可以绑定多个端口号。格式
端口号是0~65535范围的数字,在网络通信中,进程可以通过绑定一个端口号,来发送及接收网络数据。1-1024已经被占用喽(是知名端口号)。64kb6.协议:
网络协议是网络通信(即网络数据传输) 经过的所有网络设备 都必须共同遵从的一组约定、规则。如怎么样建立连接、怎么样互相识别等。最终体现为在网络上传输的数据包的格式。7.五元组:
在 TCP/IP 协议中,用五元组来标识一个网络通信:1. 源 IP :标识源主机2. 源端口号:标识源主机中该次通信发送数据的进程3. 目的 IP :标识目的主机4. 目的端口号:标识目的主机中该次通信接收数据的进程5. 协议号:标识发送进程和接收进程双方约定的数据格式8.分层协议:
分层最大的好处,类似于面向接口编程:定义好两层间的接口规范,让双方遵循这个规范来对接。在代码中,类似于定义好一个接口,一方为接口的实现类(提供方,提供服务),一方为接口的使用类 :对于使用方来说,并不关心提供方是如何实现的,只需要使用接口即可对于提供方来说,利用封装的特性,隐藏了实现的细节,只需要开放接口即可网络编程目标:写一个应用程序让这个程序可以使用网络通信,在这里就需要调用传输层提供的api.传输层api主要是UDP和TCP (socket api);可以想象一下,公司的各个管理阶层9.封装和分用
分用就是从物理层出发倒着解开。。
10.网络设备分层
对于一台 主机 ,它的 操作系统 内核实现了从传输层到物理层的内容,也即是 TCP/IP 五层模型的 下 四层 ;对于一台 路由器 ,它实现了从网络层到物理层,也即是 TCP/IP 五层模型的 下三层 ;对于一台 交换机 ,它实现了从数据链路层到物理层,也即是 TCP/IP 五层模型的 下两层 ;对于 集线器 ,它只实现了 物理层 ;就看上面9那个图就ok了,路由器下三层,交换机下两层。
二 . socket
Socket 套接字,是由系统提供用于网络通信的技术,是基于 TCP/IP 协议的网络通信的基本操作单元。基于 Socket 套接字的网络程序开发就是网络编程。分类
Socket 套接字主要针对传输层协议划分为如下三类:流套接字:使用传输层TCP协议
TCP ,即 Transmission Control Protocol (传输控制协议),传输层协议。以下为TCP的特点(细节后续再学习):有连接可靠传输面向字节流有接收缓冲区,也有发送缓冲区大小不限对于字节流来说,可以简单的理解为,传输数据是基于 IO 流,流式数据的特征就是在 IO 流没有关闭的情况下,是无边界的数据,可以多次发送,也可以分开多次接收。数据报套接字:使用传输层UDP协议
UDP,即User Datagram Protocol(用户数据报协议),传输层协议。以下为UDP的特点(细节后续再学习):无连接不可靠传输面向数据报有接收缓冲区,无发送缓冲区大小受限:一次最多传输64k对于数据报来说,可以简单的理解为,传输数据是一块一块的,发送一块数据假如 100 个字节,必须一 次发送,接收也必须一次接收100 个字节,而不能分 100 次,每次接收1个字节。udp的两个核心类——DatagramSocket和Datagrampacket
DatagramSocket() 创建一个UDP数据报套接字的Socket,绑定到本机任意一个随机端口 (一般用于客户端) DatagramSocket(int port) 创建一个UDP数据报套接字的Socket,绑定到本机指定的端口(一般用于服务端) void receive(DatagramPacket p) 从此套接字接收数据报(如果没有接收到数据报,该方法会阻 塞等待) void send(DatagramPacketp) 从此套接字发送数据报包(不会阻塞等待,直接发送) void close() 关闭此数据报套接字 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- DatagramPacket(byte[] buf, int length) 构造一个DatagramPacket以用来接收数据报,接收的数据保存在 字节数组(第一个参数buf)中,接收指定长度(第二个参数length) DatagramPacket(byte[]buf, int offset, int length, SocketAddress address) 构造一个DatagramPacket以用来发送数据报,发送的数据为字节 数组(第一个参数buf)中,从0到指定长度(第二个参数 length)。address指定目的主机的IP和端口号 InetAddress getAddress() 从接收的数据报中,获取发送端主机IP地址;或从发送的数据报中,获取 接收端主机IP地址 int getPort() 从接收的数据报中,获取发送端主机的端口号;或从发送的数据报中,获 取接收端主机端口号 byte[] getData() 获取数据报中的数据 ---------------------------------------------------------- 构造UDP发送的数据报时,需要传入 SocketAddress ,该对象可以使用 InetSocketAddress 来创 建。 比特就业课 方法签名 方法说明 InetSocketAddress(InetAddress addr, int port) 创建一个Socket地址,包含IP地址和端口号 InetSocketAddress API InetSocketAddress ( SocketAddress 的子类 )构造方法
UDP的回显服务器
客户端:
package echoserver; import javax.security.auth.login.CredentialException; import java.io.IOException; import java.net.*; import java.util.Scanner; /* 服务器的端口是固定指定的,为了方便用户端找到服务器程序 客户端的端口是系统分配的,如果手动指定可能会有客户端的其他程序端口冲突 服务器不怕冲突,因为服务器上面的程序可控,但是客户端的程序是运行在客户电脑上的,环境是复杂的,不可控 服务器要在构造方法中指定好端口 */ public class UdpEchoCLient { public DatagramSocket socket = null; //构造这个对象不需要显式的绑定一个端口,让操作系统自动分配端口(随机挑选一个空闲的) //对于服务器:端口必须是确定好的 //对于客户端来说,端口可以是系统分配的 //一次通信涉及到的IP和端口: //端口号用来标识/区分一个进程,因此不允许一个端口同时被多个进程使用(同一个主机上) //源IP和目的IP,yuan端口和目的端口 //发送方为源,接收方为目的 //由于客户端和服务器都在一个主机上,IP都是127.0.0.1(环回IP),端口是指定了的 private String serverIP = null; private int serverPort = 0; //IP是已知的127.0.0.1 // port是自动分配的 //服务器的IP和端口号也要告诉客户端,才能顺利把消息发给服务器 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){ //1.从控制台读取数据 System.out.println("> "); String request = scanner.next(); if(request.equals("exit")){ System.out.println("byebye"); break; } //2.构造成UDP请求并发送 //传入的serverIP是一个字符串,点分十进制的,而IP地址需要传入一个32位的整数形式, // InetAddress.getByName进行转换 DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length, InetAddress.getByName(serverIP),serverPort); socket.send(requestPacket); //3.读取服务器的UDP响应,并解析 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); } } public static void main(String[] args) throws IOException { UdpEchoCLient cLient = new UdpEchoCLient("127.0.0.1",1090); cLient.start(); } }
服务器端
package echoserver; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketException; //UDP版本的回显服务器 public class UdpEchoServer { /* 网络编程本质上是操作网卡 但是网卡不方便直接操作,在操作系统内核中使用了特殊的文件"socket" 来抽象地表示网卡 因此进行网络通信,势必需要创造一个socket对象 */ private DatagramSocket socket = null; /*服务器必须知道哪个端口,例如饭堂卖重庆小面的在11窗口,方便客户找,11就是指定的端口 对于服务器来说必须要在创建socket对象的时候给绑定一个具体的端口号 否则无法通信! */ public UdpEchoServer(int port) throws SocketException { socket = new DatagramSocket(port); } public void start() throws IOException { System.out.println("服务器启动!!"); //要注意服务器是不止给一个客户端服务的 //while会快速循环 while(true){ //这里的死循环是必须的,因为服务器就是要一直运行,等待客户端的请求,除了个别服务器,像12306 //每天都会定时维护 //只要有客户端过来就可以提供服务 //1.读取客户端的请求 //receive方法参数是输出型参数. //输出型参数.不是我们传数据然后方法使用,而是我i们传进去一个空壳让方法填充数据再返回 //receive方法接收请求需要先构造一个空白的DatagramPacket对象,然后交给receive来填充 DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096); //客户端发请求了receive就顺利读出,如果没有发送请求,此时receive就阻塞,类似与Scanner读取控制台的操作 //客户端请求太多处理不过来也就是"高并发" //处理高并发:多线程/多加机器(管理成本提高--分布式) socket.receive(requestPacket);//🎶输出型参数 //receive 内部会针对参数对象填充数据. //填充的数据来自于网卡,从网卡读完,填充到对象中 //此时requestPacket中包含的数据不方便直接处理,是一个特殊的对象,我们将它转换成字符串就好处理了 String request = new String(requestPacket.getData(),0,requestPacket.getLength()); //requestPacket.getLength()是获取到实际的数据的长度,避免浪费 //可能这个数据报转换成字符串后长度很小,我们就只构造相应的长度,而不是构造byte[]数组那么长的字符串 //2.根据请求,计算响应,这里是回显服务器,响应和请求相同 String response = process(request); //3.把响应写回客户端 //使用send方法,参数也是DatagramPacket,需要先构造好对象 //响应对象要使用响应数据来构造,不能是空的 DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length,requestPacket.getSocketAddress()); /* 此处的长度使用response.length()和response.getBytes().length 有什么区别呢?? 两个写法计量单位不同,一个是字节的个数,一个是字符的个数,如果存的数据都是ASCII的数据就没差别 但是如果存的汉字,那结果就大相径庭 socket api本来就是按照字节来算 DatagramPacket是按字节来处理的,所以我们要计算的是响应数据的字节个数 ___________________ requestPacket.getSocketAddress()是客户端的IP和端口号 */ //发送 socket.send(responsePacket); //4.打印中间结果 System.out.printf("[%s:%d] req: %s; resp: %s\n",requestPacket.getAddress().toString(), requestPacket.getPort(),request,response); // } } //计算响应 public String process(String request){ return request; } public static void main(String[] args) throws IOException { //端口号可以随意指定 //范围:1024-65535 UdpEchoServer server = new UdpEchoServer(1090); server.start(); /* 理解服务器的工作流程 1.读取请求并解析 2.根据请求计算响应 3.构造响应并写回给客户端 */ } }
翻译单词程序
import java.io.IOException;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;
//继承服务器,重写process方法
public class UdpDictServer extends UdpEchoserver2{
private Map<String,String> dict = new HashMap<>();
public UdpDictServer(int port) throws SocketException {
super(port);
dict.put("hello","你好");
dict.put("world","世界");
dict.put("big","大");
dict.put("deprive","剥夺剥削");
}
@Override
public String process(String request) {
return dict.getOrDefault(request,"没有查到!");
}
public static void main(String[] args) throws IOException {
UdpDictServer udpDicyServer = new UdpDictServer(1090);
udpDicyServer.start();
}
}