文章目录
I 客户端代码示例
II 服务器端代码示例
III 运行结果
I 客户端代码示例
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(); } } }
II 服务器端代码示例
import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Inet4Address; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.nio.ByteBuffer; /** * TCP 服务器端 */ public class Server { public static void main(String[] args) { try { //I. 设置服务器套接字 //1. 创建服务器端 , 注意创建一个空的服务器套接字 , 一遍后面设置更详细的参数 ServerSocket serverSocket = new ServerSocket(); System.out.println("服务器端端 ServerSocket 创建完毕"); //2. 设置从 Socket 对象输入流中读取数据的阻塞等待超时时间 // 当与 Socket 对象关联的 InputStream 输入流执行 read() 操作时 , 其阻塞时间为这个超时时间 // 如果超过了该时间还没有收到任何数据 , 就会抛出异常 serverSocket.setSoTimeout(30000); //3. 设置是否可以复用 Socket 绑定的地址和端口号 // Socket 连接在建立时 , 会使用之前绑定本地的 IP 地址和端口号 // 这个端口号在使用之后 , 2 分钟之内不允许再次使用 // 进行了该设置之后 , 可以在连接关闭之后 , 马上使用该本地 IP 地址和端口号 serverSocket.setReuseAddress(true); //4. 设置发送接收缓冲区大小 serverSocket.setReceiveBufferSize(64 * 1024 * 1024); //5. 设置性能参数 : ① 连接时长 , ② 最低延迟 , ③ 带宽 // 设置的值不是具体的参数 , 而是连接的性能权重 , 对哪个性能要求比较高 ; // 上面的延迟和带宽的性能是互斥的 , 低延迟新能好 , 带宽性能就差 serverSocket.setPerformancePreferences(0, 2, 0); System.out.println("服务器端端 ServerSocket 设置完毕"); //6. 绑定本地端口 , 只有绑定了本地端口 , 服务器端套接字才能正式工作 // 服务器端才算是正式创建完毕 // 上面的设置一定要在绑定接口之前设置完毕 , 之后在设置 serverSocket 是无效的 serverSocket.bind(new InetSocketAddress(Inet4Address.getLocalHost(), 8888), 88); System.out.println("服务器端端 ServerSocket 绑定 8888 端口完毕"); //II. 等待服务器端连接 //1. 服务器端阻塞 , 等待客户端连接服务器端的 8888 端口号 Socket clientSocket = serverSocket.accept(); //2. 创建客户端异步处理线程 , 处理服务器端与该客户端之间的交互 , 创建之后直接启动线程即可 ClientHandler clientHandler = new ClientHandler(clientSocket); clientHandler.start(); } catch (IOException e) { e.printStackTrace(); } } /** * 客户端异步处理线程 * 每当有客户端连接服务器 , 就开启一个线程处理与该客户端之间的交互 */ private static class ClientHandler extends Thread { /** * 客户端线程 */ private Socket clientSocket; public ClientHandler(Socket clientSocket) { this.clientSocket = clientSocket; } @Override public void run() { super.run(); System.out.println("客户端 : " + clientSocket.getInetAddress() + " 连接到服务器端"); try { //I. 获取数据交互的输入流 , 输出流 , 及缓冲区 //1. 从客户端 Socket 中获取与客户端进行数据交互的输入输出流 OutputStream outputStream = clientSocket.getOutputStream(); InputStream inputStream = clientSocket.getInputStream(); //2. 从客户端读取数据 , 并使用 ByteBuffer 读取其中各种类型的数据 byte[] buffer = new byte[256]; int readCount = inputStream.read(buffer); ByteBuffer byteBuffer = ByteBuffer.wrap(buffer, 0, readCount); //II. 按照顺序读取存放的数据 //注意 : 要按照存放的顺序读取 //1. 读取 byte 类型数据 byte var_byte = byteBuffer.get(); System.out.println("① byte 类型数据 : " + var_byte); //2. 读取 short 类型数据 short var_short = byteBuffer.getShort(); System.out.println("② short 类型数据 : " + var_short); //3. 读取 int 类型数据 int var_int = byteBuffer.getInt(); System.out.println("③ int 类型数据 : " + var_int); //4. 读取 char 类型数据 char var_char = byteBuffer.getChar(); System.out.println("④ char 类型数据 : " + var_char); //5. 读取 short 类型数据 boolean var_boolean = byteBuffer.get() == 1; System.out.println("⑤ boolean 类型数据 : " + var_boolean); //6. 读取 long 类型数据 long var_long = byteBuffer.getLong(); System.out.println("⑥ long 类型数据 : " + var_long); //7. 读取 float 类型数据 float var_float = byteBuffer.getFloat(); System.out.println("⑦ float 类型数据 : " + var_float); //8. 读取 double 类型数据 double var_double = byteBuffer.getDouble(); System.out.println("⑧ double 类型数据 : " + var_double); //9. 读取 short 类型数据 int start = byteBuffer.position(); String var_string = new String(buffer, start, readCount - start - 1); System.out.println("⑨ String 类型数据 : " + var_string); //III. 将接收的数据再发送回去, 并关闭连接 outputStream.write(buffer, 0, readCount); outputStream.close(); inputStream.close(); } catch (IOException e) { e.printStackTrace(); } finally { // 连接关闭 try { clientSocket.close(); } catch (IOException e) { e.printStackTrace(); } finally { System.out.println("客户端与服务器端交互完成"); } } } } }
III 运行结果
1. 先运行服务器端 :
服务器端端 ServerSocket 创建完毕 服务器端端 ServerSocket 设置完毕 服务器端端 ServerSocket 绑定 8888 端口完毕
2. 在运行客户端 :
客户端 Socket 创建完毕 客户端 Socket 参数设置完毕 客户端 Socket 连接服务器完毕 客户端 Socket 将各种类型数据发送到了服务器端 客户端 Socket 接收到服务器端数据 42 字节
3. 最终查看服务器端打印 :
服务器端端 ServerSocket 创建完毕 服务器端端 ServerSocket 设置完毕 服务器端端 ServerSocket 绑定 8888 端口完毕 客户端 : /192.168.87.2 连接到服务器端 ① byte 类型数据 : 1 ② short 类型数据 : 1 ③ int 类型数据 : 1 ④ char 类型数据 : a ⑤ boolean 类型数据 : true ⑥ long 类型数据 : 1 ⑦ float 类型数据 : 3.14 ⑧ double 类型数据 : 3.14 ⑨ String 类型数据 : Hello World 客户端与服务器端交互完成