BIO
Java BIO(Blocking IO)是一种基于阻塞模式的IO模型,用于处理输入和输出操作。在使用Java BIO时,当一个线程调用输入或输出操作时,它会被阻塞,直到数据准备好或者操作完成。
BIO中最常使用的类是Socket类和ServerSocket类。Socket类表示一个客户端套接字,它可以与服务器进行连接和通信;ServerSocket类表示一个服务器套接字,它用于接受客户端的连接请求。使用这两个类,我们可以创建简单的网络应用程序。
下面是Java BIO使用的相关步骤:
-
创建ServerSocket对象:首先,我们需要创建一个ServerSocket对象来监听指定的端口,等待客户端的连接请求。
-
接受客户端连接:通过ServerSocket的accept()方法,我们可以接受客户端的连接请求,并返回一个代表客户端连接的Socket对象。
-
创建输入流和输出流:一旦连接建立,我们可以通过Socket对象的getInputStream()和getOutputStream()方法来获取与客户端通信的输入流和输出流。
-
数据传输:使用输入流从客户端读取数据,使用输出流向客户端发送数据。这些操作都是阻塞的,在数据准备好之前,线程将保持阻塞状态。
-
关闭连接资源:通信结束后,我们需要关闭输入流、输出流和Socket对象,以释放相关的资源。
尽管Java BIO的使用相对简单,但也存在一些限制和问题。最明显的问题是阻塞特性,当一个线程被阻塞时,它无法处理其他任务,从而导致性能下降。此外,BIO模型无法适应大规模并发连接的需求,每个连接需要一个独立的线程来处理,而线程资源是有限的。
在实际开发中,为了提高性能和并发能力,我们通常使用Java NIO(Non-blocking IO)或者Netty等框架,这些框架基于异步非阻塞的IO模型,可以更好地满足高并发和高性能的需求。
TCP
TCP 是一种面向连接的协议,通信双方在进行数据传输之前必须先建立连接。TCP 还支持窗口流量控制和拥塞控制机制,能够根据网络状况动态调整传输速率,提高网络吞吐量。下面是对 TCP 的详细解析。
1. 建立连接 TCP 使用三次握手来建立连接。首先,客户端发送一个有 SYN(同步)标志的包给服务器,进入 SYN-SENT 状态。然后,服务器回应一个 SYN/ACK 包作为响应,进入 SYN-RECEIVED 状态。最后,客户端再发送一个 ACK 包给服务器,确认连接建立。至此,连接建立成功,两个节点可以开始进行数据传输。
2. 数据传输 连接建立后,发送方会将数据分成多个报文段并依次发送给接收方。每个报文段都会带有序号(Sequence Number),用来保证接收端可以按正确的顺序重新组装数据。同时,发送方还会维护一个滑动窗口(Sliding Window),用来控制流量和确认收到的报文段。
3. 确认和重传 接收方会对收到的数据进行确认,以便发送方知道哪些数据已成功传送。确认是基于累计确认(Acknowledgment)原则的,即表示接收方准备好接收下一个序号之前的所有数据。如果发送方在一定时间内没有收到确认消息,它会重新发送相应的数据。这样可以保证数据的可靠性。
4. 拥塞控制 TCP 是一种可靠的传输协议,它能够适应不同网络状况下的变化,并通过控制拥塞窗口大小来调节传输速率。当网络负载过高时,发送方会根据检测到的丢包情况逐渐降低拥塞窗口大小,以减少网络拥塞。反之,如果网络负载较轻,则可以逐渐增加拥塞窗口大小,提高传输速率。
5. 关闭连接 当数据传输完成或者发生错误时,双方需要关闭连接。TCP 使用四次挥手来终止连接。首先,发送方发送一个有 FIN(结束)标志的包给接收方,进入 FIN-WAIT-1 状态。然后,接收方回应一个 ACK 包作为确认,进入 CLOSE-WAIT 状态。接下来,接收方发送一个有 FIN 和 ACK 标志的包给发送方,进入 LAST-ACK 状态。最后,发送方再发送一个 ACK 包作为确认,两个节点都进入 CLOSED 状态。
总之,TCP 是一种可靠性较高的协议,通过创新的连接管理、流量控制和拥塞控制机制,保证了数据的可靠传输。它在互联网中的广泛应用使得客户端和服务器能够稳定地通信,并实现各种网络服务。
前言
项目需要和第三方厂商的服务需要用TCP协议通讯,考虑到彼此双方可能都会有断网重连、宕机重启的情况,需要保证 发生上述情况后,服务之间能够自动实现重新通信。研究测试之后整理如下代码实现。因为发现客户端重启后,对于服务端来说原来的客户端和服务端进程进程已经关闭,启动又和服务端新开了一个进程。所以实现原理就可以通过服务端向客户端群发实现,断开重新连接通讯。
代码
tcp服务端代码
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class HttpSocketServer {
public static void main(String[] args) {
try {
ServerSocket server=new ServerSocket(9020);
while (true){
Socket client=server.accept();
client.setKeepAlive(true);
client.setOOBInline(true);
System.out.println("进入了1个客户机连接:"+client.getRemoteSocketAddress().toString());
ServerThread st = new ServerThread(client);
st.start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
ServerThread 线程类
import java.io.*;
import java.net.Socket;
/**
* 客户机 线程 ——自动执行run
* @author Lenovo
*/
public class ServerThread extends Thread{
private Socket client;
/**
* 方法描述: 用有参构造 接收主函数那边传来的 客户机
*/
public ServerThread(Socket client) {
this.client=client;
}
@Override
public void run() {
try {
processSocket();//调用你想执行的 使线程启动时在run方法开始执行
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 调用以上方法
*/
public void processSocket() throws IOException {
//加入集合 便于服务器群发
TcpTool.addSocket(client);
}
TcpTool 消息群发工具类
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
/**
* 聊天工具类
* @author tarzan
*/
public class TcpTool {
private static List<Socket> clientList=new ArrayList<Socket>();
/**
* 便于 验证成功后 加入客户机
* @param socket
*/
public static void addSocket(Socket socket) {
clientList.add(socket);
}
/**
* 群发=遍历list中的all元素, 对每个元素 写出
* @param msg
* @throws IOException
*/
public static void sendAll(String msg){
for (int i = 0; i <clientList.size(); i++) {
Socket client = clientList.get(i);
if(clientIsClose(client)){
delSocket(client);
i--;
continue;
}
try {
OutputStream ops = client.getOutputStream();
ops.write((msg+"\r\n").getBytes());
ops.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 判断是否断开连接,断开返回true,没有返回false
* @param socket
* @return
*/
public static Boolean clientIsClose(Socket socket){
try{
//发送1个字节的紧急数据,默认情况下,服务器端没有开启紧急数据处理,不影响正常通信
socket.sendUrgentData(0xFF);
// 发送一个数据包, 如果通信正常就不会报错. 没有报错说明没有关闭., 返回false
return false;
}catch(Exception se){
return true;
}
}
/**
* 下线时删除
* @param socket
*/
public static void delSocket(Socket socket){
clientList.remove(socket);
}
}
Tcp客户端代码
import org.springblade.core.tool.utils.StringUtil;
import java.io.*;
import java.net.Socket;
/**
* @author tarzan
*/
public class HttpSocketClient {
public static void main(String[] args) throws IOException {
Socket client=new Socket("127.0.0.1",9020);
client.setKeepAlive(true);
client.setOOBInline(true);
while (true) {
try {
if (!clientIsClose(client)) {
InputStream is=client.getInputStream();
BufferedReader reader=new BufferedReader(new InputStreamReader(is));
String text=reader.readLine();
if(StringUtil.isNotBlank(text)){
System.out.println("来自服务端的消息:"+text);
}
}else{
try {
//断开5秒后重新连接
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
client=new Socket("127.0.0.1",9020);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static Boolean clientIsClose(Socket socket){
try{
//发送1个字节的紧急数据,默认情况下,服务器端没有开启紧急数据处理,不影响正常通信
socket.sendUrgentData(0xFF);
// 发送一个数据包, 如果通信正常就不会报错. 没有报错说明没有关闭., 返回false
return false;
}catch(Exception se){
return true;
}
}
运行一个服务端,启动多个客户端进行测试。
控制台输出
以上只是实现的最简单的demo,服务端,因为服务端和客户端都需要不断监听彼此通信,发送消息时候,需要另起一个线程,调用TcpTool工具类想客户端群发消息。