目录
一、网络初始
1、IP地址
2、端口号
3、协议
4、五元组
5、协议分层
6、封装和分用
二、网络编程
1、概念
2、API
3、TCP与UDP的区别
4、UDP的Socket API
描述了一个设备在网络上的地址。32位,4个字节表示,使用三个点进行分隔。
例如:192.198.1.200;
区分主机上不同的应用程序,2个字节。0一般不使用,1-1023这个范围的端口号,知名端口号,留给一些比较常见的服务器程序进行应用的。
一种约定,约定了通行双方按照什么样的方式传输数据。网络中,本质上通过光/电信号传输数据
一次通行过程中必不可少的信息:源端口、源IP、目的端口、目的IP、协议类型。
如果一个协议过于庞大不好解决问题,可以把一个大的协议拆分成多个小的协议。更好的管理这些协议,就进行了协议分层。约定了不同层次之间的调用关系,上层协议调用下层协议,下层协议给上层协议提供支持。
好处:①协议分层之后,上层和下层彼此之间进行了封装,使用上层协议不必关注下层协议,使用下层协议不必关注上层协议;②每层协议可以根据需要灵活替换。
OSI七层网络协议:
TCP/IP五层网络协议(最主要的)
(1)物理层:描述的是网络通信的硬件设备;比如:使用的网线、光纤应满足什么规格;
(2)数据链路层:两个相邻结点之间数据的传输情况,负责设备之间数据帧的传送和识别;
(3)网络层:进行路径规划,负责地址管理和路由选择;
(4)传输层:关注起点和终点,负责两个主机间数据传输;
(5)应用层:如何使用这个数据,负责应用程序间沟通。
物理层和数据链路层对应到设备驱动程序与网络接口;网络层和传输层对应到操作系统;应用层对应到应用程序。
对于一台主机,操作系统内核实现了从应用层到物理层,涉及到TCP/IP模型的5层;
对于一个路由器,实现了网络层到物理层,涉及到TCP/IP模型的下3层;
对于一个交换机(扩展路由器的端口),实现了数据链路层到物理层,涉及到TCP/IP模型的下2层;
对于集线器,只实现了物理层。
描述了网络通信过程中,基本的数据传输流程。
eg:用户A通过qq将hello发送给用户B
发送方:
(1)应用层
qq应用程序就会把用户A输入的hello打包成应用层的数据报(数据报的格式只有qq程序员知道)
假设为以下格式:
上述规则就为应用层的协议,由程序员制定的规则。具体用什么字段、字段顺序都可以灵活调整。
(2)传输层
对刚才应用层的数据报再进行打包转为传输层的数据报,本质上是字符串的拼接,拼接上传输层的报头。传输层有两种协议(UDP和TCP)
UDP报头:二进制数据,最关键的信息就是源端口和目的端口。
(3)网络层
对刚才传输层的数据报再进行打包转为网络层的数据报。网络层最主要的协议为IP协议
IP报头:最关键的信息为源IP和目的IP
(4)数据链路层
对刚才网络层的数据报再进行打包转为数据链路层的数据报。
以太网报头:最关键的信息为源mac地址和目的mac地址。
(5)物理层
把上述数据转为二进制,通过光/电信号进行传播。
数据发送成功后就会经过A和B之间的一系列交换机和路由器进行转发。当数据到达B之后,就会对数据进行“分用”(层层解析)。
接收方:
(1)物理层
拿到光/电信号,转化为二进制,得到以太网数据报,交给数据链路层的协议处理。
(2)数据链路层
针对以太网数据报,通过以太网协议解析出以太网数据报头、报尾、载荷,将载荷(IP数据报)交给网络层的协议处理。
(3)网络层
针对IP数据报,通过IP协议解析出IP报头和载荷,将载荷(UDP数据报)交给传输层的协议处理。
(4)传输层
针对UDP数据报,通过UDP协议解析出UDP报头和载荷,将载荷(应用层数据报)通过解析出的端口号交给qq程序。
(5)应用层
针对应用层数据报,根据应用层协议解析出内容,将内容显示在对应界面上。
网络上的主机,拥有不同的进程,通过编程,基于网络实现不同进程之间的网络通信(数据传输)
Socket API ,是由系统提供用于网络通信的技术,是基于TCP/UDP协议的。
①TCP是有连接的,UDP是无连接的。(双方是非保存彼此关键信息)
②TCP是可靠传输,UDP是不可靠传输。(是否知道数据成功传输给接收方)
③TCP是面向字节流的,UDP是面向数据报的。
④TCP与UDP都是全双工的,允许双向通信。
(1)DatagramSocket类:代表系统内部的Socket文件,对于文件有读数据和写数据,读数据相当于通过网卡接收数据,写数据相当于通过网卡发送数据。
receive()方法是接收数据(请求),未接收到请求时会阻塞等待;send()方法是发送数据(响应)。
在构造服务器时,程序员需要指定空闲的端口号,可控的。
在构造客户端时,不需要指定端口号,不可控,系统分配端口号。
(2)DatagramPacket类
代表数据报,UDP是面向数据报的,以数据报为基本单位传输。
(3)应用:写一个简单的Echo服务器/客户端,客户端发送请求之后,服务器响应一样的内容发送给客户端。
服务器:
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class UdpEchoServe {
DatagramSocket socket;
public UdpEchoServe(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、响应请求->Echo
String response=process(request);
//3、发送响应给客户端
DatagramPacket responsePacket=new DatagramPacket(response.getBytes(),response.getBytes().length,
requestPacket.getSocketAddress());
socket.send(responsePacket);
//4、打印信息
System.out.printf("[IP=%s:port=%d],request=%s,response=%s\n",requestPacket.getAddress().toString(),
requestPacket.getPort(),request,response);
}
}
public String process(String request){
return request;
}
public static void main(String[] args) throws IOException {
UdpEchoServe serve=new UdpEchoServe(4090);
serve.start();
}
}
客户端:
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class UdpEchoClient {
private String ip;
private int port;
DatagramSocket socket;
//记录对端的信息,不用指定客户端端口
public UdpEchoClient(String ip,int port) throws SocketException {
this.ip=ip;
this.port=port;
socket=new DatagramSocket();
}
public void start() throws IOException {
System.out.println("客户端启动!!!");
Scanner sc=new Scanner(System.in);
while (true){
//1、控制台写入请求
System.out.print("->");
String request=sc.next();
//2、发送请求给客户端
DatagramPacket requestPacket=new DatagramPacket(request.getBytes(),request.getBytes().length,
InetAddress.getByName(ip),port);
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);
}
}
public static void main(String[] args) throws IOException {
UdpEchoClient client=new UdpEchoClient("192.168.0.102",4090);
client.start();
}
}
注:UDP的DatagramSocket不需要释放资源,因为在程序中只有一个对象,贯穿整个程序,无需释放。
5、TCP的Socket API
(1)ServerSocket类:服务器使用的类,绑定端口号;
(2)Socket类:服务器或客户端使用的类;
(3)建立连接:客户端发起连接请求,内核完成建立连接的流程(三次握手)之后,就会在内核的队列(ServerSocket拥有的队列)中排队,服务器的应用程序通过accept()方法,取出连接对象。
(4)接收请求:inputStream;发送请求:OutputStream.
(5)考虑到有多个不同的客户端,线程池接收不同客户端和响应请求。
(6)应用:写一个简单的Echo服务器/客户端,客户端发送请求之后,服务器响应一样的内容发送给客户端。
服务器:
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TcpEchoServer {
ServerSocket serverSocket;
public TcpEchoServer(int port) throws IOException {
serverSocket=new ServerSocket(port);
}
public void start() throws IOException{
System.out.println("服务器启动!!!");
while (true){
ExecutorService service= Executors.newCachedThreadPool();
Socket clientSocket=serverSocket.accept(); //建立连接
service.submit(new Runnable() {
@Override
public void run() {
//处理建立好的连接
try {
processConnection(clientSocket);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
});
}
}
public void processConnection(Socket clientSocket) throws IOException {
System.out.printf("[ip=%s:port=%d ,客户端上线!!!\n",clientSocket.getInetAddress(),clientSocket.getPort());
try(InputStream inputStream=clientSocket.getInputStream();
OutputStream outputStream=clientSocket.getOutputStream()) {
while (true){
Scanner sc=new Scanner(inputStream);
//1、读取请求
if(!sc.hasNext()){
System.out.printf("[ip=%s:port=%d ,客户端下线!!!\n",clientSocket.getInetAddress(),clientSocket.getPort());
break;
}
String request=sc.next();
//2、响应请求
String response=process(request);
//3、发送响应给客户端
PrintWriter writer=new PrintWriter(outputStream);
writer.println(response);
writer.flush();
//4、打印信息
System.out.printf("[%s:%d],req=%s,res=%s\n",clientSocket.getInetAddress(),clientSocket.getPort(),request,response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
clientSocket.close();
}
}
public String process(String request){
return request;
}
public static void main(String[] args) throws IOException {
TcpEchoServer server=new TcpEchoServer(4090);
server.start();
}
}
客户端:
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
public class TcpEchoClient {
Socket clientSocket;
public TcpEchoClient(String ip, int port) throws IOException {
clientSocket = new Socket(ip, port); //三次握手建立连接
}
public void start() {
System.out.println("客户端启动!!!");
Scanner sc = new Scanner(System.in);
try (InputStream inputStream=clientSocket.getInputStream();
OutputStream outputStream=clientSocket.getOutputStream();
PrintWriter writer=new PrintWriter(outputStream);
Scanner sc1=new Scanner(inputStream);) {
while (true) {
//1、输入请求
System.out.print("->");
String request=sc.next();
//2、发送请求
writer.println(request);
writer.flush();
//3、接收响应
String response=sc1.next();
//4、打印响应
System.out.println(response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws IOException {
TcpEchoClient client=new TcpEchoClient("192.168.0.102",4090);
client.start();
}
}
注:服务器中,每次执行可能会建立多个Socket对象,用完一个客户端就需释放资源。