1. 网络编程概述
计算机网络:把分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规模大、功能强的网络系统,从而使众多的计算机可以方便地互相传递信息、共享硬件、软件、数据信息等资源。
网络编程的目的:直接或间接地通过网络协议与其它计算机实现数据交换,进行通讯。
网络编程中有两个主要的问题:
- 如何准确地定位网络上一台或多台主机;定位主机上的特定的应用
-
2. 网络通信要素概述
2.1 网络编程中的两个要素
如何实现网络中的主机互相通信:
通信双方地址:IP和端口号
- 网络通信协议:有两套参考模型:
层(传输的数据单元) | 功能作用 | 理解 | 设备/传输介质 |
---|---|---|---|
物理层(比特流Bits) | 建立、维护、断开物理连接;通过物理介质传输比特流的数据;定义物理设备的标准,如:网线的类型、光纤的接口类型、各种传输介质的传输速率等; | 数据到达物理层后,变成信号传输。数据到达目标主机后,开始进行一个逆向的过程,即从物理层传输到数据链路层–网络层–传输层–会话层–表示层–应用层。此时,数据到达乙某的电脑,乙某就可以看到甲某发送的“你好”了。 | 集线器、中继器、调制解调器、网线等 |
数据链路层(数据帧) | 建立逻辑连接;提供介质访问和链路管理;将比特流组合成字节,再组合成帧;以太网使用MAC地址来访问介质(进行硬件地址寻址);并进行差错检测; | 网络层接收到数据后需要继续往下传输,需要使用工具,就是数据链路层的网卡,继续进行传输。 | 网卡、网桥、二层交换机等 |
网络层(数据包) | 负责数据包从源到宿的传递和网际互连;IP选址和路由选择(进行逻辑地址寻址);实现不同网络之间的路径选择; | 传输层已经准备就绪,可是微信用户千万,怎么实现贾某的文字准确的发送到乙某的微信上? 这就是需要网络层的 IP 地址。只要知道了乙某的 IP 地址,就可以选择最佳路径进行准确的数据传输了。 | 路由器、多层交换机、防火墙等 |
传输层(数据段) | 建立、管理、维护端到端的连接;定义传输数据的协议端口号;可靠或不可靠数据传输;数据重传前的错误纠正;流控; | 传输层可理解为是同一个软件中的两个端口进行数据传输。我用微信发送的消息,你也需要用微信来接收。那么就是电脑端微信用户之间的传输。 | 进程、端口(socket) |
会话层 | 建立、管理、维护和终止会话;保证不同应用程序的数据独立; | 计算机知道要发送的东西后,就需要准备发送了。第一步就是要找到对方,并和对方建立会话关系。会话属于软件层面,允许不同机器上的用户之间建立会话关系。 | 服务器验证用户登录、断点续传 |
表示层 | 数据表示;数据格式转化;数据加密与解密、数据压缩与解压缩、图像编码与解码等特殊处理过程; | 计算机接收到“你好”后开始通过二进制转换成自己的语言。这一步就是翻译,当然,表示层还有其他的功能,例如安全加密,压缩等。 | URL加密、口令加密、图片编解码等 |
应用层 | 网络服务和最终用户的一个接口;为应用进程提供服务; | 人机交互界面,或者说是系统程序窗口。贾某将“你好”两个字输入电脑微信软件。 | — |
2.2.2 TCP/IP参考模型:四层
TCP/IP协议模型从更实用的角度出发,形成了高效的四层体系结构,即物理链路层、网络层、传输层和应用层。
网络访问层
-
网络层
负责为两台主机提供通信服务,并通过选择合适的路由和交换结点,将数据传递到目标主机。
- IP 协议:在 TCP/IP 体系结构中,网络层使用 IP 协议,即 Internet Protocol,因特网协议。
IP 协议由四个支撑协议组成:ARP(地址解析协议),RARP(逆地址解析协议),ICMP(网际控制报文协议)和IGMP(网际组管理协议)。
传输层
运输层的主要任务是负责为两台主机中的进程提供通信服务。
运输层主要使用以下两种协议:
最靠近用户的一层,直接为应用进程提供服务。通过应用进程间的交互来完成特定网络应用。
- 应用层交互的数据单元称为报文。
- 应用层协议定义的是应用进程间通讯和交互的规则,不同的网络应用有着不同的应用层协议。
- HTTP协议:HyperText Transfer Protocol,超文本传输协议。用于实现WWW服务。
- FTP协议:File Transfer Protocol,文件传输协议。用于实现交互式文件传输功能。
- SMTP协议:Simple Mail Transfer Protocol,简单邮件传送协议。用于实现电子邮箱传送功能。
- DNS:Domain Name System,域名系统。用于实现网络设备名字到IP地址映射的网络服务。
- Telnet:远程登录协议,用于实现远程登录功能。
- SNMP:simple Network Management Protocol,简单网络管理协议。用于管理与监视网络设备。
DNS 域名系统,将域名和IP地址相互映射的一个分布式数据库,能够使人更方便的访问互联网,而不用去记住能够被机器直接读取的IP数串。通常域名都使用该公司的名称或简称。比如 www.oracle.com。
数据封装、解封装的过程
1. A主机传输数据到B主机 1. 从应用层传输上层数据 1. 封装tcp到传输层 1. 封装IP头部到网络层 1. 封装MAC头部到数据链路层 1. 最终到物理层转封装成比特流 |
1. 数据链路层检查露出Mac的头部是否正确 1. 正确之后解封MAC头部,到网络层 1. IP头部是否正确,解封IP头部,到传输层 1. TCP头部是否正确,解封TCP头部到应有层 1. 应用层使用相应的服务传输数据 |
---|---|
3. 通信要素1:IP和端口号
IP地址:
- 唯一标识一台计算机,即通信实体 ,即网络传输的节点
- 本地回路地址 hostAddress:127.0.0.1,对应着主机名hostName:localhost
- IP地址分类方式1:
- IPv4:4个字节组成,4个0-255。以点分十进制表示,如192.168.0.1
- IPv6:128位,即16个字节,写成8个无符号整数,每个整数用四个十六进制位表示,数之间用冒号:分开,如 3ffe:3201:1401:1280:c8ff:fe4d:db39:1984
- IP地址分类方式2:
- 公网地址(万维网使用)
- 私有地址(局域网使用):192.168. 开头,范围为192.168.0.0—192.168.255.255,组织机构内部使用
端口号:
- 用来标识进程的数字标识,不同的进程有不同的端口号。
- 范围:被规定为一个 16 位的整数 0~65535。
- 端口分类:
- 公认端口:0~1023。被预先定义的服务通信占用。如:HTTP占用端口80,FTP占用端口21,Telnet占用端口23,SMTP占用端口25,DNS占用端口53
- 注册端口:1024~49151。分配给用户进程或应用程序。如:Tomcat占用端口8080,MySQL占用端口3306,Oracle占用端口1521
- 动态/私有端口:49152~65535
- 端口号与IP地址的组合得出一个网络套接字:Socket,表示网络的节点。再考虑网络通信协议,就可以进行数据的传输了。
InetAddress类:
- Internet上的主机有两种方式表示地址:
- 域名(hostName):www.atguigu.com
- IP 地址(hostAddress):202.108.35.210
- 在Java中使用InetAddress类表示IP地址,两个子类:Inet4Address、Inet6Address
- 一个InetAddress 类对象含有一个 Internet 主机地址的域名和IP地址
- 域名解析:域名容易记忆,当在连接网络时输入一个主机的域名后,域名服务器 DNS 负责将域名转化成IP地址,这样才能和主机建立连接
- InetAddress类没有提供公共的构造器,而是提供了如下几个静态方法来获取InetAddress实例:
- public static InetAddress getLocalHost()
- public static InetAddress getByName(String host)
InetAddress提供了如下几个常用的方法:
- public String getHostAddress():返回 IP 地址字符串(以文本表现形式)
- public String getHostName():获取此 IP 地址的主机名(域名)
public boolean isReachable(int timeout):测试是否可以达到该地址
public class InetAddressTest {
public static void main(String[] args) {
try {
//File file = new File("hello.txt");
InetAddress inet1 = InetAddress.getByName("192.168.10.14");
System.out.println(inet1);
InetAddress inet2 = InetAddress.getByName("www.atguigu.com");
System.out.println(inet2);
InetAddress inet3 = InetAddress.getByName("127.0.0.1");
System.out.println(inet3);
// getLocalHost() 获取本地ip地址和域名
InetAddress inet4 = InetAddress.getLocalHost();
System.out.println(inet4);
// getHostName() 获取InetAddress对象所包含的域名
System.out.println(inet2.getHostName());
// getHostAddress() 获取InetAddress对象所包含的IP地址
System.out.println(inet2.getHostAddress());
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
}
4. 通信要素2:网络协议
网络通信协议:计算机网络中实现通信必须有一些约定,即通信协议,对速率、传输代码、代码结构、传输控制步骤、出错控制等制定标准。
通信协议分层的思想:计算机网络通信涉及内容很多,比如指定源地址和目标地址、加密解密、压缩解压缩、差错控制、流量控制、路由控制等。在制定复杂的网络协议时,把复杂成份分解成一些简单的成份,再将它们复合起来。最常用的复合方式是层次方式,即同层间可以通信、上一层可以调用下一层,而与再下一层不发生关系。各层互不影响,利于系统的开发和扩展。
4.1 TCP/IP 协议簇
TCP/IP 协议以其两个主要协议:传输控制协议 TCP 和 网络互联协议 IP 而得名,但并不一定单指 TCP 和 IP 这两个协议,是因特网使用的一组协议的总称,包括多个具有不同功能且互为关联的协议,比如:TCP、UDP、IP、FTP、HTTP、ICMP、SMTP等。这些协议可以划分为四层:链路层、网络层、传输层、应用层。
- 传输层协议中有两个非常重要的协议:
- TCP 协议:Transmission Control Protocol,传输控制协议。提供面向连接的,可靠的数据传输服务。数据传输的基本单位是报文段(segment)
- UDP 协议:User Datagram Protocol,用户数据报协议。提供无连接的,尽最大努力的数据传输服务,不保证数据传输的可靠性。数据传输的基本单位是用户数据报。
- IP 协议:Internet Protocol,是网络层的主要协议,支持网间互连的数据通信。
4.2 TCP 和 UDP 协议
TCP 传输控制协议 (Transmission Control Protocol),提供面向连接的,可靠的数据传输服务。数据传输的基本单位是报文段(segment),即TCP传递给IP层的信息单位称为报文段或段 segment。
UDP 用户数据报协议 (User Datagram Protocol),提供无连接的,尽最大努力的数据传输服务,不保证数据传输的可靠性。数据传输的基本单位是用户数据报。
4.4 TCP 和 UDP 特性、异同
都属于传输层的协议。
是否连接
TCP 是面向连接的协议。
使用TCP协议前,要先在两端建立TCP连接,形成传输数据通道。采用三次握手的方式建立连接,这样能建立可靠的连接。建立连接,是为数据的可靠传输打下了基础。
UDP是面向无连接的。
UDP不需要和 TCP一样在发送数据前进行三次握手建立连接的,想发数据就可以开始发送了。并且也只是数据报文的搬运工,不会对数据报文进行任何拆分和拼接操作。
将数据、源、目的封装成数据包,不需要建立连接。在发送端,应用层将数据传递给传输层的UDP协议,UDP只会给数据增加一个UDP头,标识是UDP协议,然后就传递给网络层了。在接收端,网络层将数据传递给传输层,UDP只去除IP报文头就传递给应用层了,不做任何拼接操作。
- 是否可靠
TCP 是可靠的,除了基本的数据传输外,它还有可靠性保证、流量控制、拥塞控制、多路复用、优先权和安全性控制等功能。
- 可靠传输
提供可靠的报文传输和对上层应用的连接服务。
判断丢包,误码靠的是TCP的段编号以及确认号。TCP为了保证报文传输的可靠,就给每个包一个序号,同时序号也保证了传送到接收端实体的包的按序接收。然后接收端实体对已成功收到的字节发回一个相应的确认(ACK);如果发送端实体在合理的往返时延(RTT)内未收到确认,那么对应的数据(假设丢失了)将会被重传。
- 流量控制
如果应用程序读取数据相当慢,而发送方发送数据太多、太快,会很容易使接收方的接收缓存溢出,流量控制就是用来进行发送速度和接收速度的匹配。发送方维护一个“接收窗口”变量,这个变量表示接收方当前可用的缓存空间。
- 拥塞控制
当网络出现拥塞的时候,TCP能够减小向网络注入数据的速率和数量,缓解拥塞。
UDP 是不可靠的,不使用流量控制和拥塞控制。
首先不可靠性体现在无连接上,通信都不需要建立连接,想发就发,这样的情况肯定不可靠。并且收到什么数据就传递什么数据,也不会备份数据,发送数据也不会关心对方是否已经正确接收到数据了。发送不管对方是否准备好,接收方收到也不确认,故是不可靠的。
再者网络环境时好时坏,但是 UDP 因为没有拥塞控制,一直会以恒定的速度发送数据。即使网络条件不好,也不会对发送速率进行调整。这样实现的弊端就是在网络条件不好的情况下可能会导致丢包,但是优点也很明显,在某些实时性要求高的场景(比如电话会议)就需要使用 UDP 而不是 TCP,因为远程视频的话,丢一些数据(例如像素)并不影响视频的内容。
在TCP协议中使用了接收确认和重传机制,使得每一个信息都能保证到达,是可靠的。而UDP是尽力传送,没有应答和重传机制,UDP只是将信息发送出去,对方收不收到也不进行应答。所以UDP协议是不可靠的。
- 连接对象个数
TCP 只有一对一的传输方式,仅支持单播传输。
TCP 只能是一对一通信。每条TCP传输连接只能有两个端点,只能进行点对点的数据传输,不支持多播和广播传输方式。点对点通信(在单个发送放方和单个接收方之间的连接),是可靠的。TCP协议进行通信的两个应用进程:客户端、服务端。TCP不能一对多的原因是:TCP通信前要跟一台主机进行三次握手连接,因此TCP不能对多。
UDP 支持一对一,一对多,多对一和多对多交互通信。
UDP 不止支持一对一的传输方式,同样支持一对多,多对多,多对一的方式,也就是说 UDP 提供了单播,多播,广播的功能。
- 传输方式
TCP 是面向字节流的。UDP 是面向报文的。
怎么理解面向字节流和面向报文呢?
TCP基于流的传输表示TCP不认为消息是一条一条的,是无保护消息边界的(保护消息边界:指传输协议把数据当做一条独立的消息在网上传输,接收端一次只能接受一条独立的消息)。
UDP面向报文,是有保护消息边界的,接收方一次只能接受一条独立的消息,所以UDP不存在粘包。发送方的UDP对应用程序交下来的报文,在添加首部后就向下交付IP层。UDP对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界。因此,应用程序必须选择合适大小的报文。
举个例子:有三个数据包,大小分别为2k、4k、6k,如果采用UDP发送的话,不管接受方的接收缓存有多大,我们必须要进行至少三次以上的发送才能把数据包发送完,但是使用TCP协议发送的话,我们只需要接受方的接收缓存有12k的大小,就可以一次把这3个数据包全部发送完毕,那么就会造成接收方一次会接收不止一条消息,这就是粘包。
TCP不像UDP一样那样一个个报文独立地传输,而是在不保留报文边界的情况下以字节流方式进行传输。在连接中可进行大数据量的传输。
- 首部开销
TCP 的头部开销大
最小20字节,最大60字节
TCP 报文段结构(数据包说明)
- SYN:同步序号,为 1 表示连接请求,用于建立连接和使顺序号同步。
- FIN:用于释放连接,为 1 表示发送方已经没有数据发送了,即关闭本方数据流。
- RST:用于复位由于主机崩溃或其他原因而出现错误的连接。
- ACK:指示确认字段中的值是否有效。为 1 表示确认号有效,为 0 表示报文中不包含确认信息,忽略确认号字段。
- PSH:为 1 表示是带有 PUSH 标志的数据,指示接收方应该尽快将这个报文段(数据)交给应用层而不用等待缓冲区装满。
UDP 的头部开销小
头部开销小,仅8字节,传输数据报文时是很高效。UDP的头部很小,只有8个字节。相比TCP至少20个字节,UDP头部要小得多。下图是一个UDP数据包:可以看到,头部数据由4部分组成:源端口号、目的端口号、总长度(即UDP头部+数据的长度)、校验和。
- 粘包、丢包
TCP 会产生粘包问题。
TCP产生粘包问题的主要原因是:TCP是面向连接的,所以在TCP看来,并没有把消息看成一条条的,而是全部消息在TCP眼里都是字节流,因此A、B消息混在一起后,TCP就分不清了。
可以看到,第一次read数据,读了5个字节,但是第一个数据有10个字节啊,没办法,就只能下次再读,第二次read数据,读25个字节,就会把后面的数据也一起读了,这就是粘包情况。
粘包问题的最本质原因在与接收对等方无法分辨消息与消息之间的边界在哪。我们通过使用某种方案给出边界,例如:包头加上包体长度。包头是定长的4个字节,说明了包体的长度。接收对先接收包体长度,依据包体长度来接收包体。
UDP 会产生丢包问题
由于UDP是没有应答和重传机制,因此包很容易传丢了也不知道。
主要丢包原因:接收端处理时间过长导致丢包:调用recv方法接收端收到数据后,处理数据花了一些时间,处理完后再次调用recv方法,在这二次调用间隔里,发过来的包可能丢失。对于这种情况可以修改接收端,将包接收后存入一个缓冲区,然后迅速返回继续recv。
发送的包巨大丢包:虽然send方法会帮你做大包切割成小包发送的事情,但包太大也不行。例如超过50K的一个udp包,不切割直接通过send方法发送也会导致这个包丢失。这种情况需要切割成小包再逐个send。
发送的包较大,超过接受者缓存导致丢包:包超过mtu size(mtu表示最大传输单元)数倍,几个大的udp包可能会超过接收者的缓冲,导致丢包。这种情况可以设置socket接收缓冲。以前遇到过这种问题,我把接收缓冲设置成64K就解决了。
发送的包频率太快:虽然每个包的大小都小于mtu size 但是频率太快,例如40多个mut size的包连续发送中间不sleep,也有可能导致丢包。
- TCP全双工通信
TCP 提供全双工的服务,数据流可以双向传输。
TCP允许通信双方的应用程序在任何时候都能发送数据,因为TCP连接的两端都设有缓存,用来临时存放双向通信的数据。当然,TCP可以立即发送一个数据段,也可以缓存一段时间以便一次发送更多的数据段(最大的数据段大小取决于MSS)
- 效率
TCP 传输完毕,需释放已建立的连接,效率低
UDP 发送数据结束时无需释放资源,开销小,速度快
- 适用场景
TCP 适用于要求可靠传输的应用,例如文件传输
UDP 适用于实时应用,例如直播、视频会议等不需要 TCP 的排序和流量控制等功能的应用程序。
4.2 TCP 协议三次握手、四次挥手
在传输之前会进行三次沟通,一般称为“三次握手”,传完数据断开的时候要进行四次沟通,一般称为“四次挥手”。
4.2.1 TCP 三次握手
三次握手:服务端新建套接字,绑定地址信息后开始监听,进入LISTEN状态。客户端新建套接字绑定地址信息后调用connect,发送连接请求SYN,并进入SYN_SENT状态,等待服务器的确认。服务端一旦监听到连接请求,就会将连接放入内核等待队列中,并向客户端发送SYN和确认报文段ACK,进入SYN_RECD状态。客户端收到SYN+ACK报文后向服务端发送确认报文段ACK,并进入ESTABLISHED状态,开始读写数据。服务端一旦收到客户端的确认报文,就进入ESTABLISHED状态,就可以进行读写数据了。
4.2.1 TCP 四次挥手
1、客户端主动调用close时,向服务端发送结束报文段FIN报,同时进入FIN_WAIT1状态。
2、服务器会收到结束报文段FIN报,服务器返回确认报文段ACK并进入CLOSE_WAIT状态,此时如果服务端有数据要发送的话,客户端依然需要接收。
3、客户端收到服务器对结束报文段的确认,就会进入到FIN_WAIT2状态,开始等待服务器的结束报文段。
4、服务器端数据发送完毕后,当服务器真正调用close关闭连接时,会向客户端发送结束报文段FIN报,此时服务器进入LAST_ACK状态,等待最后一个ACK的带来。
5、客户端收到服务器发来的结束报文段, 进入TIME_WAIT, 并发出确认报文段ACK。
6、服务器收到了对结束报文段确认的ACK,进入CLOSED状态,断开连接。而客户端要等待2MSL的时间,才会进入到CLOSED状态。
4.2.1 为什么握手三次挥手四次
其实在TCP握手的时候,接收端将SYN包和ACK确认包合并到一个包中发送的,所以减少了一次包的发送。对于四次挥手,由于TCP是全双工通信,主动关闭方发送FIN请求不代表完全断开连接,只能表示主动关闭方不再发送数据了。而接收方可能还要发送数据,就不能立即关闭服务器端到客户端的数据通道,所以就不能将服务端的FIN包和对客户端的ACK包合并发送,只能先确认ACK,等服务器无需发送数据时在发送FIN包,所以四次挥手时需要四次数据包的交互。(客户端发送FIN告诉服务端我要关闭连接了;服务端发送ACK告诉客户端我这边收拾完再通知你;等客户端处理完再发送一个FIN告诉服务端我这边处理完了,可以关闭连接了;客户端回一个ACK应答报文,告诉服务端你关闭吧。)
4.5 Socket 套接字
- 利用套接字 Socket 开发网络应用程序早已被广泛的采用,以至于成为事实上的标准。
- 网络上具有唯一标识的IP地址和端口号组合在一起才能构成唯一能识别的标识符套接字。
- 通信的两端都要有Socket,是两台机器间通信的端点。
- 网络通信其实就是Socket间的通信。
- Socket允许程序把网络连接当成一个流,数据在两个Socket间通过IO传输。
- 一般主动发起通信的应用程序属客户端,等待通信请求的为服务端。
- Socket分类:
- 流套接字 stream socket:使用TCP提供可依赖的字节流服务
- 数据报套接字 datagram socket:使用UDP提供“尽力而为”的数据报服务
- Socket类的常用构造器:
- public Socket(InetAddress address,int port) 创建一个流套接字并将其连接到指定 IP 地址的指定端口号
- public Socket(String host,int port) 创建一个流套接字并将其连接到指定主机上的指定端口号
- Socket类的常用方法:
- public InputStream getInputStream() 返回此套接字的输入流。可以用于接收网络消息
- public OutputStream getOutputStream() 返回此套接字的输出流。可以用于发送网络消息
- public InetAddress getInetAddress() 此套接字连接到的远程 IP 地址;如果套接字是未连接的,则返回 null。
- public InetAddress getLocalAddress() 获取套接字绑定的本地地址,即本端的IP地址
- public int getPort() 此套接字连接到的远程端口号;如果尚未连接套接字,则返回 0。
- public int getLocalPort() 返回此套接字绑定到的本地端口。 如果尚未绑定套接字,则返回 -1。即本端的端口号。
- public void close() 关闭此套接字。套接字被关闭后,便不可在以后的网络连接中使用(即无法重新连接或重新绑定)。需要创建新的套接字对象。 关闭此套接字也将会关闭该套接字的 InputStream 和OutputStream。
- public void shutdownInput() 如果在套接字上调用 shutdownInput() 后从套接字输入流读取内容,则流将返回 EOF(文件结束符)。 即不能在从此套接字的输入流中接收任何数据。
- public void shutdownOutput() 禁用此套接字的输出流。对于 TCP 套接字,任何以前写入的数据都将被发送,并且后跟 TCP 的正常连接终止序列。如果在套接字上调用 shutdownOutput() 后写入套接字输出流,则该流将抛出 IOException。 即不能通过此套接字的输出流发送任何数据。
5. TCP网络编程
//例子1:客户端发送信息或内容给服务端,服务端将内容显示打印在控制台上
public class TCPTest1 {
//客户端
@Test
public void client() {
Socket socket = null;
OutputStream os = null;
try {
//1.创建Socket对象,指明服务器端的ip和端口号
InetAddress inet = InetAddress.getByName("192.168.14.100");
socket = new Socket(inet,8899);
//2.获取一个输出流,用于输出数据
os = socket.getOutputStream();
//3.写出数据的操作
os.write("你好,我是客户端mm".getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.资源的关闭
if(os != null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(socket != null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//服务端
@Test
public void server() {
ServerSocket ss = null;
Socket socket = null;
InputStream is = null;
ByteArrayOutputStream baos = null;
try {
//1.创建服务器端的ServerSocket,指明自己的端口号
ss = new ServerSocket(8899);
//2.调用accept()表示接收来自于客户端的socket
socket = ss.accept();
//3.获取输入流
is = socket.getInputStream();
//不建议这样写,可能会有乱码
// byte[] buffer = new byte[1024];
// int len;
// while((len = is.read(buffer)) != -1){
// String str = new String(buffer,0,len);
// System.out.print(str);
// }
//4.读取输入流中的数据
baos = new ByteArrayOutputStream();
byte[] buffer = new byte[5];
int len;
while((len = is.read(buffer)) != -1){
baos.write(buffer,0,len);
}
System.out.println(baos.toString());
System.out.println("收到了来自于:" + socket.getInetAddress().getHostAddress() + "的数据");
} catch (IOException e) {
e.printStackTrace();
} finally {
if(baos != null){
//5.关闭资源
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(is != null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(socket != null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(ss != null){
try {
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
//客户端发送文件给服务端,服务端将文件保存在本地。
public class TCPTest2 {
/*
这里涉及到的异常,应该使用try-catch-finally处理
*/
@Test
public void client() throws IOException {
//1.
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"),9090);
//2.
OutputStream os = socket.getOutputStream();
//3.
FileInputStream fis = new FileInputStream(new File("beauty.jpg"));
//4.
byte[] buffer = new byte[1024];
int len;
while((len = fis.read(buffer)) != -1){
os.write(buffer,0,len);
}
//5.
fis.close();
os.close();
socket.close();
}
/*
这里涉及到的异常,应该使用try-catch-finally处理
*/
@Test
public void server() throws IOException {
//1.
ServerSocket ss = new ServerSocket(9090);
//2.
Socket socket = ss.accept();
//3.
InputStream is = socket.getInputStream();
//4.
FileOutputStream fos = new FileOutputStream(new File("beauty1.jpg"));
//5.
byte[] buffer = new byte[1024];
int len;
while((len = is.read(buffer)) != -1){
fos.write(buffer,0,len);
}
//6.
fos.close();
is.close();
socket.close();
ss.close();
}
}
//例题3:从客户端发送文件给服务端,服务端保存到本地。并返回“发送成功”给客户端,并关闭相应的连接。
public class TCPTest3 {
/*
这里涉及到的异常,应该使用try-catch-finally处理
*/
@Test
public void client() throws IOException {
//1.
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"),9090);
//2.
OutputStream os = socket.getOutputStream();
//3.
FileInputStream fis = new FileInputStream(new File("beauty.jpg"));
//4.
byte[] buffer = new byte[1024];
int len;
while((len = fis.read(buffer)) != -1){
os.write(buffer,0,len);
}
//关闭数据的输出
socket.shutdownOutput();
//5.接收来自于服务器端的数据,并显示到控制台上
InputStream is = socket.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] bufferr = new byte[20];
int len1;
while((len1 = is.read(buffer)) != -1){
baos.write(buffer,0,len1);
}
System.out.println(baos.toString());
//6.
fis.close();
os.close();
socket.close();
baos.close();
}
/*
这里涉及到的异常,应该使用try-catch-finally处理
*/
@Test
public void server() throws IOException {
//1.
ServerSocket ss = new ServerSocket(9090);
//2.
Socket socket = ss.accept();
//3.
InputStream is = socket.getInputStream();
//4.
FileOutputStream fos = new FileOutputStream(new File("beauty2.jpg"));
//5.
byte[] buffer = new byte[1024];
int len;
while((len = is.read(buffer)) != -1){
fos.write(buffer,0,len);
}
System.out.println("图片传输完成");
//6.服务器端给予客户端反馈
OutputStream os = socket.getOutputStream();
os.write("你好,美女,照片我已收到,非常漂亮!".getBytes());
//7.
fos.close();
is.close();
socket.close();
ss.close();
os.close();
}
}
6. UDP网络编程
public class UDPTest {
//发送端
@Test
public void sender() throws IOException {
DatagramSocket socket = new DatagramSocket();
String str = "我是UDP方式发送的导弹";
byte[] data = str.getBytes();
InetAddress inet = InetAddress.getLocalHost();
DatagramPacket packet = new DatagramPacket(data,0,data.length,inet,9090);
socket.send(packet);
socket.close();
}
//接收端
@Test
public void receiver() throws IOException {
DatagramSocket socket = new DatagramSocket(9090);
byte[] buffer = new byte[100];
DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length);
socket.receive(packet);
System.out.println(new String(packet.getData(),0,packet.getLength()));
socket.close();
}
}
7. URL编程
URL(Uniform Resource Locator):统一资源定位符,它表示 Internet 上某一 资源的地址。它是一种具体的URI,即URL可以用来标识一个资源,而且还指明了如何locate 这个资源。
- URL网络编程
1.URL:统一资源定位符,对应着互联网的某一资源地址
2.格式:
http://localhost:8080/examples/beauty.jpg?username=Tom
协议 主机名 端口号 资源地址 参数列表
public class URLTest {
public static void main(String[] args) {
try {
URL url = new URL("http://localhost:8080/examples/beauty.jpg?username=Tom");
// public String getProtocol( ) 获取该URL的协议名
System.out.println(url.getProtocol());
// public String getHost( ) 获取该URL的主机名
System.out.println(url.getHost());
// public String getPort( ) 获取该URL的端口号
System.out.println(url.getPort());
// public String getPath( ) 获取该URL的文件路径
System.out.println(url.getPath());
// public String getFile( ) 获取该URL的文件名
System.out.println(url.getFile());
// public String getQuery( ) 获取该URL的查询名
System.out.println(url.getQuery());
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
}
public class URLTest1 {
public static void main(String[] args) {
HttpURLConnection urlConnection = null;
InputStream is = null;
FileOutputStream fos = null;
try {
URL url = new URL("http://localhost:8080/examples/beauty.jpg");
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.connect();
is = urlConnection.getInputStream();
fos = new FileOutputStream("day10\\beauty3.jpg");
byte[] buffer = new byte[1024];
int len;
while((len = is.read(buffer)) != -1){
fos.write(buffer,0,len);
}
System.out.println("下载完成");
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭资源
if(is != null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fos != null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(urlConnection != null){
urlConnection.disconnect();
}
}
}
}
8. HTTP协议
(1) HTTP 超文本传输协议 (HyperText Transfer Protocol)
- 协议是指双方或多方,相互约定好,大家都需要遵守的规则。
- HTTP 协议全称超文本传输协议,是指客户端和服务器之间通信时,发送的数据,需要遵守的规则。
- 是互联网上应用最为广泛的一种网络协议。所有的 WWW(万维网)文件都必须遵守这个标准。
- 设计 HTTP 最初的目的是为了提供一种发布和接收 HTML 页面的方法。
- HTTP 协议中的数据又叫报文。
(2) 请求和响应
- 客户端给服务器发送数据叫请求。服务器给客户端回传数据叫响应。
(3) HTTP 客户机及服务器
- HTTP 客户机:web 浏览器
HTTP 服务器:web 服务器,包含 web 对象(HTML 文件、JPEG 文件、java 小程序、视频片段等)
8.1 HTTP 协议的特性
HTTP 协议一共有五大特点:a. 支持客户/服务器模式;b. 简单快速;c. 灵活;d. 无连接;e. 无状态。
无连接含义:限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
无状态含义:指协议对于事务处理没有记忆能力,服务器不知道客户端是什么状态。即我们给服务器发送 HTTP 请求之后,服务器根据请求,会给我们发送数据过来,但是,发送完,不会记录任何信息。8.2 请求
8.2.1 常见的HTTP请求方式
GET: 向服务器获取数据。绝大部分 HTTP 请求报文使用 GET 方法
- POST:将实体提交到指定的资源,通常会造成服务器资源的修改。用户提交表单时(如向搜索引擎提供关键字时)。但提交表单不一定要用 POST 方法
- PUT:上传文件,修改数据。用于向服务器上传对象
- DELETE:删除服务器上的对象。用于删除服务器上的对象
- HEAD:获取报文首部,与GET相比,不返回报文主体部分。类似于 GET,区别在于服务器返回的响应报文中不包含请求对象(常用于故障跟踪)
- OPTIONS:询问支持的请求方法,用来跨域请求
- TRACE:追踪 请求—响应的传输路径
- CONNECT:要求在与代理服务器通信时建立隧道,使用隧道进行TCP通信
- TRACE: 回显服务器收到的请求,主要⽤于测试或诊断
8.3.2 GET请求和POST请求
8.3.2.1 请求的HTTP协议格式
(1)GET请求和POST请求的HTTP协议内容和格式
GET 请求 | POST 请求 | ||
---|---|---|---|
请求行 |
请求的方式 | GET | POST |
请求的资源路径[+?+请求参数] | |||
请求的协议的版本号 | HTTP/1.1 | ||
请求头 | 由key : value组成 不同的键值对,即请求头,表示不同的含义 | ||
无 | 空行 | ||
请求体 | 无 | 发送给服务器的数据 |
(2)常用请求头的说明
Accept: 表示客户端可以接收的数据类型
Accpet-Languege: 表示客户端可以接收的语言类型
User-Agent: 表示客户端浏览器的信息
Host:表示请求时的服务器 ip 和端口号
8.3.2.2 哪些是 GET / POST 请求
GET 请求:①form 标签 method=get ②a标签③link标签引入 css ④Script 标签引入 js 文件⑤img 标签引入图片⑥iframe 标签引入 html 页面⑦在浏览器地址栏中输入地址后敲回车
POST 请求:①form 标签 method=post
8.3.2.3 特点
GET 请求的特点是:
- 浏览器地址栏中的地址是:action 属性[+?+请求参数] 注:请求参数的格式是:name=value&name=value
- 不安全
- 它有数据长度的限制
POST 请求的特点是:
- 应用场景:GET 请求是一个幂等的请求,一般 Get 请求用于对服务器资源不会产生影响的场景,比如说请求一个网页。而 Post 不是一个幂等的请求,一般用于对服务器资源会产生影响的情景。比如注册用户这一类的操作。
- 是否缓存:因为不同的应用场景,所以浏览器一般会对 Get 请求缓存,但很少对 Post 请求缓存。
- 发送的报文格式:Get 请求的报文中实体部分为空,Post 请求的报文中实体部分一般为向服务器发送的数据。
- 安全性:Get 请求可以将请求的参数放入 url 中向服务器发送,这样的做法相对于 Post 请求来说,一个方面是不太安全,因为请求的 url 会被保留在历史记录中。
- 请求长度:浏览器由于对 url 有一个长度上的限制,所以会影响 get 请求发送数据时的长度。这个限制是浏览器规定的,并不是 RFC 规定的。
- 参数类型:post 的参数传递支持更多的数据类型。
8.3.3 HTTP的请求过程(DNS寻址过程)
- 首先客户端位置是一台电脑或手机,在打开浏览器以后,比如输入http://www.zdns.cn的域名,它首先是由浏览器发起一个DNS解析请求,如果本地缓存服务器中找不到结果,则首先会向根服务器查询,根服务器里面记录的都是各个顶级域所在的服务器的位置,当向根服务器请求http://www.zdns.cn的时候,根服务器就会返回.cn服务器的位置信息;
- 递归服务器拿到.cn的权威服务器地址以后,就会寻问.cn的权威服务器,知不知道http://www.zdns.cn的位置。这个时候.cn权威服务器查找并返回http://zdns.cn服务器的地址;
- 继续向http://zdns.cn的权威服务器去查询这个地址,由http://zdns.cn的服务器给出了地址:202.173.11.10;
- 最终进入http的链接,顺利访问网站;
8.3.4 请求转发和重定向
请求转发
请求转发是指,服务器收到请求后,从一个资源跳转到另一个资源的操作。
服务器上有很多资源,共同完成一个功能,且有执行的先后顺序。客户首先发送一个请求到服务器端,服务器端发现匹配的servlet,并指定它去执行,当这个servlet执行完之后,它要调用getRequestDispacther()方法,把请求转发给指定的jsp,整个流程都是在服务器端完成的,而且是在同一个请求里面完成的,因此servlet和jsp共享的是同一个request,(在servlet里面放的所有东西,在student_list中都能取出来,因此,student_list能把结果getAttribute()出来,getAttribute()出来后执行完把结果返回给客户端。整个过程是一个请求,一个响应。)
请求重定向
请求重定向是指客户端给服务器发请求,然后服务器告诉客户端,我给你一些新地址,你去新地址访问。因为之前的地址可能已经被废弃。
客户发送一个请求到服务器,服务器匹配servlet,servlet处理完之后调用了sendRedirect()方法,立即向客户端返回这个响应,响应行告诉客户端你必须要再发送一个请求,去访问.jsp,紧接着客户端收到这个请求后,立刻发出一个新的请求,去请求.jsp,这里两个请求互不干扰,相互独立,在前面request里面setAttribute()的任何东西,在后面的request里面都获得不了。可见,在sendRedirect()里面是两个请求,两个响应。(服务器向浏览器发送一个302状态码以及一个location消息头,浏览器收到请求后会向再次根据重定向地址发出请求)
二者区别
- 转发是服务器收到请求后为了完成响应跳转到一个新的地址;重定向是浏览器向服务器发送一个请求并收到响应后再次向一个新地址发出请求
- 请求次数:转发请求一次;重定向至少请求两次
- 地址栏不同:转发地址栏不会发生变化;重定向地址栏会发生变化
- 是否共享数据:转发一次请求共享数据(在request级别使用信息共享,使用重定向必然出错);重定向两次请求不共享数据
- 跳转限制:转发只能跳转本站点资源;重定向可以跳转到任意URL
-
8.3 响应
8.3.1 响应的 HTTP 协议格式
响应行:①响应的协议和版本号:HTTP/1.1;②响应状态码:200等;③响应状态描述符:ok等
- 响应头:由key : value组成。不同的键值对,即响应头,有其不同含义
- 响应体:回传给客户端的数据
8.3.2 常用的响应码
301 Permanently Moved 被请求的资源已永久移动到新位置,新的URL在Location头中给出,浏览器应该自动地访问新的URL。
302 Found 表示请求重定向,请求的资源现在临时从不同的URL响应请求。301永久重定向,302临时重定向。
200 OK 表示从客户端发来的请求在服务器端被正确处理。表示请求成功。
304 Not Modified 304状态码是告诉浏览器可以从缓存中获取所请求的资源。
400 bad request 请求报文存在语法错误
403 forbidden 表示对请求资源的访问被服务器拒绝
404 not found 表示在服务器上没有找到请求的资源。表示请求服务器已经收到了,但是你要的数据不存在(比如:请求地址错误)
500 internal sever error 表示服务器端在执行请求时发生了错误。表示服务器已经收到请求,但是服务器内部错误(比如:代码错误)
503 service unavailable 表明服务器暂时处于超负载或正在停机维护,无法处理请求
8.3.3 MIME 类型(HTTP中常见的文件格式)
MIME 是 HTTP 协议中数据类型,全称Multipurpose Internet Mail Extensions,多功能 Internet 邮件扩充服务。
MIME 类型的格式是“大类型/小类型”,并与某一种文件的扩展名相对应。
text/html: HTML格式
text/plain:纯文本格式
image/jpeg:jpg图片格式
application/json: JSON数据格式
application/x-www-form-urlencoded: form表单数据被编码为key/value格式发送到服务器(表单默认的提交数据格式)
multipart/form-data:在表单中进行文件上传时使用
8.4 谷歌浏览器如何查看 HTTP 协议
9. HTTPS协议
HTTP和HTTPS协议的区别
HTTP和HTTPS协议的主要区别如下:
- HTTPS协议需要CA证书(Certificate Authority,证书颁发机构),费用较高;HTTP协议不需要;
- HTTP协议是超文本传输协议,信息是明文传输的;HTTPS则是具有安全性的SSL加密传输协议(安全性);
- 使用不同的连接方式,用的端口也不同:HTTP协议的端口是80,HTTPS协议的端口是443;
- HTTP协议连接很简单,是无状态的;HTTPS协议是有SSL和HTTP协议构建的可进行加密传输、身份认证的网络协议,比HTTP更加安全;
无状态的意思是其数据包的发送、传输和接收都是相互独立的。无连接的意思是指通信双方都不长久的维持对方的任何信息。
10. FTP 文件传输协议
FTP 使用两个并行的 TCP 连接来传输文件:
(1)控制连接(持久):传输控制信息,如用户标识、口令、改变远程目录命令、文件获取上传的命令;
(2)数据连接(非持久):传输实际文件。
FTP 客户机发起向 FTP 服务器的控制连接,然后在该连接上发送用户标识和口令、改变远程目录的命令。FTP服务器收到命令后,发起一个到客户机的数据连接,在该连接上准确地传送一个文件并关闭连接。
有状态的协议:FTP 服务器在整个会话期间保留用户的状态信息。服务器必须把特定的用户账号和控制连接联系起来。