文章目录
一. 网络编程入门
1.1 网络编程概述
-
计算机网络
计算机网络是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。
-
网络编程
在网络通信协议下,实现网络互连的不同计算机上运行的程序间可以进行数据交换。
(建立连接,进行通信)
-
网络通信协议
位于同一个网络中的计算机在进行连接和通信时需要遵守的规则,称为网络通信协议。
它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换。
-
TCP/IP协议
传输控制协议/因特网互联协议( Transmission Control Protocol/Internet Protocol),是Internet最基本、最广泛的协议。它定义了计算机如何连入因特网,以及数据如何在它们之间传输的标准。
它的内部包含一系列的用于处理数据通信的协议,并采用了4层的分层网络模型,每一层都呼叫它的下一层所提供的协议来完成自己的需求。
TCP/IP协议中的四层分别是应用层、传输层、网络层和网络接口层 ( 数据链路层、物理层 ),每层分别负责不同的通信功能。
网络接口层:网络接口层是最低层。用于定义物理传输通道,通常是对某些网络连接设备的驱动协议,例如针对光纤、网线提供的驱动。负责接收IP数据包并通过网络发送之,或者从网络上接收物理帧,抽出IP数据报,交给IP层。
网络层:网络层是整个TCP/IP协议的核心,主要定义了IP地址格式,用于将传输的数据进行分组,将分组数据发送到目标计算机或者网络,使得不同应用类型的数据在Internet上通畅地传输。
传输层:主要是提供应用程序间的通信。TCP/IP协议族在这一层的协议有TCP和UDP。
应用层:TCP/IP协议族在这一层面有着很多协议来支持不同的应用。如进行万维网(WWW)访问用HTTP协议、文件传输用FTP协议、电子邮件发送用SMTP、域名的解析用DNS协议、远程登录用Telnet协议等等;就用户而言,看到的是由一个个软件所构筑的大多为图形化的操作界面,而实际后台运行的便是上述协议。
-
网络模型
详见网络模型
1.2 协议分类
通信的协议还是比较复杂的,java.net
包中包含的类和接口,它们提供低层次的通信细节。我们可以直接使用这些类和接口,来专注于网络程序开发,而不用考虑通信的细节。
java.net
包中提供了两种常见的网络协议的支持:
-
UDP:
- 用户数据报协议 ( User Datagram Protocol )。
- 特点:每个包的大小为64KB,超出这个范围就不能发送了。
- 数据报(Datagram):网络传输的基本单位
- UDP是无连接通信协议。在数据传输时,数据的发送端和接收端不建立逻辑连接。即,发送端发送数据时,不会确认接收端是否存在;接收端在收到数据后,也不会向发送端反馈数据接收情况。
- 由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输。
- 由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议。而视频会议等因为偶尔丢失一两个数据包,也不会对接收结果产生太大影响,所以通常都使用UDP协议。
-
TCP:
- 传输控制协议 ( Transmission Control Protocol )。
- TCP协议是面向连接的通信协议。即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。
- 为保证可靠性,建立连接的过程需要三次握手,断开连接的过程需要四次挥手。
- 由于TCP面向连接的特性,可以保证传输数据的安全,所以应用十分广泛,如下载文件、浏览网页等。
- ”三次握手,四次挥手“ 详见
TCP详解
1.3 网络编程三要素
协议
- 协议:计算机网络通信必须遵守的规则,已经介绍过了,不再赘述。
IP地址
- IP地址:互联网协议地址(Internet Protocol Address),俗称IP。IP地址用来给一个网络中的计算机设备做唯一的编号。『IP地址』之于『个人电脑』,如同『电话号码』之于『电话』。
IP地址分类
-
IPV4:4字节32位,并分成4段8位的二进制数,表示成
D.D.D.D
的形式,D为0~255之间的十进制整数,最多可以表示4.2*10^8个。例如192.168.1.1
由于互联网不断发展,IP地址的需求量愈来愈大,但是网络地址资源有限,使得IP的分配越发紧张。为了扩大地址空间,拟通过IPv6重新定义地址空间,
-
IPV6:16字节128位,并分成8段十六进制数,表示成
X:X:X:X:X:X:X:X
,X为0~65535之间的十进制整数,最多可以表示3.4*10^38个。例如ABCD:EF01:2345:6789:ABCD:EF01:2345:6789
IPV4的应用分类
A类:政府机构, 1.0.0.1 ~ 126.255.255.254 第1个字节的最高位固定为0。
A类私有:10.0.0.0到10.255.255.255
B类:中型企业, 128.0.0.1 ~ 191.255.255.254 第1个字节的前两位为固定为10.
B类私有:172.16.0.0到172.31.255.255
C类:个人用户, 192.0.0.1 ~ 223.255.255.254 第1个字节的前三位固定为110。
C类私有:192.168.0.0到192.168.255.255
D类:用于组播, 224.0.0.1 ~ 239.255.255.254 第1个字节最前面为1110。
E类:用于实验, 240.0.0.1 ~ 255.255.255.254 第1个字节最前面为1111。
回环地址: 127.0.0.1 ~ 127.255.255.254 指本机 localhost
,一般用于测试使用。
常用命令
- 查看本机IP地址,在控制台输入:
ipconfig
- 检查网络是否连通,在控制台输入:
ping D.D.D.D // D.D.D.D 为IPV4地址
端口号
网络的通信,本质上是两个进程(应用程序)的通信。如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的进程(应用程序)了。
- 端口号:用两个字节表示的整数,它的取值范围是0~65535端口分类:
- 公认端口:0~1023
- 注册端口: 1024-49151
- 动态或私有端口: 49152~65535
- 如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败。
利用协议
+IP地址
+端口号
三元组合,就可以标识网络中的进程了,那么进程间的通信就可以利用这个标识与其它进程进行交互。
1.4 InetAddress类
概念:表示互联网协议(IP)地址对象,封装了与该IP地址相关的所有信息,并提供获取信息的常用方法。
方法:
public static InetAddress getLocalHost() // 获得本地主机地址对象
public static InetAddress getByName(String host) // 根据主机名称获得地址对象.
public static InetAddress] getAllByName(String host) // 获得所有相关地址对象
public String getHostAddress() // 获取IP地址字符串
public String getHostName() // 获得IP地址主机名
【例】
public class InetAddressDemo {
public static void main(String[] args) throws UnknownHostException {
//InetAddress address = InetAddress.getByName("FanDeBiao");
InetAddress address = InetAddress.getLocalHost();
//public String getHostName():获取此IP地址的主机名
String name = address.getHostName();
//public String getHostAddress():返回文本显示中的IP地址字符串
String ip = address.getHostAddress();
System.out.println("主机名:" + name);
System.out.println("IP地址:" + ip);
}
}
二. Socket套接字
2.1 什么是Socket?
Socket的英文原义是“孔”或“插座”。在网络编程中,网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。
Socket套接字是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:
- 连接使用的协议,
- 本地主机的IP地址,
- 本地进程的协议端口,
- 远地主机的IP地址,
- 远地进程的协议端口。
2.2 Socket的原理
Socket实质上提供了进程通信的端点。
进程通信之前,双方首先必须各自创建一个端点,否则是没有办法建立联系并相互通信的。正如打电话之前,双方必须各自拥有一台电话机一样。
套接字之间的连接过程可以分为三个步骤:服务器监听,客户端请求,连接确认。
1、服务器监听:是服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态。
2、客户端请求:是指由客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。
3、连接确认:是指当服务器端套接字监听到或者说接收到客户端套接字的连接请求,它就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,连接就建立好了。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。
三. UDP通信程序
UDP协议是一种不可靠的网络协议。
它在通信的两端各建立一个Socket对象,但是这两个Socket只是发送,接收数据的对象,因此对于基于UDP协议的通信双方而言,没有所谓的客户端和服务器的概念。
Java的java.net
包提供了DatagramSocket
类用于表示基于UDP协议的收发数据包的 Socket,DatagramPacket
类用来表示数据包
构造方法
DatagramSocket (端口)
DatagramSocket() //创建数据报套接字并将其绑定到本机地址上的任何可用端口
DatagramPacket (发数据包)
DatagramPacket(byte[] buf,int len,InetAddress add,int port) // 创建数据包,发送长度为len的数据包到指定主机的指定端口
DatagramPacket (收数据包)
DatagramPacket(byte[] buf, int len) // 创建一个DatagramPacket用于接收长度为len的数据包
相关方法
发数据
void send(DatagramPacket p) // 发送数据报包
void close() // 关闭数据报套接字
收数据
void receive(DatagramPacket p) // 从此套接字接受数据报包
解析数据
byte[] getData() // 返回数据缓冲区
int getLength() // 返回要发送的数据的长度或接收的数据的长度
3.1 UDP发送数据
发送数据的步骤
- 创建发送端的Socket对象(DatagramSocket)
- 创建数据,并把数据打包
- 调用DatagramSocket对象的方法发送数据
- 关闭发送端
public class SendDemo {
public static void main(String[] args) throws IOException {
// 创建发送端的Socket对象(DatagramSocket)
// DatagramSocket()
DatagramSocket ds = new DatagramSocket();
// 创建数据,并把数据打包
// DatagramPacket(byte[] buf, int length, InetAddress address, int port)
byte[] bys = "hello,udp,我来了".getBytes();
DatagramPacket dp = new DatagramPacket
(bys,bys.length,InetAddress.getByName("FanDeBiao"),10086);
// 调用DatagramSocket对象的方法发送数据
// void send(DatagramPacket p)
ds.send(dp);
// 关闭发送端
// void close()
ds.close();
}
}
3.2 UDP接收数据
接收数据的步骤
- 创建接收端的Socket对象(DatagramSocket)
- 创建一个数据包,用于接收数据
- 调用DatagramSocket对象的方法接收数据
- 解析数据包,并把数据在控制台显示
- 关闭接收端
public class ReceiveDemo {
public static void main(String[] args) throws IOException {
//创建接收端的Socket对象(DatagramSocket)
DatagramSocket ds = new DatagramSocket(10086);
while (true) {
//创建一个数据包,用于接收数据
byte[] bys = new byte[1024];
DatagramPacket dp = new DatagramPacket(bys, bys.length);
//调用DatagramSocket对象的方法接收数据
ds.receive(dp);
//解析数据包,并把数据在控制台显示
System.out.println("数据是:" + new String(dp.getData(), 0, dp.getLength()));
}
}
}
3.3 【练习】UDP通信
-
案例需求
UDP发送数据:数据来自于键盘录入,直到输入的数据是886,发送数据结束
UDP接收数据:因为接收端不知道发送端什么时候停止发送,故采用死循环接收
-
代码实现
/* UDP发送数据: 数据来自于键盘录入,直到输入的数据是886,发送数据结束 */ public class SendDemo { public static void main(String[] args) throws IOException { //创建发送端的Socket对象(DatagramSocket) DatagramSocket ds = new DatagramSocket(); //自己封装键盘录入数据 BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String line; while ((line = br.readLine()) != null) { //输入的数据是886,发送数据结束 if ("886".equals(line)) { break; } //创建数据,并把数据打包 byte[] bys = line.getBytes(); DatagramPacket dp = new DatagramPacket(bys, bys.length, InetAddress.getByName("FanDeBiao"), 12345); //调用DatagramSocket对象的方法发送数据 ds.send(dp); } //关闭发送端 ds.close(); } } /* UDP接收数据: 因为接收端不知道发送端什么时候停止发送,故采用死循环接收 */ public class ReceiveDemo { public static void main(String[] args) throws IOException { //创建接收端的Socket对象(DatagramSocket) DatagramSocket ds = new DatagramSocket(12345); while (true) { //创建一个数据包,用于接收数据 byte[] bys = new byte[1024]; DatagramPacket dp = new DatagramPacket(bys, bys.length); //调用DatagramSocket对象的方法接收数据 ds.receive(dp); //解析数据包,并把数据在控制台显示 System.out.println("数据是:" + new String(dp.getData(), 0, dp.getLength())); } //关闭接收端 // ds.close(); } }
四. TCP通信程序
TCP通信能实现两台计算机之间的数据交互,通信的两端,要严格区分为客户端(Client)与服务端(Server)。
两端通信时步骤:
- 服务端程序,需要事先启动,等待客户端的连接。
- 客户端主动连接服务器端,连接成功才能通信。服务端不可以主动连接客户端。
在Java的java.net.
包中,提供了两个类用于实现TCP通信程序:
Socket
类表示客户端。ServerSocket
类表示服务端。
4.1 Socket类
Socket
类:该类实现客户端套接字。
构造方法
Socket(InetAddress address,int port) // 创建流套接字并将其连接到指定IP指定端口号
Socket(String host, int port) // 创建套接字对象并将其连接到指定主机上的指定端口号。
如果指定的host是null ,则相当于指定地址为回送地址。
【例】:
Socket client = new Socket("127.0.0.1", 6666);
成员方法
InputStream getInputStream() // 返回此套接字的输入流。
OutputStream getOutputStream() // 返回此套接字的输出流。
- 如果此Scoket具有相关联的通道,则生成的InputStream/OutputStream 的所有操作也关联该通道。
- 关闭生成的InputStream/OutputStream也将关闭相关的Socket。
void close() // 关闭此套接字。
- 关闭此socket也将关闭相关的InputStream和OutputStream 。
void shutdownOutput() // 禁用此套接字的输出流。
- 任何先前写出的数据将被发送,随后终止输出流。
4.2 ServerSocket类
ServerSocket
类:这个类实现了服务器套接字。
构造方法
ServerSocket(int port) // 创建绑定到指定端口的服务器套接字
【例】:
ServerSocket server = new ServerSocket(6666);
成员方法
Socket accept() // 监听要连接到此的套接字并接受,返回一个Socket对象。该方法会一直阻塞直到建立连接。
4.3 简单的TCP网络程序
TCP通信分析图解
- 【服务端】启动,创建ServerSocket对象,等待连接。
- 【客户端】启动,创建Socket对象,请求连接。
- 【服务端】接收连接,调用accept方法,并返回一个Socket对象。
- 【客户端】Socket对象,获取OutputStream,向服务端写出数据。
- 【服务端】Scoket对象,获取InputStream,读取客户端发送的数据。
到此,客户端向服务端发送数据成功。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bFlYTmNL-1598445701095)(D:/BaiduYunDownload/java/Java2018 基础和进阶/javaad/day11_网络编程/课件资料/img/5_简单通信.jpg)]
自此,服务端向客户端回写数据。
- 【服务端】Socket对象,获取OutputStream,向客户端回写数据。
- 【客户端】Scoket对象,获取InputStream,解析回写数据。
- 【客户端】释放资源,断开连接。
服务端实现:
public class ServerTCP {
public static void main(String[] args) throws IOException {
System.out.println("服务端启动 , 等待连接 .... ");
// 1.创建 ServerSocket对象,绑定端口,开始等待连接
ServerSocket ss = new ServerSocket(6666);
// 2.接收连接 accept 方法, 返回 socket 对象.
Socket server = ss.accept();
// 3.通过socket 获取输入流
InputStream is = server.getInputStream();
// 4.一次性读取数据
// 4.1 创建字节数组
byte[] b = new byte[1024];
// 4.2 据读取到字节数组中.
int len = is.read(b);
// 4.3 解析数组,打印字符串信息
String msg = new String(b, 0, len);
System.out.println(msg);
// =================回写数据=======================
// 5. 通过 socket 获取输出流
OutputStream out = server.getOutputStream();
// 6. 回写数据
out.write("服务器:我很好,谢谢你".getBytes());
// 7.关闭资源.
server.close();
}
}
客户端实现:
public class ClientTCP {
public static void main(String[] args) throws Exception {
System.out.println("客户端 发送数据");
// 1.创建 Socket ( ip , port ) , 确定连接到哪里.
Socket client = new Socket("localhost", 6666);
// 2.通过Scoket,获取输出流对象
OutputStream os = client.getOutputStream();
// 3.写出数据.
os.write("客户端:你好么?".getBytes());
// ==============解析回写=========================
// 4. 通过Scoket,获取 输入流对象
InputStream in = client.getInputStream();
// 5. 读取数据数据
byte[] b = new byte[100];
int len = in.read(b);
System.out.println(new String(b, 0, len));
// 6. 关闭资源 .
client.close();
}
}
4.4 【练习1】
-
案例需求
客户端:数据来自于键盘录入,直到输入的数据是886,发送数据结束。
服务端:接受到的数据写入文本文件中,并在控制台输出。
-
案例分析
- 客户端创建对象,使用键盘录入循环接受数据,接受一行发送一行,直到键盘录入886为止
- 服务端创建对象,使用输入流按行循环接受数据,创建输出流对象指向文件,每接受一行数据后使用输出流输出到文件中,直到接受到null为止
-
代码实现
ublic class ClientDemo { public static void main(String[] args) throws IOException { //创建客户端Socket对象 Socket s = new Socket("192.168.1.66",10000); //数据来自于键盘录入,直到输入的数据是886,发送数据结束 BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); //封装输出流对象 BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); String line; while ((line=br.readLine())!=null) { if("886".equals(line)) { break; } bw.write(line); bw.newLine(); bw.flush(); } //释放资源 s.close(); } } public class ServerDemo { public static void main(String[] args) throws IOException { //创建服务器Socket对象 ServerSocket ss = new ServerSocket(10000); //监听客户端连接,返回一个对应的Socket对象 Socket s = ss.accept(); //接收数据 BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream())); //把数据写入文本文件 BufferedWriter bw = new BufferedWriter(new FileWriter("myNet\\s.txt")); String line; while ((line=br.readLine())!=null) { System.out.println(line); bw.write(line); bw.newLine(); bw.flush(); } //释放资源 bw.close(); ss.close(); } }
4.5 【练习2】
-
案例需求
客户端:数据来自于文本文件,接收服务器反馈
服务器:接收到的数据写入文本文件,给出反馈,代码用线程进行封装,为每一个客户端开启一个线程
-
案例分析
- 创建客户端对象,创建输入流对象指向文件,每读入一行数据就给服务器输出一行数据,输出结束后使用shutdownOutput()方法告知服务端传输结束
- 创建多线程类,在run()方法中读取客户端发送的数据,为了防止文件重名,使用计数器给文件名编号,接受结束后使用输出流给客户端发送反馈信息。
- 创建服务端对象,每监听到一个客户端则开启一个新的线程接受数据。
- 客户端接受服务端的回馈信息
-
代码实现
public class ClientDemo { public static void main(String[] args) throws IOException { //创建客户端Socket对象 Socket s = new Socket("192.168.1.66",10000); //封装文本文件的数据 BufferedReader br = new BufferedReader(new FileReader("myNet\\InetAddressDemo.java")); //封装输出流写数据 BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); String line; while ((line=br.readLine())!=null) { bw.write(line); bw.newLine(); bw.flush(); } s.shutdownOutput(); //接收反馈 BufferedReader brClient = new BufferedReader(new InputStreamReader(s.getInputStream())); String data = brClient.readLine(); //等待读取数据 System.out.println("服务器的反馈:" + data); //释放资源 br.close(); s.close(); } } public class ServerThread implements Runnable { private Socket s; public ServerThread(Socket s) { this.s = s; } @Override public void run() { try { //接收数据写到文本文件 BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream())); //解决名称冲突问题 int count = 0; File file = new File("myNet\\Copy["+count+"].java"); while (file.exists()) { count++; file = new File("myNet\\Copy["+count+"].java"); } BufferedWriter bw = new BufferedWriter(new FileWriter(file)); String line; while ((line=br.readLine())!=null) { bw.write(line); bw.newLine(); bw.flush(); } //给出反馈 BufferedWriter bwServer = new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); bwServer.write("文件上传成功"); bwServer.newLine(); bwServer.flush(); //释放资源 s.close(); } catch (IOException e) { e.printStackTrace(); } } } public class ServerDemo { public static void main(String[] args) throws IOException { //创建服务器Socket对象 ServerSocket ss = new ServerSocket(10000); while (true) { //监听客户端连接,返回一个对应的Socket对象 Socket s = ss.accept(); //为每一个客户端开启一个线程 new Thread(new ServerThread(s)).start(); } } }