有人说,20世纪最伟大的发明不是计算机,而是计算机网络。
还有人说,如果你买了计算机而没有联网,就等于买了电话机而没有接电话线一样。
网络模型
计算机网络之间以何种规则进行通信,就是网络模型研究问题。
网络模型一般是指:
OSI(Open System Interconnection开放系统互连)参考模型
TCP/IP参考模型
网络模型7层概述:
物理层:主要定义物理设备标准,如网线的接口类型、光纤的接口类型、各种传输介质的传输速率等。它的主要作用是传输比特流(就是由1、0转化为电流强弱来进行传输,到达目的地后在转化为1、0,也就是我们常说的数模转换与模数转换)。这一层的数据叫做比特。
数据链路层:主要将从物理层接收的数据进行MAC地址(网卡的地址)的封装与解封装。常把这一层的数据叫做帧。在这一层工作的设备是交换机,数据通过交换机来传输。
网络层:主要将从下层接收到的数据进行IP地址(例192.168.0.1)的封装与解封装。在这一层工作的设备是路由器,常把这一层的数据叫做数据包。
传输层:定义了一些传输数据的协议和端口号(WWW端口80等),如:TCP(传输控制协议,传输效率低,可靠性强,用于传输可靠性要求高,数据量大的数据),UDP(用户数据报协议,与TCP特性恰恰相反,用于传输可靠性要求不高,数据量小的数据,如QQ聊天数据就是通过这种方式传输的)。 主要是将从下层接收的数据进行分段和传输,到达目的地址后再进行重组。常常把这一层数据叫做段。
会话层:通过传输层(端口号:传输端口与接收端口)建立数据传输的通路。主要在你的系统之间发起会话或者接受会话请求(设备之间需要互相认识可以是IP也可以是MAC或者是主机名)
表示层:主要是进行对接收的数据进行解释、加密与解密、压缩与解压缩等(也就是把计算机能够识别的东西转换成人能够能识别的东西(如图片、声音等)。
应用层: 主要是一些终端的应用,比如说FTP(各种文件下载),WEB(IE浏览),QQ之类的(可以把它理解成我们在电脑屏幕上可以看到的东西.就是终端应用)。
网络编程三要素
IP 地址
要想让网络中的计算机能够互相通信,必须为每台计算机指定一个标识号,通过这个标识号来指定要接受数据的计算机和识别发送的计算机,在TCP/IP协议中,这个标识号就是IP地址。为了方便对IP地址的获取和操作,java 提供了一个类 InetAddress 供使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23A:所谓IP地址就是给每个连接在Internet上的主机分配的一个32bit地址。按照TCP/IP规定,IP地址用二进制来表示,每个IP地址长32bit,比特换算成字节,就是4个字节。例如一个采用二进制形式的IP地址是“00001010000000000000000000000001”,这么长的地址,人们处理起来也太费劲了。为了方便人们的使用,IP地址经常被写成十进制的形式,中间使用符号“.”分开不同的字节。于是,上面的IP地址可以表示为“10.0.0.1”。IP地址的这种表示法叫做“点分十进制表示法”,这显然比1和0容易记忆得多。
B:IP地址的组成
IP地址 = 网络号码+主机地址
A类IP地址:第一段号码为网络号码,剩下的三段号码为本地计算机的号码
B类IP地址:前二段号码为网络号码,剩下的二段号码为本地计算机的号码
C类IP地址:前三段号码为网络号码,剩下的一段号码为本地计算机的号码
特殊地址:
127.0.0.1 回环地址,可用于测试本机的网络是否有问题。 ping 127.0.0.1
xxx.xxx.xxx.0 网络地址
xxx.xxx.xxx.255 广播地址,向此地址发送udp数据包将广播出去,此网段下所有主机都可以收到信息。
A类 1.0.0.1---127.255.255.254
(1)10.X.X.X是私有地址(私有地址就是在互联网上不使用,而被用在局域网络中的地址) (2)127.X.X.X是保留地址,用做循环测试用的
B类 128.0.0.1---191.255.255.254 172.16.0.0---172.31.255.255是私有地址
169.254.X.X是保留地址
C类 192.0.0.1---223.255.255.254
192.168.X.X是私有地址
D类 224.0.0.1---239.255.255.254
E类 240.0.0.1---247.255.255.254端口号
物理端口:网卡口。
逻辑端口:逻辑端口 。
A:每个网络程序都会至少有一个逻辑端口;
B:用于标识进程的逻辑地址,不同进程的标识;
C:有效端口:0
65535,其中01024系统使用或保留端口。传输协议
UDP:将数据源和目的封装成数据包中,不需要建立连接;每个数据报的大小在限制在64k;因无连接,是不可靠协议;不需要建立连接,速度快。
TCP:建立连接,形成传输数据的通道;在连接中进行大数据量传输;通过三次握手完成连接,是可靠协议;必须建立连接,效率会稍低。
1
2
3
4
5udp:面向无连接, 不可靠, 速度快, 将数据封包传输,数据包最大64k。
举例:聊天留言,在线视频,视频会议,发短信,邮局包裹。
tcp:面向连接,安全可靠,效率稍低,通过三次握手确保连接的建立。
举例:下载,打电话,QQ聊天(你在线吗,在线,就回应下,就开始聊天了)。
InetAddress
1 | 获取任意主机:getByName |
Q & A
Q
:没有构造方法,那么如何使类提供的功能呢?
如果一个类没有构造方法
成员全部都是静态的(例如:Math、Arrays、Collections);
单例设计模式(例如:Runtime);
类中有静态方法返回该类的对象。
Socket
Socket套接字:网络上具有唯一标识的IP地址和端口号组合在一起才能构成唯一能识别的标识符套接字。
Socket原理机制:
通信的两端都有Socket。
网络通信其实就是Socket间的通信。
数据在两个Socket间通过IO传输。
UDP 传输
DatagramSocket与DatagramPacket
建立发送端,接收端。
建立数据包。
调用Socket的发送接收方法。
关闭Socket。
发送端与接收端是两个独立的运行程序。
1 | UDP传输-发送端思路 |
举个🌰:
1 | // 接收端 ReceiveDemo.java |
1 | // 发送端 SendDemo.java |
UDP 案例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145从键盘录入数据进行发送,如果输入的是886那么客户端就结束输入数据。
这个时候完全可以把发送端代码发给大家了,我把接收端代码开启,大家就可以实现聊天了,但是,大家都要看我们的屏幕,即使我把接收端发给大家也是一样的,如何改进呢,使用广播地址即可。
最后,把刚才发送和接收程序分别用线程进行封装,完成一个UDP的聊天程序。
这个时候,就需要和io结合起来使用了。还得注意一个问题,这个时候接收端,要一直开启,否则接收一句就关闭了。所以,用死循环,并且,服务不关闭。代码如下:
class SendDemo2
{
public static void main(String[] args) throws Exception
{
DatagramSocket ds = new DatagramSocket();
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line = null;
while((line=br.readLine())!=null)
{
if("886".equals(line))
{
break;
}
byte[] by = line.getBytes();
DatagramPacket dp = new DatagramPacket(by,by.length,InetAddress.getByName(“192.168.1.255"),10000);
ds.send(dp);
}
ds.close();
}
}
class ReceiveDemo2
{
public static void main(String[] args) throws Exception
{
DatagramSocket ds = new DatagramSocket(10000); //如果10000端口已经被使用了,这个服务起不来
//我这边是循序接收的啊.无限循环
while(true)
{
byte[] by = new byte[1024];
DatagramPacket dp = new DatagramPacket(by,by.length);
ds.receive(dp);
//通过数据包对象的方法获取其中的数据内容,包括地址,端口,数据主体
String ip = dp.getAddress().getHostAddress();
int port = dp.getPort();
byte[] by2 = dp.getData();
String text = new String(by2,0,dp.getLength());
System.out.println(ip+"..."+port+"..."+text);
}
//关闭资源
//ds.close();
}
}
用线程封装后的代码如下:
public class SendThread implements Runnable {
private DatagramSocket ds;
public SendThread(DatagramSocket ds) {
this.ds = ds;
}
@Override
public void run() {
// 创建UDP发送端的服务
try {
// 把键盘录入数据用高效缓冲流封装
BufferedReader br = new BufferedReader(new InputStreamReader(
System.in));
String line = null;
while ((line = br.readLine()) != null) {
if ("886".equals(line)) {
break;
}
byte[] bys = line.getBytes();
// 数据包
DatagramPacket dp = new DatagramPacket(bys, bys.length,
InetAddress.getByName("192.168.1.255"), 12345);
// 发送数据
ds.send(dp);
}
// 关闭资源
ds.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class ReceiveThread implements Runnable {
private DatagramSocket ds;
public ReceiveThread(DatagramSocket ds) {
this.ds = ds;
}
@Override
public void run() {
try {
// 为了循环多次接受
while (true) {
// 创建字节数组作为数据包的缓冲区
byte[] bys = new byte[1024];
DatagramPacket dp = new DatagramPacket(bys, bys.length);
// 读取数据包数据
ds.receive(dp);
// 解析数据包
String ip = dp.getAddress().getHostAddress();
int port = dp.getPort();
String text = new String(dp.getData(), 0, dp.getLength());
System.out.println(ip + "***" + port + "***" + text);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class ChatDemo {
public static void main(String[] args) throws IOException {
DatagramSocket sds = new DatagramSocket();
DatagramSocket rds = new DatagramSocket(12345);
SendThread st = new SendThread(sds);
ReceiveThread rt = new ReceiveThread(rds);
Thread t1 = new Thread(st);
Thread t2 = new Thread(rt);
t1.start();
t2.start();
}
}
TCP 传输
Socket和ServerSocket
建立客户端和服务器端
建立连接后,通过Socket中的IO流进行数据的传输
关闭socket
同样,客户端与服务器端是两个独立的应用程序。
1 | 客户端思路 |
举个🌰:
1 | // 客户端 |
TCP传输容易出现的问题
客户端连接上服务端,两端都在等待,没有任何数据传输。
通过例程分析:因为read方法或者readLine方法是阻塞式。
解决办法:自定义结束标记,使用shutdownInput,shutdownOutput方法。