Java网络编程

Posted 我可能是个假开发

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java网络编程相关的知识,希望对你有一定的参考价值。

一、IO模型

IO模型就是说用什么样的通道进行数据的发送和接收,Java共支持3种网络编程IO模式:

  • BIO
  • NIO
  • AIO

1.BIO

1.1基本介绍

Blocking I/O,同步阻塞(传统阻塞型),服务器实现模式为一个连接一个线程,即客户端有连接请求时,服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,可以通过线程池机制改善(实现多个客户连接服务器)。

适用场景: 连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JKD1.4以前的唯一选择,但程序简单易理解。

缺点:一个客户端连接对应一个处理线程,没办法同时处理多个连接
改进:多线程处理
来一个客户端,开启一个新的线程在后端处理
弊端:C10K问题

connect 10*1000个连接:
服务端扛不住,内存不够
链接太多,服务端资源不够
改进:使用线程池:
但是这样 并发数也就限制在了线程池的数量这里

1.2工作机制

网络编程的基本模型是 Client/Server模型,也就是两个进程之间进行相互通信,其中服务端提供位置信息(绑定IP地址和端口),客户端通过连接操作向服务端监听的端口地址发起连接请求,基于TCP协议下进行三次握手连接,连接成功后,双方通过网络套接字(Socket)进行通信。

传统的同步阻塞模型开发中,
服务端 ServerSocket负责绑定IP地址,启动监听端口;
客户端Socket负责发起连接操作。
连接成功后,双方通过输入和输出流进行同步阻塞式通信。
基于BIO模式下的通信,客户端-服务端是完全同步,完全耦合的。

1.3 传统BIO编程

需求

实现客户端发消息,服务端收消息。
代码实现

Client:

package com.hcx.bio;

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;

/**
 * @author hongcaixia
 * @version 1.0
 * @date 2021/7/13 17:16
 */
public class Client 
    public static void main(String[] args) throws IOException 
        System.out.println("===客户端启动===");
        Socket socket = new Socket("127.0.0.1", 9999);
        OutputStream outputStream = socket.getOutputStream();
        PrintStream printStream = new PrintStream(outputStream);
        //此处没有换行,而服务端是一行一行的读取数据 等不到换行就会报错
        //printStream.print("hello,server");
        printStream.println("hello,server");
        printStream.flush();
    

Server:

package com.hcx.bio;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author hongcaixia
 * @version 1.0
 * @date 2021/7/13 16:21
 */
public class Server 
    public static void main(String[] args) 
        try 
            System.out.println("===服务端启动===");
            //服务端端口注册
            ServerSocket serverSocket = new ServerSocket(9999);
            //监听客户端的socket连接请求
            Socket socket = serverSocket.accept();
            //从socket管道中获得字节输入流对象
            InputStream inputStream = socket.getInputStream();
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
            String msg;
            if ((msg = bufferedReader.readLine()) != null) 
                System.out.println("服务端接收到:" + msg);
            
         catch (Exception e) 
            e.printStackTrace();
        
    

总结

  • 上述代码中,服务端会一直等待客户端的消息,如果客户端没有进行消息的发送,服务端将一直进入阻塞状态。
  • 服务端是按照行获取消息的,所以客户端也必须按照行发送消息,否则服务端将进入等待消息的阻塞状态。

1.4 BIO模式下多发和多收消息

需求
实现客户端和服务器反复收发消息。

代码实现:

Client:

package com.hcx.bio2;

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;

/**
 * @author hongcaixia
 * @version 1.0
 * @date 2021/7/13 17:16
 */
public class Client 
    public static void main(String[] args) throws IOException 
        System.out.println("===客户端启动===");
        Socket socket = new Socket("127.0.0.1", 9999);
        OutputStream outputStream = socket.getOutputStream();
        PrintStream printStream = new PrintStream(outputStream);
        Scanner scanner = new Scanner(System.in);
        while (true)
            System.out.println("请输入:");
            String msg = scanner.nextLine();
            printStream.println(msg);
            printStream.flush();
        
    

Server:

package com.hcx.bio2;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author hongcaixia
 * @version 1.0
 * @date 2021/7/13 16:21
 */
public class Server 
    public static void main(String[] args) 
        try 
            System.out.println("===服务端启动===");
            //服务端端口注册
            ServerSocket serverSocket = new ServerSocket(9999);
            //监听客户端的socket连接请求
            Socket socket = serverSocket.accept();
            //从socket管道中获得字节输入流对象
            InputStream inputStream = socket.getInputStream();
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
            String msg;
            while ((msg = bufferedReader.readLine()) != null) 
                System.out.println("服务端接收到:" + msg);
            
         catch (Exception e) 
            e.printStackTrace();
        
    

1.5 BIO模式下接收多个客户端

在服务端引入线程,客户端每发起一个请求,服务端就创建一个新的线程来处理这个客户端的请求,这样就实现了一个客户端一个线程的模型。

代码示例:

Client:

package com.hcx.bio3;

import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;

/**
 * @author hongcaixia
 * @version 1.0
 * @date 2021/7/14 11:13
 */
public class Client 

    public static void main(String[] args) 
        try 
            Socket socket = new Socket("127.0.0.1",9999);
            OutputStream outputStream = socket.getOutputStream();
            PrintStream printStream = new PrintStream(outputStream);
            Scanner scanner = new Scanner(System.in);
            //循环发送消息
            while (true)
                System.out.println("请说:");
                String msg = scanner.nextLine();
                printStream.println(msg);
                printStream.flush();
            
         catch (Exception e) 
            e.printStackTrace();
        

    

Server:

package com.hcx.bio3;

import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author hongcaixia
 * @version 1.0
 * @date 2021/7/14 10:57
 */
public class Server 

    public static void main(String[] args) 
        try 
            ServerSocket serverSocket = new ServerSocket(9999);
            //循环不断接收客户端的socket连接请求
            while (true)
                Socket socket = serverSocket.accept();
                //创建独立的线程与客户端socket通信
                new ServerReaderThread(socket).start();
            
         catch (Exception e) 
            e.printStackTrace();
        
    

ServerReaderThread:

package com.hcx.bio3;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;

/**
 * @author hongcaixia
 * @version 1.0
 * @date 2021/7/14 11:00
 */
public class ServerReaderThread extends Thread 

    private Socket socket;

    public ServerReaderThread(Socket socket) 
        this.socket = socket;
    

    @Override
    public void run() 
        try 
            InputStream inputStream = socket.getInputStream();
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
            String msg;
            while ((msg = bufferedReader.readLine()) != null) 
                System.out.println(msg);
            
         catch (Exception e) 
            e.printStackTrace();
        
    

启动一个服务端,多个客户端。

总结:

  • 1.每个Socket接收到,都会创建一个线程,线程的竞争、切换上下文影响性能;
  • 2.每个线程都会占用栈空间和CPU资源;
  • 3.并不是每个都进行IO操作,无意义的线程处理;
  • 4.客户端的并发访问增加时。服务端将呈现1:1的线程开销,访问量越大,系统将发生线程栈溢出,线程创建失败,最终导致进程宕机或者僵死,从而不能对外提供服务。

1.6 伪异步I/O

上述案例中:客户端的并发访问增加时。服务端将呈现1:1的线程开销,访问量越大,系统将发生线程栈溢出,线程创建失败,最终导致进程宕机或者僵死,从而不能对外提供服务。

改进:
采用一个伪异步/O的通信框架,采用线程池和任务队列实现,当客户端接入时,将客户端的 Socket封装成一个Task(该任务实现java.lang. Runnable线程任务接口)交给后端的线程池中进行处理。JDK的线程池维护一个消息队列和N个活跃的线程,对消息队列中 Socket任务进行处理,由于线程池可以设置消息队列的大小和最大线程数,因此,它的资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机。

代码示例:

Server:

package com.hcx.bio4;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author hongcaixia
 * @version 1.0
 * @date 2021/7/14 15:11
 */
public class Server 
    public static void main(String[] args) 
        try 
            ServerSocket serverSocket = new ServerSocket(9999);

//            while (true) 
//                Socket socket = serverSocket.accept();
//                new ThreadPoolExecutor(3, 1, 120, TimeUnit.SECONDS,
//                        new ArrayBlockingQueue<>(3)).execute(() -> 
//                    //处理接收到的客户端socket通信
//                    try 
//                        //从socket中获取字节输入流
//                        InputStream inputStream = socket.getInputStream();
//                        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
//                        String msg;
//                        if ((msg = bufferedReader.readLine()) != null) 
//                            System.out.println("服务端接收到:" + msg);
//                        
//                     catch (IOException e) 
//                        e.printStackTrace();
//                    
//                );
//            

            //初始化线程池对象
            HandlerSocketServerPool pool = new HandlerSocketServerPool(3, 10);

            //循环接收客户端socket连接请求
            while (true) 
                Socket socket = serverSocket.accept();
                //把socket封装成任务对象交给线程池处理
                Runnable runnable = new ServerRunnableTarget(socket);
                pool.execute(runnable);
            
         catch (IOException e) 
            e.printStackTrace();
        

    

HandlerSocketServerPool:

package com.hcx.bio4;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author hongcaixia
 * @version 1.0
 * @date 2021/7/14 15:11
 */
public class HandlerSocketServerPool 

    //创建一个线程池成员变量用于存储线程池对象
    private ExecutorService executorService;

    //创建对象时初始化线程池对象
    public HandlerSocketServerPool(int maxThreadNum,int queueSize)
        executorService = new ThreadPoolExecutor(3, maxThreadNum, 120, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(queueSize));
    

    //提交任务给线程池的任务队列,等待线程池处理
    public void execute(Runnable target)
        executorService.execute(target);
    

ServerRunnableTarget:

package com.hcx.bio4;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;

/**
 * @author hongcaixia
 * @version 1.0
 * @date 2021/7/14 15:34
 */
public class ServerRunnableTarget implements Runnable

    private Socket socket;
    public ServerRunnableTarget(Socket socket)
        this.socket = socket;
    

    @Override
    public void run() 
        //处理接收到的客户端socket通信
        try 
            //从socket中获取字节输入流
            InputStream inputStream = socket.getInputStream();
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
            String msg;
            if((msg = bufferedReader.readLine())!=null)
                System.out.println("服务端接收到:"+msg);
            
         catch (IOException e) 
            e.printStackTrace();
        
    

Client:

package com.hcx.bio4;

import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;

/**
 * @author hongcaixia
 * @version 1.0
 * @date 2021/7/14 11:13
 */
public class Client 

    public Google Maps API 重绘地理编码界限

ByteBuffer

第六章-分支界限法

如何在 Java 中获取当前用户的地理位置?

[Java中的“数组索引超出界限”是什么意思? [重复]

LeetCode 915 分割数组[数组 双指针] HERODING的LeetCode之路