4.网络原理及编程
1.网络发展
(1)独立模式
计算机之间相互独立
(2)网络互连
随着时代的发展,越来越需要计算机之间互相通信,共享软件和数据,即以多个计算机协同工作来完成业务,就有了网络互连。
网络互连:将多台计算机连接在一起,完成数据共享。
数据共享本质是网络数据传输,即计算机之间通过网络来传输数据,也称为网络通信。
根据网络互连的规模不同,可以划分为局域网和广域网。
局域网(LAN)
连接方式:基于网线直连,基于集线器组建,基于交换机组建,基于交换机和路由器组建
广域网(WAN)
通过路由器,将多个局域网连接起来,在物理上组成很大范围的网络,就形成了广域网。广域网内部的局域网都属于其子网。
2.网络通信
(1)IP地址
IP地址主要用于标识网络主机、其他网络设备(如路由器)的网络地址。简单说,IP地址用于定位主机的网络地址。
IP地址是一个32位的二进制数,通常被分割为4个“8位二进制数”(也就是4个字节);通常用“点分十进制”的方式来表示,即 a.b.c.d 的形式(a,b,c,d都是0~255之间的十进制整数)
(2)端口号
在网络通信中,IP地址用于标识主机网络地址,端口号可以标识主机中发送数据、接收数据的进程。简单说:端口号用于定位主机中的进程。
端口号是0~65535范围的数字,在网络通信中,进程可以通过绑定一个端口号,来发送及接收网络数
据。 两个不同的进程,不能绑定同一个端口号,但一个进程可以绑定多个端口号。
(3)协议
网络协议是网络通信(即网络数据传输)经过的所有网络设备都必须共同遵从的一组约定、规则。如怎么样建立连接、怎么样互相识别等。只有遵守这个约定,计算机之间才能相互通信交流。通常由三要素组成:
- 语法:即数据与控制信息的结构或格式;
- 语义:即需要发出何种控制信息,完成何种动作以及做出何种响应;
- 时序,即事件实现顺序的详细说明。
协议最终体现为在网络上传输的数据包的格式。
(4)五元组
在TCP/IP协议中,用五元组来标识一个网络通信:
- 源IP:标识源主机
- 源端口号:标识源主机中该次通信发送数据的进程
- 目的IP:标识目的主机
- 目的端口号:标识目的主机中该次通信接收数据的进程
- 协议号:标识发送进程和接收进程双方约定的数据格式
(5)协议分层
分层最大的好处,类似于面向接口编程:定义好两层间的接口规范,让双方遵循这个规范来对接
OSI七层模型
- OSI 七层网络模型是一个逻辑上的定义和规范:把网络从逻辑上分为了7层。
- OSI 七层模型是一种框架性的设计方法,其最主要的功能使就是帮助不同类型的主机实现数据传 输;
- 它的最大优点是将服务、接口和协议这三个概念明确地区分开来,概念清楚,理论也比较完整。通过七个层次化的结构模型使不同的系统不同的网络之间实现可靠的通讯。
OSI 七层模型既复杂又不实用:所以 OSI 七层模型没有落地、实现;实际组建网络时,只是以 OSI 七层模型设计中的部分分层,也即是以下 TCP/IP 五层(或四层)模型来实现。
TCP/IP五层协议
TCP/IP是一组协议的代名词,它还包括许多协议,组成了TCP/IP协议簇。
TCP/IP通讯协议采用了5层的层级结构,每一层都呼叫它的下一层所提供的网络来完成自己的需求。
- 应用层:负责应用程序间沟通,如简单电子邮件传输(SMTP)、文件传输协议(FTP)、网络远程访问协议(Telnet)等。我们的网络编程主要就是针对应用层。
- 传输层:负责两台主机之间的数据传输。如传输控制协议 (TCP),能够确保数据可靠的从源主机发送到目标主机。
- 网络层:负责地址管理和路由选择。例如在IP协议中,通过IP地址来标识一台主机,并通过路由表的方式规划出两台主机之间的数据传输的线路(路由)。路由器(Router)工作在网路层。
- 数据链路层:负责设备之间的数据帧的传送和识别。例如网卡设备的驱动、帧同步(就是说从网线上 检测到什么信号算作新帧的开始)、冲突检测(如果检测到冲突就自动重发)、数据差错校验等工作。 有以太网、令牌环网,无线LAN等标准。交换机(Switch)工作在数据链路层。
- 物理层:负责光/电信号的传递方式。比如现在以太网通用的网线(双绞 线)、早期以太网采用的同轴电缆(现在主要用于有线电视)、光纤,现在的wifi无线网使用电磁波等都属于物理层的概念。物理层的能力决定了最大传输速率、传输距离、抗干扰性等。集线器(Hub)工作在物理层。
(6)TCP/IP通讯过程

(7)封装和分用
不同的协议层对数据包有不同的称谓,在传输层叫做段,在网络层叫做数据报 ,在链路层叫做帧。
应用层数据通过协议栈发到网络上时,每层协议都要加上一个数据首部(header),称为封装 。
首部信息中包含了一些类似于首部有多长,载荷有多长,上层协议是什么等信息。
数据封装成帧后发到传输介质上,到达目的主机后每层协议再剥掉相应的首部,根据首部中的 “上层协议字段” 将数据交给对应的上层协议处理
封装过程

分用过程

3.网络编程
3.1定义
网络编程,指网络上的主机,通过不同的进程,以编程的方式实现网络通信(或称为网络数据传输)。
3.2基本概念
(1)发送端/接收端
发送端:数据的发送方进程,发送端主机即网络通信中的源主机。
接收端:数据的接收方进程,接收端主机即网络通信中的目的主机。
收发端:发送端和接收端两端
发送端和接收端只是相对的,只是一次网络数据传输产生数据流向后的概念。
(2)请求/响应
一般来说,获取一个网络资源,涉及到两次网络数据传输:
- 第一次:请求数据的发送
- 第二次:响应数据的发送
(3)客户端/服务端
服务端:提供服务的一方进程,可以提供对外服务。
客户端:获取服务的一方进程
(4)常见的客户端服务端模型
- 客户端先发送请求到服务端
- 服务端根据请求数据,执行相应的业务处理
- 服务端返回响应:发送业务处理结果
- 客户端根据响应数据,展示处理结果(展示获取的资源,或提示保存资源的处理结果)
4.Socket套接字
(1)概念
Socket套接字,是由系统提供用于网络通信的技术,是基于TCP/IP协议的网络通信的基本操作单元。基
于Socket套接字的网络程序开发就是网络编程。
(2)分类
流套接字:使用传输层TCP协议
传输数据是基于IO流,流式数据的特征就是在IO流没有关闭的情况下,是无边界的数据,可以多次发送,也可以分开多次接收。
数据报套接字:使用传输层UDP协议
传输数据是一块一块的,发送一块数据假如100个字节,必须一次发送,接收也必须一次接收100个节,而不能分100次,每次接收1个字节。
(3)Java数据报套接字通信模型
对于UDP协议来说,具有无连接,面向数据报的特征,即每次都是没有建立连接,并且一次发送全部数
据报,一次接收全部的数据报。

(4)Java流套接字通信模型

(5)经典问题:端口被占用
通过端口号查进程的方式:
- 在cmd输入 netstat -ano | findstr 端口号 ,则可以显示对应进程的pid。如以下命令显示了8888进程的pid
- 在任务管理器中,通过pid查找进程
如果占用端口的进程A不需要运行,就可以关闭A后,再启动需要绑定该端口的进程B ;
如果需要运行A进程,则可以修改进程B的绑定端口,换为其他没有使用的端口。
5.套接字编程
5.1UDP数据报套接字编程
(1)UDP服务端
public class UdpServer {private DatagramSocket socket=null;//服务器指定服务端口就行public UdpServer(int port) throws SocketException {socket=new DatagramSocket(port);}public void start() throws IOException {System.out.println("启动服务器!");while(true){//1.读取客户端的请求并解析DatagramPacket requestPacket=new DatagramPacket(new byte[4096],4096);socket.receive(requestPacket);String request=new String(requestPacket.getData(),0,requestPacket.getLength());//2.根据请求计算响应String response=process(request);//3.响应写回客户端DatagramPacket responsePacket=new DatagramPacket(response.getBytes(),response.getBytes().length,requestPacket.getSocketAddress());socket.send(responsePacket);//4.打印下日志System.out.printf("[%s:%d] req=%s, resp=%s\n",requestPacket.getAddress(),requestPacket.getPort(),request,response);}}private String process(String request) {return request;}public static void main(String[] args) throws IOException {UdpServer server=new UdpServer(9090);server.start();}
}
(2)UDP客户端
public class UdpClient {private DatagramSocket socket=null;private String severIp;private int severPort;public UdpClient(String severIp, int severPort) throws SocketException {socket = new DatagramSocket();this.severIp = severIp;this.severPort = severPort;}public void start() throws IOException {System.out.println("启动客户端");Scanner scan=new Scanner(System.in);while (true){//1.读取用户输入System.out.print("->");String request=scan.next();//2.构造UDP请求,发给服务器DatagramPacket requestPacket=new DatagramPacket(request.getBytes(),request.getBytes().length,InetAddress.getByName(this.severIp),this.severPort);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.打印响应System.out.println(response);}}public static void main(String[] args) throws IOException {UdpClient client=new UdpClient("127.0.0.1",9090);client.start();}
}
5.2TCP流套接字编程
(1)API介绍
ServerSocket 是创建TCP服务端Socket的API。
| 方法签名 | 方法说明 |
|---|---|
| ServerSocket(int port) | 创建一个服务端流套接字Socket,并绑定到指定端口 |
| 方法签****名 | 方法说明 |
|---|---|
| Socket accept() | 开始监听指定端口(创建时绑定的端口),有客户端连接后,返回一个服务端Socket对象,并基于该Socket建立与客户端的连接,否则阻塞等待 |
| void close() | 关闭此套接字 |
Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端 Socket。 不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的。
| 方法签名 | 方法说明 |
|---|---|
| Socket(String host, int port) | 创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的进程建立连接 |
| 方法签名 | 方法说明 |
|---|---|
| InetAddress getInetAddress() | 返回套接字所连接的地址 |
| InputStream getInputStream() | 返回此套接字的输入流 |
| OutputStream getOutputStream() | 返回此套接字的输出流 |
(2)TCP中的长短连接
TCP发送数据时,需要先建立连接,什么时候关闭连接就决定是短连接还是长连接:
- 短连接:每次接收到数据并返回响应后,都关闭连接,即是短连接。也就是说,短连接只能一次收发数据。
- 长连接:不关闭连接,一直保持连接状态,双方不停的收发数据,即是长连接。也就是说,长连接可以多次收发数据。
对比以上长短连接,两者区别如下:
- 建立连接、关闭连接的耗时:短连接每次请求、响应都需要建立连接,关闭连接;而长连接只需要 第一次建立连接,之后的请求、响应都可以直接传输。相对来说建立连接,关闭连接也是要耗时 的,长连接效率更高。
- 主动发送请求不同:短连接一般是客户端主动向服务端发送请求;而长连接可以是客户端主动发送 请求,也可以是服务端主动发。
- 两者的使用场景有不同:短连接适用于客户端请求频率不高的场景,如浏览网页等。长连接适用于客户端与服务端通信频繁的场景,如聊天室,实时游戏等。
(3)TCP客户端
private Socket socket=null;public TcpClient(String serverIp,int serverPort) throws IOException {socket=new Socket(serverIp,serverPort);}public void start(){System.out.println("客户端启动");try {//获取输入输出流//读取服务器发来的数据InputStream inputStream=socket.getInputStream();//向服务器发送数据OutputStream outputStream=socket.getOutputStream();//解析从服务器读来的数据Scanner scanner=new Scanner(inputStream);Scanner scannerIn=new Scanner(System.in);//包装输出流方便打印PrintWriter printWriter=new PrintWriter(outputStream);while (true){//1.从控制台读取数据System.out.printf("->");String request=scannerIn.next();//2.将请求发送给服务器printWriter.println(request);printWriter.flush();//3.从服务器读取响应//如果服务器断开连接,客户端退出循环if(!scanner.hasNext()){break;}String response=scanner.next();//4.打印响应结果System.out.println(response);}} catch (IOException e) {e.printStackTrace();}}public static void main(String[] args) throws IOException {TcpClient client=new TcpClient("127.0.0.1",9090);client.start();}
(4)TCP服务端
private ServerSocket serverSocket=null;public TcpServer(int port) throws IOException {serverSocket=new ServerSocket(port);}public void start() throws IOException {System.out.println("启动服务器");System.err.println("启动服务器");// 创建一个可缓存线程池。线程池会根据需要创建新线程,空闲线程会被回收。ExecutorService service= Executors.newCachedThreadPool();while (true){// accept() 阻塞等待客户端连接。一旦有连接,返回一个表示该连接的 Socket。Socket clientSocket=serverSocket.accept();// 提交一个任务给线程池去处理这个客户端连接,在线程中调用 processConnection 方法处理连接service.submit(()->{try {processConnection(clientSocket);} catch (IOException e) {e.printStackTrace();}});}}//针对一个链接,提供处理private void processConnection(Socket clientSocket) throws IOException {//打印一下客户端信息System.out.printf("[%s:%d] 客户端上线!\n",clientSocket.getInetAddress(),clientSocket.getPort());//获取到socket中持有的流对象try {InputStream inputStream=clientSocket.getInputStream();OutputStream outputStream=clientSocket.getOutputStream();Scanner scanner=new Scanner(inputStream);PrintWriter printWriter=new PrintWriter(outputStream);while(true){//1.读取请求if(!scanner.hasNext()){break;}String request=scanner.next();//2.计算响应String response=process(request);//3.响应写回给客户端printWriter.println(response);printWriter.flush();//4.打印日志System.out.printf("[%s:%d] req=%s;resp=%s\n",clientSocket.getInetAddress(),clientSocket.getPort(),request,response);}} catch (IOException e) {e.printStackTrace();}finally {System.out.printf("[%s:%d] 客户端下线!\n",clientSocket.getInetAddress(),clientSocket.getPort());clientSocket.close();}}private String process(String request) {return request;}public static void main(String[] args) throws IOException {TcpServer server=new TcpServer(9090);server.start();}
