一、什么是Socket
两个程序直接的双向通讯连接实现数据的交换,这个双向链路的一端成为Socket。Socket通常用来实现客户端和服务端的连接。
Socket是TCP/IP四层介于应用层和传输层中间的软件抽象层,他是一组接口,他把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部。

二、工作原理
先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。

三、Socket使用
package com.test;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class MyServerSocket_TCP {
public static void main(String[] args) throws IOException {
// 监听9000端口
ServerSocket serverSocket = new ServerSocket(9000);
System.out.println("服务器启动,监听9000端口");
// 接收客户端连接 阻塞直到有客户端连接
Socket clientSocket = serverSocket.accept();
System.out.println("有客户端连接" + clientSocket.getInetAddress());
// 处理客户端连接
BufferedReader reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
PrintWriter writer = new PrintWriter(clientSocket.getOutputStream(), true);
String message;
while ((message = reader.readLine()) != null) {
System.out.println("收到客户端消息:" + message);
//回发响应
writer.println("服务器收到消息:" + message);
}
// 关闭连接
writer.close();
reader.close();
clientSocket.close();
serverSocket.close();
}
}
package com.test;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
public class MyClientSocket_TCP {
public static void main(String[] args) throws IOException {
//创建客户端对象 连接本机的IP地址和端口号
Socket socket = new Socket("127.0.0.1", 9000);
System.out.println("客户端已连接");
PrintWriter printWriter = new PrintWriter(socket.getOutputStream(), true);
printWriter.println("hello server");
// 读取 服务端返回的数据
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = bufferedReader.readLine();
System.out.println("服务端返回的数据:" + line);
bufferedReader.close();
printWriter.close();
socket.close();
}
}


package test;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class MyServerSocket_UDP {
public static void main(String[] args) throws IOException {
DatagramSocket serverSocket = new DatagramSocket(9000);
System.out.println("服务启动,监听9000端口");
byte[] buffer= new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
//阻塞等待客户端发送数据
serverSocket.receive(packet);
//
InetAddress clientIP = packet.getAddress();
int clientPort = packet.getPort();
String msg = new String(packet.getData(),0,packet.getLength(), "UTF-8");
System.out.println("收到客户端 " + clientIP + ":" + clientPort + " 的消息:" + msg);
// 回发响应
String response = "服务器收到:"+msg;
byte[] responseBytes = response.getBytes();
DatagramPacket responsePacket = new DatagramPacket(
responseBytes,
responseBytes.length,
packet.getAddress(),
packet.getPort()
);
// 发送响应
serverSocket.send(responsePacket);
serverSocket.close();
}
}
package test;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class MyClientSocket_UDP {
public static void main(String[] args) throws IOException {
DatagramSocket clientSocket = new DatagramSocket();
String msg="hello server";
byte[] data=msg.getBytes();
DatagramPacket packet=new DatagramPacket(
data,
data.length,
InetAddress.getByName("127.0.0.1"),//ip地址
9000 //端口号
);
clientSocket.send(packet);
//接收服务端响应
byte[] buffer=new byte[1024];
DatagramPacket responsePacket=new DatagramPacket(buffer,buffer.length);
clientSocket.receive(responsePacket);
String response=new String(responsePacket.getData(),0,responsePacket.getLength());
System.out.println("服务端返回的数据:"+response);
}
}


以上是Socket的通过TCP和UDP的收发数据的简单使用。
package test;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class MyServerSocket_TCP {
public static void main(String[] args) throws IOException {
// 监听9000端口
ServerSocket serverSocket = new ServerSocket(9000);
System.out.println("服务器启动,监听9000端口");
while (true) {
// 接收客户端连接 阻塞直到有客户端连接
Socket clientSocket = serverSocket.accept();
System.out.println("有客户端连接" + clientSocket.getInetAddress());
new Thread(() -> handleClient(clientSocket)).start();
}
}
/**
* 处理客户端连接
* @param clientSocket 客户端连接
*/
private static void handleClient(Socket clientSocket) {
// 处理客户端连接 BufferedReader PrintWriter 都实现了 AutoCloseable 的close() 方法 所以 可以 try-with-resources
try (BufferedReader reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
PrintWriter writer = new PrintWriter(clientSocket.getOutputStream(), true)) {
String message;
while ((message = reader.readLine()) != null) {
System.out.println("收到客户端消息:" + message);
//回发响应
writer.println("服务器收到消息:" + message);
}
} catch (IOException e) {
System.out.println("客户端:" + clientSocket.getInetAddress() + " 错误:" + e.getMessage());
}
}
}
以上写法典型的阻塞式多客户端写法,使用场景和优势如下:
主线程只负责监听端口并接受连接。
每个客户端都在独立线程中处理。
适合客户端少,逻辑简单的场景。
缺点是线程数量过多时候,会消耗大量的资源。
PrintWriter printWriter = new PrintWriter(socket.getOutputStream(), true);
printWriter.print("hello server");
printWriter.flush(); // 强制把缓冲区数据发送
PS:有个小注意项,虽然 autoFlush=true,但是对print无效,还得需要手动.flush以下。=true仅对 println();printf();format();有效。
package test;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
public class MyServerSocket_TCP {
public static void main(String[] args) throws IOException {
// 创建serverSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 绑定端口
serverSocketChannel.bind(new InetSocketAddress(9000));
// 设置为非阻塞模式
serverSocketChannel.configureBlocking(false);
// 创建选择器 用于监听多个通道
Selector selector = Selector.open();
// 注册到选择器上
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("服务器启动,监听9000端口");
handle(selector);
}
/**
* 处理客户端连接
* @param selector 选择器
* @throws IOException IO异常
*/
private static void handle(Selector selector) throws IOException {
while (true) {
//创建 Selector
selector.select();
// 遍历发生事件的 SelectionKey
Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
//Iterator的删除的是当前迭代的元素必须紧跟next()之后 必须remove 否则会重复处理
keyIterator.remove();
// 处理连接事件
if (key.isAcceptable()) {
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
// 接收客户端连接
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
// 注册读事件
socketChannel.register(selector, SelectionKey.OP_READ);
System.out.println("客户端连接:" + socketChannel.getRemoteAddress());
}
// 客户端可读事件
else if (key.isReadable()) {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 非阻塞
int bytesRead = client.read(buffer);
if (bytesRead == -1) {
// 客户端关闭
client.close();
System.out.println("客户端已关闭");
} else {
buffer.flip(); // 准备读取
String msg = new String(buffer.array(), 0, buffer.limit());
System.out.println("收到客户端消息:" + msg);
// 回发数据
String response = "服务器收到:" + msg;
// 非阻塞写
client.write(ByteBuffer.wrap(response.getBytes(StandardCharsets.UTF_8)));
}
}
}
}
}
}
Socket通信原理与使用
4769

被折叠的 条评论
为什么被折叠?



