Java网络编程1:初识网络编程
什么是计算机网络
- 两台或更多的计算机组成的网络
- 同一网络内的任意两台计算机可以直接通信
- 所有计算机必须遵循同一种网络协议
什么是互联网
- 互联网是网络的网络
- 互联网采取TCP/IP协议
- 其中最重要的两个协议是TCP协议和IP协议
IP地址和网关
IP地址用于唯一标识一个网络接口
- IPv4采用32位地址
IPv4地址实际是一个二进制32位的整数,为了便于识别,用十六进制表示后可以分为4组数字,每组数字转换成十进制后用“.”隔开就是我们见到的IP地址: - IPv6采用128位地址
- IPv4采用32位地址
公网IP地址可以直接被访问
内网IP地址只能在内网访问
本机地址使用127.0.0.1
通常路由器或交换机有两个网卡(两个IP地址),分别连接两个不同的网络:
同一网络下的计算机可以直接通信,他们的网络号相同,网络号由IP地址和子掩码按组对齐做与运算得到:
不同网络下的计算机需要通过路由器或交换机网络设备间接通信,这样的网络设备叫做网关:
网关的作用是连接多个网络,负责把一个网络的数据包发送到另一个网络,过程叫做路由:
一台计算机的网络拥有IP地址,子网掩码和网关(路由器)三个关键配置:
域名
由于IP地址不便于记忆,通常使用域名来访问特定的服务,域名解析服务器DNS负责将域名翻译成对应的IP地址,客户端再根据IP地址访问服务器:
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进行通信。
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信息,终端打印出了当前的时间信息:
如果我们先运行客户端的main方法,我们会得到一个异常ConnectException: Connection refused,因为服务端并没有开始监听8090端口,无法与客户端建立socket连接。