Socket
Socket (套接字) : 是操作系统提供的可以进行网络通信的接口。Socket 是应用程序(应用层)访问传输层通信服务的接口。传输层 2 种最主要的协议 : TCP 协议 / UDP协议。TCP 和 UPD 提供了 2 组不同的 Socket API
TCP / UPD 的特点
TCP 协议特点 : 有连接、可靠传输、面向字节流、全双工
UDP 协议特点 : 无连接、不可靠传输、面向数据报、全双工
- 有链接 : 通信前通信双方的主机互相保存了对方的核心信息(IP 、端口),通信后删除对方的关键信息
- 无链接 : 不需要保存对方的信息,直接发送数据
- 可靠传输 :是指发送方和接收方通过一定的机制,尽可能确保数据能够正确、完整、按顺序地到达接收方
- 不可靠传输 :不保证数据一定到达接收方,也不保证数据按顺序到达或不会重复
- 面向字节流 :字节为单位,进行灵活的数据读写
- 面向数据报 :一个数据报为单位读写,一次读或写都是一个完整的数据报
- 全双工 : 通信双方可以同时发送和接收数据(双向通信)
UDP Socket
UPD Socket 主要是提供了 2 个 类 :
- DatagramSocket :⽤于发送和接收UDP数据报。是一个 Socket 对象,表示对网卡设备的抽象。后序操作 Socket 对象,就能操作网卡
- DatagramPacket :是 UDPSocket 发送和接收的数据报,使用 UDP 传输数据的基本单位。每次发送/接收数据,都是在传输一个 DatagramPacket 对象
回显服务
回显服务就是请求内容是啥,响应就是啥。我们把整个回显服务分为两部分:
- UdpEchoServer 服务器部分。
- UdpEchoClient 客户端部分。
服务器
- 创建 Socket 对象,网络编程都是 Socket 对象 作为中心来展开的
java
private DatagramSocket socket = null;
- 指定服务器的端口号
java
public UdpEchoServer(int port) throws SocketException {
socket = new DatagramSocket(port);
}
启动服务器
- 读取客户端请求并解析。用
socket.receive()来接收客户端发来的数据、如果客户端没有发来数据、socket.receive()就会堵塞等待 等待数据到达、 构造DatagramPacket对象(接收数据的容器)传给socket.receive()来接受客户端发来的数据 - 根据请求计算响应。这一般是个复杂的过程,由于是回响服务器没有计算的过程、直接返回接收的数据
- 返回响应给客户端。把响应再次放进
DatagramPacket对象 、传给socket.send()返回给客户端、
由于UDP协议是不会保存对方的信息的、因此返回的时候需要知道 "返回给谁" 、构造DatagramPacket对象
的时候要把 客户端的 IP 和 端口号 传过去
- 服务器代码 :
java
public class UdpEchoServer {
private DatagramSocket socket = null;
public UdpEchoServer(int port) throws SocketException {
socket = new DatagramSocket(port);
}
//启动服务器
public void start() throws IOException {
System.out.println("server start!");
while (true) {
// 1. 读取请求并解析. 此处 requestPacket 是 receive 的输出型参数.
DatagramPacket requestPacket = new DatagramPacket(new byte[1024], 1024);
socket.receive(requestPacket);
// 此处把二进制数据, 转成字符串.
String request = new String(requestPacket.getData(), 0, requestPacket.getLength());
// 2. 根据请求计算响应.
String response = process(request);
// 3. 把响应返回给客户端. 再构造一个 DatagramPacket
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),
response.getBytes().length, requestPacket.getSocketAddress());
socket.send(responsePacket);
// 4. 打印日志
System.out.printf("[%s:%d] req: %s, resp: %s\n", requestPacket.getAddress().toString(),
requestPacket.getPort(), request, response);
}
}
private String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
UdpEchoServer server = new UdpEchoServer(9090);
server.start();
}
}
传参 requestPacket.getSocketAddress() 把客户端的 IP 和 端口号 传给 responsePacket

客户端
客户端的端口号不需要指定、由操作系统来分配空闲的端口号
构造 Socket 对象 和 保存 服务器的 IP 和 端口号
java
//创建 Socker 对象
private DatagramSocket socket;
//保存服务器的端口号和IP
private String serverIp;
private int serverPort;
public UdpEchoClient(String serverIP, int serverPort) throws IOException {
// 端口号由系统指定
socket = new DatagramSocket();
this.serverIp = serverIP;
this.serverPort = serverPort;
}
启动客户端
- 客户端从控制台读取用户输入的内容
- 把用户输入的内容构造成 UPD数据报 发送给服务器
- 接收服务器返回的响应、解析响应输出在控制台
- 客户端代码 :
java
public class UdpEchoClient {
//创建 Socker 对象
private DatagramSocket socket;
//保存服务器的端口号和IP
private String serverIp;
private int serverPort;
public UdpEchoClient(String serverIP, int serverPort) throws IOException {
socket = new DatagramSocket();
this.serverIp = serverIP;
this.serverPort = serverPort;
}
public void start() throws IOException {
System.out.println("Client start");
Scanner sc = new Scanner(System.in);
while(true){
//从控制台读取输入
System.out.print("-> ");
String request = sc.next();
//退出
if(request.equals("exit")){
break;
}
//将用户输入的字符串构造成数据报 发送 UDP 数据报还需要包含 服务器的 IP 和 端口
DatagramPacket requestPacket = new DatagramPacket(
request.getBytes(),
request.getBytes().length,
InetAddress.getByName(this.serverIp),
this.serverPort
);
//发送请求给服务器
socket.send(requestPacket);
// 接收服务器的返回的响应
DatagramPacket responsePacket = new DatagramPacket(new byte[1024], 1024);
//从服务器读取响应
socket.receive(responsePacket);
// 将二进制数转化为字符串
String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
//显示响应内容
System.out.println(response);
}
}
public static void main(String[] args) throws IOException {
//服务器的 IP 和 端口号传给客户端
UdpEchoClient client = new UdpEchoClient("127.0.0.1",9090);
client.start();
}
需要把 IP 地址 字符串转换为 InetAddress 对象

- 运行程序 :


字典服务器
在回显服务器的基础上、增加查字典的功能。字典服务器的只是需要改变 process 的代码逻辑。通过继承UdpEchoServer 来复用其他代码、重写 process 方法来实现字典服务器
java
public class UdpDictServer extends UdpEchoServer{
// 通过 hash 表把 英语和翻译绑定
private Map<String, String> dict = new HashMap<>();
public UdpDictServer(int port) throws SocketException {
super(port);
dict.put("hello", "你好");
dict.put("world", "世界");
dict.put("dog", "狗");
dict.put("cat", "猫");
}
@Override
public String process(String request){
return dict.getOrDefault(request,"字典没有这个单词");
}
public static void main(String[] args) throws IOException {
UdpDictServer server = new UdpDictServer(9090);
server.start();
}
}

TCP Socket
TCP 传输的基本单位是字节、主要涉及 2 个类
- ServerSocket : 专门给服务器使用
- Socket : 既给客户端又给服务器使用
回显服务
服务器
创建 ServerSocket 对象、启动服务器 建立连接 。因为 TCP 协议是有连接的、在通信前需要建立连接
java
ServerSocket serverSocket = null;
public TcpEchoServer(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("server start");
while (true) {
//1 接受客户端的连接
Socket socket = serverSocket.accept();
prcessConnection(socket);
}
}
处理请求
java
private void prcessConnection(Socket socket) {
// 打印客户端的信息
System.out.printf("[%s:%d] 客户端已连接\n", socket.getInetAddress(), socket.getPort());
try(InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();) {
Scanner scanner = new Scanner(inputStream);
PrintWriter writer = new PrintWriter(outputStream);
while(true){
//读取请求并解析
if(!scanner.hasNext()){
//客户端关闭了连接
System.out.printf("[%s:%d] 客户端断开连接\n", socket.getInetAddress(), socket.getPort());
break;
}
String request = scanner.next();
//根据请求计算响应
String response = process(request);
//把响应写回给客户端
writer.println(response);
// 将文件缓冲区的数据 强制写入到 IO 设备
//(文件缓冲区的数据没满,数据会在一直在缓冲区,满了一起把数据写入到 IO 设备)
writer.flush();
//打印日志
System.out.printf("[%s:%d] req : %s resp: %s\n",
socket.getInetAddress(),socket.getPort(),
request, response);
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
// 关闭每次接收客户端连接的 Socket (文件资源)
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
对端的 IP 和 端口号就保存在 Socket 中

- 服务器代码 :
java
public class TcpEchoServer {
ServerSocket serverSocket = null;
public TcpEchoServer(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("server start");
while (true) {
//1 接受客户端的连接
Socket socket = serverSocket.accept();
//处理整个链接过程
prcessConnection(socket);
}
}
private void prcessConnection(Socket socket) {
System.out.printf("[%s:%d] 客户端已连接\n", socket.getInetAddress(), socket.getPort());
try(InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();) {
Scanner scanner = new Scanner(inputStream);
PrintWriter writer = new PrintWriter(outputStream);
while(true){
//读取请求并解析
if(!scanner.hasNext()){
//客户端关闭了连接
System.out.printf("[%s:%d] 客户端断开连接\n", socket.getInetAddress(), socket.getPort());
break;
}
String request = scanner.next();
//根据请求计算响应
String response = process(request);
//把响应写回给客户端
writer.println(response);
writer.flush();
//打印日志
System.out.printf("[%s:%d] req : %s resp: %s\n",
socket.getInetAddress(),socket.getPort(),
request, response);
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
private String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
TcpEchoServer server = new TcpEchoServer(9090);
server.start();
}
}
客户端
对于 TCP 的 Socket 来说,构造方法指定的端口。表示要链接的服务器的端口
- 客户端代码 :
java
public class TcpEchoClient {
private Socket socket = null;
public TcpEchoClient(String serverIp, int serverPort) throws IOException {
// 传入 服务器的 IP 和 端口号 和服务器建立连接
socket = new Socket(serverIp,serverPort);
}
public void start(){
System.out.println("client start");
try(InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()){
Scanner scanner = new Scanner(System.in);
Scanner scannerNetwork = new Scanner(inputStream);
PrintWriter writer = new PrintWriter(outputStream);
while(true) {
//控制台读取数据
System.out.println("-> ");
String request = scanner.next();
//把请求发送给服务器
writer.println(request);
writer.flush();
//从服务器读取响应
if(!scannerNetwork.hasNext()){
break;
}
String response = scannerNetwork.next();
//把结果显示到控制台上
System.out.println(response);
}
}catch(IOException e){
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
TcpEchoClient client = new TcpEchoClient("127.0.0.1",9090);
client.start();
}
}
- 运行程序 :


线程池版回响服务器
java
public class TcpEchoServer {
ServerSocket serverSocket = null;
public TcpEchoServer(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("server start");
ExecutorService executor = Executors.newCachedThreadPool();
while (true) {
//1 接受客户端的连接
Socket socket = serverSocket.accept();
//处理整个链接过程
executor.submit(() -> {
prcessConnection(socket);
});
}
}
private void prcessConnection(Socket socket) {
System.out.printf("[%s:%d] 客户端已连接\n", socket.getInetAddress(), socket.getPort());
try(InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();) {
Scanner scanner = new Scanner(inputStream);
PrintWriter writer = new PrintWriter(outputStream);
while(true){
//读取请求并解析
if(!scanner.hasNext()){
//客户端关闭了连接
System.out.printf("[%s:%d] 客户端断开连接\n", socket.getInetAddress(), socket.getPort());
break;
}
String request = scanner.next();
//根据请求计算响应
String response = process(request);
//把响应写回给客户端
writer.println(response);
writer.flush();
//打印日志
System.out.printf("[%s:%d] req : %s resp: %s\n",
socket.getInetAddress(),socket.getPort(),
request, response);
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
private String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
TcpEchoServer server = new TcpEchoServer(9090);
server.start();
}
- 运行程序 :


