import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Inet4Address;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
/**
* TCP 客户端
*/
public class Client {
public static void main(String[] args) {
try {
//I. 创建 Socket 对象并绑定本地端口
//1. 创建空的 Socket 对象 , 方便之后设置参数
Socket socket = new Socket();
//2. 绑定本地端口
socket.bind(new InetSocketAddress(Inet4Address.getLocalHost(), 8887));
System.out.println("客户端 Socket 创建完毕");
//II. 设置 Socket 对象参数 , 注意这些参数只能在客户端没有连接服务器的时候设置 , 连接服务器之后设置是无效的
//1. 设置从 Socket 对象输入流中读取数据的阻塞等待超时时间
// 当与 Socket 对象关联的 InputStream 输入流执行 read() 操作时 , 其阻塞时间为这个超时时间
// 如果超过了该时间还没有收到任何数据 , 就会抛出异常
socket.setSoTimeout(3000);
//2. 设置是否可以复用 Socket 绑定的地址和端口号
// Socket 连接在建立时 , 会使用之前绑定本地的 IP 地址和端口号
// 这个端口号在使用之后 , 2 分钟之内不允许再次使用
// 进行了该设置之后 , 可以在连接关闭之后 , 马上使用该本地 IP 地址和端口号
socket.setReuseAddress(true);
//3. 设置是否开启 Nagle 算法 , Nagle 算法会导致多次发送的少量数据合并 , 即沾包情况出现
// 在需要低延迟传输的情况下是需要关闭该算法的 , 该算法会导致数据沾包情况出现
socket.setTcpNoDelay(true);
//4. 在长时间 ( 2 小时 ) 没有数据交互 , 是否需要发送心跳包确认连接
socket.setKeepAlive(true);
//5. 调用 Socket 对象的 close 方法之后的处理方式
// 1> 默认情况 : false , 0
// 如果 boolean on 设置成false , 不处理连接的缓存数据 , 调用 close 会立刻关闭连接
// 系统底层会操作输出流发送剩余缓存数据 , 将缓冲区中的数据发送给连接对方
// 如果设置 false 不会产生阻塞操作
// 2> setSoLinger( true , 20 ) 情况 :
// 如果设置 boolean on 参数为 true , int linger 参数设置一个大于等于 0 的参数
// 那么在关闭的时候 , 阻塞 linger 毫秒 , 之后缓冲区如果还有数据 , 就会被丢弃
// 直接向连接对方发送结束命令 , 无需经过超时等待
// 3> setSoLinger( true , 0 ) 情况 :
// 如果设置成 0 , 那么其后果是不阻塞 , 也不让系统接管输出流
// 立刻丢弃缓冲区数据 , 向对方发送 RST 命令
socket.setSoLinger(true, 10);
//6. 设置紧急数据是否内敛 , 默认情况时 false 关闭的
// 紧急数据 : 紧急数据是 Socket 对象通过调用 sendUrgentData 发送出去的数据
// 该方法参数是一个 int 值 , 仅有最低的 8 位是有效的
socket.setOOBInline(true);
//7. 设置发送接收缓冲区大小
socket.setReceiveBufferSize(64 * 1024 * 1024);
socket.setSendBufferSize(64 * 1024 * 1024);
//8. 设置性能参数 : ① 连接时长 , ② 最低延迟 , ③ 带宽
// 设置的值不是具体的参数 , 而是连接的性能权重 , 对哪个性能要求比较高 ;
// 上面的延迟和带宽的性能是互斥的 , 低延迟新能好 , 带宽性能就差
socket.setPerformancePreferences(0, 2, 0);
System.out.println("客户端 Socket 参数设置完毕");
//III. 连接服务器
//1. 连接到服务器端的 8888 端口 , 设置连接超时 3000 毫秒
socket.connect(new InetSocketAddress(Inet4Address.getLocalHost(), 8888), 3000);
System.out.println("客户端 Socket 连接服务器完毕");
//IV. 数据发送与接收
//1. 获取输出流和输入流
OutputStream outputStream = socket.getOutputStream();
InputStream inputStream = socket.getInputStream();
//2. 使用 ByteBuffer 向 byte[] 数组中存储数据
byte[] buffer = new byte[256];
ByteBuffer byteBuffer = ByteBuffer.wrap(buffer);
//3. 向数组写入 byte 类型数据
byteBuffer.put((byte) 0x01);
//4. 向数组中写入 short 类型数据
byteBuffer.putShort((short) 1);
//5. 向数组中写入 int 类型数据
byteBuffer.putInt(1);
//6. 向数组中写入 char 类型数据
byteBuffer.putChar('a');
//7. 向数组中写入 boolean 类型数据
// 此处使用 byte 类型模拟 , true 为 1, false 为 0
boolean bool = true;
byteBuffer.put((byte) (bool ? 1 : 0));
//8. 向数组中写入 long 类型数据
byteBuffer.putLong((long)1);
//9. 向数组中写入 float 类型数据
byteBuffer.putFloat(3.14f);
//10. 向数组中写入 double 类型数据
byteBuffer.putDouble(3.14);
//11. 向数组中写入 String 类型数据
// 先把 String 字符串转为 byte[] 数组, 在放入 byteBuffer 中
byteBuffer.put("Hello World".getBytes());
//12. 将 byte[] 数据发送到服务器端
outputStream.write(buffer, 0, byteBuffer.position() + 1);
System.out.println("客户端 Socket 将各种类型数据发送到了服务器端");
//13. 接收服务器端反馈的数据
int readLen = inputStream.read(buffer);
System.out.println("客户端 Socket 接收到服务器端数据 " + readLen + " 字节");
//V. 释放资源
//1. 关闭输入输出流
outputStream.close();
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}