先看一个演示
服务端代码:
package io.unittest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
/**
* Author: ljf
* CreatedAt: 2021/3/28 上午10:05
*/
public class SocketIOProperties {
private static final int RECEIVE_BUFFER = 10;
private static final int SO_TIMEOUT = 0;
private static final boolean REUSE_ADDR = false;
private static final int BACK_LOG = 2; // 连接的等待队列满了之后还能拍几个,linux一切皆文件嘛,意味着等着不分配文件描述符的有两个
private static final boolean CLI_KEEPALIVE = false;
private static final boolean CLI_OOB = false;
private static final int CLi_REC_BUF = 20;
private static final boolean CLI_REUSE_ADDR = false;
private static final int CLI_SEND_BUF = 20;
private static final boolean CLI_LINGER = true; // 服务关闭后端口是否立即释放
private static final int CLI_LINGER_N = 0;
private static final int CLI_TIMEOUT = 0;
private static final boolean CLI_NO_DELAY = false;
public static void main(String[] args) {
ServerSocket server = null;
try {
server = new ServerSocket();
server.bind(new InetSocketAddress(9090), BACK_LOG);
server.setReceiveBufferSize(RECEIVE_BUFFER);
server.setReuseAddress(REUSE_ADDR);
server.setSoTimeout(SO_TIMEOUT);
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("server up use 9090 !");
try {
while (true) {
System.in.read(); // 这里阻塞住,看没有客户端的情况下,server端怎么处理连接
Socket client = server.accept();
System.out.println("client port: " + client.getPort());
client.setKeepAlive(CLI_KEEPALIVE);
client.setOOBInline(CLI_OOB);
client.setReceiveBufferSize(CLi_REC_BUF);
client.setReuseAddress(CLI_REUSE_ADDR);
client.setSendBufferSize(CLI_SEND_BUF);
client.setSoLinger(CLI_LINGER, CLI_LINGER_N);
client.setSoTimeout(CLI_TIMEOUT);
client.setTcpNoDelay(CLI_NO_DELAY);
// client.read //阻塞 没有 -1 0
new Thread(
() -> {
try {
InputStream in = client.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
char[] data = new char[1024];
while (true) {
int num = reader.read();
if (num > 0) {
System.out.println("client read some data is : " + num + " val :" + new String(data, 0, num));
} else if (num == 0) {
System.out.println("client readed nothing!");
continue;
} else {
System.out.println("client readed -1 ..."); // 客户端断开,就是-1
System.in.read();
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
).start();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
server.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
有System.in.read(),阻塞
启动服务端程序,阻塞主,没有应用程序接收,这时候缓冲区大小是0,
这时候启动一个客户端,模拟客户端发送数据,这时候缓冲区开始涨了
不够,继续发到缓冲区撑爆,比如我发到1152就不涨了
那么既然不涨了,多发的数据就被丢弃了,那是丢掉了头部的还是尾部的呢,这时候服务端回车,跳过阻塞,接收数据,发现是尾部的被丢掉了。即tcp的拥塞。
tcp怎么解决拥塞
窗口机制,还是上边那个服务器阻塞的模型,看图
客户端连接的时候告诉,我有多少个窗口 win,数据包是多大 mss(1460,是ifconfig里mtu的1500减去ip和端口各占的20个字节) ,然后服务端告诉客户端自己的win和mss,然后客户端确认,然后连接建立,就通信。
在每次的通信数据交互过程中,客户端和和服务端都告诉自己的窗口还有多少,如果服务端的窗口不够用了,就阻塞住,直到数据处理完(交给应用程序进行下一步的处理),有空闲的窗口了就给客户端发一个ack包通知队列有剩余了,客户端才继续发包给服务端。反过来客户端同理。就保证了tcp的包不能丢失。即拥塞控制。