• Java使用TCP api实现回显服务


    两个类:

    ServerSocket:

    专门给TCP服务器用的

    Socket:

    既需要给服务器用,又需要给客户端用
    主要通过这样的类来描述一个socket文件即可,而不需要专门的类来表示传输包,面向字节流以字节为单位传输的。

    举例:

    就好像买房子会有西装革履的小哥哥带你进入销售楼盘,然后喊出一个小姐姐销售,由她给你介绍这个楼盘的详细情况。然后他转身溜走了到马路牙子继续拉人。小哥哥好像就是serverSocket,销售小姐姐就是clientSocket。这里之所以分成两步就是要建立连接,一个专门负责建立连接,一个专门负责通信。

    Socket clientSocket = serverSocket.accept();
    
    • 1
    try{
        clientSocket.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    上述代码针对clientSocket已关闭了,但是对于serverClient就没有关闭。同时在UDP版本的代码里,也没有针对Socket的关闭,原因?
    关闭是为了“释放资源”,释放资源的前提是已经不需要使用这个资源了,但是对于UDP的程序和serverSocket,这些socket都是贯穿程序始终的。这些资源最迟也是跟随进程一起退出释放了。(进程才是系统分配资源的基本单位)。clientSocket每一个连接都有一个,数目很多连接断开也就不再需要了。每次都得保证处理完的连接都进行释放。

    TCP/UDP构造方法的端口比较:

    对于UDP的socket,和TCP的serverSocket来说,构造方法指定的端口表示自己绑定哪个端口。
    对于TCP的socket来说,构造方法的指定端口表示要连接的服务器的端口。

    多线程处理多连接问题:

    服务器同一时刻需要处理多个连接,如果如下所示则不能实现。

    问题描述:

    在这里插入图片描述

    当上面的代码第一次accept结束之后,就会进入processConnection,在processConnection又会有一个循环,如果processConnection里面的循环不结束,processConnection就无法执行完成,外层就无法二次调用accept,就不能接受第二个客户端的链接了。

    解决方法:

    让processConnection的执行和前面的accept的执行互不干扰,不能让processConnection里面的循环导致accpet无法及时调用。因此使用多线程。
    主线程循环调用accpet,当有客户端连接上来的时候,就直接让主线程创建一个新线程,由新线程负责对客户端的若干请求提供服务(在新线程里通过while循环来处理请求)。这个时候多个线程是并发执行的关系,就是各自执行各自的了就不会互相干扰。

    为什么UDP不需要多线程?

    因为UDP不需要处理连接,UDP只需要一个循环就可以处理所有客户端的请求。但是此处TCP既要处理连接又要处理连接中的若干次请求,就需要两个循环,里层循环就会影响到外层循环的进度了。

    服务端:

    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;
    
    /**
     * ClassName TcpEchoServer
     * Description
     * Create by 93900
     * Date 2022/8/2 22:16
     */
    public class TcpEchoServer {
        //listen => 监听
        //但是在java socket 体现不出来监听的含义
        //之所以这么叫 是因为操作系统的原生API有一个操作叫做listen
        //private ServerSocket listenSocket = null;
        private ServerSocket serverSocket = null;
    
        public TcpEchoServer(int port) throws IOException {
            serverSocket = new ServerSocket(port);
        }
    
        public void start() throws IOException {
            System.out.println("服务器启动!");
            while(true){
                //由于TCP是有连接的,不能一上来就读数据,而要先建立连接
                //accept就是在接电话,接电话的前提是有人先给你打了,如果当前没有客户端尝试建立连接,此处的accept就会阻塞
                //accept返回了一个socket对象,称为clientSocket,后续和客户端之间的沟通,都是通过clientSocket来完成的
                //进一步讲,serverSocket就干了一件事,接电话
                Socket clientSocket = serverSocket.accept();
                processConnection(clientSocket);
            }
        }
    
        private void processConnection(Socket clientSocket){
            System.out.printf("[%s:%d] 客户端建立连接!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
            //接下来处理请求和响应
            //这里的针对TCP socket的读写就和文件读写十一摸一样的!
            try(InputStream inputStream = clientSocket.getInputStream()) {
                try(OutputStream outputStream = clientSocket.getOutputStream()){
                    //循环的处理每个请求,分别返回响应
                    Scanner scanner = new Scanner(inputStream);
                    while(true){
                        //1.读取请求
                        if(!scanner.hasNext()){
                            System.out.printf("[%s:%d 客户端断开连接!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
                            break;
                        }
    
                        //此处用Scanner更方便,如果不用scanner就用原生的InputStream的read也是可以的
                        String request = scanner.next();
    
                        //2,根据请求,计算响应
                        String response = process(request);
    
                        //3.把这个响应返回给客户端
                        //为了方便起见,可以使用PrintWriter把OutputStream包裹一下
                        PrintWriter printWriter = new PrintWriter(outputStream);
                        printWriter.println(response);
                        //冲刷缓冲区,如果没有这个功能可能客户端就不能第一时间看到响应结果
                        printWriter.flush();
    
                        System.out.printf("[%s:%d] req: %s, resp: %s\n",clientSocket.getInetAddress().toString(), clientSocket.getPort(), request, response);
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                //此处记得要来个关闭操作
                try{
                    clientSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        private String process(String request){
            return request;
        }
    
        public static void main(String[] args) throws IOException {
            TcpEchoServer tcpEchoServer = new TcpEchoServer(9093);
            tcpEchoServer.start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89

    客户端:

    import java.io.*;
    import java.net.Socket;
    import java.util.Scanner;
    
    /**
     * ClassName TcpEchoClient
     * Description
     * Create by 93900
     * Date 2022/8/2 22:49
     */
    public class TcpEchoClient {
        //此处用普通的socket即可,不用SeverSocket
        //此处也不用手动给客户端指定端口号,让系统自由分配即可
        private Socket socket = null;
    
        public TcpEchoClient(String serverIP, int serverPort) throws IOException {
            socket = new Socket(serverIP, serverPort);
        }
    
        public void start(){
            System.out.println("和服务器连接成功!");
            Scanner scanner = new Scanner(System.in);
            try(InputStream inputStream = socket.getInputStream()) {
                try(OutputStream outputStream = socket.getOutputStream()){
                    while(true){
                        //1.从控制台读取字符串
                        System.out.print("-> ");
                        String request = scanner.next();
    
                        //2.根据读取的字符串,构造请求把请求发送给服务器
                        PrintWriter printWriter = new PrintWriter(outputStream);
                        printWriter.println(request);
                        printWriter.flush();//如果不刷新,可能服务器无法及时看到数据
    
                        //3.从服务器读取响应,并解析
                        Scanner respScanner = new Scanner(inputStream);
                        String response = respScanner.next();
    
                        //4.把结果显示到控制台上
                        System.out.printf("req: %s, resp: %s\n", request, response);
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        public static void main(String[] args) throws IOException {
            TcpEchoClient tcpEchoClient = new TcpEchoClient("127.0.0.1", 9094);
            tcpEchoClient.start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
  • 相关阅读:
    java计算机毕业设计江西婺源旅游文化推广系统源码+mysql数据库+系统+lw文档+部署
    Euclidean Distance Transform - EDT
    STM32CubeMX教程7 TIM 通用定时器 - 输入捕获
    Servlet(一)
    lv5 嵌入式开发-7 有名管道和无名管道
    python爬虫快速学-4张思维图高清下载
    MVCC及实现原理
    Cron表达式介绍
    如何应对继承的双面性
    洛谷刷题C语言:I wanna a feasitor(化验器)、天才⑨与天才拆分、RIJEČI、王国比赛、MORTADELA
  • 原文地址:https://blog.csdn.net/weixin_46429649/article/details/126234624