Java网络编程对于Java工程师来说是很重要的能力,尤其是大厂对网络编程有较高的要求 @mikechen
比如在直播、实时通讯、游戏服务端开发等技术领域,通信协议和网络编程就成为了很重要的一个技术课题。
再比如你想做基础技术研发,比如消息队列、RPC框架的研发,那么网络编程也是必备的基础能力。
计算机网络
在学习Java网络编程之前,我们先来了解什么是计算机网络。
计算机网络是通过传输介质、通信设施和网络通信协议,把分散在不同地点的计算机设备互连起来的,实现资源共享和数据传输的系统。
网络编程就是编写程序使互联网的两个(或多个)设备(如计算机)之间进行数据传输。
计算机网络主要包括三部分:
- 计算机 (可以包括客户端、服务器)
- 网络设备 (路由器、交换机、防火墙等)
- 传输介质(可以分为有线和无线的)
按照地域范围可以对网络进行如下分类:
- 局域网 :小范围内的私有网络,一个家庭内的网络、一个公司内的网络、一个校园内的网络都属于局域网。
- 广域网:把不同地域的局域网互相连接起来的网络。运营商搭建连接远距离区域的广域网。
- 互联网:由世界各地的局域网和广域网连接起来的网络,互联网是一个开放、互联的网络,不属于任何个人和任何机构。
网络体系结构
计算机网络是个复杂的系统,按照人们解决复杂问题的方法,把计算机网络实现的功能分到不同的层次上,层与层之间用接口连接。
七层模型
网络模型不是一开始就有的,在网络刚发展时,网络协议是由各互联网公司自己定义的,各家的协议也是不能互通的。这样大大的阻碍了互联网的发展,为了解决这个问题,国际标准化组织 1984 提出的模型标准,简称 OSI(Open Systems Interconnection Model),具体如下图:
简述每一层的含义:
1.物理层(Physical Layer)
建立、维护、断开物理连接。
2.数据链路层 (Link)
逻辑连接、进行硬件地址寻址、差错校验等。
3.网络层 (Network)
进行逻辑寻址,实现不同网络之间的路径选择。
4.传输层 (Transport)
定义传输数据的协议端口号,及流控和差错校验。
5.会话层(Session Layer)
建立、管理、终止会话。
6.表示层(Presentation Layer)
数据的表示、安全、压缩。
7.应用层 (Application)
网络服务与最终用户的一个接口
这一模型确实是绝大多数网络编程的基础,作为抽象类存在的,而TCP/IP协议栈只是这一模型的一个具体实现。
TCP/IP模型
TCP/IP 模型是由 OSI 模型演化而来,TCP/IP 模型将 OSI 模型由七层简化为五层(一开始为四层),应用层、表示层、会话层统一为应用层。
网络协议
如同人与人之间相互交流是需要遵循一定的规矩一样,计算机之间能够进行相互通信是因为它们都共同遵守一定的规则,即网络协议。
TCP协议
TCP 是面向连接的,提供端到端可靠性服务的传输层协议。
TCP协议特点
1)面向连接型的传输协议
也就是说,应用程序在使用TCP之前,必须先建立TCP传输连接,在传输数据完毕后,必须释放已建立的TCP传输连接。
2)仅支持单播传输
每条TCP传输连接只能有两个端点,只能进行点对点的数据传输,不支持多播(multicast)和广播(broadcast)传输方式。
3)支持全双工传输
TCP允许通信双方的应用程序在任何时候都能发送数据,因为TCP连接的两端都设有缓存,用来临时存放双向通信的数据。当然,TCP可以立即发送一个数据段,也可以缓存一段时间以便一次发送更多的数据段(最大的数据段大小取决于MSS)。
4)TCP连接是基于字节流的,而非报文
TCP不像UDP一样那样一个个报文独立地传输,而是在不保留报文边界的情况下以字节流方式进行传输。
5)每次发送的TCP数据段大小和数据段数都是可变的
在TCP中,因为每次发送多少字节的数据不是固定的,也不是由主机当前可用缓存决定的,而是根据双方给出的窗口大小和当前网络的拥塞程度决定的(后面会介绍UDP发送的报文长度是由应用进程给出的),所以每次可以发送的TCP数据大小是不固定的。
TCP的报文格式
1)源端口
数据发送方的端口号。
2)目的端口
数据接受方的端口号。
3)序号
seq序号,占4个字节,用来标识从TCP发送端向TCP接收端发送的数据字节流。
4)确认号
ack序号,即接收端希望收到的下一个数据报文中的第一个字节的序号。
5)数据偏移
占4个字节,用于指出TCP首部长度,若不存在选项,则这个值为20字节,数据偏移的最大值为60字节。
保留字段占6位,暂时可忽略,值全为0。
6)6个标志位
- URG(紧急):为1时表明紧急指针字段有效
- ACK(确认):为1时表明确认号字段有效
- PSH(推送):为1时接收方应尽快将这个报文段交给应用层
- RST(复位):为1时表明TCP连接出现故障必须重建连接
- SYN(同步):在连接建立时用来同步序号
- FIN(终止):为1时表明发送端数据发送完毕要求释放连接
7)接收窗口
占2个字节,用于流量控制和拥塞控制,表示当前接收缓冲区的大小。在计算机网络中,通常是用接收方的接收能力的大小来控制发送方的数据发送量。TCP连接的一端根据缓冲区大小确定自己的接收窗口值,告诉对方,使对方可以确定发送数据的字节数。
8)校验和
占2个字节,范围包括首部和数据两部分。
9)选项
长度可变,最长可达40个字节,当没有使用“选项”时,TCP的首部长度是20字节。
报文格式例子
三次握手
1. 第一次握手(客户端发送请求)
客户机发送连接请求报文段到服务器,并进入SYN_SENT状态,等待服务器确认。
发送连接请求报文段内容:SYN=1,seq=x;SYN=1意思是一个TCP的SYN标志位置为1的包,指明客户端打算连接的服务器的端口;seq=x表示客户端初始序号x,保存在包头的序列号(Sequence Number)字段里。
2. 第二次握手(服务端回传确认)
服务器收到客户端连接请求报文,如果同意建立连接,向客户机发回确认报文段(ACK)应答,并为该TCP连接分配TCP缓存和变量。
服务器发回确认报文段内容:SYN=1,ACK=1,seq=y,ack=x+1;SYN标志位和ACK标志位均为1,同时将确认序号(Acknowledgement Number)设置为客户的ISN加1,即x+1;seq=y为服务端初始序号y。
3. 第三次握手(客户端回传确认)
客户机收到服务器的确认报文段后,向服务器给出确认报文段(ACK),并且也要给该连接分配缓存和变量。
此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。客户端发回确认报文段内容:ACK=1,seq=x+1,ack=y+1;ACK=1为确认报文段;seq=x+1为客户端序号加1;ack=y+1,为服务器发来的ACK的初始序号字段+1。
四次挥手
1. TCP客户端发送一个FIN,用来关闭客户端到服务端的数据传送,客户端进入FIN_WAIT_1状态。发送报文段内容:FIN=1,seq=u;FIN=1表示请求切断连接;seq=u为客户端请求初始序号。
2. 服务端收到这个FIN,它发回一个ACK给客户端,确认序号为收到的序号加1,和SYN一样,一个FIN将占用一个序号。服务端进入CLOSE_WAIT状态。发送报文段内容:ACK=1,seq=v,ack=u+1;ACK=1为确认报文;seq=v为服务器确认初始序号;ack=u+1为客户端初始序号加1。
3. 服务器关闭客户端的连接后,发送一个FIN给客户端,服务端进入LAST_ACK状态。发送报文段内容:FIN=1,ACK=1,seq=w,ack=u+1;FIN=1为请求切断连接,ACK=1为确认报文,seq=w为服务端请求切断初始序号。
4. 客户端收到FIN后,客户端进入TIME_WAIT状态,接着发回一个ACK报文给服务端确认,并将确认序号设置为收到序号加1,服务端进入CLOSED状态,完成四次挥手。发送报文内容:ACK=1,seq=u+1,ack=w+1;ACK=1为确认报文,seq=u+1为客户端初始序号加1,ack=w+1为服务器初始序号加1。
为什么是三次握手四次挥手
因为当服务端收到客户端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当服务端收到FIN报文时,很可能并不会立即关闭socket,所以只能先回复一个ACK报文,告诉客户端,“你发的FIN报文,我收到了”。只有等到服务端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送,故需要四步挥手。
TCP/IP协议
TCP/IP协议被称为传输控制协议/互联网协议,又称网络通讯协议(Transmission Control Protocol),是由网络层的IP协议和传输层的TCP协议组成,是一个很大的协议集合。
- 物理层和数据链路层没有定义任何特定协议,支持所有的标准和专用的协议;
- 网络层定义了网络互联也就是IP协议,主要包括IP、ARP、RARP、ICMP、IGMP;
- 传输层定义了TCP和UDP(User Datagram Protocol),我们会后面重点介绍一下TCP协议;
- 应用层定义了HTTP(超文本传输协议)、FTP(文件传输协议)、DNS(域名系统)等协议。
UDP协议
UDP,用户数据报协议,英文全称是User Datagram Protocol,它是TCP/IP协议簇中无连接的运输层协议。
UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。
简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。
UDP协议格式
UDP 报文格式相对于简单,如下图:
1)源端口
发送方端口号,端口号0-65535,1-1024保留端口号,为标准的服务端口。
2)目的端口
接收方端口号
3)报文长度
UDP 用户数据报的总长度,以字节为单位。
4)校验和
检测 UDP 用户数据报在传输中是否有错,有错就丢弃。
用于校验 UDP 数据报的数字段和包含 UDP 数据报首部的“伪首部”。
5)数据
UDP 的数据部分如果不为偶数需要用 0 填补,就是说,如果数据长度为奇数,数据长度加“1”。
UPD与TCP的区别
- TCP基于连接,UDP是无连接的;
- 对系统资源的要求,TCP较多,UDP较少;
- UDP程序结构较简单;
- TCP是流模式,而UDP是数据报模式;
- TCP保证数据正确性,而UDP可能丢包;TCP保证数据顺序,而UDP不保证;
HTTP协议
HTTP,超文本传输协议,英文全称是Hypertext Transfer Protocol,它是互联网上应用最为广泛的一种网络协议。
HTTP是一种应用层协议,它是基于TCP协议之上的请求/响应式的协议,即一个客户端与服务器建立连接后,向服务器发送一个请求。
服务器接到请求后,给予相应的响应信息,HTTP协议默认的端口号为80。
http请求报文格式
HTTP请求报文主要由请求行、请求头部、请求正文3部分组成:
http返回报文格式
HTTP响应报文主要由状态行、响应头部、响应正文3部分组成
HTTP和HTTPS的区别
HTTPS(全称:Hyper Text Transfer Protocol over Secure Socket Layer),是以安全为目标的HTTP通道,简单来说就是HTTP的安全版。即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。它是一个URL scheme(抽象标识符体系),句法类同http:体系,用于安全的HTTP数据传输。https:URL表明它使用了HTTP,但HTTPS存在不同于HTTP的默认端口及一个加密/身份验证层(在HTTP与TCP之间)。
HTTPS和HTTP的区别主要为以下四点:
- https协议需要到ca申请证书,一般免费证书很少,需要缴费。
- http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。
- http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
- http的连接很简单,是无状态的,https协议是有ssl+http协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
Java网络编程
在Java中,提供了两个类用于实现TCP通信程序:
1.客户端
java.net.Socket 类表示。创建Socket对象,向服务端发出连接请求,服务端响应请求,两者建立连接开始通信。
2.服务端
java.net.ServerSocket 类表示。创建ServerSocket对象,相当于开启一个服务,并等待客户端的连接。
Java的网络编程主要涉及到的内容是Socket编程,那么什么是Socket呢?简单地说,Socket,套接字,就是两台主机之间逻辑连接的端点。
TPC/IP协议是传输层协议,主要解决数据如何在网络中传输,而HTTP是应用层协议,主要解决如何包装数据。Socket,本质上就是一组接口,是对TCP/IP协议的封装和应用(程序员层面上)。
Socket
java.net.Socket 类代表客户端和服务器都用来互相沟通的套接字,套接字指的是两台设备之间通讯的端点。
Socket 类有 5 个构造方法:
Socket 常用方法
下面列出了一些感兴趣的方法,注意客户端和服务器端都有一个 Socket 对象,所以无论客户端还是服务端都能够调用这些方法。
ServerSocket
ServerSocket类:这个类实现了服务器套接字,该对象等待通过网络的请求。
ServerSocket 有多个构造方法:
ServerSocket 类的常用方法:
Socket 通信示例
服务端示例:
public class HelloServer { public static void main(String[] args) throws Exception { // Socket 服务端 // 服务器在8888端口上监听 ServerSocket server = new ServerSocket(8888); System.out.println("服务器运行中,等待客户端连接。"); // 得到连接,程序进入到阻塞状态 Socket client = server.accept(); // 打印流输出最方便 PrintStream out = new PrintStream(client.getOutputStream()); // 向客户端输出信息 out.println("hello world"); client.close(); server.close(); System.out.println("服务器已向客户端发送消息,退出。"); } }
客户端例子:
public class HelloClient { public static void main(String[] args) throws Exception { // Socket 客户端 Socket client = new Socket("localhost", 8888); InputStreamReader inputStreamReader = new InputStreamReader(client.getInputStream()); // 一次性接收完成 BufferedReader buf = new BufferedReader(inputStreamReader); String str = buf.readLine(); buf.close(); client.close(); System.out.println("客户端接收到服务器消息:" + str + ",退出"); } }
陈睿mikechen
10年+大厂架构经验,资深技术专家,就职于阿里巴巴、淘宝、百度等一线互联网大厂。
关注「mikechen」公众号,获取更多技术干货!
后台回复【面试】即可获取《史上最全阿里Java面试题总结》,后台回复【架构】,即可获取《阿里架构师进阶专题全部合集》