传输控制协议
;是一种面向链接
的、可靠的
、基于字节流
的传输层通讯协议
,由 IETF 的 RFC 793 定义。socket()
:建立一个客户端 Socket。bind()
: 绑定一个 Socket 到一个本地地址和端口上。accept()
:服务器端接受一个新的链接。write()
:把数据写入到 Socket 输出流。read()
:从 Socket 输入流中读取数据。三次握手:java
第一次握手
:客户端发送带有 SYN 标志的链接请求报文段,而后进入 SYN_SEND 状态,等待服务端确认。第二次握手
:服务端接受到客户端的 SYN 报文段后,须要发送 ACK 信息对这个 SYN 报文段进行确认。同时,还要发送本身的 SYN 请求信息。服务端会将上述信息放到一个报文段(SYN+ACK 报文段)中,一并发送给客户端,此时服务端进入 SYN_RECV 状态。第三次握手
:客户端接收到服务端的 SYN+ACK 报文段后,会向服务端发送 ACK 确认报文段,这个报文段发送完毕后,客户端和服务端都进入 ESTABLEISHED 状态,完成 TCP 三次握手。四次挥手:算法
当被动方收到主动方的 FIN 报文通知时,它仅仅表示主动方没有数据再发送给被动方了。但未必被动方全部的数据都完整的发送给了主动方,因此被动方不会立刻关闭 SOCKET,它可能还须要发送一些数据给主动方后,再发送 FIN 报文给主动方,告诉主动方赞成关闭链接,因此这里的 ACK 报文和 FIN 报文多数状况下都是分开发送的。服务器
原理:网络
发送的数据包的二进制相加而后取反,目的是检测数据在传输过程当中的任何变化。若是收到报文段的检验和有差错,TCP 将丢弃这个报文段和不确认收到此报文段。并发
TCP 给发送的每个包进行编号,接收方对数据包进行排序,把有序数据传送给应用层。异步
当TCP 发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。若是不能及时收到一个确认,将重发这个报文段。socket
TCP 链接的每一方都有固定大小的缓冲空间,TCP 的接收端只容许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据,能提示发送方下降发送的速率,防止包丢失。TCP 使用的流量控制协议是可变大小的滑动窗口协议。ide
当网络拥塞时,减小数据的发送。函数
应用数据被分割成 TCP 认为最适合发送的数据块。性能
TCP 的接收端会丢弃重复的数据。
排序、组装流程
因为数据传输的顺序多是不必定的,因此在此中间会进行排序。
数据包丢失,进行重传
链接中断
当链接中断后,须要进行重连、而后从新进行重传。
一对多传输流程
public class Server { private static final int PORT = 20000; public static void main(String[] args) throws IOException { ServerSocket server = createServerSocket(); initServerSocket(server); // 绑定到本地端口上 server.bind(new InetSocketAddress(Inet4Address.getLocalHost(), PORT), 50); System.out.println("服务器准备就绪~"); System.out.println("服务器信息:" + server.getInetAddress() + " P:" + server.getLocalPort()); // 等待客户端链接 for (; ; ) { // 获得客户端 Socket client = server.accept(); // 客户端构建异步线程 ClientHandler clientHandler = new ClientHandler(client); // 启动线程 clientHandler.start(); } } private static ServerSocket createServerSocket() throws IOException { // 建立基础的ServerSocket ServerSocket serverSocket = new ServerSocket(); // 绑定到本地端口20000上,而且设置当前可容许等待连接的队列为50个 //serverSocket = new ServerSocket(PORT); // 等效于上面的方案,队列设置为50个 //serverSocket = new ServerSocket(PORT, 50); // 与上面等同 // serverSocket = new ServerSocket(PORT, 50, Inet4Address.getLocalHost()); return serverSocket; } private static void initServerSocket(ServerSocket serverSocket) throws IOException { // 是否复用未彻底关闭的地址端口 serverSocket.setReuseAddress(true); // 等效Socket#setReceiveBufferSize serverSocket.setReceiveBufferSize(64 * 1024 * 1024); // 设置serverSocket#accept超时时间 // serverSocket.setSoTimeout(2000); // 设置性能参数:短连接,延迟,带宽的相对重要性 serverSocket.setPerformancePreferences(1, 1, 1); } /** * 客户端消息处理 */ private static class ClientHandler extends Thread { private Socket socket; ClientHandler(Socket socket) { this.socket = socket; } @Override public void run() { super.run(); System.out.println("新客户端链接:" + socket.getInetAddress() + " P:" + socket.getPort()); try { // 获得套接字流 OutputStream outputStream = socket.getOutputStream(); InputStream inputStream = socket.getInputStream(); byte[] buffer = new byte[256]; int readCount = inputStream.read(buffer); ByteBuffer byteBuffer = ByteBuffer.wrap(buffer, 0, readCount); // byte byte be = byteBuffer.get(); // char char c = byteBuffer.getChar(); // int int i = byteBuffer.getInt(); // bool boolean b = byteBuffer.get() == 1; // Long long l = byteBuffer.getLong(); // float float f = byteBuffer.getFloat(); // double double d = byteBuffer.getDouble(); // String int pos = byteBuffer.position(); String str = new String(buffer, pos, readCount - pos - 1); System.out.println("收到数量:" + readCount + " 数据:" + be + "\n" + c + "\n" + i + "\n" + b + "\n" + l + "\n" + f + "\n" + d + "\n" + str + "\n"); outputStream.write(buffer, 0, readCount); outputStream.close(); inputStream.close(); } catch (Exception e) { System.out.println("链接异常断开"); } finally { // 链接关闭 try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } System.out.println("客户端已退出:" + socket.getInetAddress() + " P:" + socket.getPort()); } } }
public class Client { private static final int PORT = 20000; private static final int LOCAL_PORT = 20001; public static void main(String[] args) throws IOException { Socket socket = createSocket(); initSocket(socket); // 连接到本地20000端口,超时时间3秒,超过则抛出超时异常 socket.connect(new InetSocketAddress(Inet4Address.getLocalHost(), PORT), 3000); System.out.println("已发起服务器链接,并进入后续流程~"); System.out.println("客户端信息:" + socket.getLocalAddress() + " P:" + socket.getLocalPort()); System.out.println("服务器信息:" + socket.getInetAddress() + " P:" + socket.getPort()); try { // 发送接收数据 todo(socket); } catch (Exception e) { System.out.println("异常关闭"); } // 释放资源 socket.close(); System.out.println("客户端已退出~"); } private static Socket createSocket() throws IOException { /* // 无代理模式,等效于空构造函数 Socket socket = new Socket(Proxy.NO_PROXY); // 新建一份具备HTTP代理的套接字,传输数据将经过www.baidu.com:8080端口转发 Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(Inet4Address.getByName("www.baidu.com"), 8800)); socket = new Socket(proxy); // 新建一个套接字,而且直接连接到本地20000的服务器上 socket = new Socket("localhost", PORT); // 新建一个套接字,而且直接连接到本地20000的服务器上 socket = new Socket(Inet4Address.getLocalHost(), PORT); // 新建一个套接字,而且直接连接到本地20000的服务器上,而且绑定到本地20001端口上 socket = new Socket("localhost", PORT, Inet4Address.getLocalHost(), LOCAL_PORT); socket = new Socket(Inet4Address.getLocalHost(), PORT, Inet4Address.getLocalHost(), LOCAL_PORT); */ Socket socket = new Socket(); // 绑定到本地20001端口 socket.bind(new InetSocketAddress(Inet4Address.getLocalHost(), LOCAL_PORT)); return socket; } private static void initSocket(Socket socket) throws SocketException { // 设置读取超时时间为2秒 socket.setSoTimeout(2000); // 是否复用未彻底关闭的Socket地址,对于指定bind操做后的套接字有效 socket.setReuseAddress(true); // 是否开启Nagle算法 socket.setTcpNoDelay(true); // 是否须要在长时无数据响应时发送确认数据(相似心跳包),时间大约为2小时 socket.setKeepAlive(true); // 对于close关闭操做行为进行怎样的处理;默认为false,0 // false、0:默认状况,关闭时当即返回,底层系统接管输出流,将缓冲区内的数据发送完成 // true、0:关闭时当即返回,缓冲区数据抛弃,直接发送RST结束命令到对方,并没有需通过2MSL等待 // true、200:关闭时最长阻塞200毫秒,随后按第二状况处理 socket.setSoLinger(true, 20); // 是否让紧急数据内敛,默认false;紧急数据经过 socket.sendUrgentData(1);发送 socket.setOOBInline(true); // 设置接收发送缓冲器大小 socket.setReceiveBufferSize(64 * 1024 * 1024); socket.setSendBufferSize(64 * 1024 * 1024); // 设置性能参数:短连接,延迟,带宽的相对重要性 socket.setPerformancePreferences(1, 1, 0); } private static void todo(Socket client) throws IOException { // 获得Socket输出流 OutputStream outputStream = client.getOutputStream(); // 获得Socket输入流 InputStream inputStream = client.getInputStream(); byte[] buffer = new byte[256]; ByteBuffer byteBuffer = ByteBuffer.wrap(buffer); // byte byteBuffer.put((byte) 126); // char char c = 'a'; byteBuffer.putChar(c); // int int i = 2323123; byteBuffer.putInt(i); // bool boolean b = true; byteBuffer.put(b ? (byte) 1 : (byte) 0); // Long long l = 298789739; byteBuffer.putLong(l); // float float f = 12.345f; byteBuffer.putFloat(f); // double double d = 13.31241248782973; byteBuffer.putDouble(d); // String String str = "Hello你好!"; byteBuffer.put(str.getBytes()); // 发送到服务器 outputStream.write(buffer, 0, byteBuffer.position() + 1); // 接收服务器返回 int read = inputStream.read(buffer); System.out.println("收到数量:" + read); // 资源释放 outputStream.close(); inputStream.close(); } }