概述

  • 计算机网络:把分布在不同地理趋于的计算机与专门的外部设备用通信线路胡连成一个规模大、功能强的网络系统,从而使众多的计算机可以方便地互相传递信息、共享硬件、软件、数据信息等资源。
  • 网络编程的目的:直接或间接地通过网络协议与其他计算机实现数据交换,进行通讯
  • Java 提供了跨平台的网络类库,由 JVM 进行控制,程序员面对的是一个统一的网络编程环境

网络通信要素

  • 网络编程面临两个主要问题
    1. 如何定位网络上特定主机的特定应用
    2. 找到主机后如何高效的进行数据传输
  • 对应问题一:IP 和端口号
  • 对应问题二:提供网络通信协议
    • OSI 参考模型:模型过于理想化,未能广泛推广
    • TCP/IP 参考模型:事实上的国际标准(应用层、传输层、网络层、物理+数据链路层)

通信要素1:IP 和端口号

IP

  • IP 用于唯一的标识 Internet 上的计算机(通信实体),IP 分为 IPv4 和IPv6,也分为公网地址(万维网使用)和私网地址(局域网使用)
  • IPv4:4 个字节组成,十进制表示,4 个 0~255,大约 42 亿个
  • IPv6:16 字节(128 位),写成 8 个无符号整数,每个整数用 4 个 16 进制位表示,数之间用 : 分隔
  • 域名:域名容易记忆,当在连接网络时输入一个主机的域名后,域名服务器(DNS)负责将域名转化成IP地址,这样才能和主机建立连接,这就是域名解析。
  • 本地回路地址: 127.0.0.1,对应着 localhost

InetAddress 类

Java 中的 InetAddress类的一个对象就代表着一个具体的IP地址

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.net.*;

public class InetAddressTest {
    public static void main(String[] args) {
        try {
            // InetAddress 类的实例化,参数可以是域名或IP地址
            InetAddress inet = InetAddress.getByName("www.baidu.com");

            // 实例化,获取本机地址
            InetAddress localhost = InetAddress.getLocalHost();
            
            System.out.println(inet); // www.baidu.com/180.101.49.12
            
            // 获取该 IP 的主机名
            System.out.println(inet.getHostName());
            // 获取该 IP 的地址
            System.out.println(inet.getHostAddress());
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
    }
}

端口号

端口号:正在计算机上运行的进程。

  • 要求:不同的进程不同的端口号
  • 范围:被规定为一个 16 位的整数 0~65535。

公认端口:0~1023,被预先定义的服务通信占用,HTTP占用80,FTP占用21,Telnet占用23

注册端口:1024~49151,分配给用户进程或应用程序,Tomcat 8080,MySQL 3306,Oracle 1521

动态/私有端口:49152~65535

端口号与IP地址的组合得到一个网络套接字:Socket

通信要素2:网络协议

TCP 网络编程

分别写一个客户端和服务端,客户端读取文件,并建立 Socket 连接,将文件发出,服务端监听,收到客户端的内容后写入文件,完成后发送给客户端一个字符串,客户端将字符串读入 ByteArrayOutputStream,最后打印。

注意,如果先启动客户端,因为无法建立 TCP 连接,会报异常

客户端

 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
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;

public class ClientTest {
    public static void main(String[] args) {
        BufferedInputStream bis = null;
        Socket socket = null;
        BufferedOutputStream bos = null;
        try {
            // 要发送的文件
            File file = new File("视频1.mp4");
            FileInputStream fis = new FileInputStream(file);
            // 套一个缓冲流
            bis = new BufferedInputStream(fis);
            InetAddress inet = InetAddress.getByName("127.0.0.1");
            // 传入 ip 和端口,建立 socket 连接
            socket = new Socket(inet,1995);
            OutputStream os = socket.getOutputStream();
            bos = new BufferedOutputStream(os);
            System.out.println("开始读取并发送文件");
            byte[] buffer = new byte[1024];
            int len;
            // 边读边写(通过 Socket 发送)
            while((len=bis.read(buffer))!=-1) {
                bos.write(buffer,0,len);
            }
            // 关闭 Socket 的输出流,否则服务端和客户端都会进入阻塞
            socket.shutdownOutput();
            System.out.println("发送完毕");

            InputStream is = socket.getInputStream();
            ByteArrayOutputStream baos =  new ByteArrayOutputStream();
            byte[] bf = new byte[5];
            // 将接收到的字符串写入 ByteArrayOutputStream 的内部缓冲区,最后统一转成字符串打印,防止乱码
            while((len=is.read(bf))!=-1) {
                baos.write(bf,0,len);
            }
            System.out.println("收到服务端来信:" + baos.toString());

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            /* 前面已经关闭该连接,再次关闭会报异常
            if(bos!=null) {
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
             */
            if(socket!=null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(bis!=null) {
                try {
                    bis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

服务端

 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
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerTest {
    public static void main(String[] args) {
        ServerSocket ss = null;
        Socket socket = null;
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        OutputStream os = null;
        try {
            // 服务端 Socket
            ss = new ServerSocket(1995);
            System.out.println("开始监听");
            // 接受到客户端的 Socket
            socket = ss.accept();
            System.out.println("收到来信,准备接收文件");

            InputStream is = socket.getInputStream();
            bis = new BufferedInputStream(is);

            File destFile = new File("视频2.mp4");
            FileOutputStream fos = new FileOutputStream(destFile);
            bos = new BufferedOutputStream(fos);

            byte[] buffer = new byte[1024];
            int len;
            System.out.println("开始接收文件");
            // 边收边写
            while((len=bis.read(buffer))!=-1) {
                bos.write(buffer,0,len);
            }

            // 接收完毕,发送回给客户端一句话
            os = socket.getOutputStream();
            os.write("文件接收完毕".getBytes());

            System.out.println("完成,断开连接");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(os!=null) {
                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(bos!=null) {
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(bis!=null) {
                try {
                    bis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(socket!=null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(ss!=null) {
                try {
                    ss.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

UDP 网络编程

因为 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
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class UDPSender {
    public static void main(String[] args) {
        DatagramSocket socket = null;
        try {
            // 设置接收方的 IP
            InetAddress inet = InetAddress.getByName("127.0.0.1");
            // 发送端 DatagramSocket
            socket = new DatagramSocket();

            // 要发送的信息
            String str = "我是 UDP 方式发送的信息";
            byte[] data = str.getBytes();

            // 将要发送的信息、长度、接收方ip、端口号打包进 DatagramPacket
            DatagramPacket packet = new DatagramPacket(data,0,data.length,inet,9090);

            // 发送 DatagramPacket
            socket.send(packet);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭资源
            if(socket!=null) {
                socket.close();
            }
        }
    }
}

接收方

 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
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class UDPReciver {
    public static void main(String[] args) {
        DatagramSocket socket = null;
        try {
            // 接收端 DatagramSocket,设置自己的端口号
            socket = new DatagramSocket(9090);

            // 用于接受的 buffer
            byte[] buffer = new byte[1024];

            // 打包一个 DatagramPacket,指定buffer和长度
            DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length);

            // 接受
            socket.receive(packet);

            // 获取 DatagramPacket 的 data
            System.out.println(new String(packet.getData(),0,packet.getLength()));
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭资源
            if(socket!=null) {
                socket.close();
            }
        }
    }
}

URL 编程

概念

  • URL (Uniform Resource Locator)即统一资源定位符,对应着互联网的某一资源的地址

  • URL 的 5 个基本结构

1
http://localhost:8080/examples/beauty.jpg#a?username=Tom&password=123

即协议、主机名、端口号、资源地址、片段名(锚点)、参数列表

常用方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
URL url = new URL(String spec);
// 实例化一个 URL

String getProtocl()
// 获取该 URL 的协议名

String getHost()
// 获取该 URL 的主机名

String getPort()
// 获取该 URL 的端口号

String getPath()
// 获取该 URL 的文件路径

String getFile()
// 获取该 URL 的文件名

String getQuery()
// 获取该 URL 的查询名(参数列表)

一个简单的例子,建立 URL 连接,下载资源

 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
import javax.net.ssl.HttpsURLConnection;
import java.io.*;
import java.net.*;

public class URLTest {
    public static void main(String[] args) {
        HttpURLConnection urlConnection = null;
        InputStream is = null;
        FileOutputStream fos = null;
        try {
            String str = "https://www.example.com/img/example.png";
            // 创建 URL 实例
            URL url = new URL(str);
            System.out.println(url.getProtocol()); // https
            System.out.println(url.getHost()); // www.example.com
            System.out.println(url.getPort()); // -1
            System.out.println(url.getPath()); // /img/example.png
            System.out.println(url.getFile()); // /img/example.png
            System.out.println(url.getQuery()); // null

            // 获取 URL 连接
            urlConnection = (HttpsURLConnection)url.getContent();

            // 连接
            urlConnection.connect();

            // 输入流
            is = urlConnection.getInputStream();
            fos = new FileOutputStream("example.png");

            byte[] buffer = new byte[1024];
            int len;
            // 读写
            while((len=is.read(buffer))!=-1) {
                fos.write(buffer,0,len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(fos!=null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(is!=null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(urlConnection!=null) {
                // 关闭连接
                urlConnection.disconnect();
            }
        }
    }
}