Java网络编程1:初识网络编程

Java网络编程1:初识网络编程




什么是计算机网络

  • 两台或更多的计算机组成的网络
  • 同一网络内的任意两台计算机可以直接通信
  • 所有计算机必须遵循同一种网络协议



什么是互联网

  • 互联网是网络的网络
  • 互联网采取TCP/IP协议
  • 其中最重要的两个协议是TCP协议和IP协议



IP地址和网关

  • IP地址用于唯一标识一个网络接口

    • IPv4采用32位地址
      IPv4地址实际是一个二进制32位的整数,为了便于识别,用十六进制表示后可以分为4组数字,每组数字转换成十进制后用“.”隔开就是我们见到的IP地址:
      enter description here
    • IPv6采用128位地址
  • 公网IP地址可以直接被访问

  • 内网IP地址只能在内网访问

  • 本机地址使用127.0.0.1

  • 通常路由器或交换机有两个网卡(两个IP地址),分别连接两个不同的网络:
    enter description here

  • 同一网络下的计算机可以直接通信,他们的网络号相同,网络号由IP地址和子掩码按组对齐做与运算得到:
    enter description here

  • 不同网络下的计算机需要通过路由器或交换机网络设备间接通信,这样的网络设备叫做网关:
    enter description here

  • 网关的作用是连接多个网络,负责把一个网络的数据包发送到另一个网络,过程叫做路由:
    enter description here

  • 一台计算机的网络拥有IP地址,子网掩码和网关(路由器)三个关键配置:
    enter description here




域名

由于IP地址不便于记忆,通常使用域名来访问特定的服务,域名解析服务器DNS负责将域名翻译成对应的IP地址,客户端再根据IP地址访问服务器:
enter description here




TCP/IP协议

  • IP协议是一个分组交换协议,不保证可靠传输,一个数据包通过IP协议传输会自动分成若干小的数据包然后通过网络进行传输
  • TCP(Transmission Control Protocol)协议是一个传输控制协议,建立再IP协议之上,IP协议负责传输数据包,TCP协议负责控制传输数据包;TCP协议传输之前需要先建立连接,然后才能传输数据,传输完成后断开连接;TCP协议是一个可靠传输协议,他通过接收确认,超时重传实现;TCP协议支持双向通信,双方可以同时传输和接收数据



UDP协议

UDP(User Datagram Protocol)协议是数据报文协议,不面向连接,不保证可靠传输,由于UDP协议传输效率高,通常用来传输视频等能容忍丢失部分数据的文件。




Socket

Socket通常称为套接字,用于应用程序之间建立远程连接,Socket内部通过TCP/IP协议进行数据传输,可以简单的理解为对IP地址和端口号的描述。Socket接口是由计算机操作系统提供的,编程语言提供对Socket接口调用的封装。通常计算机同时运行多个应用程序,仅仅有IP地址是无法确定由哪个应用程序接收数据,所以操作系统抽象出Socket接口,每个应用程序对应不同的socket(每个网络应用程序分配不同的端口号)。端口号的范围是0~65535,小于1024的端口需要管理员权限,大于1024的端口可以任意用户的应用程序打开。

Socket编程需要实现服务器端和客户端,因为这两个设备通讯时,需要知道对方的IP和端口号。通常服务器端有个固定的端口号,客户端直接通过服务器的IP地址和端口号进行访问服务器端,同时告知客户端的端口号,于是他们之间就可以通过socket进行通信。
enter description here




TCP编程

Java提供了Socket类ServerSocket类对计算机操作系统的Socket进行调用。客户端使用Socket(InetAddress, port)构造方法传入IP地址和端口号打开Socket,与远程服务区指定端口进行连接, 然后调用socket的getInputStream和getOutputStream方法获取输入和输出流就可以读写TCP的字节流:

// 连接远程服务器
Socket socket = new Socket(InetAddress, port);
// 读写字节流
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();

服务器端通过ServerSocket(port)构造方法传入端口号来监听指定的端口,然后通过accept()方法得到一个Socket对象与远程客户端建立连接,同样调用Socket对象的getInputStream和getOutputStream方法就可以读写字节流,服务器端完成传输后可以通过close()方法关闭远程连接和监听端口:

// 监听端口
ServerSocket serverSocket = new ServerSocket(port);
// 建立远程连接
Socket socket = serverSocket.accept();
// 读写字节流
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
// 关闭连接
socket.close();
// 关闭监听端口
serverSocket.close();




Socket编程实验

我们可以在本机做一个小实验,首先编写一个客户端的TCPClient类,通过Java提供的InetAddress类的getLoopbackAddress()方法获得localhost地址,然后使用Java的Socket类创建一个与本机8090端口的连接,再将读取字节流包装成一个BufferedReader对象、写入字节流包装成BufferedWriter对象。使用BufferedWriter写入一个“time”字符串并发送到本机的8090端口,再用BufferedReader读取本机8090端口返回的数据并打印出来。代码如下:

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

public class TCPClient {

    public static void main(String[] args) throws IOException {
        // 获取本机地址,即“127.0.0.1”
        InetAddress addr = InetAddress.getLoopbackAddress(); 
        // 与本机8090端口建立连接
        try (Socket sock = new Socket(addr, 8090)) {
        // 将读写字节流包装成BufferedReader和BufferedWriter对象
            try (BufferedReader reader = new BufferedReader(
                    new InputStreamReader(sock.getInputStream(), StandardCharsets.UTF_8))) {
                try (BufferedWriter writer = new BufferedWriter(
                        new OutputStreamWriter(sock.getOutputStream(), StandardCharsets.UTF_8))) {
                    // 写入“time”字符串
                    writer.write("time\n");
                    // 将写入内存缓冲区的数据立即发送
                    writer.flush();
                    // 读取本机8090端口返回的数据
                    String resp = reader.readLine();
                    System.out.println("Response: " + resp);
                }
            }
        }
    }
}

在相同包下写一个服务端的TCPServer类,利用Java的ServerSocket类监听8090端口并打印一句话“TCP server ready.”,然后用ServerSocket类的accept()方法与监听到的访问8090端口的客户端请求建立连接,然后和客户端一样包装读写字节流。服务端首先读取数据,如果读取到的数据是一个”time”字符串,则将当前时间信息返回给客户端,如果不是则返回一个“require data”字符串给客户端,最后关闭连接和关闭监听接口。

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;

public class TCPServer {

    public LocalDateTime currentTime() {
        return LocalDateTime.now();
    }

    public static void main(String[] args) throws Exception {
        // 监听8090端口
        ServerSocket ss = new ServerSocket(8090);
        System.out.println("TCP server ready.");
        // 建立连接
        Socket sock = ss.accept();
        // 包装读写字节流
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(sock.getInputStream(), StandardCharsets.UTF_8))) {
            try (BufferedWriter writer = new BufferedWriter(
                    new OutputStreamWriter(sock.getOutputStream(), StandardCharsets.UTF_8))) {
                // 读取发送到服务端的数据 
                String cmd = reader.readLine();
                // 如果数据是“time”字符串则将当前时间信息返回客户端
                if ("time".equals(cmd)) {
                    writer.write(LocalDateTime.now().toString() + "\n");
                    // 将写入内存缓冲区的数据立即发送
                    writer.flush();
                } else {
                    writer.write("require data\n");
                    writer.flush();
                }
            }
        }
        // 关闭连接
        sock.close();
        // 关闭监听端口
        ss.close();
    }
}

我们首先运行服务端TCPServer类的main方法,开始监听8090端口,并且Console打印出“TCP server ready.”,然后运行客户端TCPClient的main方法,我们得到Response信息,终端打印出了当前的时间信息:
enter description here
如果我们先运行客户端的main方法,我们会得到一个异常ConnectException: Connection refused,因为服务端并没有开始监听8090端口,无法与客户端建立socket连接。