一、原理
1.IP协议(Internet protocol)
IP协议的作用在于把各种数据包准备无误的传递给对方,其中两个重要的条件是IP地址和MAC地址。由于IP地址是稀有资源,不可能每个人都拥有一个IP地址,所以我们通常的IP地址是路由器给我们生成的IP地址,路由器里面会记录我们的MAC地址。而MAC地址是全球唯一的。举例,IP地址就如同是我们居住小区的地址,而MAC地址就是我们住的那栋楼那个房间那个人。IP地址采用的IPv4格式,目前正在向IPv6过渡。
其中IP地址分为网络地址和主机地址,每个机器可能有相同的主机地址,但是网络地址取决你接入组织的IP地址,通过NAT你可以将主机地址转换成网络地址
例子:
// 获得本地主机IP地址对象
InetAddress inet01 = InetAddress.getLocalHost();
// 主机名/ip地址字符串
System.out.println(inet01);
// 根据IP地址字符串或主机名获得对应的IP地址对象
InetAddress inet02 = InetAddress.getByName("www.baidu.com");
System.out.println(inet02);
// 获得主机名
String hostName = inet01.getHostName();
System.out.println(hostName);
// 获得IP地址字符串
String hostAddress = inet01.getHostAddress();
System.out.println(hostName);
System.out.println(hostAddress);
value:
LTCN001705/10.225.114.108
www.baidu.com/180.101.49.11
LTCN001705
LTCN001705
10.225.114.108
2.TCP协议(Transmission Control Protocol)
TCP(传输控制协议)是面向连接的传输层协议。TCP层是位于IP层之上,应用层之下的中间层。不同主机的应用层之间经常需要可靠的、像管道一样的连接,但是IP层不提供这样的流机制,而是提供不可靠的包交换。TCP协议采用字节流传输数据。
传输层起着承上启下的作用,涉及源端节点到目的端节点之间可靠的信息传输。传输层需要解决跨越网络连接的建立和释放,对底层不可靠的网络,建立连接时需要三次握手,释放连接时需要四次挥手。
源端口:两个字节。端口用于寻找发送端和接收端的进程,通过端口号和IP地址,可以确立唯一一个TCP链接。
序号:SEq序号,用于标识TCP发送端到TCP接收端发送的数据字节流。
确认序号:认序号应该是上次已经成功收到数据字节序号加1,即Ack=Seq + 1。
SYN(同步):在连接建立时用来同步序号
ACK(确认):为1时表明确认号字段有效
PSH(推送):为1时接收方应尽快将这个报文段交给应用层
RST(复位):为1时表明TCP连接出现故障必须重建连接
FIN(终止):为1时表明发送端数据发送完毕要求释放连接
3.TCP三次握手和四次挥手
连接建立–>数据传送–>连接释放
tcp连接采用cs方式
第一次握手(客户端发送请求)
客户机发送连接请求报文段到服务器,并进入SYN_SENT状态,等待服务器确认。发送连接请求报文段内容:SYN=1,seq=x;SYN=1意思是一个TCP的SYN标志位置为1的包,指明客户端打算连接的服务器的端口;seq=x表示客户端初始序号x,保存在包头的序列号(Sequence Number)字段里。
第二次握手(服务端回传确认)
服务器收到客户端连接请求报文,如果同意建立连接,向客户机发回确认报文段(ACK)应答,并为该TCP连接分配TCP缓存和变量。服务器发回确认报文段内容:SYN=1,ACK=1,seq=y,ack=x+1;SYN标志位和ACK标志位均为1,同时将确认序号(Acknowledgement Number)设置为客户的ISN加1,即x+1;seq=y为服务端初始序号y。
第三次握手(客户端回传确认)
客户机收到服务器的确认报文段后,向服务器给出确认报文段(ACK),并且也要给该连接分配缓存和变量。此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。客户端发回确认报文段内容:ACK=1,seq=x+1,ack=y+1;ACK=1为确认报文段;seq=x+1为客户端序号加1;ack=y+1,为服务器发来的ACK的初始序号字段+1。
注意: 握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。
TCP连接是全双工的,因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
1. TCP客户端发送一个FIN,用来关闭客户端到服务端的数据传送,客户端进入FIN_WAIT_1状态。发送报文段内容:FIN=1,seq=u;FIN=1表示请求切断连接;seq=u为客户端请求初始序号。
2. 服务端收到这个FIN,它发回一个ACK给客户端,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号;服务端进入CLOSE_WAIT状态。发送报文段内容:ACK=1,seq=v,ack=u+1;ACK=1为确认报文;seq=v为服务器确认初始序号;ack=u+1为客户端初始序号加1。
3. 服务器关闭客户端的连接后,发送一个FIN给客户端,服务端进入LAST_ACK状态。发送报文段内容:FIN=1,ACK=1,seq=w,ack=u+1;FIN=1为请求切断连接,ACK=1为确认报文,seq=w为服务端请求切断初始序号。
4. 客户端收到FIN后,客户端进入TIME_WAIT状态,接着发回一个ACK报文给服务端确认,并将确认序号设置为收到序号加1,服务端进入CLOSED状态,完成四次挥手。发送报文内容:ACK=1,seq=u+1,ack=w+1;ACK=1为确认报文,seq=u+1为客户端初始序号加1,ack=w+1为服务器初始序号加1。
4.为什么是三次握手而四次挥手
多了一个FIN来告诉客户端,确认结束链接
5.UDP协议
DatagramPacket
UDP的数据包对象。
发送端:
`DatagramPacket(byte[] buf, int length, InetAddress address, int port)`
buf是要发送的内容,字节数组。
length是发送内容的长度,单位是字节。
address是接收端的IP地址对象
port是接收端的端口号。
接收端:
用于创建接收端的数据包对象
DatagramPacket(byte[] buf, int length)
buf:用来存储接收到内容。
length:能够接收内容的长度
DategramSocket
DatagramSocket()
创建发送端的Socket对象,系统随机分配一个port号
DatagramSocket(int port)
:创建接收端的Socket对象并指定端口号
例子
发送端代码
6.TCP与UDP的区别
- TCP基于连接,UDP无连接。
- TCP保证数据正确性,UDP可能丢包
7.HTTP协议
HTTP是应用层协议,基于TCP协议的请求响应协议。
http请求和响应报文都是由请求行、首部行和实体主体组成,其中分别为请求报文格式:
响应报文格式
常见状态码
HTTPS
安全版的HTTP,HTTPS的安全基础就是ssl,HTTP协议不适合传输一些敏感信息,比如信用开号、密码等。http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。http的连接很简单,是无状态的;https协议是有ssl+http协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
8.为什么f12的网络信息为空。
右边设置改为默认就行
二、应用
Socket
1.原理
Socket,套接字,就是两台主机之间逻辑连接的端点。TCP、IP是传输层协议,解决数据在网络中传输问题。HTTP是应用层协议,主要解决包装数据的问题。Socker是通信的基石,包含网络通信必须的五种信息:1.连接使用的协议、2.本地主机IP地址、3.本地机型的协议端口、远程主机的IP地址、远程进程的协议端口。socket是对端口通信开发的工具,它要更底层一些。传输层的TCP是基于网络层的IP协议的,而应用层的HTTP协议又是基于传输层的TCP协议的。
2.例子
一对一
client
public class SocketClient {
public static void main(String[] args) throws UnknownHostException, IOException {
int port = 7000;
String host = "localhost";
// 创建一个套接字并将其连接到指定端口号
Socket socket = new Socket(host, port);
DataInputStream dis = new DataInputStream(
new BufferedInputStream(socket.getInputStream()));
DataOutputStream dos = new DataOutputStream(
new BufferedOutputStream(socket.getOutputStream()));
Scanner sc = new Scanner(System.in);
boolean flag = false;
while (!flag) {
System.out.println("请输入正方形的边长:");
double length = sc.nextDouble();
dos.writeDouble(length);
dos.flush();
double area = dis.readDouble();
System.out.println("服务器返回的计算面积为:" + area);
while (true) {
System.out.println("继续计算?(Y/N)");
String str = sc.next();
if (str.equalsIgnoreCase("N")) {
dos.writeInt(0);
dos.flush();
flag = true;
break;
} else if (str.equalsIgnoreCase("Y")) {
dos.writeInt(1);
dos.flush();
break;
}
}
}
socket.close();
}
}
server
package day614;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class SocketServer {
public static void main(String[] args) throws IOException {
int port = 7000;
ServerSocket serverSocket = new ServerSocket(port);
Socket socket = serverSocket.accept();
DataInputStream dis = new DataInputStream(new BufferedInputStream(socket.getInputStream()));
DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()));
System.out.println("Start=================");
do {
double length = dis.readDouble();
System.out.println("服务器端收到的边长数据为:" + length);
double result = length * length;
dos.writeDouble(result);
dos.flush();
} while (dis.readInt() != 0);
System.out.println("运行结束");
socket.close();
serverSocket.close();
}
}
一对多,使用多线程套在服务器上
package day614;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SocketServerM {
public static void main(String[] args) throws IOException {
int port = 7000;
int clientNo = 1;
ServerSocket serverSocket = new ServerSocket(port);
// 创建线程池
ExecutorService exec = Executors.newCachedThreadPool();
try {
while (true) {
Socket socket = serverSocket.accept();
exec.execute(new SingleServer(socket, clientNo));
clientNo++;
System.out.println(clientNo);
}
} finally {
serverSocket.close();
}
}
}
package day614;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
public class SingleServer implements Runnable {
private Socket socket;
private int clientNo;
@Override
public void run() {
// TODO Auto-generated method stub
try {
DataInputStream dis = new DataInputStream(
new BufferedInputStream(socket.getInputStream()));
DataOutputStream dos = new DataOutputStream(
new BufferedOutputStream(socket.getOutputStream()));
do {
double length = dis.readDouble();
System.out.println("从客户端" + clientNo + "接收到的边长数据为:" + length);
double result = length * length;
dos.writeDouble(result);
dos.flush();
} while (dis.readInt() != 0);
} catch (IOException e) {
e.printStackTrace();
} finally {
System.out.println("与客户端" + clientNo + "通信结束");
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public SingleServer(Socket socket, int clientNo) {
this.socket = socket;
this.clientNo = clientNo;
}
}
3.对象类讲解
Socket(String host, int port):
根据ip地址字符串和端口号创建客户端Socket对象
OutputStream getOutputStream():
获得字节输出流对象。
InputStream getInputStream():
获得字节输入流对象。
ServerSocket一个该类的对象就代表一个服务器端程序。
在socket通信方式中,服务器是主动等待连接通信的到来。
利用socket进行通信时,服务器端的程序可以打开多个线程与多个客户进行通信,还可以通过服务器使各个客户之间进行通信。这种方式比较灵活,适用于一些较复杂的通信,但是服务器端的程序必须始终处于运行状态以监听端口。
利用 URL进行通信时,服务器端的程序只能与一个客户进行通信,形式比较单一。但是它不需要服务器端的CGI程序一直处于运行状态,只是在有客户申请时才被激活。所以,这种方式比较适用于客户机的浏览器与服务器之间的通信。
总之,Socket是底层实现,协议你要自己去写,不局限于http,可以是任何协议。而类似HttpClient, FtpClient,URLConnetcion之类的,是对专属协议的封装,当然由于部分实现原理,你可能无法完全控制连接操作,比如setTimeout这个参数。