22. 网络编程(2)——TCP 协议

雨点打透心脏的1/2处 2022-10-23 08:05 367阅读 0赞

网络编程需要依靠Socket API,在java标准库中有两种风格:
1.(UDP)DatagramSocket:面向数据报(发送接收数据,必须以一定的数据报为单位进行传输)
2.(TCP)ServerSocket:面向字节流

UDP和TCP就是传输层的两个最重要的协议

TCP

服务器逻辑:

1.初始化服务器
2.进入主循环
1)先去从内核中获取到一个TCP的连接
2)处理这个TCP的连接
a)读取请求并解析
b)根据请求计算响应
c)把响应写回给客户端

服务器实现:

  1. import java.io.*;
  2. import java.net.ServerSocket;
  3. import java.net.Socket;
  4. public class TcpEchoServer {
  5. //1.初始化服务器
  6. //2.进入主循环
  7. // 1)先去从内核中获取到一个TCP的连接
  8. // 2)处理这个TCP的连接
  9. // a)读取请求并解析
  10. // b)根据请求计算响应
  11. // c)把响应写回给客户端
  12. private ServerSocket serverSocket = null;
  13. public TcpEchoServer(int port) throws IOException {
  14. serverSocket = new ServerSocket(port);
  15. }
  16. public void start() throws IOException {
  17. System.out.println("服务器启动");
  18. while (true){
  19. // 1)先去从内核中获取到一个TCP的连接
  20. //TCP的连接管理是由操作系统内核来管理的(先描述,再组织【使用一个阻塞队列来组织若干个连接对象】)
  21. //当连接建立成功,内核已经把这个连接对象放到了阻塞队列中了,代码中调用到accept就是从阻塞队列中取出一个连接对象
  22. //在应用程序中就是Socket对象
  23. //如果服务器启动后,没有客户端建立连接,此时代码中的accept就会阻塞,直到有客户建立连接了才停止阻塞
  24. Socket clientSocket = serverSocket.accept();
  25. // 2)处理这个TCP的连接
  26. processConnection(clientSocket);
  27. }
  28. }
  29. private void processConnection(Socket clientSocket) {
  30. System.out.printf("[%s:%d] 客户端上线\n",clientSocket.getInetAddress().toString(),
  31. clientSocket.getPort());
  32. //clientSocket.getInetAddress().toString():获得出IP
  33. //clientSocket.getPort()):获得端口号
  34. //通过 clientSocket 来和客户端交互,先做好准备工作,获取到clientSocket中流对象
  35. try(BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
  36. BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()))) {
  37. //getInputStream();getOutputStream():字节流
  38. //InputStreamReader;OutputStreamWriter:把字节流转成字符流,
  39. //BufferedReader;BufferedWriter:套上缓冲区
  40. //此处是长连接版本:一次连接的过程中,需要处理多个请求和响应
  41. //短连接就是去掉while循环
  42. while (true) {
  43. // a)读取请求并解析
  44. String request = bufferedReader.readLine();
  45. //此处暗含一个信息(协议):
  46. //客户端发的数据必须是一个按行发送的数据(每一条数据占一行)
  47. // b)根据请求计算响应
  48. String response = process(request);
  49. // c)把响应写回给客户端(客户端要按行来读)
  50. bufferedWriter.write(response+"\n");
  51. bufferedWriter.flush();
  52. System.out.printf("[%s:%d] req: %s; resp: %s\n",clientSocket.getInetAddress().toString(),
  53. clientSocket.getPort(),request,response);
  54. }
  55. } catch (IOException e) {
  56. e.printStackTrace();
  57. System.out.printf("[%s:%d] 客户端下线\n",clientSocket.getInetAddress().toString(),
  58. clientSocket.getPort());
  59. }
  60. }
  61. private String process(String request) {
  62. return request;
  63. }
  64. public static void main(String[] args) throws IOException {
  65. TcpEchoServer server = new TcpEchoServer(9090);
  66. server.start();
  67. }
  68. }

这里的服务器方法实现中使用到了长连接,那么对应的就是短连接
长连接:一个连接中,客户端和服务器之间交互N次,直到满足一定条件在断开
短连接:一个连接中,客户端和服务器之间交互一次,交互完毕就断开连接

长连接比短连接效率更高

客户端逻辑:

1.启动客户端(一定不要绑定端口号)
2.进入主循环
a)读取用户输入内容
b)构造一个请求发送给服务器
c)读取服务器的响应数据
d)把响应数据显示到界面上

客户端:

  1. import java.io.*;
  2. import java.net.Socket;
  3. import java.util.Scanner;
  4. public class TcpEchoClient {
  5. //1.启动客户端(一定不要绑定端口号)
  6. //2.进入主循环
  7. // a)读取用户输入内容
  8. // b)构造一个请求发送给服务器
  9. // c)读取服务器的响应数据
  10. // d)把响应数据显示到界面上
  11. private Socket socket = null;
  12. public TcpEchoClient(String serverIp, int serverPort) throws IOException {
  13. socket = new Socket(serverIp,serverPort);
  14. }
  15. public void start(){
  16. System.out.println("客户端启动");
  17. Scanner scanner = new Scanner(System.in);
  18. try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
  19. BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()))){
  20. while (true){
  21. // a)读取用户输入内容
  22. System.out.println("->");
  23. String request = scanner.nextLine();
  24. if ("exit".equals(request)){
  25. break;
  26. }
  27. // b)构造一个请求发送给服务器
  28. bufferedWriter.write(request + "\n");//按行写
  29. bufferedWriter.flush();
  30. // c)读取服务器的响应数据
  31. String response = bufferedReader.readLine();
  32. // d)把响应数据显示到界面上
  33. System.out.println(response);
  34. }
  35. } catch (Exception e) {
  36. e.printStackTrace();
  37. }
  38. }
  39. }

运行结果:
在这里插入图片描述

在这里插入图片描述

存在的问题

以上的服务器和客户端交互的过程中,第一个客户端发送请求,就会进入while循环,只有当第一个客户端退出的时候,第二个客户端发送的请求才会被响应,其原因就是客户端大于一个的时候,就会在accept方法中阻塞,这时,为了提高效率,也就是说为了让多个客户端一起被服务器响应,就可以利用多线程的方式

代码如下:

  1. import java.io.*;
  2. import java.net.ServerSocket;
  3. import java.net.Socket;
  4. public class TcpThreadEchoServer {
  5. private ServerSocket serverSocket = null;
  6. public TcpThreadEchoServer(int port) throws IOException {
  7. serverSocket = new ServerSocket(port);
  8. }
  9. public void start() throws IOException {
  10. System.out.println("服务器启动");
  11. while (true){
  12. Socket clientSocket = serverSocket.accept();
  13. //针对这个连接,单独创建一个线程负责处理
  14. Thread t = new Thread(new Runnable() {
  15. @Override
  16. public void run() {
  17. processConnection(clientSocket);
  18. }
  19. });
  20. t.start();
  21. }
  22. }
  23. private void processConnection(Socket clientSocket) {
  24. System.out.printf("[%s:%d] 客户端上线\n",clientSocket.getInetAddress().toString(),
  25. clientSocket.getPort());
  26. try(BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
  27. BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()))) {
  28. while (true) {
  29. // a)读取请求并解析
  30. String request = bufferedReader.readLine();
  31. // b)根据请求计算响应
  32. String response = process(request);
  33. // c)把响应写回给客户端(客户端要按行来读)
  34. bufferedWriter.write(response+"\n");
  35. bufferedWriter.flush();
  36. System.out.printf("[%s:%d] req: %s; resp: %s\n",clientSocket.getInetAddress().toString(),
  37. clientSocket.getPort(),request,response);
  38. }
  39. } catch (IOException e) {
  40. e.printStackTrace();
  41. System.out.printf("[%s:%d] 客户端下线\n",clientSocket.getInetAddress().toString(),
  42. clientSocket.getPort());
  43. }
  44. }
  45. private String process(String request) {
  46. return request;
  47. }
  48. public static void main(String[] args) throws IOException {
  49. TcpThreadEchoServer server = new TcpThreadEchoServer(9090);
  50. server.start();
  51. }
  52. }

(只有start()方法变了,其他均与之前代码一样)

但是这也会存在一个问题,如果客户端太多了,那么创建的线程也太多了,服务器需要频繁的创建和销毁线程,这时就可以使用标准库中的线程池

  1. import java.io.*;
  2. import java.net.ServerSocket;
  3. import java.net.Socket;
  4. import java.util.concurrent.ExecutorService;
  5. import java.util.concurrent.Executors;
  6. public class TcpThreadPoolEchoServer {
  7. private ServerSocket serverSocket = null;
  8. public TcpThreadPoolEchoServer(int port) throws IOException {
  9. serverSocket = new ServerSocket(port);
  10. }
  11. public void start() throws IOException {
  12. System.out.println("服务器启动");
  13. //先创建一个线程池
  14. ExecutorService executorService = Executors.newCachedThreadPool();
  15. while (true) {
  16. Socket clientSocket = serverSocket.accept();
  17. executorService.execute(new Runnable() {
  18. @Override
  19. public void run() {
  20. processConnection(clientSocket);
  21. }
  22. });
  23. }
  24. }
  25. private void processConnection(Socket clientSocket) {
  26. System.out.printf("[%s:%d] 客户端上线\n",clientSocket.getInetAddress().toString(),
  27. clientSocket.getPort());
  28. try(BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
  29. BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()))) {
  30. while (true) {
  31. // a)读取请求并解析
  32. String request = bufferedReader.readLine();
  33. // b)根据请求计算响应
  34. String response = process(request);
  35. // c)把响应写回给客户端(客户端要按行来读)
  36. bufferedWriter.write(response+"\n");
  37. bufferedWriter.flush();
  38. System.out.printf("[%s:%d] req: %s; resp: %s\n",clientSocket.getInetAddress().toString(),
  39. clientSocket.getPort(),request,response);
  40. }
  41. } catch (IOException e) {
  42. e.printStackTrace();
  43. System.out.printf("[%s:%d] 客户端下线\n",clientSocket.getInetAddress().toString(),
  44. clientSocket.getPort());
  45. }
  46. }
  47. private String process(String request) {
  48. return request;
  49. }
  50. public static void main(String[] args) throws IOException {
  51. TcpThreadPoolEchoServer server = new TcpThreadPoolEchoServer(9090);
  52. server.start();
  53. }
  54. }

发表评论

表情:
评论列表 (有 0 条评论,367人围观)

还没有评论,来说两句吧...

相关阅读