• 网络编程套接字之三【TCP】


    目录

    1. ServerSocket API(给服务器端使用的类)

    2. Socket API(既给服务器使用,也给客户端使用)

    3. 写TCP回显—服务器

    4. 使用线程池后的TCP服务器代码(最终)

    5. 写回显-客户端

    6. TCP回显—客户端代码

    7. 运行回显服务器和客户端


    TCP流套接字编程

    1. ServerSocket API(给服务器端使用的类)

     ServerSocket 是创建TCP服务端Socket的API。

     构造方法

    方法签名说明
    ServerSocket(int port)创建一个服务端流套接字Socket,并绑定到指定端口

    方法

    方法签名说明
    Socket accept()开始监听指定端口(创建时绑定的端口),有客户端连接后,返回一个服务端Socket对象,并基于改Socket建立与客户端的连接,否则阻塞等待(接受客户端的连接)
    void close()关闭此套接字

    2. Socket API(既给服务器使用,也给客户端使用)

    Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket。

    不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的

    构造方法

    方法签名说明
    Socket(String host, int port)

    创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的

    进程建立连接(尝试和指定的服务器建立连接)

    方法

    方法签名说明
    InetAddress getInetAddress()返回套接字所连接的地址(返回套接字获取到对方的IP地址和端口)
    InputStream getInputStream()返回此套接字的输入流(通过Socket可以获取到两个流对象,分别用来读和写)
    OutputStream getOutputStream()返回此套接字的输出流

    3. 写TCP回显—服务器

    1.先写一个ServerSocket对象

        private ServerSocket listenSocket = null;

    2.下面写Tcp服务器构造方法

    1. public TcpEchoServer(int port) throws IOException {
    2. listenSocket = new ServerSocket(port);
    3. }

    3.写一个启动服务器的方法start(),在start中写上while循环来执行

       a)调用accept来接收客户端的连接

       b)再处理这个连接,这里写一个processConnection()方法来处理

    1. public void start() throws IOException {
    2. System.out.println("服务器启动!");
    3. while(true) {
    4. //1. 先调用 accept 来接受客户端的连接
    5. Socket clientSocket = listenSocket.accept();
    6. //2. 再处理这个连接
    7. processConnection(clientSocket);
    8. }
    9. }

    4.下面来写这个processConnection()方法,处理连接客户端连接

       方法中写try(这里写上InputStream(读)和OutPutStream(写)对象,写在try中帮助资源回收) {这里写上写具体处理逻辑步骤}

    注意最后必须要写上finally来close关闭clientSocket

    1. private void processConnection(Socket clientSocket) throws IOException {
    2. System.out.printf("[%s:%d] 客户端上线!\n",
    3. clientSocket.getInetAddress().toString(),
    4. clientSocket.getPort());
    5. //接下来处理客户端请求
    6. try(InputStream inputStream = clientSocket.getInputStream();
    7. OutputStream outputStream = clientSocket.getOutputStream()) {
    8. while(true) {
    9. }
    10. }finally {
    11. //这里要关闭socket,是因为
    12. //socket也是一个文件,一个进程能够同时打开的文件个数有上限(PCB文件描述符表,不是无限的
    13. clientSocket.close();
    14. }
    15. }

       a)读取请求并解析

    1. Scanner scanner = new Scanner(inputStream);
    2. if(!scanner.hasNext()) {
    3. //读完了,连接可以断开了
    4. System.out.printf("[%s:%d] 客户端下线!\n",
    5. clientSocket.getInetAddress().toString(),
    6. clientSocket.getPort());
    7. break;
    8. }
    9. String request = scanner.next();

       b)根据请求计算响应(这里写一个process方法)

                    String response = process(request);
    1. private String process(String request) {
    2. return request;
    3. }

     c)响应写回到客户端

    1. PrintWriter printWriter = new PrintWriter(outputStream);
    2. printWriter.println(response);
    3. //刷新缓冲区确保数据确实通过网卡发送出去了
    4. printWriter.flush();

       d)将发送的信息显示到服务器界面上

    1. System.out.printf("[%s:%d] req: %s; resp: %s\n",
    2. clientSocket.getInetAddress().toString(),
    3. clientSocket.getPort(),
    4. request,response);

    5.最后再写上mian方法来执行服务器

    1. public static void main(String[] args) throws IOException {
    2. TcpEchoServer tcpEchoServer = new TcpEchoServer(9090);
    3. tcpEchoServer.start();
    4. }
    1. import java.io.IOException;
    2. import java.io.InputStream;
    3. import java.io.OutputStream;
    4. import java.io.PrintWriter;
    5. import java.net.ServerSocket;
    6. import java.net.Socket;
    7. import java.util.Scanner;
    8. import java.util.concurrent.Semaphore;
    9. /**
    10. * Created with IntelliJ IDEA.
    11. * Description:
    12. * User: 28463
    13. * Date: 2022—10—14
    14. * Time: 17:05
    15. */
    16. public class TcpEchoServer {
    17. //代码中会设计到多个 socket 对象,使用不同的名字来区分
    18. private ServerSocket listenSocket = null;
    19. public TcpEchoServer(int port) throws IOException {
    20. listenSocket = new ServerSocket(port);
    21. }
    22. public void start() throws IOException {
    23. System.out.println("服务器启动!");
    24. while(true) {
    25. //1. 先调用 accept 来接受客户端的连接
    26. Socket clientSocket = listenSocket.accept();
    27. //2. 再处理这个连接
    28. processConnection(clientSocket);
    29. }
    30. }
    31. private void processConnection(Socket clientSocket) throws IOException {
    32. System.out.printf("[%s:%d] 客户端上线!\n",
    33. clientSocket.getInetAddress().toString(),
    34. clientSocket.getPort());
    35. //接下来处理客户端请求
    36. try(InputStream inputStream = clientSocket.getInputStream();
    37. OutputStream outputStream = clientSocket.getOutputStream()) {
    38. while(true) {
    39. //1.读取请求并解析
    40. Scanner scanner = new Scanner(inputStream);
    41. if(!scanner.hasNext()) {
    42. //读完了,连接可以断开了
    43. System.out.printf("[%s:%d] 客户端下线!\n",
    44. clientSocket.getInetAddress().toString(),
    45. clientSocket.getPort());
    46. break;
    47. }
    48. String request = scanner.next();
    49. //2.根据请求计算响应
    50. String response = process(request);
    51. //3.响应写回到客户端
    52. PrintWriter printWriter = new PrintWriter(outputStream);
    53. printWriter.println(response);
    54. //刷新缓冲区确保数据确实通过网卡发送出去了
    55. printWriter.flush();
    56. System.out.printf("[%s:%d] req: %s; resp: %s\n",
    57. clientSocket.getInetAddress().toString(),
    58. clientSocket.getPort(),
    59. request,response);
    60. }
    61. } catch (IOException e) {
    62. e.printStackTrace();
    63. } finally {
    64. clientSocket.close();
    65. }
    66. }
    67. private String process(String request) {
    68. return request;
    69. }
    70. public static void main(String[] args) throws IOException {
    71. TcpEchoServer tcpEchoServer = new TcpEchoServer(9090);
    72. tcpEchoServer.start();
    73. }
    74. }

     下面思考为啥代码最后finally要执行clientSocket的close,而前面的listenSocket以及UDP程序中的socekt为啥就没close?

    但是上面的代码有个问题,只能处理一个客户端的请求,(本质上第二个客户端的消息是发出去了,但服务器此时还在执行第一个客户端的请求,只要从第一个客户端这里出来,服务器就会立刻执行第二个客户端的消息)

    我们希望的是既能够快速重复的调用到accept(也就是连接多个客户端),又能够循环的处理客户端的请求。所以就需要使用到多线程了

    那么为什么前面UDP就不需要考虑这个问题,而TCP需要考虑 

    UDP是无连接,客户端直接发消息就行(不必专注于处理某一个客户端)

    TCP建立连接之后,要处理客户端的多次请求,才导致无法快速的调用到accept(长连接)(主要原因)

    如果TCP每个连接只处理一个客户端的请求,也能够保证快速调用到accept(短连接)

     下面使用多线程,给每个客户端连上来的都分配一个新的线程负责处理请求

     但是直接这样使用多线程,如果循环多次,对应就会创建很多线程,等线程执行完,又会消毁很多的线程,所以更好的方法就是使用线程池

    4. 使用线程池后的TCP服务器代码(最终)

    1. import java.io.IOException;
    2. import java.io.InputStream;
    3. import java.io.OutputStream;
    4. import java.io.PrintWriter;
    5. import java.net.ServerSocket;
    6. import java.net.Socket;
    7. import java.util.Scanner;
    8. import java.util.concurrent.Executor;
    9. import java.util.concurrent.ExecutorService;
    10. import java.util.concurrent.Executors;
    11. import java.util.concurrent.Semaphore;
    12. /**
    13. * Created with IntelliJ IDEA.
    14. * Description:
    15. * User: 28463
    16. * Date: 2022—10—14
    17. * Time: 17:05
    18. */
    19. public class TcpEchoServer {
    20. //代码中会设计到多个 socket 对象,使用不同的名字来区分
    21. private ServerSocket listenSocket = null;
    22. public TcpEchoServer(int port) throws IOException {
    23. listenSocket = new ServerSocket(port);
    24. }
    25. public void start() throws IOException {
    26. System.out.println("服务器启动!");
    27. ExecutorService service = Executors.newCachedThreadPool();
    28. while(true) {
    29. //1. 先调用 accept 来接受客户端的连接
    30. Socket clientSocket = listenSocket.accept();
    31. //2. 再处理这个连接,这里应该要使用多线程,每个客户端连上来都分配一个新的线程负责处理
    32. service.submit(new Runnable() {
    33. @Override
    34. public void run() {
    35. try {
    36. processConnection(clientSocket);
    37. } catch (IOException e) {
    38. e.printStackTrace();
    39. }
    40. }
    41. });
    42. }
    43. }
    44. private void processConnection(Socket clientSocket) throws IOException {
    45. System.out.printf("[%s:%d] 客户端上线!\n",
    46. clientSocket.getInetAddress().toString(),
    47. clientSocket.getPort());
    48. //接下来处理客户端请求
    49. try(InputStream inputStream = clientSocket.getInputStream();
    50. OutputStream outputStream = clientSocket.getOutputStream()) {
    51. while(true) {
    52. //1.读取请求并解析
    53. Scanner scanner = new Scanner(inputStream);
    54. if(!scanner.hasNext()) {
    55. //读完了,连接可以断开了
    56. System.out.printf("[%s:%d] 客户端下线!\n",
    57. clientSocket.getInetAddress().toString(),
    58. clientSocket.getPort());
    59. break;
    60. }
    61. String request = scanner.next();
    62. //2.根据请求计算响应
    63. String response = process(request);
    64. //3.响应写回到客户端
    65. PrintWriter printWriter = new PrintWriter(outputStream);
    66. printWriter.println(response);
    67. //刷新缓冲区确保数据确实通过网卡发送出去了
    68. printWriter.flush();
    69. System.out.printf("[%s:%d] req: %s; resp: %s\n",
    70. clientSocket.getInetAddress().toString(),
    71. clientSocket.getPort(),
    72. request,response);
    73. }
    74. } catch (IOException e) {
    75. e.printStackTrace();
    76. } finally {
    77. //这里要关闭socket,是因为
    78. //socket也是一个文件,一个进程能够同时打开的文件个数有上限(PCB文件描述符表,不是无限的
    79. clientSocket.close();
    80. }
    81. }
    82. private String process(String request) {
    83. return request;
    84. }
    85. public static void main(String[] args) throws IOException {
    86. TcpEchoServer tcpEchoServer = new TcpEchoServer(9090);
    87. tcpEchoServer.start();
    88. }
    89. }

    5. 写回显-客户端

     1. 先写一个Socket对象,客户端用Socket来建立连接

    private Socket socket = null;

    2.下面写Tcp客户端构造(和Udp区别较大,有连接和无连接的区别)

    1. public TcpEchoClient(String serverIP, int serverPort) throws IOException {
    2. //和服务器建立连接,就需要知道服务器在哪
    3. socket = new Socket(serverIP,serverPort);
    4. }

    3.写客户端执行方法start(),给start里面放try,try中执行的就是while,来让客户端循环输入,还要在try后面括号中写上InputStream(读)和OutputStream(写)的对象(写在括号中中try会自动帮助,关掉资源)

    1. public void start() throws IOException {
    2. Scanner scan = new Scanner(System.in);
    3. try(InputStream inputStream = socket.getInputStream();
    4. OutputStream outputStream = socket.getOutputStream()) {
    5. while(true) {
    6. }
    7. }
    8. }

    while中写  a)从控制台读取数据,构造一个请求

    1. System.out.println("-> ");
    2. String request = scan.next();

                     b)发送请求给服务器(PrintWriter)

    1. PrintWriter printWriter = new PrintWriter(outputStream);
    2. printWriter.println(request);
    3. //这个 flush 不要忘记,否则可能导致请求没有真发出去
    4. printWriter.flush();

                     c)从服务器读取响应

    1. Scanner respScanner = new Scanner(inputStream);
    2. String response = respScanner.next();

                     d)把响应显示到界面上

                    System.out.println(response);

    4.最后再写上mian方法来执行客户端

    1. public static void main(String[] args) throws IOException {
    2. TcpEchoClient tcpEchoClient = new TcpEchoClient("127.0.0.1",9090);
    3. tcpEchoClient.start();

     Udp和Tcp构造的区别,有连接和无连接 

    6. TCP回显—客户端代码

    1. import java.io.IOException;
    2. import java.io.InputStream;
    3. import java.io.OutputStream;
    4. import java.io.PrintWriter;
    5. import java.net.Socket;
    6. import java.util.Scanner;
    7. /**
    8. * Created with IntelliJ IDEA.
    9. * Description:
    10. * User: 28463
    11. * Date: 2022—10—14
    12. * Time: 17:06
    13. */
    14. public class TcpEchoClient {
    15. //客户端需要使用这个 Socket 对象来建立连接
    16. private Socket socket = null;
    17. public TcpEchoClient(String serverIP, int serverPort) throws IOException {
    18. //和服务器建立连接,就需要知道服务器在哪
    19. socket = new Socket(serverIP,serverPort);
    20. }
    21. public void start() throws IOException {
    22. Scanner scan = new Scanner(System.in);
    23. try(InputStream inputStream = socket.getInputStream();
    24. OutputStream outputStream = socket.getOutputStream()) {
    25. while(true) {
    26. //1.从控制台读取数据,构造成一个请求
    27. System.out.println("-> ");
    28. String request = scan.next();
    29. //2.发送请求给服务器
    30. PrintWriter printWriter = new PrintWriter(outputStream);
    31. printWriter.println(request);
    32. //这个 flush 不要忘记,否则可能导致请求没有真发出去
    33. printWriter.flush();
    34. //3.从服务器读取响应
    35. Scanner respScanner = new Scanner(inputStream);
    36. String response = respScanner.next();
    37. //4.把响应显示到界面上
    38. System.out.println(response);
    39. }
    40. }
    41. }
    42. public static void main(String[] args) throws IOException {
    43. TcpEchoClient tcpEchoClient = new TcpEchoClient("127.0.0.1",9090);
    44. tcpEchoClient.start();
    45. }
    46. }

    7. 运行回显服务器和客户端

     

     

  • 相关阅读:
    Mysql存储过程记录
    Linux教程:如何安装redis服务并搭建三主三从集群部署环境
    进程入门与PCB基础知识.
    (附源码)springboot美食分享系统 毕业设计 612231
    java基于springboot+vue的二手车信息网站系统
    【开源】基于JAVA的校园二手交易系统
    输入法显示到语言栏_状态栏
    金融业信贷风控算法5-时间序列模型概述
    问题:Qt中软件移植到笔记本中界面出现塌缩
    408-2013
  • 原文地址:https://blog.csdn.net/m0_58761900/article/details/127358914