目录
今天在写上传交互程序中,当我把服务器和客户端成功启动后,在客户端输入请求指令的时候,出现了上述情况。
下面是服务器的代码
- package network;
-
- import java.io.*;
- import java.net.ServerSocket;
- import java.net.Socket;
- import java.util.Scanner;
-
- public class TcpEchoServer {
- ServerSocket serverSocket = null;
- public TcpEchoServer(int port) throws IOException {
- serverSocket = new ServerSocket(port);
- }
- public void start() throws IOException {
- // 服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket。
- Socket clientSocket = serverSocket.accept();
- processConnection(clientSocket); // 单线程模式——即该服务器无法同时为多个客户端同时提供服务
- }
- // 这里我们建立长连接——一次连接中,会有 N次请求,N次响应
- public void processConnection(Socket clientSocket) throws IOException {
- System.out.printf("连接成功![%s:%d]\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
- try (InputStream inputStream = clientSocket.getInputStream();
- OutputStream outputStream = clientSocket.getOutputStream() ) {
- Scanner scannerNet = new Scanner(inputStream); // 对我们的读取进行嵌套
- PrintWriter printWriter = new PrintWriter(outputStream);
- while (true) {
- if (!scannerNet.hasNext()) {
- // 说明此时连接中断,服务器无法继续从客户端读取到请求
- // 连接断开. 当客户端断开连接的时候, 此时 hasNext 就会返回 false
- System.out.printf("[%s:%d] 断开连接!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
- break;
- }
- // 1、读取客户端的请求
- String request = scannerNet.nextLine();
- // 从客户端读取到的请求,如果客户端在输入请求的时候,只是通过nextLine敲了回车,然后就通过printWrite.write发送给我们的服务器,
- // 这时服务器接收到的数据是不带换行符的,换行符在我们的客户端nextLine输入的过程中就已经被吞吃了,所有为了避免String request = scanner.nextLine();无法正常读取(nextLine只有遇到换行才结束)
- // 所以在我们的客户端在发送时要通过PrintWrite.println()来发送,会给我们自动添加一个换行符
- // 2、处理客户端发来的请求
- String response = process(request);
- // 3、把响应发送给客户端
- printWriter.write(response); // 这样写有bug,因为我们当前的response中没有带换行符,
- printWriter.flush(); // 刷新缓冲区
- // 打印日志
- System.out.printf("request: %s, response: %s\n", request, response);
- }
- }
- finally {
- clientSocket.close(); // 每一次建立连接都会创建一个clientSocket,该连接结束后要及时关闭,避免内存泄漏
- }
-
- }
- public String process(String request) {
- return request;
- }
-
- public static void main(String[] args) throws IOException {
- TcpEchoServer server = new TcpEchoServer(8000);
- server.start();
- }
- }
客户端的代码:
- package network;
-
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.OutputStream;
- import java.io.PrintWriter;
- import java.net.Socket;
- import java.util.Scanner;
-
- public class TcpEchoClient {
- Socket socket = null;
-
- public TcpEchoClient() throws IOException {
- // 指定客户端要连接的服务器
- socket = new Socket("127.0.0.1", 8000);
- // 程序走到了这里,说明客户端和服务器就已经成功建立了连接
- }
-
- public void start() throws IOException {
- Scanner scanner = new Scanner(System.in);
- System.out.println("当前是客户端,请输入请求");
-
- try (InputStream inputStream = socket.getInputStream();
- OutputStream outputStream = socket.getOutputStream()) { // InputStream用于读取, OutputStream用于发送
- // 对我们的读取和发送进行写包装,使得读取和包装更方便
- Scanner scannerNet = new Scanner(inputStream);
- PrintWriter printWriter = new PrintWriter(outputStream);
- // 这里我们建立长连接——一次连接中,会有 N次请求,N次响应
- while (true) {
- System.out.print("> ");
- // 1、客户端从键盘输入请求
- String request = scanner.nextLine();
- // 在这里我们虽然在输入内容后用换行符来进行了结束,但我们用于接收的request并没有读取的换行符——它被nextLine()给吞吃了
- // 2、把请求发送给服务器
-
- printWriter.write(request);
- // 这么写会产生一个bug, 要printWriter.println()会自动给我们要写入的添加一个换行符
- // 可以让服务器在读取请求时遇到换行就结束,不至于陷入阻塞
- printWriter.flush(); // 刷新缓冲区
- // 3、从服务器读取响应内容
- String response = scannerNet.nextLine();
- // 4、打印日志
- System.out.printf("request: %s, response: %s\n", request, response);
- }
- }
- finally {
- socket.close();
- }
- }
- public static void main(String[] args) throws IOException {
- TcpEchoClient client = new TcpEchoClient();
- client.start();
- }
- }
于是我打开java的工具包jconsole.exe查看相关的线程执行情况
客户端相关代码:
那么客户端在这里出现异常正常吗?很正常因为我们的服务器在正常情况下,当把响应发送给客户端后,是要打印日志的。但从我们上述的运行结果来看,我们的服务器并没有打印响应(这说明服务器没有成功的把响应发送给客户端,而是陷入了阻塞)
所以客户端在这里的阻塞是正常的
对应的服务器代码:
那么我们在来看看服务器的代码究竟在哪里出现了问题:’
对应的服务器代码:
为什么会在scannerNet.nextLine()这里陷入阻塞呢?nextLine不是遇到回车就结束吗?我们在客户端输入请求后明明敲了一个回车呀!
通过下面这个栗子我们可以发现一些问题:
那么如果这样的话, 我们服务器从客户端读取request的时候,接收到的数据也是没有换行符的——也就是说:
String request = scannerNet.nextLine();
这一行的读取一直无法正常的结束,线程自然就陷入了阻塞当中。
清楚了这一点就好办了,我们在下面的客户端代码中:
对应的服务器代码:
- package network;
-
- import java.io.*;
- import java.net.ServerSocket;
- import java.net.Socket;
- import java.util.Scanner;
-
- public class TcpEchoServer {
- ServerSocket serverSocket = null;
- public TcpEchoServer(int port) throws IOException {
- serverSocket = new ServerSocket(port);
- }
- public void start() throws IOException {
- // 服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket。
- Socket clientSocket = serverSocket.accept();
- processConnection(clientSocket); // 单线程模式——即该服务器无法同时为多个客户端同时提供服务
- }
- // 这里我们建立长连接——一次连接中,会有 N次请求,N次响应
- public void processConnection(Socket clientSocket) throws IOException {
- System.out.printf("连接成功![%s:%d]\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
- try (InputStream inputStream = clientSocket.getInputStream();
- OutputStream outputStream = clientSocket.getOutputStream() ) {
- Scanner scannerNet = new Scanner(inputStream); // 对我们的读取进行嵌套
- PrintWriter printWriter = new PrintWriter(outputStream);
- while (true) {
- if (!scannerNet.hasNext()) {
- // 说明此时连接中断,服务器无法继续从客户端读取到请求
- // 连接断开. 当客户端断开连接的时候, 此时 hasNext 就会返回 false
- System.out.printf("[%s:%d] 断开连接!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
- break;
- }
- // 1、读取客户端的请求
- String request = scannerNet.nextLine();
- // 从客户端读取到的请求,如果客户端在输入请求的时候,只是通过nextLine敲了回车,然后就通过printWrite.write发送给我们的服务器,
- // 这时服务器接收到的数据是不带换行符的,换行符在我们的客户端nextLine输入的过程中就已经被吞吃了,所有为了避免String request = scanner.nextLine();无法正常读取(nextLine只有遇到换行才结束)
- // 所以在我们的客户端在发送时要通过PrintWrite.println()来发送,会给我们自动添加一个换行符
- // 2、处理客户端发来的请求
- String response = process(request);
- // 3、把响应发送给客户端
- printWriter.println(response);
- // printWriter.write(response); // 这样写有bug,因为我们当前的response中没有带换行符,
- printWriter.flush(); // 刷新缓冲区
- // 打印日志
- System.out.printf("request: %s, response: %s\n", request, response);
- }
- }
- finally {
- clientSocket.close(); // 每一次建立连接都会创建一个clientSocket,该连接结束后要及时关闭,避免内存泄漏
- }
-
- }
- public String process(String request) {
- return request;
- }
-
- public static void main(String[] args) throws IOException {
- TcpEchoServer server = new TcpEchoServer(8000);
- server.start();
- }
- }
- package network;
-
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.OutputStream;
- import java.io.PrintWriter;
- import java.net.Socket;
- import java.util.Scanner;
-
- public class TcpEchoClient {
- Socket socket = null;
-
- public TcpEchoClient() throws IOException {
- // 指定客户端要连接的服务器
- socket = new Socket("127.0.0.1", 8000);
- // 程序走到了这里,说明客户端和服务器就已经成功建立了连接
- }
-
- public void start() throws IOException {
- Scanner scanner = new Scanner(System.in);
- System.out.println("当前是客户端,请输入请求");
-
- try (InputStream inputStream = socket.getInputStream();
- OutputStream outputStream = socket.getOutputStream()) { // InputStream用于读取, OutputStream用于发送
- // 对我们的读取和发送进行写包装,使得读取和包装更方便
- Scanner scannerNet = new Scanner(inputStream);
- PrintWriter printWriter = new PrintWriter(outputStream);
- // 这里我们建立长连接——一次连接中,会有 N次请求,N次响应
- while (true) {
- System.out.print("> ");
- // 1、客户端从键盘输入请求
- String request = scanner.nextLine();
- // 在这里我们虽然在输入内容后用换行符来进行了结束,但我们用于接收的request并没有读取的换行符——它被nextLine()给吞吃了
- // 2、把请求发送给服务器
-
- printWriter.println(request);
- // printWriter.write(request);
- // 这么写会产生一个bug, 要printWriter.println()会自动给我们要写入的添加一个换行符
- // 可以让服务器在读取请求时遇到换行就结束,不至于陷入阻塞
- printWriter.flush(); // 刷新缓冲区
- // 3、从服务器读取响应内容
- String response = scannerNet.nextLine();
- // 4、打印日志
- System.out.printf("request: %s, response: %s\n", request, response);
- }
- }
- finally {
- socket.close();
- }
- }
- public static void main(String[] args) throws IOException {
- TcpEchoClient client = new TcpEchoClient();
- client.start();
- }
- }
测试案例
实现多线程服务器
- package network;
-
- import java.io.*;
- import java.net.ServerSocket;
- import java.net.Socket;
- import java.util.Scanner;
-
- public class TcpEchoServer {
- ServerSocket serverSocket = null;
- public TcpEchoServer(int port) throws IOException {
- serverSocket = new ServerSocket(port);
- }
- public void start() throws IOException {
- while (true) {
- // 服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket
- // 如果当前没有客户端来建立连接, 就会阻塞等待!!
- Socket clientSocket = serverSocket.accept();
- // processConnection(clientSocket); // 单线程模式——即该服务器无法同时为多个客户端同时提供服务
- // [版本2] 多线程版本. 主线程负责拉客, 新线程负责通信
- // 虽然比版本1 有提升了, 但是涉及到频繁创建销毁线程, 在高并发的情况下, 负担比较重的.
- Thread thread = new Thread(() -> {
- try {
- processConnection(clientSocket);
- } catch (IOException e) {
- e.printStackTrace();
- }
- });
- thread.start();
- }
-
- }
- // 通过这个方法, 给当前连上的这个客户端, 提供服务!!
- // 这里我们建立长连接——一次连接中,会有 N次请求,N次响应
- public void processConnection(Socket clientSocket) throws IOException {
- System.out.printf("连接成功![%s:%d]\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
- try (InputStream inputStream = clientSocket.getInputStream();
- OutputStream outputStream = clientSocket.getOutputStream() ) {
- Scanner scannerNet = new Scanner(inputStream); // 对我们的读取进行嵌套
- PrintWriter printWriter = new PrintWriter(outputStream);
- while (true) {
- if (!scannerNet.hasNext()) {
- // 说明此时连接中断,服务器无法继续从客户端读取到请求
- // 连接断开. 当客户端断开连接的时候, 此时 hasNext 就会返回 falseSystem.out.printf("[%s:%d] 断开连接!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
- break;
- }
- // 1、读取客户端的请求
- String request = scannerNet.nextLine();
- // 从客户端读取到的请求,如果客户端在输入请求的时候,只是通过nextLine敲了回车,然后就通过printWrite.write发送给我们的服务器,
- // 这时服务器接收到的数据是不带换行符的,换行符在我们的客户端nextLine输入的过程中就已经被吞吃了,所有为了避免String request = scanner.nextLine();无法正常读取(nextLine只有遇到换行才结束)
- // 所以在我们的客户端在发送时要通过PrintWrite.println()来发送,会给我们自动添加一个换行符
- // 2、处理客户端发来的请求
- String response = process(request);
- // 3、把响应发送给客户端
- printWriter.println(response);
- // printWriter.write(response); // 这样写有bug,因为我们当前的response中没有带换行符,
- printWriter.flush(); // 刷新缓冲区
- // 打印日志
- System.out.printf("[%s:%d] request: %s, response: %s\n",
- clientSocket.getInetAddress(), clientSocket.getPort(),
- request, response);
- }
- }
- finally {
- clientSocket.close(); // 每一次建立连接都会创建一个clientSocket,该连接结束后要及时关闭,避免内存泄漏
- }
-
- }
- public String process(String request) {
- return request;
- }
-
- public static void main(String[] args) throws IOException {
- TcpEchoServer server = new TcpEchoServer(8000);
- server.start();
- }
- }
通过线程池实现多线程服务器
- package network;
-
- import java.io.*;
- import java.net.ServerSocket;
- import java.net.Socket;
- import java.util.Scanner;
- import java.util.concurrent.Executor;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
-
- public class TcpEchoServer {
- ServerSocket serverSocket = null;
- public TcpEchoServer(int port) throws IOException {
- serverSocket = new ServerSocket(port);
- }
- public void start() throws IOException {
- ExecutorService service = Executors.newCachedThreadPool();// 此处不太适合使用 "固定个数的"
- while (true) {
- // 服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket
- // 如果当前没有客户端来建立连接, 就会阻塞等待!!
- Socket clientSocket = serverSocket.accept();
- // processConnection(clientSocket); // 单线程模式——即该服务器无法同时为多个客户端同时提供服务
-
- // [版本2] 多线程版本. 主线程负责拉客, 新线程负责通信
- // 虽然比版本1 有提升了, 但是涉及到频繁创建销毁线程, 在高并发的情况下, 负担比较重的.
- // Thread thread = new Thread(() -> {
- // try {
- // processConnection(clientSocket);
- // } catch (IOException e) {
- // e.printStackTrace();
- // }
- // });
- // thread.start();
-
- // [版本3] 使用线程池, 来解决频繁创建销毁线程的问题.
- // 此处不太适合使用 "固定个数的"
- service.submit(new Runnable() {
- @Override
- public void run() {
- try {
- processConnection(clientSocket);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- });
-
- }
-
- }
- // 通过这个方法, 给当前连上的这个客户端, 提供服务!!
- // 这里我们建立长连接——一次连接中,会有 N次请求,N次响应
- public void processConnection(Socket clientSocket) throws IOException {
- System.out.printf("连接成功![%s:%d]\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
- try (InputStream inputStream = clientSocket.getInputStream();
- OutputStream outputStream = clientSocket.getOutputStream() ) {
- Scanner scannerNet = new Scanner(inputStream); // 对我们的读取进行嵌套
- PrintWriter printWriter = new PrintWriter(outputStream);
- while (true) {
- if (!scannerNet.hasNext()) {
- // 说明此时连接中断,服务器无法继续从客户端读取到请求
- // 连接断开. 当客户端断开连接的时候, 此时 hasNext 就会返回 false
- System.out.printf("[%s:%d] 断开连接!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
- break;
- }
- // 1、读取客户端的请求
- String request = scannerNet.nextLine();
- // 从客户端读取到的请求,如果客户端在输入请求的时候,只是通过nextLine敲了回车,然后就通过printWrite.write发送给我们的服务器,
- // 这时服务器接收到的数据是不带换行符的,换行符在我们的客户端nextLine输入的过程中就已经被吞吃了,所有为了避免String request = scanner.nextLine();无法正常读取(nextLine只有遇到换行才结束)
- // 所以在我们的客户端在发送时要通过PrintWrite.println()来发送,会给我们自动添加一个换行符
- // 2、处理客户端发来的请求
- String response = process(request);
- // 3、把响应发送给客户端
- printWriter.println(response);
- // printWriter.write(response); // 这样写有bug,因为我们当前的response中没有带换行符,
- printWriter.flush(); // 刷新缓冲区
- // 打印日志
- System.out.printf("[%s:%d] request: %s, response: %s\n",
- clientSocket.getInetAddress(), clientSocket.getPort(),
- request, response);
- }
- }
- finally {
- clientSocket.close(); // 每一次建立连接都会创建一个clientSocket,该连接结束后要及时关闭,避免内存泄漏
- }
-
- }
- public String process(String request) {
- return request;
- }
-
- public static void main(String[] args) throws IOException {
- TcpEchoServer server = new TcpEchoServer(8000);
- server.start();
- }
- }