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五层协议,它们之间的对应关系如下图所示:

7. 网络编程 - 图1

  • 物理层(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模型

  1. ┌────------────┐┌─┬─┬─-┬─┬─-┬─┬─-┬─┬─-┬─┬─-┐<br />

  │        ││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位的二进制数)表示的,它的取值范围是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 用于实验

  1. public static void main(String[] args) {
  2. try {
  3. // 获取主机
  4. InetAddress local = InetAddress.getLocalHost();
  5. System.out.println(local);
  6. // 获取主机名字字符串
  7. String name = local.getHostName();
  8. System.out.println(name);
  9. // 获取主机地址字符串
  10. String address = local.getHostAddress();
  11. System.out.println(address);
  12. // 通过主机名获取
  13. InetAddress localhost = InetAddress.getByName("localhost");
  14. System.out.println(localhost);
  15. // 通过一个域名获取
  16. InetAddress kaikeba = InetAddreshosttByName("www.kaikeba.com");
  17. System.out.println(kaikeba);
  18. InetAddress[] addresses = InetAddress.getAllByName("www.kaikeba.com");
  19. for (InetAddress addr : addresses) {
  20. System.out.println(addr);
  21. }
  22. } catch (UnknownHostException e) {
  23. e.printStackTrace();
  24. }
  25. }

7.3 TCP

TCP协议是面向连接的通信协议,即在传输数据前先在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。在TCP连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”。

三次握手: 建立一个TCP连接时,需要客户端和服务端总共发送3个包以确认连接的建立, 在Socket编程中,这一过程由客户端执行connect来触发,具体流程图如下:

7. 网络编程 - 图2

  • 第一次握手: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来触发,具体流程图如下:

7. 网络编程 - 图3

  • 第一次挥手: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状态,完成四次挥手。 另外也可能是同时发起主动关闭的情况:

7. 网络编程 - 图4

另外还可能有一个常见的问题就是:为什么建立连接是三次握手,而关闭连接却是四次挥手呢? 答:因为服务端在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里 发送给客户端。而关闭连接时,当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还 能接收数据,己方也未必全部数据都发送给对方了,所以己方可以立即close,也可以发送一些 数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会 分开发送。

由于TCP协议的面向连接特性,它可以保证传输数据的安全性,所以是一个被广泛采用的协议,例如在下载文件时,如果数据接收不完整,将会导致文件数据丢失而不能被打开,因此,下载文件时必须采用TCP协议。

TCP特点:

  • 面向连接。
  • 安全的。(不会存在数据丢失)
  • 传输的数据大小是有限制的。

三次握手:

  • 客户端向服务端发送一个请求。
  • 服务端收到请求后,返回给客户端一个响应。
  • 客户端接收到服务端的响应后,再回服务端一个确认信息。

重点涉及到的类:

Socket类:客户端

ServerSocket类:服务端

Socket用来描述IP地址和端口,是通信链的句柄,应用程序通过Socket向网络发送请求或者应答网络请求,socket是支持TCP/IP协议的网络通信的基本操作单元

Socket

7. 网络编程 - 图5

  1. /* TCP 服务器端
  2. *
  3. * 1,创建服务器ServerSocket对象(指定服务器端口号)
  4. * 2,开启服务器了,等待客户端的连接,当客户端连接后,可以获取到连接服务器的客户端Socket对象
  5. * 3,给客户端反馈信息
  6. * 4,关闭流资源
  7. */
  8. public class TCPServer {
  9. public static void main(String[] args) throws IOException {
  10. //1,创建服务器ServerSocket对象(指定服务器端口号)
  11. ServerSocket ss = new ServerSocket(8888);
  12. //2,开启服务器了,等待客户端的连接,当客户端连接后,可以获取到连接服务器的客户端Socket对象
  13. Socket s = ss.accept();
  14. //3,给客户端反馈信息
  15. /*
  16. * a,获取客户端的输出流
  17. * b,在服务端端,通过客户端的输出流写数据给客户端
  18. */
  19. //a,获取客户端的输出流
  20. OutputStream out = s.getOutputStream();
  21. //b,在服务端端,通过客户端的输出流写数据给客户端
  22. out.write("你已经连接上了服务器".getBytes());
  23. //4,关闭流资源
  24. out.close();
  25. s.close();
  26. //ss.close(); 服务器流 通常都是不关闭的
  27. }
  28. }
  29. //完成了服务器端程序的编写,接下来编写客户端程序。
  30. /*
  31. * TCP 客户端
  32. *
  33. * 1,创建客户端Socket对象,(指定要连接的服务器地址与端口号)
  34. * 2,获取服务器端的反馈回来的信息
  35. * 3,关闭流资源
  36. */
  37. public class TCPClient {
  38. public static void main(String[] args) throws IOException {
  39. //1,创建客户端Socket对象,(指定要连接的服务器地址与端口号)
  40. Socket s = new Socket("127.0.0.1", 8888);
  41. //2,获取服务器端的反馈回来的信息
  42. InputStream in = s.getInputStream();
  43. //获取获取流中的数据
  44. byte[] buffer = new byte[1024];
  45. //把流中的数据存储到数组中,并记录读取字节的个数
  46. int length = in.read(buffer);
  47. //显示数据
  48. System.out.println( new String(buffer, 0 , length) );
  49. //3,关闭流资源
  50. in.close();
  51. s.close();
  52. }
  53. }

7.4 UDP

UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。

由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输例如视频会议都使用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。

但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议。UDP的交换过程如下图所示。

  1. //UDP完成数据的发送
  2. /*
  3. * 发送端
  4. * 1,创建DatagramSocket对象
  5. * 2,创建DatagramPacket对象,并封装数据
  6. * 3,发送数据
  7. * 4,释放流资源
  8. */
  9. public class UDPSend {
  10. public static void main(String[] args) throws IOException {
  11. //1,创建DatagramSocket对象
  12. DatagramSocket sendSocket = new DatagramSocket();
  13. //2,创建DatagramPacket对象,并封装数据
  14. //public DatagramPacket(byte[] buf, int length, InetAddress address, int port)
  15. //构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。
  16. byte[] buffer = "hello,UDP".getBytes();
  17. DatagramPacket dp = new DatagramPacket(buffer, buffer.length, InetAddress.getByName("127.0.0.1"), 6666);
  18. //3,发送数据
  19. //public void send(DatagramPacket p) 从此套接字发送数据报包
  20. sendSocket.send(dp);
  21. //4,释放流资源
  22. sendSocket.close();
  23. }
  24. }
  25. //UDP完成数据的接收
  26. /*
  27. * UDP接收端
  28. *
  29. * 1,创建DatagramSocket对象
  30. * 2,创建DatagramPacket对象
  31. * 3,接收数据存储到DatagramPacket对象中
  32. * 4,获取DatagramPacket对象的内容
  33. * 5,释放流资源
  34. */
  35. public class UDPReceive {
  36. public static void main(String[] args) throws IOException {
  37. //1,创建DatagramSocket对象,并指定端口号
  38. DatagramSocket receiveSocket = new DatagramSocket(6666);
  39. //2,创建DatagramPacket对象, 创建一个空的仓库
  40. byte[] buffer = new byte[1024];
  41. DatagramPacket dp = new DatagramPacket(buffer, 1024);
  42. //3,接收数据存储到DatagramPacket对象中
  43. receiveSocket.receive(dp);
  44. //4,获取DatagramPacket对象的内容
  45. //谁发来的数据 getAddress()
  46. InetAddress ipAddress = dp.getAddress();
  47. String ip = ipAddress.getHostAddress();//获取到了IP地址
  48. //发来了什么数据 getData()
  49. byte[] data = dp.getData();
  50. //发来了多少数据 getLenth()
  51. int length = dp.getLength();
  52. //显示收到的数据
  53. String dataStr = new String(data,0,length);
  54. System.out.println("IP地址:"+ip+ "数据是"+ dataStr);
  55. //5,释放流资源
  56. receiveSocket.close();
  57. }
  58. }

7.5 综合案例

  1. /*
  2. * 文件上传 服务器端
  3. *
  4. */
  5. public class TCPServer {
  6. public static void main(String[] args) throws IOException {
  7. //1,创建服务器,等待客户端连接
  8. ServerSocket serverSocket = new ServerSocket(8888);
  9. Socket clientSocket = serverSocket.accept();
  10. //显示哪个客户端Socket连接上了服务器
  11. InetAddress ipObject = clientSocket.getInetAddress();//得到IP地址对象
  12. String ip = ipObject.getHostAddress(); //得到IP地址字符串
  13. System.out.println("谁在传文件给我" + "IP:" + ip);
  14. //2,获取Socket的输入流
  15. InputStream in = clientSocket.getInputStream();
  16. //3,创建目的地的字节输出流
  17. BufferedOutputStream fileOut = new BufferedOutputStream(new FileOutputStream("myfileRecieved.txt"));
  18. //4,把Socket输入流中的数据,写入目的地的字节输出流中
  19. byte[] buffer = new byte[1024];
  20. int len = -1;
  21. while((len = in.read(buffer)) != -1){
  22. //写入目的地的字节输出流中
  23. fileOut.write(buffer, 0, len);
  24. }
  25. //-----------------反馈信息---------------------
  26. //5,获取Socket的输出流, 作用:写反馈信息给客户端
  27. OutputStream out = clientSocket.getOutputStream();
  28. //6,写反馈信息给客户端
  29. out.write("文件上传成功".getBytes());
  30. out.close();
  31. fileOut.close();
  32. in.close();
  33. clientSocket.close();
  34. //serverSocket.close();
  35. }
  36. }
  37. //编写客户端,完成上传文件
  38. /*
  39. * 文件上传 客户端
  40. *
  41. * public void shutdownOutput() 禁用此Socket的输出流,间接的相当于告知了服务器数据写入完毕
  42. */
  43. public class TCPClient {
  44. public static void main(String[] args) throws IOException {
  45. //1,创建客户端Socket,连接服务器
  46. Socket socket = new Socket("127.0.0.1", 8888);
  47. //2,获取Socket流中的输出流,功能:用来把数据写到服务器
  48. OutputStream out = socket.getOutputStream();
  49. //3,创建字节输入流,功能:用来读取数据源(图片)的字节
  50. BufferedInputStream fileIn = new BufferedInputStream(new FileInputStream("myfile.txt"));
  51. //4,把图片数据写到Socket的输出流中(把数据传给服务器)
  52. byte[] buffer = new byte[1024];
  53. int len = -1;
  54. while ((len = fileIn.read(buffer)) != -1){
  55. //把数据写到Socket的输出流中
  56. out.write(buffer, 0, len);
  57. }
  58. //5,客户端发送数据完毕,结束Socket输出流的写入操作,告知服务器端
  59. socket.shutdownOutput();
  60. //-----------------反馈信息---------------------
  61. //6,获取Socket的输入流 作用: 读反馈信息
  62. InputStream in = socket.getInputStream();
  63. //7,读反馈信息
  64. byte[] info = new byte[1024];
  65. //把反馈信息存储到info数组中,并记录字节个数
  66. int length = in.read(info);
  67. //显示反馈结果
  68. System.out.println( new String(info, 0, length) );
  69. //关闭流
  70. in.close();
  71. fileIn.close();
  72. out.close();
  73. socket.close();
  74. }
  75. }