Socket套接字
ServerSocket
ServerSocket是创建TCP服务端Socket的API.
ServerSocket构造方法:
方法签名 | 方法说明 |
---|---|
ServerSocket(int port) | 创建一个服务端套接字Socket,并绑定到指定端口 |
ServerSocket方法:
方法签名 | 方法说明 |
---|---|
Socket accept() | 开始监听指定端口(创建时绑定的端口),当有客户端连接时,返回一个服务端Socket对象,并基于该Socket建立与客户端的连接 ,否则阻塞等待 |
void close() | 关闭套接字 |
Socket
Socket是客户端Socket,或者是服务端中收到客户端建立连接的请求后,返回的服务端Socket.
Socket的构造方法:
方法签名 | 方法说明 |
---|---|
Socket(String host, int port) | 创建一个客户端套接字Socket, 并与对应主机,对应ip建立连接 |
Socket方法:
方法签名 | 方法说明 |
---|---|
InetAddress getInetAddress() | 返回套接字连接的地址 |
InputStream getInputStream() | 返回套接字的输入流 |
OutputStream getOutputStream() | 返回套接字的输出流 |
案例演示
TCP回显服务器
java
public class TcpEchoServer {
private ServerSocket serverSocket = null;
public TcpEchoServer(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动!");
while (true) {
// 1. 处理客户端的连接
// 把内核中的连接获取到应用程序中了,连接相当于一个个任务,放在队列中
// 类似于生产者消费者模型
// 如果没有客户端连接成功,就会进入阻塞.
// 从队列中出序号最前面的连接, 与三次握手没有关系
Socket clientSocket = serverSocket.accept();
Thread thread = new Thread(() -> {
try {
processConnection(clientSocket);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
thread.start();
}
}
private void processConnection(Socket clientSocket) throws IOException {
System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
// 读取请求 计算响应 返回性相应
// Socket 对象内部包含了两个字节流对象, 可以把这俩字节流对象获取到, 完成后续的读写工作
try(InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()) {
// 一次连接中,可能会涉及多次请求和响应
while (true) {
// 1. 读取请求并解析, 为了读取方便,直接使用scanner
Scanner scanner = new Scanner(inputStream);
if(!scanner.hasNext()) {
System.out.printf("[%s:%d] 客户端下线\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
break;
}
// 客户端发送的数据,得是文本数据
// next()读取数据,一直读到空白符结束(换行,回车,空格,制表符,垂直制表符)
String request = scanner.next();
// 2. 根据请求计算响应
String response = process(request);
// 3. 把响应写回给客户端,把outputStream用PrintWriter包裹一下,方便发送数据
PrintWriter writer = new PrintWriter(outputStream);
// 使用 PrintWriter的println方法,把响应返回给客户端
// 在结尾加上'\n', 客户端就可以用scanner.next()访问了
writer.println(response);
// 刷新缓冲区
writer.flush();
// 日志, 打印当前的请求信息
System.out.printf("[%s:%d] req: %s, resp: %s\n",clientSocket.getInetAddress().toString(), clientSocket.getPort(),
request, response);
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
// 不关闭会内存泄漏
// 在finally中加入close方法,确保socket被关闭
clientSocket.close();
}
}
public String process(String request) {
// 回显服务器
return request;
}
public static void main(String[] args) throws IOException {
TcpEchoServer server = new TcpEchoServer(900);
server.start();
}
}
TCP客户端
java
public class TcpEchoClient {
private Socket socket = null;
public TcpEchoClient(String serverIp, int serverPort) throws IOException {
// new操作后,就完成了tcp连接的建立
// 建立了三次握手
socket = new Socket(serverIp, serverPort);
}
public void start() throws IOException {
//
System.out.println("客户端启动");
Scanner scannerConsole = new Scanner(System.in);
try(InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()) {
while (true) {
System.out.println("-> ");
// 1. 从控制台输入字符串
String request = scannerConsole.next();
// 2. 把请求发送给服务器
PrintWriter writer = new PrintWriter(outputStream);
// 使用 println 带上换行. 后续服务器读取请求, 就可以使用 scanner.next 来获取了
writer.println(request);
// 确保数据发出去了
writer.flush();
// 3. 从服务器读取响应
Scanner scannerNetWork = new Scanner(inputStream);
String response = scannerNetWork.next();
// 4. 把响应打印出来
System.out.println(response);
}
}
}
public static void main(String[] args) throws IOException {
TcpEchoClient client = new TcpEchoClient("127.0.0.1", 900);
client.start();
}
}
Scanner对象和PrintWriter没有进行close,是否有文件资源泄漏呢>
答案是不会的.
流对象持有的资源分为两部分:
- 内存(对象销毁,内存就回收了)
- 文件描述符
while循环结束,内存被销毁.Scanner和PrintWriter没有文件描述符,有的是InputStream和OutputStream,准确说是Socket对象,把Socket对象关闭就可以了.
java
实际上客户端往往会发送大量的请求,我们可以使用线程池的方式来实现高并发.
但是即使使用了线程池,避免了频繁创建销毁线程.
毕竟是每个客户端对应一个线程,如果客户端很多,就需要创建大量线程,
对于服务器是开销很大的.
我们可以引入"IO多路复用"的方式解决多并发,利用"节流",是消耗的硬件资源更少了,减少
了线程的数量.
小结
本博客总结了Socket套接字(TCP流)的相关知识,有收获的小伙伴多多支持.