之前一直没弄明白我使用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
来源:CSDN
作者:Small0716
链接:https://blog.csdn.net/u014022405/article/details/82082186