# Java教程 - 1 网络编程

什么是网络编程

网络编程就是实现网络中计算机之间数据交互和通信。


IP与端口

两台计算机上的两个程序进行通信,如何在网络上确定是这两个程序呢?

通过 IP 地址,可以确定是哪两台主机,但是主机上的程序有很多,如何确定是哪两个程序进行通信,这里涉及到端口信息。

端口是每个网络程序在设备上的唯一标识,每个网络程序都需要绑定一个端口号,传输数据的时候,通过 IP 确定发到哪台机器上,通过端口确定发到哪个程序。

端口号范围从0~63335,编写网络应用就需要绑定一个端口号,尽量使用1024以上的,1024以下的基本都被系统程序占用了;

常用的软件使用的端口好:

mysql:3306
oracle:1521
web:80
tomcat:8080
Redis:6379
1
2
3
4
5

通信协议

我们要在两个应用程序之间进行网络通信,需要使用套接字 socket 来实现。

socketIP地址端口号 组成。IP 地址用来确定是哪台设备,端口号用来确定是设备上的哪个程序。

在网络编程中,TCPUDP 是两个常用的协议,而 socket 是在应用层和传输层之间的接口,用于方便地使用 TCP 或 UDP 进行网络通信。

TCP 和 UDP 协议的区别:

TCP是一种可靠的面向连接的协议,它在传输数据之前先建立一个连接,确保数据的可靠性和完整性,然后再进行数据传输。

UDP是一种不可靠的无连接协议,它直接将数据分组发送到目的地址,不需要建立连接,速度快,但数据传输的可靠性较差。

# 1.1 TCP通信

TCP通信是区分服务端和客户端的,服务器启动服务,指定IP和端口,等待客户端的连接。

客户端创建连接,指定服务端的IP和端口,连接到服务端,这样服务端和客户端就可以发送数据了。

# 1 编写服务端

首先编写服务端的程序,创建一个 TcpServer.java 的类。

服务器端需要指定监听的端口号。

package com.doubibiji;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class TcpServer {

    private static final int SERVER_PORT = 8888;

    public static void main(String[] args) throws IOException {

        // 1.创建Socket,指定端口号
        ServerSocket server = new ServerSocket(SERVER_PORT);

        // 2.等待接收客户端的请求,程序在这里会阻塞等待客户端连接
        Socket socket = server.accept();

        // 3.获取客户端输入和输出流
        InputStream is = socket.getInputStream();
        OutputStream os = socket.getOutputStream();

        // 从客户端读取数据
        byte[] data = new byte[1024];
        while (true) {
            // 4.从客户端读取数据
            int len = is.read(data);
            if (len > 0) {
                //将数据转换为字符串
                String message = new String(data, 0, len);
                System.out.println(message);

                // 如果接收到客户端发送exit,服务器就退出
                if ("exit".equals(message)) {
                    break;
                }

                // 服务器将收到的内容发回给客户端
                String reply = "服务器收到:" + message;
                os.write(reply.getBytes("UTF-8"));
            }
        }

        // 5.关闭服务端
        is.close();
        os.close();
        socket.close();
        // 关闭socket,正常情况下server是不需要关的
        server.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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

服务端首先创建 socket,等待客户端的连接,客户端连接后,就可以获取客户端的输入或输出流,然后进行数据的发送和读取了。

上面用到了 while 循环,不停的从客户端读取数据,当接收到客户端发送 exit 时,退出程序。

# 2 编写客户端

下面开始编写客户端的程序,客户端需要指定连接的服务器的 IP 和端口号。

创建 TcpClient.java,代码如下:

package com.doubibiji;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

public class TcpClient {

    // 127.0.0.1表示本地IP,服务器在本机运行
    private static final String SERVER_IP = "127.0.0.1";
    private static final int SERVER_PORT = 8888;

    public static void main(String[] args) throws IOException {
        // 1.创建socket
        Socket socket = new Socket(SERVER_IP, SERVER_PORT);

        // 2.获取输入输出流
        InputStream is = socket.getInputStream();
        OutputStream os = socket.getOutputStream();

        // 从键盘输入内容发送给服务器
        Scanner scanner = new Scanner(System.in);
        byte[] data = new byte[1024];

        while (true) {
            String input = scanner.nextLine();
            if (input.length() > 0) {
                // 3.向服务器发送数据
                os.write(input.getBytes("UTF-8"));
            }

            // 如果输入的exit就退出
            if ("exit".equals(input)) {
                break;
            }

            // 4.从服务器读取内容
            int len = is.read(data);
            //将数据转换为字符串
            String reply = new String(data, 0, len);
            System.out.println(reply);
        }

        // 关闭客户端
        is.close();
        os.close();
        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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

首先创建 socket,指定服务器的 IP 地址和端口,这里服务器和客户端都是在本地运行的,所以指定了本地的地址 127.0.0.1

获取输入输出流就可以读取和发送数据了,上面的代码读取键盘的输入,让内容发送给服务端。

上面使用了一个 while 循环,主要是为了可以不停的通过键盘输入,向服务端发送数据。

当输入 exit 的时候,可以退出程序。

# 3 运行程序

在运行的时候,我们先运行服务端程序,然后再运行客户端程序。

然后在客户端模块通过键盘输入内容发送到服务端,服务端会响应客户端的数据请求。

客户端:

服务端:

# 4 接收多个客户端连接

上面的服务端只能接收一个客户端的连接,因为只在一个线程中进行的。

如果要服务端接收多个客户端的连接,则需要使用多线程来实现。

package com.doubibiji;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class TcpServer {

    private static final int SERVER_PORT = 8888;

    public static void main(String[] args) throws IOException {

        // 1.创建Socket,指定端口号
        ServerSocket server = new ServerSocket(SERVER_PORT);

        while (true) {
            // 2.等待接收客户端的请求,程序在这里会阻塞等待客户端连接
            Socket socket = server.accept();

            // 为每个客户端创建一个新的线程来处理
            new Thread(new ClientHandler(socket)).start();
        }

        // 关闭socket,正常情况下server是不需要关的
        // server.close();
    }

    private static class ClientHandler implements Runnable {

        private final Socket socket;

        public ClientHandler(Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            InputStream is = null;
            OutputStream os = null;
            try {
                // 3.获取客户端输入和输出流
                is = socket.getInputStream();
                os = socket.getOutputStream();

                // 从客户端读取数据
                byte[] data = new byte[1024];
                while (true) {
                    // 4.从客户端读取数据
                    int len = is.read(data);
                    if (len > 0) {
                        //将数据转换为字符串
                        String message = new String(data, 0, len);
                        System.out.println(message);

                        // 如果接收到客户端发送exit,服务器就退出
                        if ("exit".equals(message)) {
                            break;
                        }

                        // 服务器将收到的内容发回给客户端
                        String reply = "服务器收到:来自" + socket.getInetAddress() + ", 消息" + message;
                        os.write(reply.getBytes("UTF-8"));
                    }
                }

            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                // 5.关闭服务端
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

                try {
                    socket.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
81
82
83
84
85
86
87
88
89
90
91
92
93

这样就可以接收多个客户端的连接了,每连接一个客户端会开启一个线程。

# 1.2 UDP通信

UDP通信比TCP通信更简单一些。使用UDP协议进行通信不需要建立连接,可以直接发送数据包。

UDP通信也不分客户端和服务端。

# 1 编写接收端

首先创建 UdpReceiver.java,编写代码如下:

package com.doubibiji;

import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class UdpReceiver {

    public static void main(String[] args) throws Exception {

        // 1.创建DatagramSocket,需要指定接收端的端口
        int port = 8888;
        DatagramSocket socket = new DatagramSocket(port);

        // 2.创建数据接收包
        DatagramPacket packet = new DatagramPacket(new byte[1024], 1024);

        // 使用for循环,不停的接收数据
        while (true) {
            // 3.接收数据
            socket.receive(packet);

            // 4.获取数据,获取不是有效的数据,是获取整个接收包传递的数组
            byte[] data = packet.getData();
            // 获取有效的字节个数
            int len = packet.getLength();
            // 将数据转换成字符串
            String str = new String(data, 0, len);
            System.out.println("接收到:" + str);

            if ("exit".equals(str)) {
                break;
            }
        }

        // 5.关闭
        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
33
34
35
36
37
38

接收端不需要等待客户端的连接,直接接收客户端的数据即可。

上面的程序在接收到发送端发送 exit 后,就会退出。

# 2 编写发送端

发送端在发送数据的时候,需要指定接收端的 IP 和 端口。

编写发送端,创建 UdpSender.java,编写代码如下:

package com.doubibiji;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;

public class UdpSender {
    public static void main(String[] args) throws Exception {
        // 1.创建创建DatagramSocket,发送可以不指定端口号,因为不接收数据
        DatagramSocket socket = new DatagramSocket();

        // 构建地址
        InetAddress address = InetAddress.getByName("127.0.0.1");
        // 指定接收的端口号
        int port = 8888;

        // 从键盘输入内容发送给服务器
        Scanner scanner = new Scanner(System.in);
        while (true) {
            String input = scanner.nextLine();
            // 2.创建DatagramPacket,指定数据,数据长度,地址,端口
            byte[] data = input.getBytes("UTF-8");
            DatagramPacket packet = new DatagramPacket(data, data.length, address, port);

            // 3.发送
            socket.send(packet);

            // 如果输入的exit就退出
            if ("exit".equals(input)) {
                break;
            }
        }

        // 4.关闭
        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
33
34
35
36
37
38

上面的程序接收键盘的数据,将输入信息发送给接收端,当输入 exit 的时候退出程序。

# 3 运行程序

运行的时候,先运行接接收端的程序,在运行发送端的程序。

然后在发送端模块通过键盘输入内容发送到接收端:

接收端就可以接收到信息了: