Java socket server接收并返回数据(多线程socket)

Java socket套接字编程入门

服务之间的通信,有基于应用层的http,也有基于底层的tcp协议。通信会涉及到数据的发送、接收以及拆组包,信息控制等网络流程。网络分层分为两种:五层、七层。

img

java socket 套接字编程指的是编写跨多台计算机执行的程序。在这些计算机之间都是通过网络进行连接。

有两种协议用于套接字开发:UDPUser Datagram Protocol)用户数据报协议和TCPTransfer Control Protocol)传输控制协议。因此socket是属于传输层。

UDP和TCP的主要区别:UDP是无连接传输模式,这意味着客户端与服务端没有会话。 TCP是面向连接传输模式,意味着必须首先要在客户端与服务端建立一个连接(排他性连接),以便于通信。

UDP没有会话,没有办法保证通信内容的完整性,因有可能会出现丢包情况。没有建立会话,因此每次通信更快传输更多,使用于就算偶尔丢包也能正常使用的场景。

TCP通过会话的方式来保证内容的完整性,会话的过程自然会涉及到创建连接,监听,接收处理,返回等流程。

img

清楚流程,再来看下各端代码逻辑。

server服务端

public static void main(String[] args)  {
    
    

        try {
    
    
            //1、建立服务连接
            //服务运行于特定的服务器中并绑定一个socket 端口
            ServerSocket serverSocket = new ServerSocket(9090);

            //2、创建socket并监听
            //监听socket创建的连接并接受到它,此为阻塞方法,直到连接创建
            Socket socket = serverSocket.accept();
            //接收传输过来的数据
            //设置每次接收块的大小
            char[] data = new char[10];
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            int len = 0;
            //3、接收数据
            //处理传输数据大于接收块的容量
            //读取的字符数,如果已到达流的末尾则为-1
            while ((len = br.read(data)) != -1){
    
    
                System.out.println(String.valueOf(data, 0, len));
            }

            //关闭
            //顺序:按打开的顺序进行反序关闭
            br.close();
            socket.close();
            serverSocket.close();

        } catch (Exception e){
    
    
            e.printStackTrace();
        }
    }

client客户端

    public static void main(String[] args) throws IOException {
    
    

        //创建socket连接
        Socket socket = new Socket("127.0.0.1", 9090);

        //发送数据到服务端
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        writer.write("这是一个测试");
        writer.flush();//刷新缓冲流,把write提交进去,不然服务端没接收到内容
     // 关闭连接,否则服务端抛出:java.net.SocketException: Connection reset     //通知服务端,客户端发送数据完成。
        writer.close();
    }

关于java.net.SocketException: Connection reset。 谈不上通知,是一种标识,让服务端知道发送数据已经完成。服务端是通过阻塞的方式来监听和接收数据,在整个过程中有二处阻塞:1、监听accept();2、读取read(data)。

如果客户端没有close(),服务端read() 并不知道是否结束,但随着客户main主线程运行完成,就会强行切断关闭此连接,因此才会有此异常抛出。

Java socket server接收并返回数据

在上边的学习中,会发现,服务端只能处理一次,accept()是一种阻塞状态,因此它只能同时处理一个请求,其它的请求只能排队等待前面的处理完成。

为了支持多任务同时处理的能力,首先不要让主服务运行完成即结束,而是一种死循环的方式,让一直等待接收,其次,处理数据的需要另开线程进行,即socket的生命周期置于新开线程中。

img

先定义SocketHandler作为线程单独处理socket。

public class SocketHandler implements Runnable {
    
    

    private Socket socket;

    SocketHandler(Socket socket) {
    
    
        this.socket = socket;
    }

    @Override
    public void run() {
    
    
        BufferedReader bufferedReader = null;
        PrintWriter writer = null;
        try {
    
    
            System.out.println("thread:\t" + Thread.currentThread().getName() );
            bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));       //true,表示自动刷新,不需要人为触发
            writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true);

            String userInput;
            while ((userInput = bufferedReader.readLine()) != null) {
    
    
                if ("exit".equals(userInput)) {
    
    
                    System.out.println("退出连接通信\t");
                    break;
                }
                System.out.println("接收内容:\t" + userInput);
                String result = "服务器时间:" + LocalDateTime.now() +"\t" + new StringBuilder(userInput).reverse();
                writer.println(result);
            }
        } catch (IOException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            try {
    
    
                if (writer != null) writer.close();
                if (bufferedReader != null) bufferedReader.close();
                socket.close();
            } catch (IOException ioException) {
    
    
                ioException.printStackTrace();
            }
        }
    }
}

server服务端:

public static void main(String[] args) throws IOException {
    
    
        ServerSocket serverSocket = new ServerSocket(9090);

        //固定线程池来接收处理
        Executor executor = Executors.newFixedThreadPool(3);

        //死循环,保证主线程不退出
        while (true){
    
    
            executor.execute(new SocketHandler(serverSocket.accept()));
        }
    }

client客户端:

public class Client {
    
    

    public void send(String message){
    
    
        Socket socket = null;
        BufferedReader bufferedReader = null;
        PrintWriter writer = null;
        try {
    
    
            socket = new Socket("127.0.0.1",9090);

            //发送数据到服务端
            writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true);
            writer.println(message);
            System.out.println("发送内容:\t"+message);

            //接收服务端返回数据流
            bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String input = null;
            while ((input = bufferedReader.readLine()) != null){
    
    
                System.out.println("接收服务端数据:\t"+input);
                //退出指令,关闭连接
                writer.println("exit");
                break;
            }
        } catch (UnknownHostException e) {
    
    
            e.printStackTrace();
        } catch (IOException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            try {
    
    
                if (writer != null) writer.close();
                if (bufferedReader != null) bufferedReader.close();
                socket.close();
            } catch (IOException e) {
    
    
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
    
    
        Client client = new Client();
        for (int i = 0; i < 10; i++) {
    
    
            String text = RandomStringUtils.randomAlphabetic(5);
            client.send(text);
        }
    }

PrintWriter 与 BufferedWriter 的区别:

PrintWriter:是一种格式化对象的输出流,是一种高级流。此类实现了所有的PrintStream的print方法,它不包含用于写原始字节的方法,而对于原始字节,程序应该使用未编码的字节流。

与PrintStream相比较,它具有自动行刷新的缓冲字符输出流。只需要在构造函数中指定参数autoFlush=true,使用方法println()即可实现自动行刷新。(println(xx) = print(xx) + println())。

这个类中的方法永远不会抛出I/O异常,尽管它的一些构造函数可能抛出I/O异常。客户端可以通过调用checkError()来查询是否发生了错误。

PrintWriter还可以直接与文件通信:

new PrintWriter("c://text.txt");

BufferedWriter:字符缓冲输出流(高级流),将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。 可以指定缓冲区的大小,或者接受默认的大小。在大多数情况下,默认值就足够大了。

提供了一个newLine()方法,通知流结束。因此如果把上面的PrintWriter 换成BufferedWriter

BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bufferedWriter.write(result);
bufferedWriter.newLine();
bufferedWriter.flush();

猜你喜欢

转载自blog.csdn.net/qq_43842093/article/details/129964892