目录
- 有连接
- 可靠传输
- 面向字节流
- 有接收缓冲区,也有发送缓冲区
- 大小不限
- package network;
-
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.OutputStream;
- import java.io.PrintWriter;
- import java.net.ServerSocket;
- import java.net.Socket;
- import java.util.Scanner;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
-
- 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("服务器启动!");
- ExecutorService service = Executors.newCachedThreadPool();
- while (true) {
- // 通过 accept 方法, 把内核中已经建立好的连接拿到应用程序中.
- // 建立连接的细节流程都是内核自动完成的. 应用程序只需要 "捡现成" 的.
- Socket clientSocket = serverSocket.accept();
- // processConnection(clientSocket);
- // 此处不应该直接调用 processConnection, 会导致服务器不能处理多个客户端.
- // 创建新的线程来调用更合理的做法.
- // 这种做法可行, 不够好
- // Thread t = new Thread(() -> {
- // processConnection(clientSocket);
- // });
- // t.start();
-
- // 更好一点的办法, 是使用线程池.
- service.submit(new Runnable() {
- @Override
- public void run() {
- processConnection(clientSocket);
- }
- });
- }
- }
-
- // 通过这个方法, 来处理当前的连接.
- public void processConnection(Socket clientSocket) {
- // 进入方法, 先打印一个日志, 表示当前有客户端连上了.
- System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress(), clientSocket.getPort());
- // 接下来进行数据的交互.
- try (InputStream inputStream = clientSocket.getInputStream();
- OutputStream outputStream = clientSocket.getOutputStream()) {
- // 使用 try ( ) 方式, 避免后续用完了流对象, 忘记关闭.
- // 由于客户端发来的数据, 可能是 "多条数据", 针对多条数据, 就循环的处理.
- while (true) {
- Scanner scanner = new Scanner(inputStream);
- if (!scanner.hasNext()) {
- // 连接断开了. 此时循环就应该结束
- System.out.printf("[%s:%d] 客户端下线!\n", clientSocket.getInetAddress(), clientSocket.getPort());
- break;
- }
- // 1. 读取请求并解析. 此处就以 next 来作为读取请求的方式. next 的规则是, 读到 "空白符" 就返回.
- String request = scanner.next();
- // 2. 根据请求, 计算响应.
- String response = process(request);
- // 3. 把响应写回到客户端.
- // 可以把 String 转成字节数组, 写入到 OutputStream
- // 也可以使用 PrintWriter 把 OutputStream 包裹一下, 来写入字符串.
- PrintWriter printWriter = new PrintWriter(outputStream);
- // 此处的 println 不是打印到控制台了, 而是写入到 outputStream 对应的流对象中, 也就是写入到 clientSocket 里面.
- // 自然这个数据也就通过网络发送出去了. (发给当前这个连接的另外一端)
- // 此处使用 println 带有 \n 也是为了后续 客户端这边 可以使用 scanner.next 来读取数据.
- printWriter.println(response);
- // 此处还要记得有个操作, 刷新缓冲区. 如果没有刷新操作, 可能数据仍然是在内存中, 没有被写入网卡.
- printWriter.flush();
- // 4. 打印一下这次请求交互过程的内容
- System.out.printf("[%s:%d] req=%s, resp=%s\n", clientSocket.getInetAddress(), clientSocket.getPort(),
- request, response);
- }
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- try {
- // 在这个地方, 进行 clientSocket 的关闭.
- // processConnection 就是在处理一个连接. 这个方法执行完毕, 这个连接也就处理完了.
- clientSocket.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
-
- public String process(String request) {
- // 此处也是写的回显服务器. 响应和请求是一样的.
- return request;
- }
-
- public static void main(String[] args) throws IOException {
- TcpEchoServer server = new TcpEchoServer(9090);
- 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 {
- private Socket socket = null;
-
- public TcpEchoClient(String serverIp, int serverPort) throws IOException {
- // 需要在创建 Socket 的同时, 和服务器 "建立连接", 此时就得告诉 Socket 服务器在哪里~~
- // 具体建立连接的细节, 不需要咱们代码手动干预. 是内核自动负责的.
- // 当我们 new 这个对象的时候, 操作系统内核, 就开始进行 三次握手 具体细节, 完成建立连接的过程了.
- socket = new Socket(serverIp, serverPort);
- }
-
- public void start() {
- // tcp 的客户端行为和 udp 的客户端差不多.
- // 都是:
- // 3. 从服务器读取响应.
- // 4. 把响应显示到界面上.
- Scanner scanner = new Scanner(System.in);
- try (InputStream inputStream = socket.getInputStream();
- OutputStream outputStream = socket.getOutputStream()) {
- PrintWriter writer = new PrintWriter(outputStream);
- Scanner scannerNetwork = new Scanner(inputStream);
- while (true) {
- // 1. 从控制台读取用户输入的内容
- System.out.print("-> ");
- String request = scanner.next();
- // 2. 把字符串作为请求, 发送给服务器
- // 这里使用 println, 是为了让请求后面带上换行.
- // 也就是和服务器读取请求, scanner.next 呼应
- writer.println(request);
- writer.flush();
- // 3. 读取服务器返回的响应.
- String response = scannerNetwork.next();
- // 4. 在界面上显示内容了.
- 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();
- }
- }