Java-网络编程

有人说,20世纪最伟大的发明不是计算机,而是计算机网络。

还有人说,如果你买了计算机而没有联网,就等于买了电话机而没有接电话线一样。

网络模型

计算机网络之间以何种规则进行通信,就是网络模型研究问题。

网络模型一般是指:

  • OSI(Open System Interconnection开放系统互连)参考模型

  • TCP/IP参考模型

网络模型

网络模型7层概述:

  1. 物理层:主要定义物理设备标准,如网线的接口类型、光纤的接口类型、各种传输介质的传输速率等。它的主要作用是传输比特流(就是由1、0转化为电流强弱来进行传输,到达目的地后在转化为1、0,也就是我们常说的数模转换与模数转换)。这一层的数据叫做比特。

  2. 数据链路层:主要将从物理层接收的数据进行MAC地址(网卡的地址)的封装与解封装。常把这一层的数据叫做帧。在这一层工作的设备是交换机,数据通过交换机来传输。

  3. 网络层:主要将从下层接收到的数据进行IP地址(例192.168.0.1)的封装与解封装。在这一层工作的设备是路由器,常把这一层的数据叫做数据包。

  4. 传输层:定义了一些传输数据的协议和端口号(WWW端口80等),如:TCP(传输控制协议,传输效率低,可靠性强,用于传输可靠性要求高,数据量大的数据),UDP(用户数据报协议,与TCP特性恰恰相反,用于传输可靠性要求不高,数据量小的数据,如QQ聊天数据就是通过这种方式传输的)。 主要是将从下层接收的数据进行分段和传输,到达目的地址后再进行重组。常常把这一层数据叫做段。

  5. 会话层:通过传输层(端口号:传输端口与接收端口)建立数据传输的通路。主要在你的系统之间发起会话或者接受会话请求(设备之间需要互相认识可以是IP也可以是MAC或者是主机名)

  6. 表示层:主要是进行对接收的数据进行解释、加密与解密、压缩与解压缩等(也就是把计算机能够识别的东西转换成人能够能识别的东西(如图片、声音等)。

  7. 应用层: 主要是一些终端的应用,比如说FTP(各种文件下载),WEB(IE浏览),QQ之类的(可以把它理解成我们在电脑屏幕上可以看到的东西.就是终端应用)。

网络编程三要素

  1. 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
    23
    A:所谓IP地址就是给每个连接在Internet上的主机分配的一个32bit地址。按照TCP/IP规定,IP地址用二进制来表示,每个IP地址长32bit,比特换算成字节,就是4个字节。例如一个采用二进制形式的IP地址是“00001010000000000000000000000001”,这么长的地址,人们处理起来也太费劲了。为了方便人们的使用,IP地址经常被写成十进制的形式,中间使用符号“.”分开不同的字节。于是,上面的IP地址可以表示为“10.0.0.1”。IP地址的这种表示法叫做“点分十进制表示法”,这显然比10容易记忆得多。

    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
  2. 端口号

    物理端口:网卡口。

    逻辑端口:逻辑端口 。

    A:每个网络程序都会至少有一个逻辑端口;

    B:用于标识进程的逻辑地址,不同进程的标识;

    C:有效端口:0~65535,其中0~1024系统使用或保留端口。

  3. 传输协议

    UDP:将数据源和目的封装成数据包中,不需要建立连接;每个数据报的大小在限制在64k;因无连接,是不可靠协议;不需要建立连接,速度快。

    TCP:建立连接,形成传输数据的通道;在连接中进行大数据量传输;通过三次握手完成连接,是可靠协议;必须建立连接,效率会稍低。

    1
    2
    3
    4
    5
    udp:面向无连接, 不可靠, 速度快, 将数据封包传输,数据包最大64k。 
    举例:聊天留言,在线视频,视频会议,发短信,邮局包裹。

    tcp:面向连接,安全可靠,效率稍低,通过三次握手确保连接的建立。
    举例:下载,打电话,QQ聊天(你在线吗,在线,就回应下,就开始聊天了)。

InetAddress

1
2
3
4
5
6
7
8
9
10
11
12
13
14
获取任意主机:getByName
主机名:getHostName
主机Ip地址:getHostAddress

public class InetAddressDemo {
public static void main(String[] args) throws UnknownHostException {
InetAddress add = InetAddress.getByName("8.8.8.8");

String name = add.getHostName();
String ip = add.getHostAddress();

System.out.println(name + "---" + ip);
}
}

Q & A

Q:没有构造方法,那么如何使类提供的功能呢?

如果一个类没有构造方法

  1. 成员全部都是静态的(例如:Math、Arrays、Collections);

  2. 单例设计模式(例如:Runtime);

  3. 类中有静态方法返回该类的对象。

Socket

Socket套接字:网络上具有唯一标识的IP地址和端口号组合在一起才能构成唯一能识别的标识符套接字。

Socket原理机制:

  • 通信的两端都有Socket。

  • 网络通信其实就是Socket间的通信。

  • 数据在两个Socket间通过IO传输。

Socket原理

UDP 传输

  • DatagramSocket与DatagramPacket

  • 建立发送端,接收端。

  • 建立数据包。

  • 调用Socket的发送接收方法。

  • 关闭Socket。

  • 发送端与接收端是两个独立的运行程序。

1
2
3
4
5
6
7
8
9
10
11
12
UDP传输-发送端思路
1:建立udp的socket服务
2:将要发送的数据封装成数据包
3:通过udp的socket服务,将数据包发送出
4:关闭资源

UDP传输-接收端思路
1:建立udp的socket服务.
2:通过receive方法接收数据
3:将收到的数据存储到数据包对象中
4:通过数据包对象的功能来完成对接收到数据进行解析.
5:可以对资源进行关闭

举个🌰:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 接收端 ReceiveDemo.java
public class ReceiveDemo {
public static void main(String[] args) throws IOException {
DatagramSocket ds = new DatagramSocket(10086);

byte[] buf = new byte[1024];
int length = buf.length;

DatagramPacket dp = new DatagramPacket(buf, length);

ds.receive(dp); // 阻塞式方法,没数据就等着。

byte[] buffer = dp.getData();
int len = dp.getLength();
String str = new String(buffer,0, len);

InetAddress address = dp.getAddress();
String ip = address.getHostAddress();

System.out.println(ip + " said : " + str);

ds.close();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 发送端 SendDemo.java
public class SendDemo {
public static void main(String[] args) throws IOException{
DatagramSocket ds = new DatagramSocket();

byte[] buf = "hello udp".getBytes();
int length = buf.length;

InetAddress address = InetAddress.getByName("127.0.0.1");

DatagramPacket dp = new DatagramPacket(buf, length, address, 10086);

ds.send(dp);
ds.close();
}
}
  • 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
2
3
4
5
6
7
8
9
10
11
12
客户端思路
1:建立客户端的Socket服务,并明确要连接的服务器。
2:如果连接建立成功,就表明,已经建立了数据传输的通道.就可以在该通道通过IO进行数据的读取和写入.该通道称为Socket流,Socket流中既有读取流,也有写入流.
3:通过Socket对象的方法,可以获取这两个流
4:通过流的对象可以对数据进行传输
5:如果传输数据完毕,关闭资源

服务端思路
1:建立服务器端的socket服务,需要一个端口
2:服务端没有直接流的操作,而是通过accept方法获取客户端对象,在通过获取到的客户端对象的流和客户端进行通信
3:通过客户端的获取流对象的方法,读取数据或者写入数据
4:如果服务完成,需要关闭客户端,然后关闭服务器,但是,一般会关闭客户端,不会关闭服务器,因为服务端是一直提供服务的

举个🌰:

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
// 客户端
public class ClientDemo {
public static void main(String[] args) throws IOException {
Socket s = new Socket("127.0.0.1", 9999);

// 向服务器发送数据
OutputStream os = s.getOutputStream();
os.write("吼吼吼".getBytes());

// 从服务器接收返回的数据
InputStream is = s.getInputStream();
byte[] buf = new byte[1024];
int len = is.read(buf);
String Client = new String(buf, 0, len);
System.out.println("client : " + Client);

s.close();
}
}

// 服务端
public class ServerDemo {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(9999);

// 监听 9999 端口,与客户端建立连接
Socket s = ss.accept();

// 接收数据
InputStream is = s.getInputStream();
byte[] str = new byte[1024];
int len = is.read(str);
String server = new String(str, 0, len);
System.out.println("server : " + server);

// 向客户端返回一个数据
OutputStream os = s.getOutputStream();
os.write("数据已收到".getBytes());

// 关闭连接
s.close();
}
}

TCP传输容易出现的问题

客户端连接上服务端,两端都在等待,没有任何数据传输。

通过例程分析:因为read方法或者readLine方法是阻塞式。

解决办法:自定义结束标记,使用shutdownInput,shutdownOutput方法。


文章作者: ahoj
文章链接: https://ahoj.cc/2019/02/Java-网络编程/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 ahoj 的小本本