网络编程
网络编程是 Java 最擅长的方向之一,使用 Java 进行网络编程时,由虚拟机实现了底层复杂的网络协议,Java 程序只需要调用 Java 标准库提供的接口,就可以简单高效地编写网络程序。
TCP 编程
在开发应用程序时,会遇到 Socket 这个概念。Socket 是一个抽象概念,一个应用程序通过一个 Socket 来建立一个远程连接,而 Socket 内部通过 TCP/IP 协议把数据传输到网络:
Socket,TCP 和部分 IP 的功能都是由操作系统提供的,不同的编程语言只是提供了对操作系统调用的简单封装。例如,Java 提供的几个 Socket 相关的类就封装了操作系统提供的接口。
为什么需要 Socket 进行通信?
因为仅仅通过 IP 地址进行通信是不够的,同一台计算机同一时间会运行多个网络应用程序,例如浏览器,QQ,邮件客户端等。当操作系统接收到一个数据包的时候,如果只有 IP 地址,它没法判断应该发给那个应用程序,所以,操作系统抽象出 Socket 接口,每个应用程序需要各自对应到不同的 Socket,数据包才能根据 Socket 正确地发送到相应的应用程序。
一个 Socket 就是由 IP 地址和端口号组成,可以把 Socket 简单理解为 IP 地址加端口号。
使用 Socket 进行网络编程时,本质上就是两个进程之间的网络通信。其中一个进程必须充当服务端,它会主动监听某个指定的端口,另一个进程必须充当客户端,它必须主动连接服务器的 IP 地址和指定端口,如果连接成功,服务器端和客户端就成功地建立一个 TCP 连接,双方后续就可以随时发送和接收数据。
因此,当 Socket 连接成功地在服务器端和客户端之间建立后:
- 对服务器端来说,它的 Socket 是指定的 IP 地址和指定的端口号。
- 对客户端来说,它的 Socket 是它所在计算机的 IP 地址和一个由操作系统分配的随机端口号。
服务器端
package net;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(6666);//监听指定端口
System.out.println("server is running...");
//使用无限循环来处理客户端连接
for (;;) {
Socket sock = ss.accept();//每当有新的客户端连接进来,就返回一个 Socket 实例
System.out.println("connected from " + sock.getRemoteSocketAddress());
Thread t = new Handler(sock);
t.start();
}
}
}
package net;
import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
public class Handler extends Thread{
Socket sock;
public Handler(Socket sock) {
this.sock = sock;
}
public void run() {
try (InputStream input = this.sock.getInputStream()) {
try (OutputStream output = this.sock.getOutputStream()) {
handle(input,output);
}
} catch (Exception e) {
try {
this.sock.close();
} catch (IOException ioe) {
}
System.out.println("client disconnected.");
}
}
private void handle(InputStream input,OutputStream output) throws IOException {
var writer = new BufferedWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8));
var reader = new BufferedReader(new InputStreamReader(input,StandardCharsets.UTF_8));
writer.write("hello\n");
writer.flush();
for (;;) {
String s = reader.readLine();
if (s.equals("bye")) {
writer.write("bye\n");
writer.flush();
break;
}
writer.write("ok: " + s + "\n");
writer.flush();
}
}
}
客户端
package net;
import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
public class Client {
public static void main(String[] args) throws IOException {
Socket sock = new Socket("localhost",6666);//连接指定服务器和端口
try (InputStream input = sock.getInputStream()) {
try (OutputStream output = sock.getOutputStream()) {
handle(input,output);
}
}
sock.close();
System.out.println("disconnected.");
}
private static void handle(InputStream input,OutputStream output) throws IOException {
var writer = new BufferedWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8));
var reader = new BufferedReader(new InputStreamReader(input,StandardCharsets.UTF_8));
Scanner scanner = new Scanner(System.in);
System.out.println("[server] " + reader.readLine());
for (;;) {
System.out.print(">>>");
String s = scanner.nextLine();//读取一行输入
writer.write(s);
writer.newLine();
writer.flush();
String resp = reader.readLine();
System.out.println("<<< " + resp);
if (resp.equals("bye")) {
break;
}
}
}
}
UDP 编程
和 TCP 编程相比,UDP 编程就简单的多,因为 UDP 没有创建连接,数据包也是一次收发一个,所以没有流的概念。
在 Java 中使用 UDP 编程,仍然需要使用 Socket,因为应用程序在使用 UDP 时必须指定网络接口(IP)和端口号。
注意:UDP 端口和 TCP 端口虽然都使用 0 ~ 65535,但它们是两套独立的端口,即一个应用程序用 TCP 占用了端口 1234,不影响另一个应用程序用 UDP 占用端口 1234。
服务端
package net;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.nio.charset.StandardCharsets;
public class UdpServer {
public static void main(String[] args) throws IOException {
DatagramSocket ds = new DatagramSocket(6666); //监听指定端口
for (;;) {
//数据缓冲区
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
ds.receive(packet);//收取一个 UDP 数据包
String s = new String(packet.getData(),packet.getOffset(),packet.getLength(), StandardCharsets.UTF_8);
//发送数据
byte[] data = "ACK".getBytes(StandardCharsets.UTF_8);
packet.setData(data);
ds.send(packet);
}
}
}
客户端
package net;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UdpClient {
public static void main(String[] args) throws IOException {
DatagramSocket ds = new DatagramSocket();
ds.setSoTimeout(1000);
ds.connect(InetAddress.getByName("localhost"),6666);//不是真的连接
//发送
byte[] data = "Hello".getBytes();
DatagramPacket packet = new DatagramPacket(data,data.length);
ds.send(packet);
//接收
byte[] buffer = new byte[1024];
packet = new DatagramPacket(buffer, buffer.length);
ds.receive(packet);
String resp = new String(packet.getData(), packet.getOffset(), packet.getLength());
ds.disconnect();
}
}