网络编程
网络介绍
网络设备
互联网是什么
IP
端口
传输控制协议
TCP
UDP
URL
单工
半双工
全双工
综合练习即时聊天系统
网络简介
网络设备
要组成一个局域网,必须要有路由器,每一个连接到路由器上的设备必须有网卡,每一个网卡都会有一个mac地址(物理地址),mac地址是唯一的。
在局域网下,所有的数据传输,都会通过路由器去中转,这样通过路由器组成一个局域网,那么路由器和路由器之间再进行互联,就可以形成一个超大的互联网了。
互联网是什么
互联网就是一个超大的局域网
IP
IP是Internet Protocol(网际互连协议)的缩写,是TCP/IP体系中的网络层协议。设计IP的目的是提高网络的可扩展性:一是解决互联网问题,实现大规模、异构网络的互联互通;二是分割顶层网络应用和底层网络技术之间的耦合关系,以利于两者的独立发展。根据端到端的设计原则,IP只为主机提供一种无连接、不可靠的、尽力而为的数据包传输服务。
IPV4
IPV4的长度是32位,理论上有2^32-1这么多个。格式是有三个点,把ip分成了4个部分,每一个部分的值是0-255;比如:xxx.xxx .xxx.xxx
IP是用来识别网络上的地址使用的,由两部分组成:网络地址和主机地址
ipv4分为了5类
IPV4因为快要耗尽的原因,开发了IPV6
IPV6
ipv6的长度是128位,可以解决IPV4耗尽的问题
特殊IP
回路IP:127.0.0.1 127开头后面除了全0或者全1都称为回路IP
0.0.0.0 也可以代表本机
255.255.255.0
端口
端口的长度是16位,端口可以理解为进入计算机的门,理论上来说端口有2^16个,取值范围是0-65535,每一个端口只能被一个程序占用,一个程序可以占用多个端口,端口打开的越少越安全。
netstat -ano 可以查看所有的端口监听情况
netstat -ano | findstr 3306 可以查看指定端口的情况
上述的两个命令都可以查看到对应的pid,也就可以结合任务管理器,去关闭对应的程序,释放对应监听的端口
固定端口
80 http协议的缺省端口
443 https协议的缺省端口 相比http加密更安全
21 ftp协议的确认端口
3306 mysql的默认端口
25 smtp 邮件发送服务器占用的端口
110 pop3 邮件接收服务器占用的端口
一般来说,自己的程序需要占用端口的时候,尽可能使用的端口往后一些,避免这个端口被占用
传输控制协议
tcp协议
当使用tcp协议的时候,如果A给B发送消息,B收到消息后会给出反馈,A可以知道对方接收到没有,协议更加可靠
七层理论模型
OSI七层模型
7 应用层 提供应用程序之间的通信
6 表示层 处理数据格式,是应用层和网络层之间的翻译官
5 会话层 建立、维护和管理会话
4 传输层 端到端之间的连接
3 网络层 寻址和路由选择,主要功能就是将网络地址翻译成对应的物理地址,并决定如何将数据从发送方路由到接收方
2 数据链路层 介质访问,链路管理,主要功能就是如何在不可靠的物理线路上进行可靠的数据传递
1 物理层 比特流传输
四层模型
应用层
传输层
网络层
数据链路层
三次握手
建立连接需要三次握手,才可以保证这个连接是可靠的
假设A 给B 发消息
- A给B发一个消息 B收到了 B知道A可以发消息,B可以收消息
- B 给A 回一个消息 A收到了 A知道B可以发送和接收消息 A知道自己可以发送和接口
- A 给B回一个消息 B收到消息 B知道B可以发送消息
这样的连接建立起来才是可靠的
第一次
第一次握手:建立连接时,客户端发送syn包(seq=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。
第二次
第二次握手:服务器收到syn包,必须确认客户端的SYN(ack=j+1),同时自己也发送一个SYN包(seq=k),即SYN+ACK包,此时服务器进入SYN_RECV状态。 [3]
第三次
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
完成三次握手,客户端与服务器开始传送数据,在上述过程中,还有一些重要的概念:
(1)未连接队列
在三次握手协议中,服务器维护一个未连接队列,该队列为每个客户端的SYN包(seq=j)开设一个条目,该条目表明服务器已收到SYN包,并向客户发出确认,正在等待客户的确认包。这些条目所标识的连接在服务器处于 Syn_RECV状态,当服务器收到客户的确认包时,删除该条目,服务器进入ESTABLISHED状态。 [3]
(2)Backlog参数
三次握手协议
表示内核为相应套接字排队的最大连接个数。SYN-ACK重传次数服务器发送完SYN-ACK包,如果未收到客户确认包,服务器进行首次重传,等待一段时间仍未收到客户确认包,进行第二次重传,如果重传次数超过系统规定的最大重传次数,系统将该连接信息从半连接队列中删除。注意,每次重传等待的时间不一定相同。 [3]
(3)半连接存活时间
是指半连接队列的条目存活的最长时间,也即服务器从收到SYN包到确认这个报文无效的最长时间,该时间值是所有重传请求包的最长等待时间总和。有时我们也称半连接存活时间为Timeout时间、SYN_RECV存活时间。
四次挥手
断开连接的时候,需要经过四次挥手
- A 给B 发送消息需要断开连接
- B 给A 回消息 等一等
- B 给A 回消息 处理的工作已完成可以断开
- A 给B 回收到 断开连接
对于一个已经建立的连接,TCP使用改进的四次挥手来释放连接(使用一个带有FIN附加标记的报文段)。TCP关闭连接的步骤如下:
第一步,当主机A的应用程序通知TCP数据已经发送完毕时,TCP向主机B发送一个带有FIN附加标记的报文段(FIN表示英文finish)。 [4]
第二步,主机B收到这个FIN报文段之后,并不立即用FIN报文段回复主机A,而是先向主机A发送一个确认序号ACK,同时通知自己相应的应用程序:对方要求关闭连接(先发送ACK的目的是为了防止在这段时间内,对方重传FIN报文段)。 [4]
第三步,主机B的应用程序告诉TCP:我要彻底的关闭连接,TCP向主机A送一个FIN报文段。 [4]
第四步,主机A收到这个FIN报文段后,向主机B发送一个ACK表示连接彻底释放。
为什么建立连接是三次握手,断开连接是四次挥手
答:因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,“你发的FIN报文我收到了”。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步挥手。
udp协议
当使用udp协议的时候,如果A给B发送消息,B不会给出反馈,A不知道对方接收到没有,协议更快
URL
统一资源定位符
一个正确的url应该包含四个 部分
https://www.baidu.com:443/index.html
https://www.baidu.com
https 协议
www.baidu.com 主机
443 端口
index.html 资源位置
当输入一个URL在浏览器后,点击回车到页面完整显示,中间经历了什么?
当点击回车以后,首先确认传输协议,然后开始解析域名,首先在hosts文件中解析域名,如果解析成功则得到ip,直接去访问,如果没有解析成功则去dns服务器上解析域名,这里一般都能够解析成功,如果没有解析成功则提示无法访问。解析成功后通过路由器转发找到目标服务器,通过指定的端口进入目标服务器找到对应的资源,然后响应给浏览器显示出来。如果没有找到对应的资源则提示404。
套接字(socket)
在java中实现网络编程主要通过socket来完成,前面所讲的所有内容,java都封装好了,我们只需要直接使用即可
单工
数据传输时单向的,不能逆向传输。比如:BB机,打印机
package com.zlt.socket20220307.simplex;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
public class Client {
public static void main(String[] args) {
try {
//对象创建后会自动去连接服务器
Socket socket = new Socket("127.0.0.1",10000);
//客户端连接服务器成功
OutputStream outputStream = socket.getOutputStream();//客户端发送数据到服务器去就是输出
OutputStreamWriter oow = new OutputStreamWriter(outputStream,"UTF-8");
oow.write("你好服务器\n");//将数据发送出去
oow.flush();//刷新流
} catch (IOException e) {
e.printStackTrace();
}
}
}
package com.zlt.socket20220307.simplex;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(10000);//创建一个服务器,监听9999端口
//阻塞线程等待客户端的连接
Socket accept = serverSocket.accept();//监听9999端口
System.out.println("客户端连接成功");
InputStream inputStream = accept.getInputStream();
InputStreamReader isr = new InputStreamReader(inputStream,"UTF-8");
BufferedReader br = new BufferedReader(isr);
String s = br.readLine();
System.out.println("客户端对服务器说:" + s);
} catch (IOException e) {
e.printStackTrace();
}
}
}
半双工
双向发送数据,但是不能同时进行,比如:对讲机
package com.zlt.socket20220307.halfduplex;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class Server {
public static void main(String[] args) {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(10000);//创建一个服务器,监听9999端口
//阻塞线程等待客户端的连接
Socket accept = serverSocket.accept();//监听9999端口
System.out.println("客户端连接成功");
while(true){
//收消息
InputStream inputStream = accept.getInputStream();
InputStreamReader isr = new InputStreamReader(inputStream,"UTF-8");
BufferedReader br = new BufferedReader(isr);
String s = br.readLine();
System.out.println("客户端对服务器说:" + s);
//回消息
OutputStream outputStream = accept.getOutputStream();//客户端发送数据到服务器去就是输出
OutputStreamWriter oow = new OutputStreamWriter(outputStream,"UTF-8");
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要对客户端说的内容");
oow.write(scanner.nextLine() + "\n");//将数据发送出去
oow.flush();//刷新流
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
package com.zlt.socket20220307.halfduplex;
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
public class Client {
public static void main(String[] args) {
try {
//对象创建后会自动去连接服务器
Socket socket = new Socket("127.0.0.1",10000);
while(true){
//客户端连接服务器成功
OutputStream outputStream = socket.getOutputStream();//客户端发送数据到服务器去就是输出
OutputStreamWriter oow = new OutputStreamWriter(outputStream,"UTF-8");
System.out.println("请输入要对服务器说的内容");
oow.write(new Scanner(System.in).nextLine() + "\n");//将数据发送出去
oow.flush();//刷新流
//收消息
//收消息
InputStream inputStream = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(inputStream,"UTF-8");
BufferedReader br = new BufferedReader(isr);
String s = br.readLine();
System.out.println("服务器对客户端说:" + s);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
全双工
有两个通道,发送和接收数据互不影响,可以同时进行 比如:QQ
package com.zlt.socket20220307.fulldeplex;
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
public class Client {
public static void main(String[] args) {
try {
//对象创建后会自动去连接服务器
Socket socket = new Socket("127.0.0.1",10000);
Thread thread = new Thread(){
@Override
public void run() {
super.run();
while (true){
//收消息
//收消息
InputStream inputStream = null;
try {
inputStream = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(inputStream,"UTF-8");
BufferedReader br = new BufferedReader(isr);
String s = br.readLine();
System.out.println("服务器对客户端说:" + s);
} catch (IOException e) {
e.printStackTrace();
}
}
}
};
thread.start();
while(true){
//客户端连接服务器成功
OutputStream outputStream = socket.getOutputStream();//客户端发送数据到服务器去就是输出
OutputStreamWriter oow = new OutputStreamWriter(outputStream,"UTF-8");
System.out.println("请输入要对服务器说的内容");
oow.write(new Scanner(System.in).nextLine() + "\n");//将数据发送出去
oow.flush();//刷新流
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
package com.zlt.socket20220307.fulldeplex;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class Server {
public static void main(String[] args) {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(10000);//创建一个服务器,监听9999端口
//阻塞线程等待客户端的连接
Socket accept = serverSocket.accept();//监听9999端口
System.out.println("客户端连接成功");
Thread thread = new Thread(){
@Override
public void run() {
super.run();
while (true){
//收消息
//收消息
InputStream inputStream = null;
try {
inputStream = accept.getInputStream();
InputStreamReader isr = new InputStreamReader(inputStream,"UTF-8");
BufferedReader br = new BufferedReader(isr);
String s = br.readLine();
System.out.println("客户端对服务器说:" + s);
} catch (IOException e) {
e.printStackTrace();
}
}
}
};
thread.start();
while(true){
//回消息
OutputStream outputStream = accept.getOutputStream();//客户端发送数据到服务器去就是输出
OutputStreamWriter oow = new OutputStreamWriter(outputStream,"UTF-8");
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要对客户端说的内容");
oow.write(scanner.nextLine() + "\n");//将数据发送出去
oow.flush();//刷新流
}
} catch (IOException e) {
e.printStackTrace();
}
}
}