1.通信协议分层
上图中,TCP/IP协议中的四层分别是应用层、传输层、网络层和链路层,每层分别负责不同的通信功能。
应用层:主要负责应用程序的协议,例如HTTP协议、FTP协议等。
传输层:主要使网络程序进行通信,在进行网络通信时,可以采用TCP协议,也可以采用UDP协议。
网络层:网络层是整个TCP/IP协议的核心,它主要用于将传输的数据进行分组,将分组数据发送到目标计算机或者网络。
数据链路层:链路层是用于定义物理传输通道,通常是对某些网络连接设备的驱动协议,例如针对光纤、网线提供的驱动。
我们编写的程序位于应用层,因此我们的程序是和TCP/UDP打交道的。
2.协议分类
java.net包中提供了两种常见的网络协议的支持:TCP和UDP
TCP是可靠的连接,TCP就像打电话,需要先打通对方电话,等待对方有回应后才会跟对方继续说话,也就是一定要确认可以发信息以后才会把信息发出去。TCP上传任何东西都是可靠的,只要两台机器上建立起了连接,在本机上发送的数据就一定能传到对方的机器上。
UDP就好比发短信,对方有没有接收到它都不管,所以UDP是不可靠的。
TCP传送数据虽然可靠,但传送得比较慢;UDP传送数据不可靠,但是传送得快。
1.UDP
用户数据报协议(User Datagram Protocol)。
数据报(Datagram):网络传输的基本单位
UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。
由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输例如视频会议都使用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。
但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时,不建议使用UDP协议。
特点:数据被限制在64kb以内,超出这个范围就不能发送了。
2.TCP
传输控制协议(Transmission Control Protocol)。
TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。
在TCP连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”。
为什么挥手需要四次?
关闭连接时,客户端向服务端发送 FIN 时,仅仅表示客户端不再发送数据了但是还能接收数据。服务器收到客户端的 FIN 报文时,先回一个 ACK 应答报文,而服务端可能还有数据需要处理和发送,等服务端不再发送数据时,才发送 FIN 报文给客户端来表示同意现在关闭连接。从上面过程可知,服务端通常需要等待完成数据的发送和处理,所以服务端的 ACK 和 FIN 一般都会分开发送,从而比三次握手导致多了一次。
通俗理解:
三次握手
A:我要过来了!B:我知道你要过来了!A:我现在过来!
四次挥手
A:我们分手吧!B:真的分手吗?B:真的真的要分手吗?A:是的!
3.InetAddress类
此类表示互联网协议 (IP) 地址。InetAddress类无构造方法.
1.常用方法摘要
byte[] getAddress()
返回此 InetAddress 对象的原始 IP 地址。static InetAddress getByName(String host)
在给定主机名的情况下确定主机的 IP 地址。String getHostAddress()
返回 IP 地址字符串(以文本表现形式)。String getHostName()
获取此 IP 地址的主机名。static InetAddress getLocalHost()
返回本地主机。
127.0.0.1:本机地址,主要用于测试。别名:Localhost
public static void main(String[] args) throws UnknownHostException {
//InetAdress类表示IP地址
//获取本机IP
InetAddress ip = InetAddress.getLocalHost();
System.out.println(ip);
//获得主机名
System.out.println(ip.getHostName());
//获得IP地址
System.out.println(ip.getHostAddress());
//getLocalHost=getHostName+getHostAddress
System.out.println(InetAddress.getByName("localhost"));
InetAddress inetAddress = InetAddress.getByName("www.baidu.com");
// 获取此 IP 地址的主机名。
System.out.println(inetAddress.getHostName());
//返回 IP 地址字符串(以文本表现形式)。
System.out.println(inetAddress.getHostAddress());
}
4.端口
- IP地址用来标识一台计算机,但是一台计算机上可能提供多种网络应用程序,如何来区分这些不同的程序呢?这就要用到端口。端口号是用来区分一台机器上不同的应用程序的。
- 我们使用IP地址加端口号,就可以保证数据准确无误地发送到对方计算机的指定软件上了。
- 端口是虚拟的概念,是一个逻辑端口。
- 当我们使用网络软件一打开,那么操作系统就会为网络软件分配一个随机的端口号,或者打开的时候向系统要指定的端口号。
- 通过端口,可以在一个主机上运行多个网络应用程序。端口的表示是一个16位的二进制整数,2个字节,对应十进制的0~65535。
- 端口号在计算机内部是占2个字节。一台机器上最多有65536个端口号。一个应用程序可以占用多个端口号。端口号如果被一个应用程序占用了,那么其他的应用程序就无法再使用这个端口号了。
- 记住一点,我们编写的程序要占用端口号的话占用1024以上的端口号,1024以下的端口号不要去占用,因为系统有可能会随时征用。端口号本身又分为TCP端口和UDP端口,TCP的8888端口和UDP的8888端口是完全不同的两个端口。TCP端口和UDP端口都有65536个
1.分类
- 公有端口:0~1023
- HTTP:80
- HTTPS:443
- FTP:21
- Telnet:23
- 程序注册端口(分配给用户或者程序):1024~49151
- Tomcat:8080
- MySQL:3306
- Oracle:1521
- 动态、私有端口:49152~65535
3.InetSocketAddress类
说到端口,则要引入一个类:InetSocketAddress
此类实现 IP 套接字地址(IP 地址 + 端口号)。
InetSocketAddress(InetAddress addr, int port)
根据 IP 地址和端口号创建套接字地址。InetSocketAddress(int port)
创建套接字地址,其中 IP 地址为通配符地址,端口号为指定值。InetSocketAddress(String hostname, int port)
根据主机名和端口号创建套接字地址。
InetAddress getAddress()
获取 InetAddress。String getHostName()
获取 hostname。int getPort()
获取端口号。
public static void main(String[] args) {
InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1",8082);
System.out.println(inetSocketAddress);
//返回主机名
System.out.println(inetSocketAddress.getHostName());
//获得InetSocketAddress的端口
System.out.println(inetSocketAddress.getPort());
//返回一个InetAddress对象(IP对象)
InetAddress address = inetSocketAddress.getAddress();
System.out.println(address);
}
5.TCP网络编程和UDP网络编程
网络编程也叫做Socket编程,即套接字编程。套接字指的是两台设备之间通讯的端点。
1.TCP
TCP通信能实现两台计算机之间的数据交互,通信的两端,要严格区分为客户端(Client)与服务端(Server)。
- 客户端:
java.net.Socket
类表示。创建Socket
对象,向服务端发出连接请求,服务端响应请求,两者建立连接开始通信。 - 服务端:
java.net.ServerSocket
类表示。创建ServerSocket
对象,相当于开启一个服务,并等待客户端的连接。
1.Socket 类
该类实现客户端套接字(也称为“套接字”)。 套接字是两台机器之间通讯的端点。
构造方法:
- public Socket(String host, int port) :创建套接字对象并将其连接到指定主机上的指定端口号。如果指定的host是null ,则相当于指定地址为回送地址。
回送地址(127.x.x.x) 是本机回送地址(Loopback Address),主要用于网络软件测试以及本地机进程间通信,无论什么程序,一旦使用回送地址发送数据,立即返回,不进行任何网络传输。
常用方法:
-
public InputStream getInputStream()
: 返回此套接字的输入流。- 如果此Scoket具有相关联的通道,则生成的InputStream 的所有操作也关联该通道。
- 关闭生成的InputStream也将关闭相关的Socket。
-
public OutputStream getOutputStream()
: 返回此套接字的输出流。- 如果此Socket具有相关联的通道,则生成的OutputStream 的所有操作也关联该通道。
- 关闭生成的OutputStream也将关闭相关的Socket。
-
public void close()
:关闭此套接字。- 一旦一个socket被关闭,它不可再使用。
- 关闭此socket也将关闭相关的InputStream和OutputStream 。
-
public void shutdownOutput()
: 禁用此套接字的输出流。- 任何先前写出的数据将被发送,随后终止输出流。
2.ServerSocket类
ServerSocket类:这个类实现了服务器套接字,该对象等待通过网络的请求。
构造方法:
public ServerSocket(int port)
:使用该构造方法在创建ServerSocket对象时,就可以将其绑定到一个指定的端口号上,参数port就是端口号。
常用方法:
public Socket accept()
:监听并接受连接,返回一个新的Socket对象,用于和客户端实现通信。该方法会一直阻塞直到建立连接。
private static boolean client(){
boolean flag = false;
System.out.println("客户端运行");
InetAddress inetAddress = null;
String ip;
Socket socket = null;
try {
inetAddress = InetAddress.getLocalHost();
ip = inetAddress.getHostAddress();
socket = new Socket(ip,10001);
PrintWriter pw = new PrintWriter(socket.getOutputStream(),true);
pw.println(type);
pw.println(userName);
pw.println(password);
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
if ("true".equals(br.readLine())){
flag = true;
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if (socket != null) {
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return flag;
}
private static void service(){
System.out.println("服务端运行");
try (ServerSocket serverSocket = new ServerSocket(10001);Socket socket = serverSocket.accept()){
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String type = br.readLine();
String userName = br.readLine();
String password = br.readLine();
boolean flag = false;
if ("register".equals(type)){
flag = userRegister(userName,password);
}
if ("signIn".equals(type)) {
flag = userSignIn(userName, password);
}
PrintWriter pw = new PrintWriter(socket.getOutputStream(),true);
pw.println(flag);
} catch (IOException e) {
e.printStackTrace();
}
}
2.UDP
UDP是无连接协议,所以可以只有任何一端,例如客户端数据发往服务端,服务端存在与否无所谓。
构造方法:
-
-
DatagramSocket()
构造数据报套接字并将其绑定到本地主机上的任何可用端口。
protected
DatagramSocket(DatagramSocketImpl impl)
使用指定的DatagramSocketImpl创建一个未绑定的数据报套接字。
DatagramSocket(int port)
构造数据报套接字并将其绑定到本地主机上的指定端口。
DatagramSocket(int port, InetAddress laddr)
创建一个数据报套接字,绑定到指定的本地地址。
-
常用方法:
-
-
void
receive(DatagramPacket p)
从此套接字接收数据报包。
void
send(DatagramPacket p)
从此套接字发送数据报包。
-
2.DatagramPacket类
构造方法:
-
-
DatagramPacket(byte[] buf, int length)
构造一个
DatagramPacket
用于接收长度的数据包length
。DatagramPacket(byte[] buf, int length, InetAddress address, int port)
构造用于发送长度的分组的数据报包
length
指定主机上到指定的端口号。
-
3.TCP发送Object
发送某个类时,需要将那个类变得可序列化,一般让那个类实现implements Serializable接口即可
4.UDP发送Object
需要使用两对类,ByteArrayOutputStream,ByteArrayInputStream和ObjectOutputStream,ObjectInputStream例:
//发送端
byte[] bytes = null;
ByteArrayOutputStream os = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeObject(data);
bytes = os.toByteArray();
DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length, inetAddress, 10008);
datagramSocket.send(datagramPacket);
//接收端
ByteArrayInputStream is = new ByteArrayInputStream(bytes,0, bytes.length);
ObjectInputStream ois = new ObjectInputStream(is);
HashMap<String,Integer> data = (HashMap<String, Integer>) ois.readObject();