这里面的网络初始,都是先简单的介绍一下,为了方便理解后面的网络编程,其中里面介绍了重要的TCP和UDP都是简单了解下概念,后面会详细讲TCP和UDP的八股文。
随着时代的发展,越来越需要计算机之间互相通信,共享软件和数据,即以多个计算机协同工作来完成
业务,就有了网络互连;也就是说 由计算机互连而成的通信网络。
计算机网络是计算机技术与通信技术发展相结合的产物。
网络互连就是为了多台计算机连接在一起,实现数据共享。
最本质的功能就是,资源数据共享,是最本质的一个功能。
网络通信:数据共享本质是网络数据传输,即计算机之间通过网络来传输数据 。
根据网络互连的规模不同,就可以划分为局域网and广域网(还有个城域网,相对于前两个而言重要性要逊色一点)。
局域网,Local Area Network,简称LAN ,一般覆盖范围在10km之内之内的计算机网络。个人,家庭,公司,校园…
广域网,Wide Area Network ,简称WAN,一般覆盖范围在100km之内之内的计算机网络。城与城之间,国家,全球…
通过路由器,将多个局域网连接起来,在物理上组成很大范围的网络,就形成了广域网。广域网内部的
局域网都属于其子网。
lan口方向的是局域网,wan口是连接广域网的,wan是ip唯一的地址。ip地址分类,怎么划分,点击这个Ip地址 & 子网掩码 链接查看详细解说。
协议就是网络中做核心的概念;协议是进行网络通信
的必要环节。
协议简单来说就是通信的双方必须共同遵守的一组规范或约定。
网络上的传输本质就是传输一些电信号或者光信号,我们传输的数据是以二进制形式传输出去的,就可以使用高电平/低电平/高频率/低频率…来分别代表0/1,但是 还要明确约定组合起来0 1是啥意思。
就好像考试的时候,如果两个人位置挨的近,私下就可以商量协议是脚跺一下表示选A,两下就是B,一次类推,这样双方指定了这份规则,到时候,传过来的信号,另一方就能明白是什么意思。
上面所说的约定就是协议了,传输的双方约定好数据传输的格式,发送方按照格式构造数据,接收方按照格式来解析数据。我们协议是进行有效通信的必要条件。
在计算机网络的体系结构,就是指计算机网络的分层协议模型。最终体现为在网络上传输的数据包的格式 。
分层,就是把一个太复杂的大问题分解为相对简单的N个小问题看,依次解决每个小问题,合起来就形成了大问题的解,这种方法也可叫理解为“分而治之”,
聊到分而治之,大家是否想到归并排序尼,忘了的可以去复习下哟
在协议中就是把一个协议拆分成多个协议,让多个协议相互配合,根据功能和定位,来对协议进行拆分,起到协议分层的效果。
1、上层协议不必关注下层协议的细节;
接电话为例:在电话里说的人,不必知道电话机的工作原理,就能进行打电话操作。
2、任意一层的协议都可以灵活的替换;
在语言方面,我和另一个人打电话,可以使用 English;
在设备方面,我和另一个人打电话,可以使用座机也可以使用手机;
总之底层是为上层提供服务的,上层的功能也依赖于底层的服务。
OSI/RM,(Open System Interconnection/Reference Model) 开放系统互连/参考模型
这是国际标准化组织 (OSI )提出 的一个参考模型,但并没有在Internet上实装。
OSI 这个东西本身就很抽象,我用一个现实生活中寄快递的栗子,帮助大家的理解。
Internet事实上使用的工业标准。
它分为四层协议模型,和五层协议模型
TCP/IP的最底层,网络接口层是没有实际的内容的,但是它支持OSI/RM中的物理层和数据链路层全部协议。
采取折中的方法,综合了OSI/RM和TCP/IP的优点,就采用了五层协议。
因为网络接口层对应的是OSI/RM的第一二层,所以也包含了IEEE802.3; IEEE802.6 ; IEEE802.8 ;IEEE802.11 等标准。
在软件行业中,我们更关注的就是网络层以上的内容,相对于物理层来说纯硬件,硬件就不是程序员改管的范围了。
网络层:点对点,负责的是任一节点之间的数据传输,关注传输过程具体走了啥样的线路。(关注过程)例如在IP协议 中,通过IP地址来标识一台主机,并通过路由表的方式规划出两台主机之间的数据传输的线路(路由)。路由器(Router)工作在网路层。
传输层:端对端,只关注结果(TCP/UDP)。
应用层:应用程序完成的逻辑,数据拿到了之后要干什么。
当协议分层之后,就涉及两个重要的过程就是封装和分用
这里面的封装就是在每一层协议中封装好,传输给下一层协议的时候,头部加上该层对应的协议格式,在数据链路层中头部和尾部都要加上,以太网首部为了传输数据把数据交给相邻的设备(小区中的菜鸟驿站),以太网的尾部就是目的地相邻的设备(别人家小区的菜鸟驿站)。
不同协议层对数据包有不同的称呼。
1、传输层:数据段
2、网络层:数据报
3、链路层:帧
4、物理层:比特(bit)
这样子层层封装的过程理解为包装快递,分用就是拆快递。
封装分用不仅仅是存在于发送方和接收方,中间的设备(路由器/交换机)也会针对数据进行封装和分用。
通常情况下,交换机,只是分装分用到数据链路层就完了。A的数据发给交换机,交换机物理层进行处理,交给数据链路层,数据链路层针对这里数据解析并且重新打包(修改里面的MAC地址,也是决定接下来数据要发给那个相邻的设备)。
通常的情况下,路由器,只是封装分用到网络层就完了。A的数据发给路由器,路由器物理层进行处理,交给数据链路层,数据链路层再处理,交给网络层,网络层要根据这里的目的地址,来规划接下来的传输路线,规划好了之后再重新交给数据链路层和物理层进行封装。(在实际情况中,路由器/交换机,是可能解析到传输层甚至应用层的)
有了这些协议就能实现网络之间的通信。当然通信也会涉及到 ip地址 ,mac地址…
在上面聊了这么多的协议,而这些协议就是为了让我们在网络中可以资源共享以及数据通信,并且提高计算机系统的可靠性和可用性。
网络互连的目的是进行网络通信,也即是网络数据传输,更具体一点,是网络主机中的不同进程间,基于网络传输数据。
那么,在组建的网络中,如何判断到底是从哪台主机,将数据传输到那台主机呢?这就需要使用IP地址来标识。
ip地址描述了网络上的一个主机;
Port描述了主机上的一个程序。(跟URL里面的Port是一样的)
ip地址有详细解说,可以跳转 Ip地址 & 子网掩码 里面看。
网络编程,指网络上的主机,通过不同的进程,以编程的方式实现网络通信(或称为网络数据传输)。
这里满足进程不同就行了,即便是同一个主机只要是不同的进程,基于网络来传输数据,也属于网络编程。但是这种情况只是在开发或者练习的时候在一个主机上进行来完成的网络编程,我们的主要目的是提供网络上不同主机,基于网络来传输数据来源。
这里的获取和提供,也对应着两个概念,也就是客服端和服务器 。
Socket是属于网络编程套接字里面的范畴,是操作系统提供给应用程序的一组 的 API(Application Programming Interface), 通过调用这些API就可以实现把数据交给传输层,传输层拿到数据程序员就主要关注的就是应用层,在应用层里面就会用到这些API。
就像Tomcat,Tomcat是部署在服务器上的一个应用程序,对外提供服务,只要用户通过自己电脑上 的浏览器,就能访问到Tomcat。
Tomcat的这个过程,通过“网络” ,浏览器 和 Tomcat的底层就使用的了 Socket 编程,而我们就把操作系统里面提供的这些支持网络编程的 API 称为 Socket API
。
Socket 本质上就是 应用层 和 传输层 之间的交互接口
在传输层中有很多的网络协议,这里涉及到最重要的两个协议就是 TCP ,UDP , 因此 Socket API 对应的也有两大类。在上面介绍协议模型的时候,就给 TCP 和 UDP 简单的解释了一下,这里我们再来看看 TCP 和 UDP 之间的具体的特点对照:
TCP:
UDP:
1、有连接 vs 无连接:
有连接:类似于打电话,要等到对方接起来才可以说话(发送数据资源,接受数据资源)
无连接:发qq,不管你在不在线都会给你发送信息。
2、可靠传输 vs 不可靠传输
可靠传输:淘宝给客服发信息,前面提示未读/已读,这样是能够知道对方是否知道的
不可靠传输:信息发送出去,对方有没有收到,咱是不知道的
3、面向字节流 vs 面向数据报
面向字节流:以字节为单位进行读写
面向数据报:以一个报文为单位进行读写 (有具体的大小,在 Socket API 里面封装好的)
4、全双工 vs 半双工
全双工:一个Socket对象既可以用来发送,也可以用来接收
半双工:某个对象,只能用来发送,或者说是只能接收
UDP 面向数据报
DatagramSocket API ,DatagramSocket 是 UDP Sockt,用于发送和接收UDP数据报。
DatagramSocket 的构造方法:
方法签名 | 方法说明 |
---|---|
DatagramSocket() | 创建一个UDP数据报套接字的Socket,绑定到本机任意一个随机端口 (一般用于客户端) |
DatagramSocket(int port) | 创建一个UDP数据报套接字的Socket,绑定到本机指定的端口(一般用 于服务端) |
DatagramSocket 方法:
方法签名 | 方法说明 |
---|---|
void receive(DatagramPacket p) | 从此套接字接收数据报(如果没有接收到数据报,该方法会阻 塞等待) |
void send(DatagramPacket p) | 从此套接字发送数据报包(不会阻塞等待,直接发送) |
void close() | 关闭此数据报套接字 |
DatagramSocket 这个类是描述了 Socket 对象,类比FIle对象,本质上是一个文件,类似于“网卡“这个设备的抽象。想要操作网卡,先创建 Socket 对象,通过操作Socket对象,就可以反应到网卡上,此处的 Socket可以视为”网卡的遥控器“。
DatagramPacket API ,DatagramPacket是UDP Socket发送和接收的数据报
DatagramPacket 构造方法:
方法签名 | 方法说明 |
---|---|
DatagramPacket(byte[] buf, int length) | 构造一个DatagramPacket以用来接收数据报,接收的数据保存在 字节数组(第一个参数buf)中,接收指定长度(第二个参数 length) |
DatagramPacket(byte[] buf, int offset, int length, SocketAddress address) | 构造一个DatagramPacket以用来发送数据报,发送的数据为字节 数组(第一个参数buf)中,从0到指定长度(第二个参数 length)。address指定目的主机的IP和端口号 |
DatagramPacket 方法:
方法签名 | 方法说明 |
---|---|
InetAddress getAddress() | 从接收的数据报中,获取发送端主机IP地址;或从发送的数据报中,获取 接收端主机IP地址 |
int getPort() | 从接收的数据报中,获取发送端主机的端口号;或从发送的数据报中,获 取接收端主机端口号 |
byte[] getData() | 获取数据报中的数据 |
DatagramPacket 对象描述了一个UDP数据报,是使用UDP传输数据的基本单位。
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class UdpEchoServer {
// 这里 创建了一个 socket 对象,绑定了本机中随机端口
private DatagramSocket socket = null;
// 这里是服务器上面的端口,由于ip没有指定,相当于0.0.0.0 服务器监听地址(绑定到当前主机上所有网卡上)
public UdpEchoServer(int port) throws SocketException {
// 切记这里的 端口,不能被别的进程绑定,一个进程对应一个端口,
// 否则,抛出异常,构造socket 失败
socket = new DatagramSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动!");
// 服务器需要 7*24 开着
while(true){
/**
* 这里面的操作设计三步:
* 1、读取客户端发来的请求
* 2、根据请求,计算出响应
* 3、把响应返回给客服端
*/
DatagramPacket requestPacket = new DatagramPacket(new byte[1024],1024);
socket.receive(requestPacket);
// 这里需要把DatagramPacket 中的数据提取传来,转换成 String ,String 方便之后的代码处理
String request = new String(requestPacket.getData(),0,requestPacket.getLength());
// 2.
// 因为这个代码是 echo server ,故客服端发什么就响应什么。
String response = process(request);
// 3.
// 数据从哪里来就返回哪里去
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,
requestPacket.getSocketAddress());
socket.send(responsePacket);
System.out.printf("[%s:%d] req: %s; resp: %s\n", requestPacket.getAddress().toString(),
requestPacket.getPort(), request, response);
}
}
public String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
UdpEchoServer server = new UdpEchoServer(9090);
server.start();
}
}
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class UdpEchoClient {
private DatagramSocket socket = null;
private String serverIp;
private int serverPort;
public UdpEchoClient(String ip,int port) throws SocketException {
this.serverIp = ip;
this.serverPort = port;
socket = new DatagramSocket();
}
public void start() throws IOException {
Scanner scan = new Scanner(System.in);
while(true) {
/**
* 1、从控制台,读取用户输入的数据
* 2、把数据构造层 UDP数据报,发送给服务器
* 3、从服务器读取响应数据
* 4、把响应数据进行解析,并显示
*/
System.out.println("-> ");
String request = scan.next();
// 2、
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,
InetAddress.getByName(serverIp),serverPort);
socket.send(requestPacket);
// 3、
DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
socket.receive(responsePacket);
// 4、
// 第三个参数 不能写成 requestPacket.getData().length ,这是把数据全读出来,
// requestPacket.getLength() 是读有效数据
String response = new String(responsePacket.getData(),0,requestPacket.getLength());
System.out.printf("req: %s; resp: %s\n", request, response);
}
}
public static void main(String[] args) throws IOException {
UdpEchoClient client = new UdpEchoClient("127.0.0.1",9090);
client.start();
}
}
UDP 提供的 socket API 就两个最重要的类:
1、DatagramSocket:表示一个Socket对像 => 对应着操作系统socket 文件=> 对于网卡的封装
2、DatagramPacket:表示一个传输的UDP数据报
这个就是面向字节流,读写 Socket 的时候,不需要xxxPacket 这样的类,直接以字节为单位进行读写。
ServerSocket API
1、ServerSocket 是创建TUP服务器端Socket API。
方法签名 | 方法说明 |
---|---|
ServerSocket(int port) | 创建一个服务端流套接字Socket,并绑定到指定端口 |
方法签 名 | 方法说明 |
---|---|
Socket accept() | 开始监听指定端口(创建时绑定的端口),有客户端连接后,返回一个服务端Socket 对象,并基于该Socket建立与客户端的连接,否则阻塞等待 |
void close() | 关闭此套接字 |
这里 accept()需要注意下:
在 A 和 B 建立TCP连接时候,A就要调用 socket api,让A的内核和B的内核建立连接,这里进行建立连接的过程就是三次握手;accept()的作用就是,B上的应用程序为了能够使用TCP连接通信就需要先从B内核中拿到这个连接到用户程序中,而accept()并不知道对方什么时候会建立连接就会阻塞等待。
Socket API
2、Socket 是既给服务器使用也给客户端使用
方法签名 | 方法说明 |
---|---|
Socket(String host, int port) | 创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的 进程建立连接 |
方法签名 | 方法说明 |
---|---|
InetAddress getInetAddress() | 返回套接字所连接的地址 |
InputStream getInputStream() | 返回此套接字的输入流 |
OutputStream getOutputStream() | 返回此套接字的输出流 |
在TCP的服务器里,需要用到两类 Socket:
1、ServerSocket
2、Socket(clientSocket)
这两个起到的作用也是完全不一样的。
serverSocket,就是负责建立连接的;Socket负责和客服端进行通信的
package net;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class TcpEchoServer {
private ServerSocket serverSocket = null;
// 此处是服务器要绑定的端口号
public TcpEchoServer(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
// TCP 是需要建立连接才能通信
public void start() throws IOException {
System.out.println("服务器启动!");
while(true) {
// 需要建立好连接,才能通信
Socket clientSocket = serverSocket.accept();
// 建立好了连接,需要和客户端进行通信
processConnection(clientSocket);
}
}
private void processConnection(Socket clientSocket) {
// 客服端的ip ,和端口号
System.out.printf("[%s:%d] 客户端建立连接!\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
// 因为是面向字节流,所以要用InputStream和OutputStream
try(InputStream inputStream = clientSocket.getInputStream()) {
try(OutputStream outputStream = clientSocket.getOutputStream()) {
Scanner scan = new Scanner(inputStream);
while(true) {
/**
* 1、读取请求并解析
* 2、根据请求计算响应
* 3、把响应写回到客服端
*/
// 1、
if (!scan.hasNext()) {
System.out.printf("[%s:%d] 客户端断开连接!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
break;
}
String request = scan.next();
// 2、因为是回显模式,这节返回request就行
String response = process(request);
// 3、
// 可以使用 PrintWriter 来封装一下 OutputStream
PrintWriter writer = new PrintWriter(outputStream);
// 这里面的数据只是写到了内核中的接受缓存区中,等全部写完了,一起响应给客服端
writer.println(response);
// 为了保证写入的数据能够及时返回给客户端, 手动加上一个刷新缓冲区的操作
writer.flush();
System.out.printf("[%s:%d] req: %s, resp: %s\n", clientSocket.getInetAddress().toString(),
clientSocket.getPort(), request, response);
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
clientSocket.close(); // 关闭资源可不能省!!!!!!
} catch (IOException e) {
e.printStackTrace();
}
}
}
private String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
TcpEchoServer server = new TcpEchoServer(9090);
server.start();
}
}
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
public class TcpEchoClient {
// 客服端操作系统会自动分配ip地址and端口号
private Socket socket = null;
public TcpEchoClient(String serverIp, int serverPort) throws IOException {
// 客服端在 new Socket的时候已经建立好连接了
// 与服务器建立连接,首先肯定要知道服务器的ip地址和端口号
socket = new Socket("127.0.0.1",9090);
}
public void start() {
System.out.println("客服端启动!");
Scanner scan = new Scanner(System.in);
try(InputStream inputStream = socket.getInputStream()) {
try(OutputStream outputStream = socket.getOutputStream()) {
Scanner respScan = new Scanner(inputStream);
while(true) {
/**
* 1、从控制台读取用户输入的数据
* 2、根据用户输入的数据,构造请求,发送给服务端
* 3、从服务器读取响应
* 4、把响应显示出来
*/
System.out.println("-> ");
String request = scan.next();
// 2、
PrintWriter writer = new PrintWriter(outputStream);
writer.println(request);
writer.flush();
//3、
String response = respScan.next();
// 4、
System.out.printf("req: %s, resp: %s\n", request, response);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
TcpEchoClient client = new TcpEchoClient("127.0.0.1",9090);
client.start();
}
}
针对面对上诉问题,我们在处理客服端的时候,不影响accept()第二次调用就行了;那么我们可以使用多线程,一个线程进行accept()的处理,另一个线程进行客服端的多次请求处理,这样就需要给每个建立好连接的客服端都发送一个线程
// TCP 是需要建立连接才能通信
public void start() throws IOException {
System.out.println("服务器启动!");
while(true) {
// 需要建立好连接,才能通信
Socket clientSocket = serverSocket.accept();
// 建立好了连接,需要和客户端进行通信
// 需要改动这里,把每次建立好的连接,创建一个新的线程来处理
Thread t = new Thread(()->{
processConnection(clientSocket);
});
t.start();
}
}
铁汁们,觉得笔者写的不错的可以点个赞哟❤,收藏关注呗,你们支持就是我写博客最大的动力!!!!