1. 计算机网络基础
1.1 什么是协议?
两个计算机之间进行通信的规则
1.2 常用的协议有哪些?
- tcp
- ip
- udp
- smtp
- http
- https
- wx
- …
1.3 ip地址
在网络中逻辑上唯一标识一台机器
ip是由32bit二进制组成,每8位标示ip地址的一个字段
1.4 mac地址
物理地址(网卡地址): 物理上唯一标识一台机器
1.5 子网掩码
与ip地址共同组合得到子网地址
1.6 子网
同处的网络
1.7 常用的ip地址有哪些分类?
-
A类ip地址
255.0.0.0 10.0.0.0
-
B类IP地址
255.255.0.0 172.254.x.x
-
C类IP地址
255,255,255,0 192.168.1.x
1.8 端口
端口: 使用端口号可以标识唯一的一个端口(进程)
我们在实际的生产环境中如果自定义端口,尽量不要使用1024以前的端口号;
1.9 windows中查看ip地址
cmd—>ipconfig
ipconfig /all: 查看完整的网卡信息
1.10 dns
域名解析服务: http://www.baidu.com
把域名解析成ip
1.11 公网地址与内网地址
2. Java中的网络编程
- 可以使用Java编程语言开发网络的引用程序
- java中提供的一些列的api来供我们进行网络编程
3. 网络编程的目的
- 网络就是希望通过 编程+网络协议 的这种方式来实现远程计算机之间的通信
4. 网络协议
在网络通信中有 OSI七层网络模型,过于理想化,在实际的应用中可能不会完全遵守这个osi七层网络模型
我们在实际的网络中,现在有一个大家事实上的标准,TCP/IP网络网络模型
5. OSI七层网络模型
6. TCP/IP网络模型(事实标准)
7. TCP和UDP协议
传输层协议中有两个非常重要的协议
-
传输控制协议TCP(Transmission Control Protocol)
-
用户数据报协议UDP(User Datagram Protocol)
7.1 TCP(传输控制协议)
-
首先基于tcp协议的通信是要在通信之前建立连接,通信之后关闭连接
-
数据传输的可靠性增大了
-
资源消耗较大
7.2 UDP(用户数据报协议)
- udp协议是不可靠的协议
- udp协议的通信效率高
7.3 我们如何选择?
对数据的传输可靠性有要求则使用tcp协议,对数据的传输可靠性没有要求,则使用udp协议
8. Tcp建立连接的过程
https://www.zhihu.com/question/24853633/answer/254224088
上面的过程就是tcp建立连接过程中的三次握手机制
9. TCP协议的四次挥手
10. ping命令
两个机器可以Ping通,证明可以通信;
11. InetAddress类
java.net.InetAddress
InetAddress是封装了域名(主机名称)+ip地址
//InetAddress传递域名,内部会解析其ip地址
InetAddress inetAddress = InetAddress.getByName("baidu.com");
//InetAddress.getLocalHost();返回的InetAddress封装的是本地的主机名称+ip地址
InetAddress inetAddress1 = InetAddress.getLocalHost();
//获取主机名称
System.out.println(inetAddress1.getHostName());
//获取主机地址(ip地址)
System.out.println(inetAddress1.getHostAddress());
12. Java中的远程进程通信
套接字:Socket
Socket就是用来进行远程进程之间的通信的;
13. 基于Tcp的java网络编程
tcp协议:我们如果需要使用tcp协议进行远程进程通信,必须先建立连接,然后才能进行通信,最后释放资源;
13.1 Tcp的服务(Server)端
package com.uplooking.tcp;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class TcpServer {
private static BufferedInputStream bis;
private static ServerSocket serverSocket;
private static Socket socket;
public static void main(String[] args) {
try {
//1. 建立连接(三次握手)
//1.1 内部绑定端口
//1.2 内部监听端口
serverSocket = new ServerSocket(8899);
//2. 如果有客户端连接到我(服务器),我接收连接
socket = serverSocket.accept();//接收一个新的客户端的"连接"
InetAddress inetAddress = socket.getInetAddress();
System.out.println("有新的客户端连接到我了: " + inetAddress.getHostAddress());
//3. 开始读取客户端给我发送的数据
//获取从客户端发送到我(服务器)的数据
InputStream inputStream = socket.getInputStream();
//把基础的字节流对象进行包装(缓冲流)
bis = new BufferedInputStream(inputStream);
byte[] buf = new byte[1024];
int len = 0;
//read()也会阻塞,当连接断开时返回-1
while ((len = bis.read(buf)) != -1) {
System.out.println(new String(buf, 0, len));
OutputStream outputStream = socket.getOutputStream();
outputStream.write("word".getBytes());
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
bis.close();
socket.close();
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
总结:
1)accept()阻塞方法,等待客户端的连接,如果有客户端进来才会解阻塞
2)read()阻塞方法,等待客户端发过来的数据,只有当有数据发送过来的时候,解除阻塞,当连接断开时,read()返回值-1
13.2 Tcp的客户(Client)端
package com.uplooking.tcp;
import sun.reflect.generics.scope.Scope;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
/**
* 基于Tcp的客户端
*/
public class TcpClient {
private static Socket socket;
private static OutputStream outputStream;
public static void main(String[] args) {
try {
//1. 创建Socket的客户端的对象
socket = new Socket();
//2. 给客户端指定一个端口
socket.bind(new InetSocketAddress(9999));
//3. 我(客户端)连接到tcp的服务端
socket.connect(new InetSocketAddress("172.16.6.6", 8899));
//4. 获取网络字节输出流
outputStream = socket.getOutputStream();
//5. 使用输出流对象写数据到网络中
outputStream.write("hello up".getBytes());
} catch (IOException e) {
System.out.println(e);
} finally {
try {
outputStream.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
总结:
tcp客户端没有任何的阻塞
14. 基于UDP的java网络编程(了解)
14.1 Udp的发送端的实现
package com.uplooking.udp;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;
public class UdpClinet {
private static DatagramSocket datagramSocket;
public static void main(String[] args) {
try {
//1. 创建DatagramSocket,并且绑定端口
datagramSocket = new DatagramSocket(9999);
//2. 连接到udp的主机
datagramSocket.connect(new InetSocketAddress("172.16.6.6", 8899));
//3. 创建数据报文
byte[] buf = "hello".getBytes();
DatagramPacket datagramPacket = new DatagramPacket(buf, 0, 3);
//4. 发送数据报文
datagramSocket.send(datagramPacket);
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
datagramSocket.close();
}
}
}
14.2 Udp的接收端的实现
package com.uplooking.udp;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
/**
* udp的接收端的实现
*/
public class UdpRec {
private static DatagramSocket datagramSocket;
public static void main(String[] args) {
try {
//1. 创建DatagramerSocket对象,并且绑定端口
datagramSocket = new DatagramSocket(9999);
//2. 创建数据报对象,用来接收数据
byte[] buf = new byte[10];
int len = buf.length;
DatagramPacket datagramPacket = new DatagramPacket(buf, 0, len);
//3. 接收数据(阻塞方法)
datagramSocket.receive(datagramPacket);
System.out.println(new String(buf, 0, datagramPacket.getLength()));
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
datagramSocket.close();
}
}
}
15. tcp的网络编程优化
上面13中写的tcp的网络编程存在问题吗?
- 我写的服务器同时只能处理一个客户端的请求
如何解决?
主线程专门用于接收客户端的请求,工作线程用于处理具体的请求
package com.uplooking.tcp1;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
public class HandlerTaskRunnable implements Runnable {
private Socket socket;
private BufferedInputStream bis;
public HandlerTaskRunnable() {
}
public HandlerTaskRunnable(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
InetAddress inetAddress = socket.getInetAddress();
System.out.println("有新的客户端连接到我了: " + inetAddress.getHostAddress());
//3. 开始读取客户端给我(服务器)发送的数据
//获取从客户端发送到我(服务器)的数据
InputStream inputStream = socket.getInputStream();
//把基础的字节流对象进行包装(缓冲流)
bis = new BufferedInputStream(inputStream);
byte[] buf = new byte[1024];
int len = 0;
//read()也会阻塞,当连接断开时返回-1
while ((len = bis.read(buf)) != -1) {
System.out.println(socket.getInetAddress().getHostAddress() + "====>" + new String(buf, 0, len));
OutputStream outputStream = socket.getOutputStream();
outputStream.write("word".getBytes());
}
System.out.println(socket.getInetAddress().getHostAddress() + "::断开了");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
bis.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
}
}
}
}
package com.uplooking.tcp1;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
public class HandlerTaskRunnable implements Runnable {
private Socket socket;
private BufferedInputStream bis;
public HandlerTaskRunnable() {
}
public HandlerTaskRunnable(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
InetAddress inetAddress = socket.getInetAddress();
System.out.println("有新的客户端连接到我了: " + inetAddress.getHostAddress());
//3. 开始读取客户端给我(服务器)发送的数据
//获取从客户端发送到我(服务器)的数据
InputStream inputStream = socket.getInputStream();
//把基础的字节流对象进行包装(缓冲流)
bis = new BufferedInputStream(inputStream);
byte[] buf = new byte[1024];
int len = 0;
//read()也会阻塞,当连接断开时返回-1
while ((len = bis.read(buf)) != -1) {
System.out.println(socket.getInetAddress().getHostAddress() + "====>" + new String(buf, 0, len));
OutputStream outputStream = socket.getOutputStream();
outputStream.write("word".getBytes());
}
System.out.println(socket.getInetAddress().getHostAddress() + "::断开了");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
bis.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
}
}
}
}
16. 通过上面的改良还有啥问题?
-
传统的socket存在两个地方的阻塞(accept read),这样我们如果处理的客户端的请求特别多的时候(并发量大),cpu轮训执行的线程太多了,cpu压力大;
-
传统的Socket把其成为BIO(block input output);同步(阻塞)IO
-
**后面讲(nio aio netty)