7. 网络编程
7.1 网络模型
OSI模型
为了使不同计算机厂家生产的计算机能够相互通信,以便在更大的范围内建立计算机网络,国际标准化组织(ISO)在1978年提出了"开放系统互联参考模型",即著名的OSI/RM模型(Open System Interconnection/Reference Model)。它将计算机网络体系结构的通信协议划分为七层,自下而上依次为:物理层(Physics Layer)、数据链路层(Data Link Layer)、网络层(Network Layer)、传输层(Transport Layer)、会话层(Session Layer)、表示层(Presentation Layer)、应用层(Application Layer)。其中第四层完成数据传送服务,上面三层面向用户。
除了标准的OSI七层模型以外,常见的网络层次划分还有TCP/IP四层协议以及TCP/IP五层协议,它们之间的对应关系如下图所示:
- 物理层(Physical Layer)
在OSI参考模型中,物理层(Physical Layer)是参考模型的最低层,也是OSI模型的第一层。
物理层的主要功能是:利用传输介质为数据链路层提供物理连接,实现比特流的透明传输。
物理层的作用是实现相邻计算机节点之间比特流的透明传送,尽可能屏蔽掉具体传输介质和物理设备的差异。使其上面的数据链路层不必考虑网络的具体传输介质是什么。
- 数据链路层(Data Link Layer)
数据链路层(Data Link Layer)是OSI模型的第二层,负责建立和管理节点间的链路。该层的主要功能是:通过各种控制协议,将有差错的物理信道变为无差错的、能可靠传输数据帧的数据链路。
在计算机网络中由于各种干扰的存在,物理链路是不可靠的。因此,这一层的主要功能是在物理层提供的比特流的基础上,通过差错控制、流量控制方法,使有差错的物理线路变为无差错的数据链路,即提供可靠的通过物理介质传输数据的方法。
- 网络层(Network Layer)
网络层(Network Layer)是OSI模型的第三层,它是OSI参考模型中最复杂的一层,也是通信子网的最高一层。它在下两层的基础上向资源子网提供服务。其主要任务是:通过路由选择算法,为报文或分组通过通信子网选择最适当的路径。该层控制数据链路层与传输层之间的信息转发,建立、维持和终止网络的连接。具体地说,数据链路层的数据在这一层被转换为数据包,然后通过路径选择、分段组合、顺序、进/出路由等控制,将信息从一个网络设备传送到另一个网络设备。
- 传输层
OSI下3层的主要任务是数据通信,上3层的任务是数据处理。而传输层(Transport Layer)是OSI模型的第4层。因此该层是通信子网和资源子网的接口和桥梁,起到承上启下的作用。
该层的主要任务是:向用户提供可靠的端到端的差错和流量控制,保证报文的正确传输。传输层的作用是向高层屏蔽下层数据通信的细节,即向用户透明地传送报文。该层常见的协议:TCP/IP中的TCP协议、Novell网络中的SPX协议和微软的NetBIOS/NetBEUI协议。
传输层提供会话层和网络层之间的传输服务,这种服务从会话层获得数据,并在必要时,对数据进行分割。然后,传输层将数据传递到网络层,并确保数据能正确无误地传送到网络层。因此,传输层负责提供两节点之间数据的可靠传送,当两节点的联系确定之后,传输层则负责监督工作。综上,传输层的主要功能如下:
传输连接管理:提供建立、维护和拆除传输连接的功能。传输层在网络层的基础上为高层提供“面向连接”和“面向无接连”的两种服务。
处理传输差错:提供可靠的“面向连接”和不太可靠的“面向无连接”的数据传输服务、差错控制和流量控制。在提供“面向连接”服务时,通过这一层传输的数据将由目标设备确认,如果在指定的时间内未收到确认信息,数据将被重发。
- 会话层
会话层(Session Layer)是OSI模型的第5层,是用户应用程序和网络之间的接口,主要任务是:向两个实体的表示层提供建立和使用连接的方法。将不同实体之间的表示层的连接称为会话。因此会话层的任务就是组织和协调两个会话进程之间的通信,并对数据交换进行管理。
- 表示层
表示层(Presentation Layer)是OSI模型的第六层,它对来自应用层的命令和数据进行解释,对各种语法赋予相应的含义,并按照一定的格式传送给会话层。其主要功能是“处理用户信息的表示问题,如编码、数据格式转换和加密解密”等。
- 应用层
应用层(Application Layer)是OSI参考模型的最高层,它是计算机用户,以及各种应用程序和网络之间的接口,其功能是直接向用户提供服务,完成用户希望在网络上完成的各种工作。它在其他6层工作的基础上,负责完成网络中应用程序与网络操作系统之间的联系,建立与结束使用者之间的联系,并完成网络用户提出的各种网络服务及应用所需的监督、管理和服务等各种协议。此外,该层还负责协调各个应用程序间的工作。
OSI是一个理想的模型,一般的网络系统只涉及其中的几层,在七层模型中,每一层都提供一个特殊 的网络功能,从网络功能角度观察:
- 下面4层(物理层、数据链路层、网络层和传输层)主要提供数据传输和交换功能, 即以节点到节点之间的通信为主
- 第4层作为上下两部分的桥梁,是整个网络体系结构中最关键的部分;
- 上3层(会话层、表示层和应用层)则以提供用户与应用程序之间的信息和数据处理功能为主。
简言之,下4层主要完成通信子网的功能,上3层主要完成资源子网的功能。
TCP/IP模型
┌────------────┐┌─┬─ ┬─ ┬─┬─ ┬─–┬─-┬─┬─-┬─┬─-┐
│ ││D│F│W│F│H│G│T│I│S│U│ │
│ ││N│I│H│T│T│O│E│R│M│S│其│
│第四层,应用层 ││S│N│O│P│T│P│L│C│T│E│ │
│ ││ │G│I│ │P│H│N│ │P│N│ │
│ ││ │E│S│ │ │E│E│ │ │E│它│
│ ││ │R│ │ │ │R│T│ │ │T│ │
└───────------─┘└─┴─┴─-┴─┴─-┴─┴─-┴─┴─-┴─┴-─–┘
┌───────-----─┐┌─────────-------┬──--------─────────┐
│第三层,传输层 ││ TCP │ UDP │
└───────-----─┘└────────-------─┴──────────--------─┘
┌───────-----─┐┌───----──┬───—─┬────────-------──┐
│ ││ │ICMP│ │
│第二层,网间层 ││ └──—──┘ │
│ ││ IP │
└────────-----┘└────────────────────-------------─-┘
┌────────-----┐┌─────────-------┬──────--------─────┐
│第一层,网络接口││ARP/RARP │ 其它 │
└────────------┘└─────────------┴─────--------──────┘
TCP/IP四层参考模型
TCP/IP协议被组织成四个概念层,其中有三层对应于ISO参考模型中的相应层。ICP/IP协议族并不包含物理层和数据链路层,因此它不能独立完成整个计算机网络系统的功能,必须与许多其他的协议协同工作。
TCP/IP分层模型的四个协议层分别完成以下的功能:
第一层:网络接口层
包括用于协作IP数据在已有网络介质上传输的协议。实际上TCP/IP标准并不定义与ISO数据链路层和物理层相对应的功能。相反,它定义像地址解析协议(Address Resolution Protocol,ARP)这样的协议,提供TCP/IP协议的数据结构和实际物理硬件之间的接口。
第二层:网间层
对应于OSI七层参考模型的网络层。本层包含IP协议、RIP协议(Routing Information Protocol,路由信息协议),负责数据的包装、寻址和路由。同时还包含网间控制报文协议(Internet Control Message Protocol,ICMP)用来提供网络诊断信息。
第三层:传输层
对应于OSI七层参考模型的传输层,它提供两种端到端的通信服务。其中TCP协议(Transmission Control Protocol)提供可靠的数据流运输服务,UDP协议(Use Datagram Protocol)提供不可靠的用户数据报服务。
第四层:应用层
对应于OSI七层参考模型的应用层和表达层。因特网的应用层协议包括Finger、Whois、FTP(文件传输协议)、Gopher、HTTP(超文本传输协议)、Telent(远程终端协议)、SMTP(简单邮件传送协议)、IRC(因特网中继会话)、NNTP(网络新闻传输协议)等。
7.2 相关概念
IP地址
要想使网络中的计算机能够进行通信,必须为每台计算机指定一个标识号,通过这个标识号来指定接受数据的计算机或者发送数据的计算机。
在TCP/IP协议中,这个标识号就是IP地址,它可以唯一标识一台计算机,目前,IP地址广泛使用的版本是IPv4,它是由4个字节大小的二进制数来表示,如:00001010000000000000000000000001。由于二进制形式表示的IP地址非常不便记忆和处理,因此通常会将IP地址写成十进制的形式,每个字节用一个十进制数字(0-255)表示,数字间用符号“.”分开,如 “192.168.1.100”。
随着计算机网络规模的不断扩大,对IP地址的需求也越来越多,IPV4这种用4个字节表示的IP地址面临枯竭,因此IPv6 便应运而生了,IPv6使用16个字节表示IP地址,它所拥有的地址容量约是IPv4的8×1028倍,达到2128个(算上全零的),这样就解决了网络地址资源数量不够的问题。
端口号
通过IP地址可以连接到指定计算机,但如果想访问目标计算机中的某个应用程序,还需要指定端口号。在计算机中,不同的应用程序是通过端口号区分的。端口号是用两个字节(16位的二进制数)表示的,它的取值范围是065535,其中,01023之间的端口号用于一些知名的网络服务和应用,用户的普通应用程序需要使用1024以上的端口号,从而避免端口号被另外一个应用或服务所占用。
端口: 数据的发送与接收都需要通过端口。可以理解为一个计算机的门。同一个计算机中两个应用程序不允许占用同一个端口。
端口号:0 ~ 65535
0~1023: 公共端口
1025 ~ 49151: 注册端口
1024 ~ 65535: 动态或私有端口
常见的端口:
8080:tomcat
3306:mysql
1521:oracle
InetAddress类
是一个用来描述IP的一个类,有两个常用的子类:Inet4Address、Inet6Address
IPv4: 是用四个字节来描述IP地址,IP地址中的每一位都是一个字节,范围[0, 255]
192.168.1.1
IPv6: 是用六个字节来描述IP地址
A类:1.0.0.1 ~ 126.255.255.254 保留给政府机构
B类:128.0.0.1 ~ 191.255.255.254 分配给大中型企业
C类:192.0.0.1 ~ 223.255.255.254 分配给任何有需要的个人
D类:224.0.0.1 ~ 239.255.255.254 用于组播
E类:240.0.0.1 ~ 255.255.255.254 用于实验
public static void main(String[] args) {
try {
// 获取主机
InetAddress local = InetAddress.getLocalHost();
System.out.println(local);
// 获取主机名字字符串
String name = local.getHostName();
System.out.println(name);
// 获取主机地址字符串
String address = local.getHostAddress();
System.out.println(address);
// 通过主机名获取
InetAddress localhost = InetAddress.getByName("localhost");
System.out.println(localhost);
// 通过一个域名获取
InetAddress kaikeba = InetAddreshosttByName("www.kaikeba.com");
System.out.println(kaikeba);
InetAddress[] addresses = InetAddress.getAllByName("www.kaikeba.com");
for (InetAddress addr : addresses) {
System.out.println(addr);
}
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
7.3 TCP
TCP协议是面向连接的通信协议,即在传输数据前先在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。在TCP连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”。
三次握手: 建立一个TCP连接时,需要客户端和服务端总共发送3个包以确认连接的建立, 在Socket编程中,这一过程由客户端执行connect来触发,具体流程图如下:
- **第一次握手:**Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server, Client进入SYN_SENT状态,等待Server确认。
- **第二次握手:**Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位 SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认连接请求 ,Server进入SYN_RCVD状态。
- **第三次握手:**Client收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK 置为1,ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,ACK是否为1,如果正确则 连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以 开始传输数据了。
四次挥手: 终止TCP连接,就是指断开一个TCP连接时,需要客户端和服务端总共发送4个包以确认连接的断开。 在Socket编程中,这一过程由客户端或服务端任一方执行close来触发,具体流程图如下:
- 第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入 FIN_WAIT_1状态
- 第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同, 一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
- 第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK 状态。
- 第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。 另外也可能是同时发起主动关闭的情况:
另外还可能有一个常见的问题就是:为什么建立连接是三次握手,而关闭连接却是四次挥手呢? 答:因为服务端在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里 发送给客户端。而关闭连接时,当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还 能接收数据,己方也未必全部数据都发送给对方了,所以己方可以立即close,也可以发送一些 数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会 分开发送。
由于TCP协议的面向连接特性,它可以保证传输数据的安全性,所以是一个被广泛采用的协议,例如在下载文件时,如果数据接收不完整,将会导致文件数据丢失而不能被打开,因此,下载文件时必须采用TCP协议。
TCP特点:
- 面向连接。
- 安全的。(不会存在数据丢失)
- 传输的数据大小是有限制的。
三次握手:
- 客户端向服务端发送一个请求。
- 服务端收到请求后,返回给客户端一个响应。
- 客户端接收到服务端的响应后,再回服务端一个确认信息。
重点涉及到的类:
Socket类:客户端
ServerSocket类:服务端
Socket用来描述IP地址和端口,是通信链的句柄,应用程序通过Socket向网络发送请求或者应答网络请求,socket是支持TCP/IP协议的网络通信的基本操作单元
Socket
/* TCP 服务器端
*
* 1,创建服务器ServerSocket对象(指定服务器端口号)
* 2,开启服务器了,等待客户端的连接,当客户端连接后,可以获取到连接服务器的客户端Socket对象
* 3,给客户端反馈信息
* 4,关闭流资源
*/
public class TCPServer {
public static void main(String[] args) throws IOException {
//1,创建服务器ServerSocket对象(指定服务器端口号)
ServerSocket ss = new ServerSocket(8888);
//2,开启服务器了,等待客户端的连接,当客户端连接后,可以获取到连接服务器的客户端Socket对象
Socket s = ss.accept();
//3,给客户端反馈信息
/*
* a,获取客户端的输出流
* b,在服务端端,通过客户端的输出流写数据给客户端
*/
//a,获取客户端的输出流
OutputStream out = s.getOutputStream();
//b,在服务端端,通过客户端的输出流写数据给客户端
out.write("你已经连接上了服务器".getBytes());
//4,关闭流资源
out.close();
s.close();
//ss.close(); 服务器流 通常都是不关闭的
}
}
//完成了服务器端程序的编写,接下来编写客户端程序。
/*
* TCP 客户端
*
* 1,创建客户端Socket对象,(指定要连接的服务器地址与端口号)
* 2,获取服务器端的反馈回来的信息
* 3,关闭流资源
*/
public class TCPClient {
public static void main(String[] args) throws IOException {
//1,创建客户端Socket对象,(指定要连接的服务器地址与端口号)
Socket s = new Socket("127.0.0.1", 8888);
//2,获取服务器端的反馈回来的信息
InputStream in = s.getInputStream();
//获取获取流中的数据
byte[] buffer = new byte[1024];
//把流中的数据存储到数组中,并记录读取字节的个数
int length = in.read(buffer);
//显示数据
System.out.println( new String(buffer, 0 , length) );
//3,关闭流资源
in.close();
s.close();
}
}
7.4 UDP
UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。
由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输例如视频会议都使用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。
但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议。UDP的交换过程如下图所示。
//UDP完成数据的发送
/*
* 发送端
* 1,创建DatagramSocket对象
* 2,创建DatagramPacket对象,并封装数据
* 3,发送数据
* 4,释放流资源
*/
public class UDPSend {
public static void main(String[] args) throws IOException {
//1,创建DatagramSocket对象
DatagramSocket sendSocket = new DatagramSocket();
//2,创建DatagramPacket对象,并封装数据
//public DatagramPacket(byte[] buf, int length, InetAddress address, int port)
//构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。
byte[] buffer = "hello,UDP".getBytes();
DatagramPacket dp = new DatagramPacket(buffer, buffer.length, InetAddress.getByName("127.0.0.1"), 6666);
//3,发送数据
//public void send(DatagramPacket p) 从此套接字发送数据报包
sendSocket.send(dp);
//4,释放流资源
sendSocket.close();
}
}
//UDP完成数据的接收
/*
* UDP接收端
*
* 1,创建DatagramSocket对象
* 2,创建DatagramPacket对象
* 3,接收数据存储到DatagramPacket对象中
* 4,获取DatagramPacket对象的内容
* 5,释放流资源
*/
public class UDPReceive {
public static void main(String[] args) throws IOException {
//1,创建DatagramSocket对象,并指定端口号
DatagramSocket receiveSocket = new DatagramSocket(6666);
//2,创建DatagramPacket对象, 创建一个空的仓库
byte[] buffer = new byte[1024];
DatagramPacket dp = new DatagramPacket(buffer, 1024);
//3,接收数据存储到DatagramPacket对象中
receiveSocket.receive(dp);
//4,获取DatagramPacket对象的内容
//谁发来的数据 getAddress()
InetAddress ipAddress = dp.getAddress();
String ip = ipAddress.getHostAddress();//获取到了IP地址
//发来了什么数据 getData()
byte[] data = dp.getData();
//发来了多少数据 getLenth()
int length = dp.getLength();
//显示收到的数据
String dataStr = new String(data,0,length);
System.out.println("IP地址:"+ip+ "数据是"+ dataStr);
//5,释放流资源
receiveSocket.close();
}
}
7.5 综合案例
/*
* 文件上传 服务器端
*
*/
public class TCPServer {
public static void main(String[] args) throws IOException {
//1,创建服务器,等待客户端连接
ServerSocket serverSocket = new ServerSocket(8888);
Socket clientSocket = serverSocket.accept();
//显示哪个客户端Socket连接上了服务器
InetAddress ipObject = clientSocket.getInetAddress();//得到IP地址对象
String ip = ipObject.getHostAddress(); //得到IP地址字符串
System.out.println("谁在传文件给我" + "IP:" + ip);
//2,获取Socket的输入流
InputStream in = clientSocket.getInputStream();
//3,创建目的地的字节输出流
BufferedOutputStream fileOut = new BufferedOutputStream(new FileOutputStream("myfileRecieved.txt"));
//4,把Socket输入流中的数据,写入目的地的字节输出流中
byte[] buffer = new byte[1024];
int len = -1;
while((len = in.read(buffer)) != -1){
//写入目的地的字节输出流中
fileOut.write(buffer, 0, len);
}
//-----------------反馈信息---------------------
//5,获取Socket的输出流, 作用:写反馈信息给客户端
OutputStream out = clientSocket.getOutputStream();
//6,写反馈信息给客户端
out.write("文件上传成功".getBytes());
out.close();
fileOut.close();
in.close();
clientSocket.close();
//serverSocket.close();
}
}
//编写客户端,完成上传文件
/*
* 文件上传 客户端
*
* public void shutdownOutput() 禁用此Socket的输出流,间接的相当于告知了服务器数据写入完毕
*/
public class TCPClient {
public static void main(String[] args) throws IOException {
//1,创建客户端Socket,连接服务器
Socket socket = new Socket("127.0.0.1", 8888);
//2,获取Socket流中的输出流,功能:用来把数据写到服务器
OutputStream out = socket.getOutputStream();
//3,创建字节输入流,功能:用来读取数据源(图片)的字节
BufferedInputStream fileIn = new BufferedInputStream(new FileInputStream("myfile.txt"));
//4,把图片数据写到Socket的输出流中(把数据传给服务器)
byte[] buffer = new byte[1024];
int len = -1;
while ((len = fileIn.read(buffer)) != -1){
//把数据写到Socket的输出流中
out.write(buffer, 0, len);
}
//5,客户端发送数据完毕,结束Socket输出流的写入操作,告知服务器端
socket.shutdownOutput();
//-----------------反馈信息---------------------
//6,获取Socket的输入流 作用: 读反馈信息
InputStream in = socket.getInputStream();
//7,读反馈信息
byte[] info = new byte[1024];
//把反馈信息存储到info数组中,并记录字节个数
int length = in.read(info);
//显示反馈结果
System.out.println( new String(info, 0, length) );
//关闭流
in.close();
fileIn.close();
out.close();
socket.close();
}
}