前言
TCP是面向连接的通信协议,通过三次握手建立连接,通讯完成时要拆除连接,由于TCP是面向连接的所以只能用于端到端的通讯。
TCP提供的是一种可靠的数据流服务,采用“带重传的肯定确认”技术来实现传输的可靠性。TCP还采用一种称为“滑动窗口”的方式进行流量控制,所谓窗口实际表示接收能力,用以限制发送方的发送速度。
如果IP数据包中有已经封好的TCP数据包,那么IP将把它们向‘上’传送到TCP层。TCP将包排序并进行错误检查,同时实现虚电路间的连接。TCP数据包中包括序号和确认,所以未按照顺序收到的包可以被排序,而损坏的包可以被重传。
TCP将它的信息送到更高层的应用程序,例如Telnet的服务程序和客户程序。应用程序轮流将信息送回TCP层,TCP层便将它们向下传送到IP层,设备驱动程序和物理介质,最后到接收方。
基于TCP通讯的三次握手
原生网络编程BIO
服务端提供IP和监听端口,客户端通过连接操作想服务端监听的地址发起连接请求,通过三次握手连接,如果连接成功建立,双方就可以通过套接字进行通信。
传统的同步阻塞模型开发中,ServerSocket负责绑定IP地址,启动监听端口;Socket负责发起连接操作。连接成功后,双方通过输入和输出流进行同步阻塞式通信。
传统BIO通信模型:采用BIO通信模型的服务端,通常由一个独立的Acceptor线程负责监听客户端的连接,它接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理没处理完成后,通过输出流返回应答给客户端,线程销毁。即典型的一请求一应答模型。
该模型最大的问题就是缺乏弹性伸缩能力,当客户端并发访问量增加后,服务端的线程个数和客户端并发访问数呈1:1的正比关系,Java中的线程也是比较宝贵的系统资源,线程数量快速膨胀后,系统的性能将急剧下降,随着访问量的继续增大,系统最终就死-掉-了。
为了改进这种一连接一线程的模型,我们可以使用线程池来管理这些线程,实现1个或多个线程处理N个客户端的模型(但是底层还是使用的同步阻塞I/O),通常被称为“伪异步I/O模型“。
代码实战
有以上网络通信知识和BIO模型的基础了解后,我们进入代码实战:来实现一个基于BIO的伪异步、高并发、全双工、长连接服务器编程模型。
服务端实现
/**
* @author andychen https://blog.51cto.com/14815984
* BIO 服务器端
* */
public class Server {
/**
* 连接处理请求服务池
* */
private static final ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
/*调用实现服务器-客户端TCP通讯*/
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket();
//创建服务器地址
SocketAddress socketAddress = new InetSocketAddress(Constant.SERVER, Constant.SERV_PORT);
//绑定socket服务端IP+端口
serverSocket.bind(socketAddress);
//循环监听
System.out.println("================Start listen message===========================");
for (;;){
//接收数据:因消息返回前这里会阻塞
Socket result = serverSocket.accept();
//接收到数据在单独线程中处理
executorService.execute(new ServerTask(result));
}
} finally {
if(null != serverSocket && !serverSocket.isClosed()){
serverSocket.close();
}
}
}
/*定义服务器任务线程*/
static class ServerTask implements Runnable{
private Socket socket = null;
private List<String> msgList = new LinkedList<>();
public ServerTask(Socket socket) {
this.socket = socket;
}
/*处理读入和写出消息数据*/
@Override
public void run() {
String msg = null;
String newMsg = null;
//ARM写法
try(BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
OutputStream outputStream = new ObjectOutputStream(this.socket.getOutputStream())){
//读取返回的数据
while (!Thread.interrupted()){
msg = bufferedReader.readLine();
System.out.println("Client sent message: "+msg);
//双向消息反馈
newMsg = "Server received the sent message: "+msg;
((ObjectOutputStream) outputStream).writeUTF(newMsg);
outputStream.flush();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(!this.socket.isClosed()){
try {
this.socket.close();
this.socket = null;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
客户端实现
/**
* @author andychen https://blog.51cto.com/14815984
* BIO 客户端
* */
public class Client {
/**发送消息到服务器端
* 同时接收服务器反馈
* */
public static void main(String[] args) {
Socket socket = null;
String msg = null;
InputStream inputStream = null;
PrintWriter printWriter = null;
Scanner scanner = null;
try {
//定义服务器端地址
SocketAddress address = new InetSocketAddress(Constant.SERVER, Constant.SERV_PORT);
socket = new Socket();
//连接服务器端
socket.connect(address);
if(socket.isConnected()){
try{
printWriter = new PrintWriter(socket.getOutputStream());
inputStream = new ObjectInputStream(socket.getInputStream());
scanner = new Scanner(System.in);
do {
msg = scanner.nextLine();
printWriter.println(msg);
printWriter.flush();
//确认消息
msg = ((ObjectInputStream) inputStream).readUTF();
System.out.println(msg);
}while (!Constant.EXIT_TAG.equals(msg));
}finally {
printWriter.close();
printWriter = null;
inputStream.close();
inputStream = null;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if(null != socket && !socket.isClosed()){
try {
socket.close();
socket = null;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
端到端持续消息发送结果
结论
结合以上通讯机制和原理、核心实现思路,我们可以扩展这种模型的应用。下次我们将基于以上思想和实现,手写一个RPC定义的同步框架的核心业务和扩展。
文章来源: blog.51cto.com,作者:wavebeed,版权归原作者所有,如需转载,请联系作者。
原文链接:blog.51cto.com/14815984/2505409