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 重绘地理编码界限