最开始的时候客户端和服务器都是处于CLOSED状态。主动打开连接的为客户端,被动打开连接的是服务器。
1.TCP服务器进程先创建传输控制块TCB,时刻准备接受客户进程的连接请求,此时服务器就进入了LISTEN(监听)状态;
2,TCP客户进程也是先创建传输控制块TCB,然后向服务器发出连接请求报文,这是报文首部中的同部位SYN=1,同时选择一个初始序列号 seq=x ,此时,TCP客户端进程进入了 SYN-SENT(同步已发送状态)状态。TCP规定,SYN报文段(SYN=1的报文段)不能携带数据,但需要消耗掉一个序号。
3.TCP服务器收到请求报文后,如果同意连接,则发出确认报文。确认报文中应该 ACK=1,SYN=1,确认号是ack=x+1,同时也要为自己初始化一个序列号seq=y,此时,TCP服务器进程进入了SYN-RCVD(同步收到)状态。这个报文也不能携带数据,但是同样要消耗一个序号。
4.TCP客户进程收到确认后,还要向服务器给出确认。确认报文的ACK=1,ack=y+1,自己的序列号seq=x+1,此时,TCP连接建立,客户端进入ESTABLISHED(已建立连接)状态。TCP规定,ACK报文段可以携带数据,但是如果不携带数据则不消耗序号。
5.当服务器收到客户端的确认后也进入ESTABLISHED状态,此后双方就可以开始通信了。
为什么TCP客户端最后还要发送一次确认呢?
一句话,主要防止已经失效的连接请求报文突然又传送到了服务器,从而产生错误。
如果使用的是两次握手建立连接,假设有这样一种场景,客户端发送了第一个请求连接并且没有丢失,只是因为在网络结点中滞留的时间太长了,由于TCP的客户端迟迟没有收到确认报文,以为服务器没有收到,此时重新向服务器发送这条报文,此后客户端和服务
器经过两次握手完成连接,传输数据,然后关闭连接。此时此前滞留的那一次请求连接,网络通畅了到达了服务器,这个报文本该是失效的,但是,两次握手的机制将会让客户端和服务器再次建立连接,这将导致不必要的错误和资源的浪费。如果采用的是三次握
手,就算是那一次失效的报文传送过来了,服务端接受到了那条失效报文并且回复了确认报文,但是客户端不会再次发出确认。由于服务器收不到确认,就知道客户端并没有请求连接。
数据传输完毕后,双方都可释放连接。最开始的时候,客户端和服务器都是处于ESTABLISHED状态,然后客户端主动关闭,服务器被动关闭。
1.客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
2.服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
3.客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
4.服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
5.客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗ *∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
6.服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些
InetAddress
package com.demo.inet; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Arrays; public class InetAddressDemo { public static void main(String[] args) { // 创建一个字节数组byte 127 ~ -128 byte bt[] = {-64,-88,3,44}; try { // 创建InetAddress对象 InetAddress id = InetAddress.getByAddress(bt); System.out.println(id); // 返回原始IP地址 byte[] bs = id.getAddress(); System.out.println(Arrays.toString(bs)); // 返回对象对应IP地址 System.out.println(id.getHostAddress()); // 返回全名计算名称 System.out.println(id.getCanonicalHostName()+"------------------"); // 返回本机计算机名及对应ip地址 System.out.println(InetAddress.getLocalHost()); // 返回host名称对应域名及ip地址 InetAddress[] allByName = InetAddress.getAllByName("www.baidu.com"); System.out.println(Arrays.toString(allByName)); System.out.println(allByName[0].getHostName()); // 域名 System.out.println(allByName[0].getHostAddress()); // ip地址 // 同主机名称获取IP信息 InetAddress addr = InetAddress.getByName("localhost"); System.out.println(addr.getHostAddress()); System.out.println(addr.getCanonicalHostName()); // 返回ip地址 } catch (UnknownHostException e) { e.printStackTrace(); } } }
server端
package com.demo.server; import java.io.BufferedOutputStream; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; public class Server { static int port = 12345; public static void main(String[] args) { // 创建服务端Socket对象 ServerSocket serverSocket = null; while (true) { try { // 创建服务端Socket对象 serverSocket = new ServerSocket(port); System.out.println("等待客户端连接服务器......."); // 阻塞服务器 Socket socket = serverSocket.accept(); System.out.println("客户端连接到服务器......."); // 创建文件输出流 BufferedOutputStream os = new BufferedOutputStream(socket.getOutputStream()); os.write("服务器端: 我收到了客户端连接请求。".getBytes()); os.flush(); os.close(); } catch (Exception e) { e.printStackTrace(); } finally { try { if (serverSocket != null) { serverSocket.close(); } } catch (IOException e) { e.printStackTrace(); } } } } }
client端
package com.demo.client; import java.io.BufferedInputStream; import java.io.IOException; import java.net.Socket; public class Client { static int port = 12345; static String host = "111.231.93.134"; public static void main(String[] args) { // 创建客户端对象 Socket socket = null; try { // 创建客户端对象 socket = new Socket(host, port); // 获取输入流对象 BufferedInputStream bis = new BufferedInputStream(socket.getInputStream()); // 定义缓冲字节数组 byte[] buffer = new byte[1024]; // 定义接收实际长度 int len = 0; while ((len = bis.read(buffer)) != -1) { String content = new String(buffer, 0, len); System.out.println("客户端接收的内容:"+content); } } catch (Exception e) { e.printStackTrace(); } finally { try { if (socket != null) { socket.close(); } } catch (IOException e) { e.printStackTrace(); } } } }