1 问题背景
毕业设计做高并发相关的研究,前面粗略研究了解决高并发的核心——LinuxIO模型,笔者认为需要再粗略研究Socket相关的问题。
2 什么是Socket
计算机之间通信需要知道对方的地址,Socket就是存储这个地址,实质上Socket是IP加端口号,格式是xx.xx.xx.xx:xxxx
。如下图所示:
Socket可以理解成处于应用层与传输层之间。类比传输层的TCP三次握手,Socket也有三次握手,介绍Socket三次握手之前,先回顾TCP的三次握手。
3 TCP的三次握手
模型图如下:
如上图所示,客户端A主动发送SYB数据包,服务端B接收到后发送确认给A,A收到后对此确认发送确认数据包,A的连接建立了,B收到确认后也建立了连接。
4 Socket三次握手
4.1 模型概览
模型图如下:
4.2 具体函数
此处给出客户端单向发送数据给服务端为例子讲解。双向通信的例子还未研究。本以为双向通信很简单,然而写玩代码做测试的时候才发现没有想象中那么容易。
5 客户端单向通信服务端例子
场景:使用BIO方式实现,服务端监听客户端发来的连接请求,启动子线程处理客户端发来的信息。参考自B站的尚硅谷Netty教学视频
@Slf4j
public class BioServer {
public static void main(String[] args) {
// 阿里开发规范必须指定参数来构造线程池
ThreadPoolExecutor serverPoolExecutor = new ThreadPoolExecutor(10,
20,
10,
TimeUnit.MINUTES,
new LinkedBlockingDeque<>(10),
new DefaultThreadFactory("server-pool"));
// 服务端创建socket
try {
ServerSocket serverSocket = new ServerSocket(8099);
log.info("服务端已启动");
while (true) {
log.info("服务端等待连接");
Socket socket = serverSocket.accept();
log.info("主线程" + Thread.currentThread().getId() + "接收到一个客户端请求连接");
// 创建线程池,启动一个线程去与客户端通信
serverPoolExecutor.execute(new Runnable() {
@Override
public void run() {
// 处理客户端的信息
handler(socket);
}
});
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void handler(Socket socket) {
try{
// 存储客户端发来的信息
byte[] bytes = new byte[1024];
InputStream inputStream = socket.getInputStream();
while (true) {
log.info("等待客户端发来数据...");
// 从socket中读取发来的信息
int read = inputStream.read(bytes);
if (read != -1) {
// 输出客户端发来的信息
log.info("线程" + Thread.currentThread().getId() + "接收到数据:" + new String(bytes, 0, read));
} else {
break;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭与客户端的连接
try {
socket.close();
log.info("客户端已关闭连接");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
使用telnet命令模拟客户端,ip和端口号是根据上面代码中的来的。输入
telnet 127.0.0.1 8099
。即可连接到服务端,按ctrl + ]
可以进入发送数据的状态,使用send xxx
命令发送数据。如下: