TCP(Transmission Control Protocol)和Socket在某种程度上可以类比为交通系统中的公路和车辆。
就像公路是实现车辆之间运输的基础设施一样,TCP协议也是实现网络数据传输的基础设施。TCP负责在网络中建立数据传输的连接,并保证数据的可靠传输。Socket则类似于汽车、卡车等车辆,它们通过公路来相互通信、运输货物、人员等。
在计算机网络中,Socket是实现TCP协议的编程接口,它提供了一组用于建立TCP连接、发送和接收数据的API。程序员可以利用Socket API来创建一个TCP连接,并通过该连接发送和接收数据。
HTTP(HyperText Transfer Protocol)是万维网(World Wide Web)的基础协议。它在现有的 TCP/IP 协议基础之上建立。TCP可以看作是道路,Socket可以看作是车辆,而HTTP就相当于车辆运输的货物。
- TCP - 道路
- Socket - 车辆
- HTTP - 货物
道路不只有TCP这一种,还有一个UDP,只不过TCP这条路上。跑的都是安全性很高的车辆,并且都是按顺序、按轨道整整齐齐的向前开,而UDP则是没有固定的轨道,大家都是加足马力往前开,跑的可能很快,但是顺序就无法保证了。
而Socket也不止一种,不同道路跑不同的车辆,UDP这条道上跑的是DatagramSocket这种车。
而货物种类就更多了,除了HTTP,FTP(File Transfer Protocol)、SMTP(Simple Mail Transfer Protocol)等都可以用Socket来装载。
了解了它们之间的关系,那我们能不能使用Socket实现一个HTTP服务器或HTTP客户端呢,当然可以,完整的HTTP协议当然很复杂,但是我们只是为了了解其原理,所以怎么简单怎么来。
先使用Socket实现一个HTTP服务器:
public class HttpServer {
public static void main(String[] args) throws Exception {
// 本机地址5555,启动之后,打开浏览器访问:http://localhost:5555
ServerSocket serverSocket = new ServerSocket(5555);
while (true) {
Socket socket = serverSocket.accept();
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = null;
while (!"".equals(line) && (line = reader.readLine()) != null) {
// 这里会输出请求头
// 比如下面这样
// GET /hello HTTP/1.1
// Host: localhost:5555
// 其他请求头 ...
System.out.println(line);
}
// 输出响应
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
String response =
"HTTP/1.1 200 OK\n" +
"Content-Type: text/html\n" +
"Content-Length: 14\n" +
"\n" +
"<h1>hello</h1>";
writer.write(response);
writer.flush();
writer.close();
socket.close();
}
}
}
浏览器访问以下地址 http://localhost:5555,可以看到下面的页面:
是不是很简单,知道了原理,剩下的就需要了解HTTP协议的标准,等完全实现了HTTP标准协议,一个HTTP服务器做就出来了。
再用Socket做一个HTTP客户端:
public class SocketUtil {
private static final Logger logger = LoggerFactory.getLogger(SocketUtil.class);
public static void main(String[] args) throws Exception {
String request =
"GET /hello HTTP/1.1\n" +
"Host: 127.0.0.1:5555\n" +
"\n";
byte[] bytes = sendSocket("127.0.0.1", 5555, request.getBytes(), true);
System.out.println("响应内容 = " + new String(bytes));
}
/**
* Socket 请求方法
*
* @param host socket 地址
* @param port socket 端口
* @param data 要发送的数据
* @param isReadResponse 是否读取响应结果,true读取后续响应内容,false不读取返回null
* @return 返回接收到的数据 byte[]
* @throws IOException
*/
public static byte[] sendSocket(String host, int port, byte[] data, boolean isReadResponse) throws Exception {
logger.info("准备连接socket,address={},port={}", host, port);
// 使用try-with-resources语句,自动关闭资源
try (Socket socket = new Socket(host, port);
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(socket.getOutputStream());
BufferedInputStream bufferedInputStream = new BufferedInputStream(socket.getInputStream())) {
logger.info("socket连接成功,准备发送数据...");
bufferedOutputStream.write(data);
bufferedOutputStream.flush();
logger.info("socket发送数据成功");
// 是否需要读取响应内容,true读取后续响应内容,false不读取直接返回
if (!isReadResponse) {
return null;
}
// 从bufferedInputStream读取响应内容,3秒内读取不到,超时退出
int i = 1;
while (bufferedInputStream.available() <= 0) {
Thread.sleep(1000);
if (i++ >= 3) {
logger.info("socket读取返回结果超时");
return null;
}
}
int len;
byte[] result = new byte[1024];
List<byte[]> list = new ArrayList<>();
logger.info("socket正在读取返回结果...");
while ((len = bufferedInputStream.read(result)) != -1) {
byte[] bytes = new byte[len];
System.arraycopy(result, 0, bytes, 0, len);
list.add(bytes);
if (bufferedInputStream.available() <= 0) {
break;
}
}
logger.info("socket已经读取完成返回结果");
if (list.size() == 0) {
return new byte[0];
}
// 组合所有的bytes为一个byte[]
return list.stream().reduce((a, b) -> {
byte[] bytes = new byte[a.length + b.length];
System.arraycopy(a, 0, bytes, 0, a.length);
System.arraycopy(b, 0, bytes, a.length, b.length);
return bytes;
}).get();
}
}
}
访问刚才启动的服务器,接收到响应:
可以在这里看到HTTP 1.1协议标准 RFC 2068 - 超文本传输协议 -- HTTP/1.1 (ietf.org)
现在流行微服务架构,服务之间的通信方式有很多,可以直接用http,也可以用dubbo之类的rpc框架,现在了解了Socket的用法,你完全可以自己定义一套协议标准,然后实现这套协议,用于服务间的通信。