编程语言
首页 > 编程语言> > J2SE-网络编程

J2SE-网络编程

作者:互联网

Socket概览

Socket:所谓套接字(Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。

C/S通信模式:C(Client)即客户端、S(Server)即服务端,一般情况下会存在多个客户端同时访问服务端;下述服务端简称S,客户端1简称C1,客户2简称C2。下述以此结构为例。

按照百科定义,从C1访问S需要两个Socket,客户端Socket-C1,服务端Socket-S,用于数据流的发送和接收。

服务端Socket(Socket-S)创建方式:创建ServerSocket,创建时需要指定端口(S-port),创建后通过阻塞方法accept监听客户端接入,直到ServerSocket关闭。当C1发送数据被S后,ServerSocket的accept方法监听到C1接入会生成一个Socket-S,Socket-S中包含的信息C1-ip、C1-port、S-port。所以ServerSocket的作用:监听客户端接入,然后在服务端创建Socket-S来处理数据流。

客户端Socket(Socket-C1)创建方式:Socket-C1,通过指定S-ip(服务端ip)、S-port(服务端端口)创建,C1-ip(客户端端口)在Socket创建的时候由系统自动分配;Socket-S,通过ServerSocket的accept方法获得。

根据Java编程思想,Socket服务端、客户端代码如下:

// Socket服务端
@Slf4j
public class TestSocketServer {

    @SneakyThrows
    public static void main(String[] args) {
        int port = 8081;

        try (ServerSocket serverSocket = new ServerSocket(port)) {
            log.debug("服务端开始监听客户端的连接");

            try(Socket accept = serverSocket.accept()) {
                log.debug("监听客户端接入,socket建立 {}", accept);

                // 通过输入流从socket中获取输入数据
                BufferedReader reader = new BufferedReader(new InputStreamReader(accept.getInputStream()));

                // 通过输出流将响应数据传给socket, 指定println方法自动flush数据
                PrintWriter printWriter = new PrintWriter(new BufferedOutputStream(accept.getOutputStream()), true);

                while (true) {
                    // 读取接收到的数据并打印, 读取不到会等待,数据流异常或者断开会返回null
                    String readLine = reader.readLine();
                    if ("END".equals(readLine)) {
                        break;
                    }
                    log.debug(readLine);

                    // 接收到的数据原样返回
                    printWriter.println(readLine);
                }
            }
        }

    }
}

// Socket客户端
@Slf4j
public class TestSocketClient {

    @SneakyThrows
    public static void main(String[] args) {

        try(Socket socket = new Socket(InetAddress.getByName(null), 8081)){
            log.info("数据发给服务端 {}", socket);

            // 客户端发送数据
            PrintWriter printWriter = new PrintWriter(new BufferedOutputStream(socket.getOutputStream()), true);
            String send = System.currentTimeMillis() + "";
            printWriter.println(send);
            log.info(">>>>> {}", send);

            // 数据需要发送10s
            Thread.sleep(30000);

            // 发送结束标志
            printWriter.println("END");

            // 客户端接收数据
            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            while (true) {
                String readLine = reader.readLine();
                if(Objects.isNull(readLine)){
                    break;
                }

                log.info("<<<<< {}", readLine);
            }
        }

        log.debug("消息发送结束");
    }
}

问题和改进

ServerSocket监听到客户端接入后,创建Socket-S的时间一般很短;但是创建Socket-S后,其接收数据并处理数据流可能很耗费时间。

Socket-S处理数据流的流程,从Socket-S获取输入流,通过while(true)读取输入流,直到从数据流中读取到结束标志才会结束循环,如果Socket-C1一分钟后发送结束标志,则循环会阻塞一分钟,所以Socket-S处理流耗费的时间可能很长。

如果Socket-S耗时很长,则ServerSocket只能等待Socket-S处理结束,才能继续处理新的客户端C2接入。多个客户端并发接入,则会因为服务端数据流处理耗时的问题产生很大的延时,因此必须解决Socket-S处理数据流耗时(IO耗时)的问题。

改进方案一:ServerSocket生成Socket-S后,开启一个线程处理数据流,则不会影响服务端继续处理其他客户端接入。

改进方案二:传统的IO模型在处理IO的时候是阻塞,通过新的IO模型NIO处理数据,即可以在一个线程中通过轮询的方式解决IO处理阻塞的问题。

多线程的可以解决IO耗时的问题,但是线程的创建、销毁是很耗时CPU资源的,一般情况下不会无限制的创建线程。因此只能采取第二种方式,通过新的IO模型解决IO耗时的问题。

参考资料

标签:J2SE,Socket,编程,网络,ServerSocket,new,C1,服务端,客户端
来源: https://www.cnblogs.com/raifujisann/p/14387976.html