Socket通信简介
参考文章:socket通讯原理及例程(一看就懂)
socket是介于应用层(http协议)和传输层(TCP/UDP协议)之间的一层虚拟层
Socket是一个程序,符合TCP/UDP协议的规范,并封装了TCP/UDP等协议
在CS模式(client-server模式,即客户端-服务端模式)中,Socket是客户端和服务端的共同组成部分
从图中我们可以看到,socket负责建立连接,请求数据与响应数据,结束连接
而tomcat负责其中具体的请求处理
Socket的具体实现
第一步,建立连接
/**
* tomcat启动类
*/
public class TomcatStart {
private static Request request = new Request();
public static void main(String[] args) throws IOException {
System.out.println("socket服务器启动!!!");
// 1. 打开相关通信端口
// tomcat:8080,mysql:3306,应用软件独占一个端口的全部信息
ServerSocket serverSocket = new ServerSocket(8666);
// 线程持续扫描当前网卡xxxx端口(死循环),如果有数据就拿过来,交给端口对应的程序处理
// 2. 监听并接收请求数据
while (true) {
// 一旦发现有数据,就打开socket通信
// 这里没有创建新的线程,所以这里是main线程监听数据
Socket socket = serverSocket.accept();
System.out.println(socket.getInetAddress().getCanonicalHostName() + "进行了连接!");
// 第二步监听并接收到了数据,处理数据可以用主线程,但是没必要,创建子线程处理
// 每接收一次数据,创建一个子线程
Thread t1 = new Thread(() -> {
// 处理数据包括两部分:读和写
try {
dataHandle(socket);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
t1.start();
}
}
}
第二步,读入并处理请求数据,写出响应数据给浏览器
读入时应该做一步判断,这里没有考虑到动态资源,只有静态资源,所以不需要判断
// 处理数据的方法,读+写
public static void dataHandle(Socket socket) throws Exception {
// 1. 读取请求的数据
// 1.1打开输入流对象,读取socket对象中的数据,这里的数据都是0101010的二进制数据
InputStream inputStream = socket.getInputStream();
requestContext(inputStream);
// 数据的输出
Response response = new Response(socket.getOutputStream());
// 访问资源
response.writeHtml(request.getUrl());
}
public static void requestContext(InputStream inputStream) throws IOException {
// 1.2二进制数据的翻译并读取
int count = 0;
while (count == 0) {
// 可以不受阻塞地从此输入流读取(或跳过)的估计字节数;如果到达输入流末尾,则返回 0
count = inputStream.available();
}
byte[] bytes = new byte[count];
inputStream.read(bytes);
// 这里用URLDecoder是为了防止路径中出现特殊符号,经过get请求之后会被URLEncode为乱码
String context = URLDecoder.decode(new String(bytes, "utf-8"));
System.out.println("===context:" + context);
// 空请求
if ("".equals(context)) {
System.out.println("null request!");
} else {
// 非空请求,逐行获取request内容
//根据换行来获取第一行数据
String firstLine = context.split("\\n")[0];
// 第一行数据的第2个字符串
System.out.println("===url:" + firstLine.split("\\s")[1]);
request.setUrl(firstLine.split("\\s")[1]);
// 第一行数据的第1个字符串
System.out.println("===methodType:" + firstLine.split("\\s")[0]);
request.setMethodType(firstLine.split("\\s")[0]);
}
我们不难发现完成这一步的关键在于Request类和Response类的具体实现
public class Request implements MyHttpServletRequest{
private String url;
private String methodType;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getMethodType() {
return methodType;
}
public void setMethodType(String methodType) {
this.methodType = methodType;
}
}
public class Response implements MyHttpServletResponse {
// 获取输出流
private OutputStream outputStream;
public Response(OutputStream outputStream) {
this.outputStream = outputStream;
}
// 静态资源的输出
public void writeHtml(String path) throws Exception {
//
// 根据路径返回资源路径地址,例如http://localhost:8666/index.html
String resource = FileUtil.getResoucePath(path);
File file = new File(resource);
if (file.exists()) {
// 静态资源存在!
System.out.println("静态资源存在!");
FileUtil.writeFile(file, outputStream);
} else {
System.out.println(path + "对应的该静态资源不存在!");
}
}
// 数据写回
public void write(String context) throws IOException {
outputStream.write(context.getBytes());
outputStream.flush();
}
}
FileUtil在这里完成了前端路径到本地资源路径的转化+响应头的添加+文件输入流转为socket输出流的操作
import java.io.*;
import java.nio.file.Files;
/**
* 该类的主要作用是进行读取文件
*/
public class FileUtil {
public static boolean writeFile(InputStream inputStream, OutputStream outputStream) {
boolean success = false;
// buffer是缓冲的意思
BufferedInputStream bufferedInputStream;
BufferedOutputStream bufferedOutputStream;
try {
bufferedInputStream = new BufferedInputStream(inputStream);
bufferedOutputStream = new BufferedOutputStream(outputStream);
// 先写入响应头,为Content-Type:text/html
// Http/1.1 200 \r\nContent-Type:text/html \r\n\r\n
bufferedOutputStream.write(ResponseUtil.htmlResponseHeader.getBytes());
int count = 0;
while (count == 0) {
count = inputStream.available();
}
int fileSize = inputStream.available();
long written = 0;
int beteSize = 1024;
byte[] bytes = new byte[beteSize];
while (written < fileSize) {
if (written + beteSize > fileSize) {
beteSize = (int) (fileSize - written);
bytes = new byte[beteSize];
}
bufferedInputStream.read(bytes);
bufferedOutputStream.write(bytes);
bufferedOutputStream.flush();
written += beteSize;
}
success = true;
} catch (IOException e) {
e.printStackTrace();
}
return success;
}
public static boolean writeFile(File file, OutputStream outputStream) throws Exception {
return writeFile(Files.newInputStream(file.toPath()), outputStream);
}
/**
* 获取资源地址
*
* @param path
* @return
*/
public static String getResoucePath(String path) {
String resource = FileUtil.class.getResource("/").getPath();
return resource + "\\" + path;
}
}
我们启动项目,在浏览器访问localhost:8666/index.html时
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p>Hello TomcatDemo!!!</p>
</body>
</html>
控制台输出
至此完成了socket通信+tomcat静态资源获取的仿写