Java网络编程
Java网络编程
网络通信
网络通信是指在不同计算机或设备之间通过传输介质(电缆、无线信号等)进行数据交换的过程。它是计算机网络的基础功能之一,使得分布在不同地理位置的设备能够相互连接和交换信息。
网络通信的过程大致可以分为以下几个步骤:
1.建立连接:通信的双方需要建立一个通信链路,确保数据可以在两者之间传输
2.数据传输:按照一定的协议(如TCP/IP),将数据分割成数据包,然后通过网络发送到目标设备
3.数据接收:目标设备接收到数据包后,按照协议进行数据重组,确保数据的完整性和正确性。
4.结束连接:数据传输完成后,通信双方断开连接,释放网络资源
网络通信可以基于不同的拓扑结构,如星型、环型、总线型等,并且可以根据通信范围分为局域网(LAN)、城域网(MAN)、广域网(WAN)。
在Java中提供了java.net包下提供了一系列的类和接口,用来完成网络通信。
IP地址(Internet Protocol Address)
IP地址(Internet Protocol Address)是指互联网协议地址,它是分配给每一台联网计算机的唯一标识符。IP地址用于确保数据包能够在网络中正确地从源头传送到目的地。
在windows命令中查看ip地址的命令为: ipconfig
IP地址由网络地址与主机地址组成。
IPv4地址
形式: 由32位二进制数组成,通常表示为四组十进制数字,每组数组范围是0到255。
分类: IPv4地址分为A、B、C、D、E五类,其中A,B,C三类地址用于常规的网络寻址,D类用于多播,E类则保留用于实验目的。
eg: 一个IPv4的地址: 192.168.1.1
IPv6地址
形式: 由128位二进制组成,通常表示为八组四位十六进制数。
特点: IPv6地址空间巨大,足以给地球上每一台设备分配,解决了IPv4地址耗尽的问题。
eg: 一个IPv6地址: fe80:c785:0000:0000:0db8:c785:4ef9:7ae8。也可以简写为fe80:c785::0db8:c785:4ef9:7ae8。
在IPv6地址表示法中,“::”表示零压缩表示法,用于压缩连续的零位序列。可以表示一个或多个连续的16位零组,但"::"在IPv6地址中只能出现一次,避免产生歧义。
域名(Domain Name)
域名(Domain Name)是互联网上用于识别和定位计算机地址的一种方式。相对于计算机的IP地址来说,域名更加直观和便于用户理解。
例如"www.baidu.com"就是一个域名,在一个域名系统中,从右向左的各个部分分别代表不同的级别,"com"是顶级域名,"baidu"是二级域名,"www"则是三级域名。
域名需要通过域名系统(DNS)进行解析,将便于记忆的域名转换为机器能够识别的IP地址,这样才能方位对应的网站或服务。在互联网中,每一个域名都是唯一的,不可以重复注册。人们可以通过购买或注册域名来获取其使用权。
端口号
端口号是计算机网络通信中用于区分不同网络服务的逻辑地址。在互联网协议(IP)中,IP地址用于标识网络中的设备,而端口号则用于标识设备上的具体服务或进程。
每一个网络服务通常都会绑定到一个特定的端口号上,这样当数据包到达一个设备时,操作系统就能根据端口号将数据包正确转发到相应的服务或应用程序。端口号是一个16位的数字,其取值范围从0到65535,其中0到1023是系统端口,也成为知名端口。这些端口通常被分配给了特定的服务。
80端口用于HTTP(超文本传输协议)服务
443端口用于HTTPS(安全的超文本传输协议)服务
21端口用于FTP(文件传输协议)服务
22端口用于SSH(安全外壳协议)服务
在网络通信中,端口号与IP地址结合在一起,形成了网络套接字(Socket),这样就可以精确地定位到网络上的一个具体服务。比如一台服务器IP为192.168.1.1,并且HTTP服务在运行,那么可以通过192.168.1.1:80来访问该服务器的HTTP服务。
套接字(Socket)
套接字(Socket)是计算机网络编程中一种抽象概念,用于描述网络中两个程序之间的通信链路。它是通信的端点,类似于插座。
通信端点:
套接字可以看作是网路中两个程序之间通信的端点。每个套接字都与一个IP地址和端口号相关联,用于唯一标识通信的目标或来源。
在TCP/IP协议中,套接字由四元组(源IP地址、源端口号、目标IP地址、目标端口号)唯一确定.
套接字类型:
流套接字(Stream Socket):使用TCP协议,提供面向连接的、可靠的数据流传输。通过TCP连接,数据传输具有顺序性与可靠性。
数据报套接字(Datagram Socket):使用UDP协议,提供无连接的、不可靠的数据传输。通过UDP可以发送数据包,但不保证顺序性和可靠性。
网络编程中的使用:
开发人员通过套件字API来创建、配置、连接和通信套接字。常见的操作包括创建套接字、绑定到地址和端口、监听连接请求、接收连接、发送和接受数据等。
网络通信协议
网络通信协议是指在计算机网络中,通信双方在数据传输过程中必须遵守的规则和约定。它定义了数据传输的格式,传输的顺序,错误检测和纠正的方法以及数据交换的机制等等,以确保不同计算机和网络设备之间能够有效地进行通信。
网络通信协议模型
网络通信协议模型是计算机网络中用于规范数据通信的一套抽象分层结构。这些模型定义了每一层的功能、服务以及协议,以确保不同设备和网络之间的互操作性。这种抽象模型最著名的时OSI模型(开发式系统互联网通信参考模型)和TCP/IP模型(传输控制协议/互联网协议模型)。
OSI模型是由国际标准化组织(ISO)提出,将网络通信过程分为七个层次。TCP/IP模型则是一个更实用的网络模型,划分成了五层或四层,这两种没有本质区别,只是分类方式不同(物理层和数据链路层分开或合并)。
TCP/IP四层模型的常见协议:
网络接口层(Link Layer):
以太网(Ethernet),Wi-Fi(802.11),PPP(点对点协议)
网络层(Internet Layer):
IP(互联网协议),ICMP(互联网控制消息协议),IGMP(互联网组管理协议)
传输层(Transport Layer):
TCP(传输控制协议),UDP(用户数据报协议),SCTP(流控制传输协议)
应用层(Application Layer)
HTTP(超文本传输协议),FTP(文件传输协议),SSH(安全外壳协议)
TCP协议和UDP协议
TCP协议和UDP协议都是传输层的协议,但两者在网络通信中有着各自的特定与适用场景
TCP协议与UDP协议的区别
- 连接性
TCP是面向连接的协议,通信双方在数据传输前需要先建立连接,确保数据可靠性和顺序性,然后在进行数据传输,传输完成后释放连接(三次握手、传输确认、四次挥手)。
UDP是无连接的协议,通信双方在传输数据前不需要建立连接,数据包的发送者与接收者之间关系松散,每个数据包都独立处理,不保证顺序性和可靠性。
- 可靠性
TCP提供可靠的数据传输,它使用序列号、确认应答、重传机制等保证数据的可靠性,确保数据能够完整、按顺序地传输到接收端。
UDP不保证数据的可靠性,数据包发送后不会进行确认,也不会重传丢失的数据包,因此可能会出现丢包或乱序的情况。
- 效率
TCP相对于UDP来说,因为提供了可靠性保证,传输过程中会增加较多的控制和管理机制,传输效率不如UDP。
UDP由于不需要建立连接和维护状态,整体效率较高,适合对实时性要求较高的数据传输,如音频、视频等。
- 应用场景
TCP常用于要求可靠性较高的应用,如网页浏览、文件传输、电子邮件等,这些应用需要确保数据完整性和顺序性。
UDP适用于实时性要求较高,对可靠性要求较低的应用,如在线游戏,视频会议,实时通信等,能够快速传输数据而不需要等待建立连接和确认。
- 头部开销
TCP的头部开销较大,包含较多控制信息,如序列号、确认应答等,使得每个TCP数据包的大小比UDP大。
UDP的头部较小,只包含基本的源端口、目标端口、长度等信息,因此每个UDP数据包开销较小。
TCP协议的三次握手
第一步:SYN
客户端发送一个带有SYN(同步)标志的TCP数据包给服务器,表明客户端请求建立连接。
数据包中的序列号字段会包含一个随机的初始序列号,用于后序数据传输时的标识和排序。
第二步:SYN+ACK
服务器收到客户端发送的SYN数据包后,如果同意建立连接,会回复一个带有SYN+ACK(确认)标志的数据包给客户端。
ACK标志确认服务器已收到客户端的SYN请求,并且服务器也发送了一个随机的初始序列号。
数据包中的确认序列号字段会设置为客户端发送的序列号加一,以确认收到的序列号。
第三步:ACK
客户端收到服务器发送的SYN+ACK数据包后,会向服务器发送一个带有ACK标志的数据包,表示已收到服务器的确认。
客户端的确认序列号设置为服务器发送的序列号加一,表示客户端已准备好接收从服务器发来的数据。
TCP协议的四次挥手
第一步:FIN1
主动关闭方(通常是客户端)发送一个带有FIN(finsh)标志的TCP报文给被动关闭方(通常是服务器),表示它已经完成数据发送任务,希望关闭连接。
主动关闭方进入FIN_WAIT_1状态,等待被动关闭方的确认。
第二步:ACK1
被动关闭方收到FIN报文后,会发送一个确认应答(ACK)给主动关闭方,确认收到了关闭请求。
被动关闭方进入了CLOSE_WAIT状态。
第三步:FIN2
被动关闭方现在也想关闭连接,于是它也发送一个带有FIN标志的TCP报文给主动关闭方。
被动关闭方进入LAST_ACK状态,等待主动关闭方确认。
第四步:ACK2
主动关闭方收到FIN后,发送一个确认应答(ACK)给被动关闭方。
主动关闭方进入TIME_WAIT状态,等待可能延迟的确认消息,确保被动关闭方接收到了自己的确认。
最后:TIME_WAIT
在TIME_WAIT状态持续了一段时间(通常是等待2倍的最大报文段生存时间-MSL,以确保最后的ACK到达),然后主动关闭方关闭连接,释放资源。
被动关闭方在收到主动关闭方的确认后,也关闭连接,释放资源。
windows命令-netstat
netstat是一个用于显示网络统计信息的命令。它提供了关于当前网络连接状态、路由表、接口统计等信息
netstat -选项
常见选项:
a:显示所有连接和监听端口(包含TCP和UDP)
n:以数字形式显示地址和端口号,而不进行域名解析
b:显示每个连接的应用程序名称
o:显示每个连接的进程ID
-p 协议名:显示指定的TCP或UDP的连接
-s:显示每个协议的统计信息
-p:显示路由表
eg: 查看当前主机的网络情况
netstat -an
协议 本地地址 外部地址 状态
TCP 0.0.0.0:49668 0.0.0.0:0 LISTENING
TCP 10.0.23.64:139 0.0.0.0:0 LISTENING
TCP 10.0.23.64:5421 20.42.144.52:443 ESTABLISHED
TCP 10.0.23.64:5433 40.83.247.108:443 ESTABLISHED
LISTENING表示该端口在监听
ESTABLISHED表示该端口与外部地址已建立连接,正在进行传输
TCP网络编程
TCP是一种面向连接的、可靠的传输协议。在网络编程中,利用TCP协议进行通信通常涉及以下概念与步骤
套接字(Socket)
套接字是网络编程中的基本概念,用于描述通信的端点。TCP网络编程中,通信双方各自创建一个套接字,用于连接建立和数据传输。
客户端-服务器模型
TCP通常基于客户端-服务器模型进行通信。客户端通过套接字连接到服务器,发送请求并接收响应。
通信过程
1.创建套接字
2.建立连接
3.数据传输
4.关闭连接
TCP网络编程示例1-发送消息
编写一个客户端和服务端,客户端发送hello server,服务端接收后发送hello client
客户端代码
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
/**
* TCP网络编程示例-客户端
*/
@Slf4j
public class SocketTcpClient {
public static void main(String[] args) throws IOException {
//连接服务端(ip,端口号),连接上服务端后生成socket
Socket socket = new Socket(InetAddress.getLocalHost(),18099);
//通过输出流,写入数据到数据通道
OutputStream outputStream = socket.getOutputStream();
outputStream.write("hello server".getBytes());
//设置结束标志
socket.shutdownOutput();
//通过getInputStream()方法,读取服务端写入到数据通道的数据
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int len;
while ((len = inputStream.read(bytes)) != -1) {
log.info(new String(bytes, 0, len));
}
//关闭流和socket
inputStream.close();
outputStream.close();
socket.close();
log.info("客户端 结束");
}
}
服务端代码
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* TCP网络编程示例-服务端
*/
@Slf4j
public class SocketTcpServer {
public static void main(String[] args) throws IOException {
//在本机监听18089端口,等待连接(无连接时阻塞)
ServerSocket serverSocket = new ServerSocket(18099);
log.info("正在监听 18099 端口号");
//serverSocket可以通过accept()方法返回多个Socket
Socket socket = serverSocket.accept();
//通过getInputStream()方法,读取客户端写入到数据通道的数据
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int len;
while ((len = inputStream.read(bytes)) != -1) {
log.info(new String(bytes, 0, len));
}
//通过getOutputStream()方法,将数据写入数据通道
OutputStream outputStream = socket.getOutputStream();
outputStream.write("hello client".getBytes());
//写入后设置结束标志
socket.shutdownOutput();
//关闭流和socket
outputStream.close();
inputStream.close();
socket.close();
serverSocket.close();
log.info("服务端 结束");
}
}
TCP网络编程示例2-上传文件
编写一个服务端和客户端,客户端发送一张图片,服务端接收后放到指定路径后向客户端端发送收到。由于上传文件实际上是上传字节数组,因此再编写一个工具类用于将文件转换成字节数组
工具类代码
import lombok.extern.slf4j.Slf4j;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* 工具类-转换流为字节数组
*/
@Slf4j
public class StringUtils {
public static byte[] streamToByteArray(InputStream inputStream) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] bytes = new byte[1024];
int len;
while ((len = inputStream.read(bytes)) != -1) {
byteArrayOutputStream.write(bytes, 0, len);
}
byte[] array = byteArrayOutputStream.toByteArray();
byteArrayOutputStream.close();
return array;
}
private StringUtils() {
}
}
客户端代码
import lombok.extern.slf4j.Slf4j;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
/**
* TCP网络编程示例-上传文件
*/
@Slf4j
public class TcpUploadClient {
public static void main(String[] args) throws IOException {
//连接服务端(ip,端口号),连接上服务端后生成socket
Socket socket = new Socket(InetAddress.getLocalHost(),18099);
//通过输出流,写入数据到数据通道
OutputStream outputStream = socket.getOutputStream();
File file = new File("E:\\test.png");
FileInputStream fileInputStream = new FileInputStream(file);
byte[] fileBytes = StringUtils.streamToByteArray(fileInputStream);
outputStream.write(fileBytes);
socket.shutdownOutput();
//通过getInputStream()方法,读取服务端写入到数据通道的数据
InputStream inputStream = socket.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String s = bufferedReader.readLine();
log.info("{}", s);
//关闭外层流和socket
bufferedReader.close();
fileInputStream.close();
outputStream.close();
socket.close();
log.info("客户端 结束");
}
}
服务器代码
import lombok.extern.slf4j.Slf4j;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* TCP网络编程示例-上传文件
*/
@Slf4j
public class TcpUploadServer {
public static void main(String[] args) throws IOException {
//在本机监听18089端口,等待连接(无连接时阻塞)
ServerSocket serverSocket = new ServerSocket(18099);
log.info("正在监听 18099 端口号");
//serverSocket可以通过accept()方法返回多个Socket
Socket socket = serverSocket.accept();
//通过getInputStream()方法,读取客户端写入到数据通道的数据
InputStream inputStream = socket.getInputStream();
byte[] fileBytes = StringUtils.streamToByteArray(inputStream);
String filePath = System.getProperty("user.dir") + File.separator + "1.png";
FileOutputStream fileOutputStream = new FileOutputStream(filePath);
fileOutputStream.write(fileBytes);
//通过getOutputStream()方法,将数据写入数据通道
OutputStream outputStream = socket.getOutputStream();
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
bufferedWriter.write("收到 test 图片");
bufferedWriter.newLine();
bufferedWriter.flush();
//关闭外层流和socket
bufferedWriter.close();
socket.close();
serverSocket.close();
log.info("服务端 结束");
}
}
UDP网络编程
UDP是一种无连接的、不可靠的传输协议,适用于那些对实时性要求高、可以容忍少量数据丢失的应用场景。在网络编程中,利用UDP协议进行通信通常涉及以下概念与步骤
套接字(Socket)
套接字是网络编程中的基本概念,用于描述通信的端点。TCP网络编程中,通信双方各自创建一个套接字,用于连接建立和数据传输。
客户端-服务器模型
TCP通常基于客户端-服务器模型进行通信。客户端通过套接字连接到服务器,发送请求并接收响应。(不一定有客户端-服务端,两端都是发送端与接收端)
通信过程
1.创建套接字
2.数据传输
UDP网络编程示例1-发送消息
编写一个发送端和接受端,发送端发送hello receiver,接收端收到后回复发送端
发送端代码
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
* UDP网络编程 接收端-发送端
*/
@Slf4j
public class UdpSender {
public static void main(String[] args) throws IOException {
//创建一个 DatagramSocket 对象,准备接收18088端口的数据
DatagramSocket datagramSocket = new DatagramSocket(18088);
//构建一个DatagramPacket对象,用于发送数据
byte[] bytes = "hello receiver".getBytes();
DatagramPacket sendDatagramPacket = new DatagramPacket(bytes, bytes.length, InetAddress.getLocalHost(), 18099);
//调用发送数据方法,输出到DatagramPacket对象中,如果没有接收到则阻塞
datagramSocket.send(sendDatagramPacket);
//对接受到数据的DatagramPacket对象进行解包
//获取实际数据包的长度和数据
byte[] receiveBytes = new byte[1024];
DatagramPacket receiveDatagramPacket = new DatagramPacket(receiveBytes, receiveBytes.length);
datagramSocket.receive(receiveDatagramPacket);
int packageLength = receiveDatagramPacket.getLength();
byte[] packageData = receiveDatagramPacket.getData();
log.info("发送端收到的数据为: {}", new String(packageData, 0, packageLength));
//关闭资源
datagramSocket.close();
log.info("Sender 结束");
}
}
接收端代码
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
* UDP网络编程 发送端-接收端
*/
@Slf4j
public class UdpReceiver {
public static void main(String[] args) throws IOException {
//创建一个 DatagramSocket 对象,准备接收18099端口的数据
DatagramSocket datagramSocket = new DatagramSocket(18099);
//构建一个DatagramPacket对象,用于接收数据
byte[] bytes = new byte[1024];
DatagramPacket receiveDatagramPacket = new DatagramPacket(bytes, bytes.length);
//调用接受数据方法,输出到DatagramPacket对象中,如果没有接收到则阻塞
datagramSocket.receive(receiveDatagramPacket);
//对接受到数据的DatagramPacket对象进行解包
//获取实际数据包的长度和数据
int packageLength = receiveDatagramPacket.getLength();
byte[] packageData = receiveDatagramPacket.getData();
log.info("接收端收到的数据为: {}", new String(packageData, 0, packageLength));
byte[] sendBytes = "I'm fine, hello sender".getBytes();
DatagramPacket sendDatagramPacket = new DatagramPacket(sendBytes, sendBytes.length, InetAddress.getLocalHost(), 18088);
//调用发送数据方法,输出到DatagramPacket对象中,如果没有接收到则阻塞
datagramSocket.send(sendDatagramPacket);
//关闭资源
datagramSocket.close();
log.info("Receiver 结束");
}
}