通过JAVA 代码来看TCP的3次握手和4次挥手

风流意气都作罢 提交于 2019-11-26 00:29:27

之前一直没弄明白我使用JAVA API进行socket编程的时候,到底调用哪个API的时候,TCP底层进行了3次握手,调用哪个API的时候,TCP底层进行了4次握手。网上查阅一番资料后没找到想要的,于是自己利用周末时间搞搞明白,记录一下,下次好查阅!

阅读提前

1.TCP3次握手和4次挥手理解  传送门:TCP3次握手连接协议和4次握手断开连接协议   TCP三次握手连接及四次挥手断开过程   理解TCP三次握手/四次断开的必要性

2.NIO(IO)相关知识、socket相关知识  

此次使用NIO 做例子(原阻塞方式同理)

先上代码

服务端代码

package com.mtl.day20180825;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * @author motianlong
 * @date 2018年8月25日 下午9:21:10
 * @jdk 1.8
 */
public class NIOSocketService {
	public static void main(String[] args) throws IOException {
		ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
		//绑定端口
		serverSocketChannel.bind(new InetSocketAddress(10086));
		//设置serverSocketChannel为非阻塞模式
		serverSocketChannel.configureBlocking(false);
		Selector selector = Selector.open();
		//将serverSocketChannel注册到selector,并设置对连接事件感兴趣
		serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
		while (true) {
			//非阻塞式查询selector是否有准备好的读、写、连接事件
			int select = selector.selectNow();
			//如果返回大于0,则有准备好的读、写、连接事件,然后处理
			if (select>0) {
				Set<SelectionKey> selectedKeys = selector.selectedKeys();
				Iterator<SelectionKey> iterator = selectedKeys.iterator();
				while (iterator.hasNext()) {
					SelectionKey selectionKey = (SelectionKey) iterator.next();
					//如果事件为准备好可读
					if (selectionKey.isReadable()) {
						System.out.println("readable");
						//处理读事件的handler
						new ReadHandler().readHandler(selectionKey);
					}
					//如果事件为准备好连接
					else if (selectionKey.isAcceptable()) {
						ServerSocketChannel ssc=(ServerSocketChannel) selectionKey.channel();
						//同意连接,返回一个SocketChannel
						SocketChannel accept = ssc.accept();
						System.out.println("有连接进来!"+accept.getRemoteAddress());
						//将SocketChannel也设置为非阻塞模式
						accept.configureBlocking(false);
						//注册到selector,并设置对读感兴趣
						accept.register(selector, SelectionKey.OP_READ);
					}
					iterator.remove(); 
				}
			}
		}
	}
}

服务端读数据handler类

package com.mtl.day20180825;

import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;

/**
 * @author motianlong
 * @date 2018年8月25日 下午10:20:50
 * @jdk 1.8
 */
public class ReadHandler {
	public void readHandler(SelectionKey selectionKey) {
		SocketChannel channel = (SocketChannel) selectionKey.channel();
		ByteBuffer buffer=ByteBuffer.allocate(64);
		StringBuilder stringBuilder=new StringBuilder();
		try {
			//循环读取客户端传过来的数据
			while(channel.read(buffer)!=-1) {
				buffer.flip();
				stringBuilder.append(new String(buffer.array(), 0, buffer.limit(), "UTF-8"));
				buffer.clear();
			}
			System.out.println(stringBuilder.toString());
			//组织服务发送给客户端的数据
			String re=new String("我是服务器,我已经收到你的消息了!");
			buffer.put(re.getBytes("UTF-8"));
			//将buffer设置为写模式
			buffer.flip();
			//写入SocketChannel通道
			while(buffer.hasRemaining()) {
				channel.write(buffer);
			}
			//关闭服务端的写出通道,此时服务端会发送FIN到客户端,客户端收到后,会返回ACK确认字符
			channel.socket().shutdownOutput();
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			try {
				channel.close();
				channel.socket().close();
			} catch (Exception e2) {
				e2.printStackTrace();
			}
		}
	}
}

客户端代码:

package com.mtl.day20180825;
/**
 * @author motianlong
 * @date 2018年8月26日 下午4:14:35
 * @jdk 1.8
 */

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class NIOSocketClient {
	public static void main(String[] args) throws IOException {
		SocketChannel socketChannel=SocketChannel.open();
		socketChannel.configureBlocking(false);
		//连接到服务端,此时客服端会发送SYN到服务端,服务端收到SYN后,会把SYN和ACK一并发送给客户端,客户端收到后,返回ACK给服务端,然后完成3次握手。
		//(如果SocketChannel为阻塞模式,执行完这一句代码,客户端和服务的3次握手已经完成!)
		socketChannel.connect(new InetSocketAddress("127.0.0.1", 10086));
		//因为SocketChannel设置为非阻塞模式,所有执行下面语句的时候可能还没有连接成功,所以循环检查是否已经连接成功!
		while(!socketChannel.finishConnect()) {
			System.out.println("等待连接完成。。。");
		}
		//代码执行到这里,证明客服端和服务端已连接成功!完成3次握手!
		ByteBuffer buffer=ByteBuffer.allocate(16);
		buffer.put("A".getBytes("UTF-8"));
		buffer.flip();
		//向服务器发送1个字节的数据
		while(buffer.hasRemaining()) {
			socketChannel.write(buffer);
		}
		//关闭客服端的写出通道,此时执行完这句客户端会向发送FIN到服务端,并且服务端会返回ACK确认字符
		socketChannel.socket().shutdownOutput();
		buffer.clear();
		ByteArrayOutputStream outputStream=new ByteArrayOutputStream();
		//循环读取服务端发送回来的数据
		while(socketChannel.read(buffer)!=-1) {
			buffer.flip();
			outputStream.write(buffer.array(), 0, buffer.limit());
			buffer.clear();
		}
		System.out.println(new String(outputStream.toByteArray(), 0, outputStream.size(), "UTF-8"));
		socketChannel.shutdownInput();
		socketChannel.close();
		socketChannel.socket().close();
	}
}

以下为抓包工具数据(图中结论由代码中打断点执行,总结得出)

服务器端端口为10086     客户端端口为:2649

抓包工具:npcap+Wireshark

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!