版本 说明 发布日期 1.0 发布文章第一版 2020-12-29
前言
这篇文章是我个人的学习笔记,可能无法做到面面俱到,也可能会有各种纰漏。如果任何疑惑的地方,欢迎一起讨论~ 如果想完整阅读这个系列的文章,欢迎关注我的专栏《Java基础系列文章》~ 对于七层网络模型、五层网络模型、IP、端口、各种协议,属于计算机网络的知识范畴,所以我就不在这里费口舌啦。如果有不清楚的小伙伴,建议先去了解一下,再来看这篇文章哦。 哦对了!请不要吝啬->点赞、关注、收藏~
Socket使用了应用层协议么?其和HTTP协议有关系么?
这个问题困扰了我一段时间,我还花了不少时间查阅资料。发现StackOverflow上有一个比较好的答案:https://stackoverflow.com/questions/38650547/is-http-based-on-socket
HTTP is an application protocol, Socket is an operating system API. This means HTTP can not be based on sockets the same as cars are not based on gasoline. Relationship between Socket and HTTP: Sockets can be used to implement a HTTP server/client since sockets can be used to implement any kind of TCP server/client and HTTP is an application layer protocol on top of TCP. But note that sockets are not essential to implement HTTP, i.e. you could use any other kind of API which manages to send network packets to implement it.
Socket是一TCP和UDP协议的接口,HTTP是应用层协议。所以二者在概念上没有交叉。 Socket不使用任何应用层协议。所以例如QQ,其使用Socket通信,并在此基础上自行封装了应用层协议。 因为Socket可以实现任何基于TCP/UDP协议的CS架构系统的通信,所以Socket可以用来实现HTTP协议的通信。 但Socket不是唯一的传输层实现方式,所以HTTP协议通信并不一定得使用Socket。
基于TCP协议的网络编程
ServerSocket类
位于java.net.ServerSocket。 主要用于管理服务器套接字信息。 常用方法如下:
方法声明 功能 ServerSocket(int port) 根据指定的端口号来构造服务器对象。 Socket accept() 侦听并接收连接请求。如果没有连接,则会持续等待。连接成功后,返回供服务器使用的套接字对象。 void close() 关闭服务器。
Socket类
位于java.net.Socket。 主要用于描述客户端套接字,是两台机器间通信的端点。 常用方法如下:
方法声明 功能 Socket(String host, int port) 根据指定的主机名和端口来构造套接字对象。 InputStream getInputStream() 获取套接字的输入流。 OutputStream getOutputStream() 获取套接字的输出流。 void close() 关闭套接字。
一个客户端Socket与一个服务器端Socket一一对应。 客户端的输入流连接于服务器输出流对应,客户端的输出流连接于服务器端的输入流对应。
整一个聊天功能出来!
既然是TCP协议,那么讲究的就是持续性会话。所以通常基于TCP协议的网络编程模型如下:
服务器
创建ServerSocket类型的对象并提供端口号; 调用accept()方法,等待客户端的连接请求; 使用输入输出流进行通信; 关闭Socket。 客户端
创建Socket类型的对象并提供服务器的IP地址和端口号; 使用输入输出流进行通信; 关闭Socket。 下面我们来分成几步,一点点实现一个简单的dos聊天功能。
先搭建C/S连接框架
public class ChatServer {
public static void main ( String[ ] args) throws IOException {
Socket socket = null;
try ( ServerSocket server = new ServerSocket ( 1314 ) ) {
System. out. println ( "等待客户端连接..." ) ;
socket = server. accept ( ) ;
System. out. println ( "客户端连接成功" ) ;
} catch ( IOException e) {
e. printStackTrace ( ) ;
} finally {
if ( socket!= null) {
socket. close ( ) ;
System. out. println ( "服务器连接关闭" ) ;
}
}
}
}
public class ChatClient {
public static void main ( String[ ] args) throws IOException {
try ( Socket socket = new Socket ( "127.0.0.1" , 1314 ) ) {
System. out. println ( "成功连接服务器" ) ;
} catch ( IOException e) {
e. printStackTrace ( ) ;
}
}
}
其实为了省事儿,虽然说是客户端和服务端,其实都在我自己电脑上跑着的,所以IP就用的127.0.0.1。哈哈哈,见谅~ 这样一来,先运行服务器,再运行客户端,就可以发现能够正常连接了。
注意,如果先运行客户端,会抛出ConnectException。道理大家都懂的。
让服务器和客户端能互相发点消息呗~
虽然服务器应该是消息的中转站,但是我们应该先能够让服务器和客户端可以互相发消息才行哈。那我们现在就让服务器和客户端可以一直互相发条消息。
public class ChatServer {
public static void main ( String[ ] args) {
try ( ServerSocket server = new ServerSocket ( 1314 ) ;
Socket socket = server. accept ( ) ;
BufferedReader reader = new BufferedReader ( new InputStreamReader ( socket. getInputStream ( ) ) ) ;
PrintWriter writer = new PrintWriter ( new OutputStreamWriter ( socket. getOutputStream ( ) ) ) )
{
System. out. println ( "客户端连接成功" ) ;
while ( true ) {
String message = reader. readLine ( ) ;
System. out. println ( "获取到聊天内容:" + message) ;
if ( message. equals ( "exit" ) ) {
break ;
}
System. out. println ( "准备中转内容..." ) ;
writer. println ( "服务器已接收" ) ;
writer. flush ( ) ;
}
} catch ( IOException e) {
e. printStackTrace ( ) ;
} finally {
System. out. println ( "服务器连接关闭" ) ;
}
}
}
public class ChatClient {
public static void main ( String[ ] args) {
try ( Socket socket = new Socket ( "127.0.0.1" , 1314 ) ;
BufferedWriter writer = new BufferedWriter ( new OutputStreamWriter ( socket. getOutputStream ( ) ) ) ;
BufferedReader consoleIn = new BufferedReader ( new InputStreamReader ( System. in) ) ;
BufferedReader reader = new BufferedReader ( new InputStreamReader ( socket. getInputStream ( ) ) ) )
{
System. out. println ( "成功连接服务器,请发送消息。输入exit结束:" ) ;
while ( true ) {
String message = consoleIn. readLine ( ) ;
writer. write ( message) ;
writer. newLine ( ) ;
writer. flush ( ) ;
if ( message. equals ( "exit" ) ) {
break ;
}
message = reader. readLine ( ) ;
System. out. println ( "收到消息:" + message) ;
}
} catch ( IOException e) {
e. printStackTrace ( ) ;
} finally {
System. out. println ( "客户端连接关闭" ) ;
}
}
}
客户端连接成功
获取到聊天内容:1234
准备中转内容...
获取到聊天内容:exit
服务器连接关闭
成功连接服务器,请发送消息。输入exit结束:
1234
收到消息:服务器已接收
exit
客户端连接关闭
如果对流不太熟悉的小伙伴,可以去看该专栏前面的文章哟~ try的语法糖我在前面的文章也用过。
他会在执行try里面的代码之前,按顺序实例化括号中的流和套接字。 在try里面的代码结束之前,会关闭括号中的流和套接字。 注意了!如果你发现readLine始终处在IO阻塞的状态,那肯定是发生了下面两种情况的一种:
可能是因为缓冲的问题。此时需要手动flush(),将缓冲中的内容刷出。 因为我们用的readLine,也就是说遇到换行符的时候才会停止读取,所以我们的输出,也得带上换行符。例如缓冲流的newLine()、打印流的println()。 那有小伙伴要问了,为什么我有时候既没有加newLine(),也没有加flush(),也没问题呢?
因为如果在输出之后,流紧接着就关闭的话。在流关闭之前,会强制刷出缓冲中的内容。客户端就会强制读取到剩余的字符。
扩充规模
现在可以一对一连接了。但是实际的聊天软件肯定都是一个服务器,多个客户端的。所以我们现在需要干什么?没错,整个线程池,来扩充我们的聊天规模! 先来思考一下:一个服务器肯定就用一个ServerSocket对象。但是每一个客户端接入,都会有一个独立的Socket对象。所以我们应该将ServerSocket对象放在主线程,Socket对象放在子线程。
public class ChatServerThread implements Runnable {
private final Socket socket;
public ChatServerThread ( Socket socket) {
this . socket = socket;
}
@Override
public void run ( ) {
try ( BufferedReader reader = new BufferedReader ( new InputStreamReader ( socket. getInputStream ( ) ) ) ;
PrintWriter writer = new PrintWriter ( new OutputStreamWriter ( socket. getOutputStream ( ) ) ) )
{
while ( true ) {
String message = reader. readLine ( ) ;
System. out. println ( "获取到聊天内容:" + message) ;
if ( message. equals ( "exit" ) ) {
break ;
}
System. out. println ( "准备中转内容..." ) ;
writer. println ( "服务器已接收" ) ;
writer. flush ( ) ;
}
} catch ( IOException e) {
e. printStackTrace ( ) ;
} finally {
System. out. println ( "客户端断开连接" ) ;
}
}
}
public class ChatServer {
public static void main ( String[ ] args) {
ExecutorService threadPool = Executors. newCachedThreadPool ( ) ;
try ( ServerSocket server = new ServerSocket ( 1314 ) )
{
System. out. println ( "服务器已启动" ) ;
while ( true ) {
Socket socket = server. accept ( ) ;
System. out. println ( "客户端连接成功" ) ;
ChatServerThread chatThread = new ChatServerThread ( socket) ;
threadPool. execute ( chatThread) ;
}
} catch ( IOException e) {
e. printStackTrace ( ) ;
} finally {
threadPool. shutdown ( ) ;
System. out. println ( "服务器连接关闭" ) ;
}
}
}
public class ChatClient {
public static void main ( String[ ] args) {
try ( Socket socket = new Socket ( "10.3.108.162" , 1314 ) ;
BufferedWriter writer = new BufferedWriter ( new OutputStreamWriter ( socket. getOutputStream ( ) ) ) ;
BufferedReader consoleIn = new BufferedReader ( new InputStreamReader ( System. in) ) ;
BufferedReader reader = new BufferedReader ( new InputStreamReader ( socket. getInputStream ( ) ) ) )
{
System. out. println ( "成功连接服务器,请发送消息。输入exit结束:" ) ;
while ( true ) {
String message = consoleIn. readLine ( ) ;
writer. write ( message) ;
writer. newLine ( ) ;
writer. flush ( ) ;
if ( message. equals ( "exit" ) ) {
break ;
}
message = reader. readLine ( ) ;
System. out. println ( "收到消息:" + message) ;
}
} catch ( IOException e) {
e. printStackTrace ( ) ;
} finally {
System. out. println ( "客户端连接关闭" ) ;
}
}
}
运行结果我就不展示了,和上一节效果一样,只是可以同时让好多好多个客户端来连接。
用IDEA的小伙伴们,会发现客户端无法同时开多个,此时有两种处理方式:
将类代码拷贝成多个类,然后就可以分别起了。 随机选择几位幸运观众(室友、同事),然他来跑这个代码。只是需要改一下IP哈。
转发聊天内容
上面已经可以让多个客户端给服务器发消息,然后服务端回复已经收到消息。但是客户端之间还没有正常产生交流。所以接下来,就需要服务器转发客户端发来的消息,从而实现多人聊天。 先修改服务端代码。
我们上一节中对每一个连接来的客户端都开了一个通信线程。这一节,我继续沿用这个通信线程,但是其作用变成了仅仅接收客户端发来的消息,然后将消息加入转发队列。其本身并不负责转发。 因为无论有多少个客户端,转发逻辑都是相同的,即:
收到消息; 将消息加入待转发队列; 遍历客户端,将消息依次转发。 因此我这里用了一个专门的线程TransferThread来处理消息转发。提供了添加消息、新增客户端、删除客户端的方法。同时,因为所有客户端共用这一个实例,所以其中存在临界资源,需要用到synchronized锁。 服务端的入口类仅做了一些微调。为了更好地支持客户端之间聊天,引入了一个“客户端昵称”,每一个客户端有一个唯一的昵称,在连接之初需要设置。
public class ChatServerThread implements Runnable {
private final Socket socket;
private final TransferThread transfer;
private final String name;
public ChatServerThread ( Socket socket, TransferThread transfer, String name) {
this . socket = socket;
this . transfer = transfer;
this . name = name;
}
@Override
public void run ( ) {
try ( BufferedReader reader = new BufferedReader ( new InputStreamReader ( socket. getInputStream ( ) ) ) ;
PrintWriter writer = new PrintWriter ( new OutputStreamWriter ( socket. getOutputStream ( ) ) ) )
{
String message;
while ( true ) {
message = reader. readLine ( ) ;
if ( message == null || message. equals ( "exit" ) ) {
break ;
}
message = name + ":" + message;
System. out. println ( "中转内容:" + message) ;
if ( ! transfer. offerMessage ( message) ) {
System. out. println ( "消息中转失败" ) ;
writer. println ( "发送失败" ) ;
writer. flush ( ) ;
}
}
} catch ( IOException e) {
System. out. println ( "客户端异常结束" ) ;
} finally {
transfer. close ( name) ;
transfer. offerMessage ( name + "退出聊天室" ) ;
System. out. println ( "客户端断开连接" ) ;
}
}
}
public class TransferThread implements Runnable {
private final Queue< String> messageQueue = new LinkedList < > ( ) ;
private final HashMap< String, PrintWriter> writers = new HashMap < > ( ) ;
@Override
public void run ( ) {
while ( true ) {
synchronized ( this ) {
if ( messageQueue. size ( ) == 0 ) {
try {
wait ( ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
}
String message = messageQueue. poll ( ) ;
if ( message == null) {
break ;
}
String name = message. split ( ":" ) [ 0 ] ;
for ( Map. Entry< String, PrintWriter> entry : writers. entrySet ( ) ) {
if ( name. equals ( entry. getKey ( ) ) ) {
entry. getValue ( ) . println ( "发送成功" ) ;
entry. getValue ( ) . println ( "" ) ;
} else {
entry. getValue ( ) . println ( "-----------------" ) ;
entry. getValue ( ) . println ( message) ;
entry. getValue ( ) . println ( "-----------------" ) ;
}
entry. getValue ( ) . flush ( ) ;
}
}
}
}
public synchronized boolean offerMessage ( String message) {
notify ( ) ;
return messageQueue. offer ( message) ;
}
public synchronized boolean putWriter ( String name, PrintWriter writer) {
if ( writers. containsKey ( name) ) {
return false ;
}
writers. put ( name, writer) ;
return true ;
}
public synchronized void close ( String name) {
writers. get ( name) . close ( ) ;
writers. remove ( name) ;
}
}
public class ChatServer {
public static void main ( String[ ] args) {
ExecutorService threadPool = Executors. newCachedThreadPool ( ) ;
TransferThread transfer = new TransferThread ( ) ;
try ( ServerSocket server = new ServerSocket ( 1314 ) )
{
threadPool. execute ( transfer) ;
System. out. println ( "服务器已启动" ) ;
Socket socket;
ChatServerThread chatThread;
String name;
while ( true ) {
socket = server. accept ( ) ;
System. out. println ( "客户端连接成功" ) ;
name = ChatServer. getClientName ( socket, transfer) ;
chatThread = new ChatServerThread ( socket, transfer, name) ;
threadPool. execute ( chatThread) ;
transfer. offerMessage ( name + "进入了聊天室" ) ;
}
} catch ( IOException e) {
e. printStackTrace ( ) ;
} finally {
threadPool. shutdown ( ) ;
System. out. println ( "服务器连接关闭" ) ;
}
}
private static String getClientName ( Socket socket, TransferThread transfer) throws IOException {
PrintWriter writer = new PrintWriter ( new OutputStreamWriter ( socket. getOutputStream ( ) ) ) ;
BufferedReader reader = new BufferedReader ( new InputStreamReader ( socket. getInputStream ( ) ) ) ;
String name;
while ( true ) {
name = reader. readLine ( ) ;
if ( ! transfer. putWriter ( name, writer) ) {
writer. println ( "昵称重复,请重新输入:" ) ;
writer. flush ( ) ;
} else {
break ;
}
}
return name;
}
}
接下来是客户端代码。客户端代码很简单了,只需要一个发送消息的线程(图省事,直接用主线程了)和一个接收消息的线程。这两个线程都很普通,没有什么需要讲的啦~看代码就好了。
public class ChatClient {
public static void main ( String[ ] args) {
try ( Socket socket = new Socket ( "10.3.108.162" , 1314 ) ;
BufferedWriter writer = new BufferedWriter ( new OutputStreamWriter ( socket. getOutputStream ( ) ) ) ;
BufferedReader consoleIn = new BufferedReader ( new InputStreamReader ( System. in) ) )
{
System. out. println ( "成功连接服务器,请输入昵称:" ) ;
String message = consoleIn. readLine ( ) ;
writer. write ( message) ;
writer. newLine ( ) ;
writer. flush ( ) ;
ChatClientThread chatClient = new ChatClientThread ( socket) ;
Thread thread = new Thread ( chatClient) ;
thread. start ( ) ;
System. out. println ( "请输入聊天内容,输入exit退出:" ) ;
while ( true ) {
message = consoleIn. readLine ( ) ;
writer. write ( message) ;
writer. newLine ( ) ;
writer. flush ( ) ;
if ( message. equals ( "exit" ) ) {
break ;
}
}
} catch ( IOException e) {
e. printStackTrace ( ) ;
} finally {
System. out. println ( "客户端连接关闭" ) ;
}
}
}
public class ChatClientThread implements Runnable {
private final BufferedReader reader;
public ChatClientThread ( Socket socket) throws IOException {
reader = new BufferedReader ( new InputStreamReader ( socket. getInputStream ( ) ) ) ;
}
@Override
public void run ( ) {
String message;
try {
while ( true ) {
message = reader. readLine ( ) ;
System. out. println ( message) ;
}
} catch ( IOException e) {
e. printStackTrace ( ) ;
} finally {
try {
reader. close ( ) ;
} catch ( IOException e) {
e. printStackTrace ( ) ;
}
System. out. println ( "客户端连接关闭" ) ;
}
}
}
运行服务器,然后随机选择几位幸运观众,陪我一起起了一下客户端。其中一个客户端内容如下:
成功连接服务器,请输入昵称:
吴安琪
请输入聊天内容,输入exit退出:
-----------------
吴安琪进入了聊天室
-----------------
-----------------
gun进入了聊天室
-----------------
-----------------
gun:gun
-----------------
牛逼
发送成功
-----------------
gun退出聊天室
-----------------
基于UDP协议的网络编程
上面的TCP协议网络编程耗费了我太大精力,所以UDP就简单说一说吧哈哈哈。 UDP通信需要用到三个类:
DatagramSocket类
位于java.net.DatagramSocket。用于描述发送和接收数据的套接字。
方法声明 功能 DatagramSocket() 无参构造。用于发送方。 DatagramSocket(int port) 指定端口号的构造。用于接收方。 void receive(DatagramPacket p) 将接收到的数据报放入参数指定的数据报对象。用于接收方。 void send(DatagramPacket p) 将参数指定的数据报对象发送出去。用于发送方。 void close() 关闭Socket并释放相关资源。
DatagramPacket类
位于java.net.DatagramPacket。用于描述用来实现无连接通信的数据报。
方法声明 功能 DatagramPacket(byte[] buf, int length) 用指定的数组来构造对象,并提供length长度的缓冲区。用于接收端,接收到的数据会被放入数组中。通常缓冲区长度等于数组长度即可。 DatagramPacket(byte[] buf, int length, InetAddress address, int port) 用指定数组来构造对象,将数据报发送到指定通信地址和端口。用于发送端,数组中的数据将会被发送。 InetAddress getAddress() 获取发送方或接收方的通信地址。 int getPort() 获取发送方或接收方的端口号。 int getLength() 用于获取发送数据或接收数据的长度。
InetAddress类
位于java.net.InetAddress。用于描述通信地址信息的工具类。
方法声明 功能 static InetAddress getLocalHost() 获取当前主机的通信地址 static InetAddress getByName(String host) 根据指定的主机名或(IP地址获)取通信地址
光说不练假老练
UDP网络编程通常采用以下模型:
接收方
创建DatagramSocket类型的对象并提供端口号; 创建DatagramPacket类型的对象并提供缓冲区; 通过Socket接收数据内容存放到Packet中,调用receive方法; 关闭Socket。 发送方
创建DatagramSocket类型的对象; 创建DatagramPacket类型的对象并提供接收方的通信地址; 通过Socket将Packet中的数据内容发送出去,调用send方法; 关闭Socket; UDP因为用得比较少,所以简单来个小栗子吧~
public class Server {
public static void main ( String[ ] args) {
try ( DatagramSocket ds = new DatagramSocket ( 1314 ) ) {
byte [ ] data = new byte [ 20 ] ;
DatagramPacket dpReceive = new DatagramPacket ( data, data. length) ;
System. out. println ( "wait for packet..." ) ;
ds. receive ( dpReceive) ;
System. out. println ( "succeed to receive: " + Arrays. toString ( data) ) ;
System. out. println ( "translate: " + new String ( data) ) ;
data = "收到啦~" . getBytes ( ) ;
DatagramPacket dpSend = new DatagramPacket ( data, data. length, dpReceive. getAddress ( ) , dpReceive. getPort ( ) ) ;
ds. send ( dpSend) ;
System. out. println ( "向" + dpReceive. getAddress ( ) + ":" + dpReceive. getPort ( ) + "发送了消息:" + Arrays. toString ( data) ) ;
} catch ( IOException e) {
e. printStackTrace ( ) ;
}
}
}
public class Client {
public static void main ( String[ ] args) {
try ( DatagramSocket ds = new DatagramSocket ( ) ) {
byte [ ] data = "hello~" . getBytes ( ) ;
DatagramPacket dpSend = new DatagramPacket ( data, data. length, InetAddress. getLocalHost ( ) , 1314 ) ;
System. out. println ( "send: " + Arrays. toString ( data) ) ;
ds. send ( dpSend) ;
System. out. println ( "succeed to send." ) ;
DatagramPacket dpReceive = new DatagramPacket ( data, data. length) ;
ds. receive ( dpReceive) ;
System. out. println ( "服务器返回消息:" + new String ( data, 0 , dpReceive. getLength ( ) ) ) ;
} catch ( IOException e) {
e. printStackTrace ( ) ;
}
}
}
执行结果如下。可以看到,客户端的data长度为6,所以服务器用长度为20的数组来接收时,并没有放满。而当服务器回发10个字节给客户端时,客户端用长度为6的data接收,只能收到前6个字节。所以客户端收到的内容是:收到。
wait for packet...
succeed to receive: [104, 101, 108, 108, 111, 126, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
translate: hello~
向/192.168.0.107:55965发送了消息:[-26, -108, -74, -27, -120, -80, -27, -107, -90, 126]
send: [104, 101, 108, 108, 111, 126]
succeed to send.
服务器返回消息:收到
URL相关
URL是“统一资源定位符”的意思,也是计网的内容,这里就不赘述啦~ 我们小学二年级的时候学过,通过URL可以访问万维网上的网络资源,最常见的就是通过http协议访问www站点。
URL的基本结构如下:<传输协议>://<主机名>:<端口号>/<资源地址>
。
URL类
基本概念
位于java.net.URL,是一个用于处理URL相关内容的类。
常用方法
方法声明 功能 URL(String spec) 根据指定的url字符串构造对象。 String getProtocol() 获取协议名称。 String getHost() 获取主机名称。 int getPort() 获取端口号。返回-1表示获取失败。 String getPath() 获取路径信息。 String getFile() 获取文件名。 URLConnection openConnection() 获取URLConnection类的实例。
URLConnection类
基本概念
位于java.net.URLConnection,是一个抽象类。 用于描述本程序和URL之间的通信链接,主要实现类有HttpURLConnection。
常用方法
方法声明 功能 InputStream getInputStream() 获取输入流 void disconnect() 断开连接
我们来对CSDN的url来进行一些测试
public class URLTest {
public static void main ( String[ ] args) throws IOException {
BufferedReader reader = null;
try {
URL url = new URL ( "https://blog.csdn.net/" ) ;
System. out. println ( "获取到的协议是:" + url. getProtocol ( ) ) ;
System. out. println ( "获取到的主机名称是:" + url. getHost ( ) ) ;
System. out. println ( "获取到的端口是:" + url. getPort ( ) ) ;
URLConnection conn = url. openConnection ( ) ;
reader = new BufferedReader ( new InputStreamReader ( conn. getInputStream ( ) ) ) ;
String str;
while ( ( str = reader. readLine ( ) ) != null) {
System. out. println ( str) ;
}
} catch ( IOException e) {
e. printStackTrace ( ) ;
} finally {
if ( null != reader) {
reader. close ( ) ;
}
}
}
}
控制台部分输出如下。因为CSDN首页的html代码实在是太长了,所以只截了一小段。
获取到的协议是:https
获取到的主机名称是:blog.csdn.net
获取到的端口是:-1
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="referrer"content="always">
<meta name="msvalidate.01" content="3189512127C34C46BC74BED5852D45E4" />
<title>CSDN博客 - 专业IT技术发表平台</title>
<meta data-n-head="true" data-hid="description" name="description" content="CSDN博客为中国软件开发者、IT从业人员、IT初学者打造交流的专业IT技术发表平台,全心致力于帮助开发者通过互联网分享知识,让更多开发者从中受益,一同和IT开发者用代码改变未来.">
<script src='//g.csdnimg.cn/tingyun/1.8.3/www.js' type='text/javascript'></script>
<link ref="canonical" href="https://blog.csdn.net/">
<link href="https://g.csdnimg.cn/static/logo/favicon32.ico" rel="shortcut icon" type="image/x-icon"/>
<link rel="stylesheet" href="//csdnimg.cn/public/common/toolbar/content_toolbar_css/content_toolbar.css">
<link rel="stylesheet" href="//csdnimg.cn/public/common/libs/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="//csdnimg.cn/public/static/css/avatar.css">