第十六章、网络编程
网络编程
1. 网编先导知识
-
网络应用开发架构
-
C / S即client(客户端) / server(服务端)
飞秋、Git、百度云、输入法....
-
B / S即browser(浏览器)/ server(服务器)
淘宝、邮箱、百度、知乎....
-
B / S是特殊的C / S架构
-
-
网卡:每个实际存在在计算机硬件里面的
-
mac地址:每块网卡上都有一个全球唯一的mac地址
-
交换机:是连接多台机器并帮助通讯的物理设备,只可以识别mac地址
-
协议:两台物理设备之间对于要发送的内容,长度和顺序做的一些规范
-
ip地址(规格)
-
ipv4协议:韦德点分十进制,32位的二进制
范围:0.0.0.0~255.255.255.255
-
ipv6协议:8位的冒分十六制,128位十进制来表示 (点分十进制不足以表示)
范围:0:0:0:0:0:0:0:0 ~ FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF
-
-
ip地址的使用:
-
如果一个ip地址向北所有人都看到,这个ip地址是必须要申请的,即公网ip
-
提供内网ip,供局域网使用:
192.168.0.0 - 192.168.255.255
172.16.0.0 - 172.31.255.255
10.0.0.0 - 10.255.255.255
-
-
交换机实现的ARP协议(地址解析协议)
-
通过ip地址获取一台机器的mac地址
-
交换机工作原理:收到一个请求,通知所有连接他的ip地址,获取对应的ip地址的mac地址并返回给请求的ip地址
-
-
网关ip:一个局域网的网络出口,访问局域网以外的区域都需要经过路由器的网关
-
网段:一般是一个末尾为0的地址段
-
子网掩码:判断两个机器是否在同一个网段
屏蔽一个IP地址的网络部分的“全1”比特模式。对于A类地址来说,默认的子网掩码是255.0.0.0;对于B类地址来说默认的子网掩码是255.255.0.0;对于C类地址来说默认的子网掩码是255.255.255.0
# 示例:通过将请求的计算的子网掩码和两个要匹配的计算机的二进制按位与运算 ip = 255.255.255.255 11111111.11111111.11111111.11111111 ip = 192.168.12.87 11000000.10101000.00001100.00000111 ip = 192.168.12.7
-
ip地址可以确认一台机器,port端口可以确认一台机器的一个应用,端口的范围:0~65535
-
一般实现互联,使用127.0.0.1,是自己的地址,不过减缓及ip地址是可以过交换机的。
2. ISO模型(五层结构)
-
物理层
- 物理层是ISO模型的最底层,负责网络设备在各种物理介质上传输比特流,并规定各种各种物理传输介质、接口的机械特性和电气特性。一般用位表示。
-
数据链路层
- mac地址,ARP协议 物理设备:网卡,交换机。
-
网络层
- IPV4/IPV6协议,物理设备:路由器,三层交换机(交换机具有路由功能)ip通过DNS解析获取(DNS域名和ip互相映射的数据库)
-
传输层
- tcp协议和udp协议,物理设备:端口,四层路由器,四层交换机。
-
应用层
- 应用层 :https/http/ftp/smtp协议 所有的应用层协议是基于tcp或者是udp协议
-
数据封装和解封
数据封装
数据解封
3. 传输层的两种协议(tcp/udp)
3.1 tcp协议
-
特点
- 面向连接的,可靠,但是慢,可以实现全双工通信,即双方都是实时的,区别于半双工(传呼机)
- 无边界,流式传输(导致粘包问题)
- 长连接:会一直占用双方的端口
- 能够传输的数据长度几乎没有限制
-
三次握手和四次挥手
-
具体的三次握手(连接方式):
# accept接受过程中等待客户端的连接 # connect客户端发起了一个syn连接请求(附带了一个随机数) # 如果得到了server端的响应ack的同时还会再收到一个由server端发来的syn链接请求 # client端进行回复ack之后,就建立起了一个tcp协议的链接 # 备注:三次握手的过程再代码中是由accept和connect共同完成的,具体的细节再socket中没有体现出来
-
具体的四次挥手(断开连接方式):
# server和client端对应的在代码中都有close方法 # 每一端发起的close操作都是一次fin的断开请求,得到'断开确认ack'之后,就可以结束一端的数据发送 # 如果两端都发起close,那么就是两次请求和两次回复,一共是四次操作 # 可以结束两端的数据发送,表示链接断开了
-
-
应用场景
- QQ和微信等上面的传输压缩文件,缓存下载的电影等等
3.2 udp协议
- 特点
- 面向数据报的,无连接,速度很快,类似于发短信,能实现一对一,多对一,一对多的高效通讯
- 由于没有回执,对于发送信息和接受信息的双方来说,可能存在丢失消息的情况
- 能够传递的长度有限,是根据数据传递设备的位置有关系
- 应用场景
- 通信类的 如QQ 微信,发短信, 在线观看小视频等
3.3 Socket
-
概念:网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。
-
原理:进程通信之前,双方首先必须各自创建一个端点,否则是没有办法建立联系并相互通信的
-
Socket之间的连接过程
- 服务器监听
- 客户端请求
- 连接确认
-
java实现Socket步骤
-
创建Socket
-
打开连接到Socket的输入/出流
-
按照一定的协议对Socket进行读/写操作
-
关闭Socket
-
-
代码实现
// 服务端 package demo2; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.ServerSocket; import java.net.Socket; public class Server { public static void main(String[] args) { try { // 1.创建服务器socket服务。通过ServerSocket对象,指定端口号 ServerSocket ss = new ServerSocket(8888); System.out.println("服务端等待连接...."); // 2.获取连接过来的客户端对象,使用accept() Socket s = ss.accept(); // 进行阻塞态 System.out.println("客户端连接成功...."); // 3.通过客户端对象获取socket流读取客户端发来的数据 BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream())); String str = br.readLine(); System.out.println("客户端发过来的消息是:" + str); // 4.通过客户端对象获取socket流向客户端发送数据 BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); bw.write("你好,客户端!"); // 关闭相关资源 bw.close(); br.close(); s.close(); ss.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
// 客户端 package demo2; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.Socket; public class Client { public static void main(String[] args) { try { // Socket(String host,int port); // 1.创建Socket对象,指定服务器的ip地址和端口号 Socket s = new Socket("127.0.0.1", 8888); // 2.通过客户端对象获取socket流读取服务端发来的数据 BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); bw.write("你好,服务端!"); bw.flush(); s.shutdownOutput(); // 3.通过客户端对象获取socket流读取客户端发来的数据 BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream())); String str = br.readLine(); System.out.println("服务端发送过来的信息是:" + str); // 4.关闭相关资源 br.close(); bw.close(); s.close(); } catch (Exception e) { e.printStackTrace(); } } }
4.基于TCP实现Socket编程模型
-
服务端
-
创建ServerSocket类的对象,并提供端口号。
-
等待客户端连接,使用accept()方法
-
等Socket对象,并使用输入输出流进行通信
-
关闭相互资源
-
-
客户端
- 创建Socket类型的对象,并指定服务器的IP地址和端口号
- 使用输入输出流进行通信
- 关闭相互资源
-
代码示例
-
双向通信
// 服务端 package demo2; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.ServerSocket; import java.net.Socket; /* * 建立TCP服务器端思路 * 1.创建服务器socket服务。通过ServerSocket对象 * 2.服务器端必须对外提供一个端口,否则客户端无法连接 * 3.获取连接过来的客户端对象 * 4.通过客户端对象获取socket流读取客户端发来的数据 * 5.关闭资源,关闭流,关闭服务器 */ public class Server { public static void main(String[] args) { try { // 1.创建服务器socket服务。通过ServerSocket对象,指定端口号 ServerSocket ss = new ServerSocket(8888); System.out.println("服务端等待连接...."); // 2.获取连接过来的客户端对象,使用accept() Socket s = ss.accept(); // 进行阻塞态 System.out.println("客户端连接成功...."); // 3.通过客户端对象获取socket流读取客户端发来的数据 BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream())); String str = br.readLine(); System.out.println("客户端发过来的消息是:" + str); // 4.通过客户端对象获取socket流向客户端发送数据 BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); bw.write("你好,客户端!"); // 关闭相关资源 bw.close(); br.close(); s.close(); ss.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
// 客户端 package demo2; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.Socket; /* * 客户端建立思路 * 1.创建TCP客户端socket服务,使用的是Socket对象 * 2.如果连接成功,说明数据传输链接建立成功 * 通过的是socket流,是底层建立好的 * 既然是流的话,就应该有输入输出 * 想要获取输入或输出对象,可以找socket获取 * 可以通过getOutputStream()和getInputStream()来获取两个字符流 * 3.使用输入流,将数据写入 * 4.关闭资源 * * 建立连接后,通过socket中的IO流进行数据传输 * 向服务器发送:你好,服务器 * 如果想要使用字符流就需要使用OutputStreamWriter/InputStreamReader 转换流 */ public class Client { public static void main(String[] args) { try { // Socket(String host,int port); // 1.创建Socket对象,指定服务器的ip地址和端口号 Socket s = new Socket("127.0.0.1", 8888); // 2.通过客户端对象获取socket流读取服务端发来的数据 BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); bw.write("你好,服务端!"); bw.flush(); s.shutdownOutput(); // 3.通过客户端对象获取socket流读取客户端发来的数据 BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream())); String str = br.readLine(); System.out.println("服务端发送过来的信息是:" + str); // 4.关闭相关资源 br.close(); bw.close(); s.close(); } catch (Exception e) { e.printStackTrace(); } } }
-
多人聊天室
// 服务端 package demo4; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; /* * 要求客户端发送的内容由用户手动输入,使用BufferedReader类 要求服务器收到客户端的消息之后,想客户端回发消息”I Receive!” 要求服务器和客户端可以不断地进行通信,当客户端发送”bye”时结束通信。 要求服务能够同时支持多个客户端的连接,而且能够和多个客户端同时聊天,多线程。 */ public class Server { public static void main(String[] args) { try { // 1.创建ServerSocket类型的对象。并绑定端口 ServerSocket ss = new ServerSocket(8999); while (true) { System.out.println("等待客户端连接..."); // 等待客户端连接请求 Socket s = ss.accept(); System.out.println("客户端" + s.getInetAddress() + "连接成功!"); // 只要有客户端连接成功,应该启动一个新线程为之服务,主线程始终接待 new ServerThread(s).start(); } } catch (IOException e) { e.printStackTrace(); } } }
// 服务端线程 package demo4; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.Socket; public class ServerThread extends Thread { private Socket s; public ServerThread(Socket s) { this.s = s; } @Override public void run() { try { // 用来接收客户端发来的内容 BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream())); // 向客户端发送字符串内容”I Receive!“ PrintStream ps = new PrintStream(s.getOutputStream()); while (true) { String str = br.readLine(); if ("bye".equalsIgnoreCase(str)) { System.out.println("客户端" + s.getInetAddress() + "已下线"); break; } System.out.println("客户端" + s.getInetAddress() + "发来的消息是:" + str); ps.println("I Receive!"); } ps.close(); br.close(); s.close(); } catch (Exception e) { e.printStackTrace(); } } }
// 客户端 package demo4; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.Socket; import java.net.UnknownHostException; public class Client { public static void main(String[] args) { try { // 创建Socket类型的对象,并提供IP地址和端口号 Socket s = new Socket("localhost", 8999); // 使用输入输出流进行通信 BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); // 用来接收服务端发来的内容 BufferedReader br2 = new BufferedReader(new InputStreamReader(s.getInputStream())); PrintStream ps = new PrintStream(s.getOutputStream()); while (true) { System.out.println("请输入要发送的内容:"); String str = br.readLine(); ps.println(str); System.out.println("成功发送数据到服务器"); // 判断客户端发送的内容是否是"bye",若是则结束通信 if ("bye".equalsIgnoreCase(str)) { break; } String str2 = br2.readLine(); System.out.println("服务器端发来的消息是:" + str2); } br2.close(); br.close(); ps.close(); s.close(); } catch (UnknownHostException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
-
5. 基于UDP实现Socket编程模型
-
接收方
-
创建DatagramSocket类型的对象,并提供端口号
-
创建DatagramPacket类型的对象,用于接收发来的数据
-
使用上述的对象接收数据内容,使用recieve()方法
-
关闭相关资源
-
-
发送方
- 创建DatagramSocket类型的对象。
- 创建DatagramPacket类型的对象,并提供端口号和IP地址
- 使用上述的对象发送数据内容,使用send()方法。
- 关闭相关资源
-
代码示例
// 发送方 package demo5; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; public class TestRecieve { public static void main(String[] args) { try { // 1.创建DatagramSocket类型的对象 DatagramSocket ds = new DatagramSocket(); // 2.创建DatagramPacket类型的对象,并向外提供一个端口号和ip地址 byte[] data = "hello!".getBytes(); InetAddress ia = InetAddress.getLocalHost(); DatagramPacket dp = new DatagramPacket(data, data.length, ia, 8888); // 3.发送数据内容,使用send()方法 ds.send(dp); System.out.println("成功发送数据内容!"); // 4.关闭套接字 ds.close(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
// 接收方 package demo5; import java.net.DatagramPacket; import java.net.DatagramSocket; public class TestSend { public static void main(String[] args) { try { // 1.创建DatagramSocket类型的对象,并提供端口号 DatagramSocket ds = new DatagramSocket(8888); // 2.创建DatagramPacket类型的对象,用于接收数据内容 byte[] data = new byte[1024]; DatagramPacket dp = new DatagramPacket(data, data.length); // 3.接收数据内容并保存到数据报中,使用receive()方法 ds.receive(dp); System.out.println("接收到的数据是:" + new String(data, 0, dp.getLength()) + "!"); // 4.关闭套接字 ds.close(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }