Socket
在Java中,Socket是一种用于网络通信的编程接口,它允许不同计算机之间的程序进行数据交换和通信。Socket使得网络应用程序能够通过TCP或UDP协议在不同主机之间建立连接、发送数据和接收数据。以下是Socket的基本介绍:
-
Socket类型:在Java中,有两种主要类型的Socket,分别用于不同类型的通信协议:
-
TCP Socket:使用TCP协议进行通信,提供可靠的、面向连接的数据传输。TCP Socket使用
Socket
类进行创建和管理。 -
UDP Socket:使用UDP协议进行通信,提供不可靠的、面向无连接的数据传输。UDP Socket使用
DatagramSocket
类进行创建和管理。 -
套接字连接:Socket允许两台计算机之间建立连接,一台计算机充当服务器,另一台计算机充当客户端。服务器Socket等待客户端连接请求,客户端Socket通过指定服务器地址和端口号来连接服务器。客户端和服务器端都有Socket,是两台机器通信的端点
-
通信协议:Socket可以使用不同的通信协议,最常见的是TCP和UDP。TCP提供可靠的、面向连接的通信,确保数据按顺序到达和错误处理。UDP提供不可靠的、面向无连接的通信,适用于低延迟应用。
-
数据传输:Socket允许应用程序通过输入和输出流(InputStream和OutputStream)进行数据传输。应用程序可以使用这些流发送和接收数据。
-
阻塞和非阻塞模式:Socket可以以阻塞或非阻塞模式工作。在阻塞模式下,Socket操作将等待直到完成,而在非阻塞模式下,Socket操作可以立即返回。
-
安全性:Socket通信可以通过安全套接字层(SSL)或传输层安全性(TLS)来加密,以提供数据的保密性和完整性。
TCP网络通信编程
TCP(传输控制协议)是一种可靠的、面向连接的网络通信协议,它用于在计算机网络上可靠地传输数据。TCP通信是客户端-服务器模型的基础,它确保了数据的可靠性和有序性。以下是TCP网络通信编程的基本介绍:
-
面向连接:TCP是一种面向连接的协议,通信的两端(客户端和服务器)在开始通信之前必须建立连接。连接建立后,数据可以在两端之间可靠地传输。
-
可靠性:TCP提供可靠的数据传输。它使用确认机制来确保数据的完整性和有序性。如果接收方收到数据,它会发送一个确认(ACK)给发送方,如果发送方没有收到确认,它会重新发送数据。
-
有序性:TCP保证数据按发送的顺序到达接收方。即使数据包在传输过程中可能以不同的顺序到达,TCP会重新排列它们,以确保接收方按正确的顺序接收数据。
-
流式数据传输:TCP提供了流式数据传输,数据被视为连续的字节流,而不是分成离散的数据包。这使得TCP适用于传输大文件和多媒体数据。
-
双向通信:TCP连接是全双工的,允许双方同时发送和接收数据。客户端和服务器都可以发送请求和响应。
-
端口号:TCP通信使用端口号来标识不同的服务或应用程序。服务器应用程序通常监听特定端口,客户端通过指定目标主机和端口来连接到服务器。
-
阻塞式通信:TCP通信默认是阻塞的,这意味着发送和接收数据的操作会一直阻塞,直到完成。这可以通过多线程或非阻塞I/O来处理,以确保应用程序的响应性。
-
安全性:TCP通信可以通过安全套接字层(SSL)或传输层安全性(TLS)来加密,以提供数据的保密性和完整性。
在Java中,你可以使用java.net
包中的Socket
和ServerSocket
类来实现TCP网络通信。客户端使用Socket
类来建立与服务器的连接,而服务器使用ServerSocket
类来监听客户端连接请求。以下是一些简单的Java TCP服务器和客户端的示例,以便更好地理解TCP通信的基本原理:
1.使用字节流的方式发送数据和接收数据
服务器端代码:
public class Server {
public static void main(String[] args) throws IOException {
//1. 在本机 的 9999 端口监听, 等待连接
// 细节: 要求在本机没有其它服务在监听 9999,客户端的端口号只能被一个服务监听
// 细节:这个 ServerSocket 可以通过 accept() 返回多个 Socket[多个客户端连接服务器的并发]
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务端,在 9999 端口监听,等待连接..");
//2. 当没有客户端连接 9999 端口时,程序会 阻塞, 等待连接
// 如果有客户端连接,则会返回 Socket 对象,程序继续
Socket socket = serverSocket.accept();
//3.从一个已建立的网络套接字(Socket)中获取输入流,以便从该套接字接收数据
InputStream inputStream = socket.getInputStream();
//4.通过输入流从网络套接字中读取数据
byte[] buf = new byte[1024];
int readLen = 0;
while ((readLen = inputStream.read(buf)) != -1) {
System.out.println(new String(buf,0,readLen));
}
//5.从一个已建立的网络套接字(Socket)中获取输出流,以便将数据发送到该套接字
OutputStream outputStream = socket.getOutputStream();
//6.通过输出流 (outputStream) 发送内容到网络套接字
outputStream.write("hello,client".getBytes());
//7.设置结束标记。用于关闭套接字(Socket)的输出流。具体来说,它关闭套接字的输出流,表示在此之后不再发送数据到套接字的另一端
socket.shutdownOutput();
//8.关闭流和 socket
inputStream.close();
outputStream.close();
socket.close();
serverSocket.close();
}
}
客户端代码:
public class Client {
public static void main(String[] args) throws IOException {
//1. 连接服务端 (ip , 端口)
//连接本机的 9999 端口, 如果连接成功,返回 Socket 对象
Socket socket = new Socket(InetAddress.getLocalHost(),9999);
//2.从一个已建立的网络套接字(Socket)中获取输出流,以便将数据发送到该套接字
OutputStream outputStream = socket.getOutputStream();
//3.通过输出流 (outputStream) 发送内容到网络套接字
outputStream.write("hello,server".getBytes());
//4.设置结束标记。用于关闭套接字(Socket)的输出流。具体来说,它关闭套接字的输出流,表示在此之后不再发送数据到套接字的另一端
socket.shutdownOutput();
//5.从一个已建立的网络套接字(Socket)中获取输入流,以便从该套接字接收数据
InputStream inputStream = socket.getInputStream();
//6.通过输入流从网络套接字中读取数据
byte[] buf = new byte[1024];
int readLen = 0;
while ((readLen = inputStream.read(buf)) != -1){
System.out.println(new String(buf,0,readLen));
}
//7.关闭流和 socket
outputStream.close();
inputStream.close();
socket.close();
System.out.println("客户端退出");
}
}
2.使用字符流的方式发送数据和接收数据
服务器端代码:
public class Server {
public static void main(String[] args) throws IOException {
//1. 在本机 的 9999 端口监听, 等待连接
// 细节: 要求在本机没有其它服务在监听 9999,客户端的端口号只能被一个服务监听
// 细节:这个 ServerSocket 可以通过 accept() 返回多个 Socket[多个客户端连接服务器的并发]
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务端,在 9999 端口监听,等待连接..");
//2. 当没有客户端连接 9999 端口时,程序会 阻塞, 等待连接
// 如果有客户端连接,则会返回 Socket 对象,程序继续
Socket socket = serverSocket.accept();
//3.从一个已建立的网络套接字(Socket)中获取输入流,以便从该套接字接收数据
InputStream inputStream = socket.getInputStream();
//4.将字节流转换为字符流
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
//5.对字符流进行包装,提供了更方便的读取方法,如readLine()可以一次读取一行数据
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
//6.通过输入流从网络套接字中读取数据
String line = "";
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
//7.从一个已建立的网络套接字(Socket)中获取输出流,以便将数据发送到该套接字
OutputStream outputStream = socket.getOutputStream();
//8.将字节流转换为字符流
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
//9.对字符流进行包装,提供了更方便的写入方法,如writer()可以直接写入字符串而不用转换为字符数组
BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter);
//10.通过输出流发送内容到网络套接字
bufferedWriter.write("hello,client");
//11.当我们使用缓冲写入数据时,数据会先被写入到缓冲区中,而不是直接写入到目标文件或流中。
// 调用flush()方法会强制将缓冲区中的数据立即写入到目标文件或流中,确保数据的及时更新。
// 这样可以避免数据在缓冲区中滞留而没有被写入到目标文件或流中的情况发生。
bufferedWriter.flush();
//12.设置结束标记。用于关闭套接字(Socket)的输出流。具体来说,它关闭套接字的输出流,表示在此之后不再发送数据到套接字的另一端
socket.shutdownOutput();
//13.关闭流和 socket
bufferedReader.close();
bufferedWriter.close();
socket.close();
serverSocket.close();
}
}
客户端代码:
public class Client {
public static void main(String[] args) throws IOException {
//1. 连接服务端 (ip , 端口)
//连接本机的 9999 端口, 如果连接成功,返回 Socket 对象
Socket socket = new Socket(InetAddress.getLocalHost(),9999);
//2.从一个已建立的网络套接字(Socket)中获取输出流,以便将数据发送到该套接字
OutputStream outputStream = socket.getOutputStream();
//3.将字节流转换为字符流
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
//4..对字符流进行包装,提供了更方便的写入方法,如如writer()可以直接写入字符串而不用转换为字符数组
BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter);
//5.通过输出流发送内容到网络套接字
bufferedWriter.write("hello,server");
//6.当我们使用缓冲写入数据时,数据会先被写入到缓冲区中,而不是直接写入到目标文件或流中。
// 调用flush()方法会强制将缓冲区中的数据立即写入到目标文件或流中,确保数据的及时更新。
// 这样可以避免数据在缓冲区中滞留而没有被写入到目标文件或流中的情况发生。
bufferedWriter.flush();//需要刷新否则写不进去
//7.设置结束标记。用于关闭套接字(Socket)的输出流。具体来说,它关闭套接字的输出流,表示在此之后不再发送数据到套接字的另一端
socket.shutdownOutput();
//8.从一个已建立的网络套接字(Socket)中获取输入流,以便从该套接字接收数据
InputStream inputStream = socket.getInputStream();
//9.将字节流转换为字符流
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
//10.对字符流进行包装,提供了更方便的读取方法,如readLine()可以一次读取一行数据
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
//11.通过输入流从网络套接字中读取数据
String line = "";
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);}
//12.关闭流和 socket
bufferedWriter.close();
bufferedReader.close();
socket.close();
System.out.println("客户端退出");
}
}
3.发送图片的代码:
服务器端代码:
public class Server {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(6666);
System.out.println("服务端,在 6666 端口监听,等待连接..");
Socket socket = serverSocket.accept();
//通过将socket的输入流包装在BufferedInputStream中,可以在读取数据时先将数据存储在缓冲区中,
// 然后再从缓冲区中读取数据,减少了直接从输入流中读取数据的次数,提高了读取数据的效率。
//当然可以不用缓冲流也是可以的,但是效率不高,最好还是用缓冲流
BufferedInputStream bufferedInputStream = new BufferedInputStream(socket.getInputStream());
byte[] bytes = StreamUtils.streamToByteArray(bufferedInputStream);
String descFile = "src\\main\\java\\com\\example\\retcode\\test\\net\\socket\\tcp\\tcp4\\线程状态.bmp";
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(descFile));
bufferedOutputStream.write(bytes);
bufferedOutputStream.close();
//通过 socket 获取到输出流(字符)
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bufferedWriter.write("收到图片");
bufferedWriter.flush();
socket.shutdownOutput();
bufferedOutputStream.close();
bufferedInputStream.close();
bufferedWriter.close();
socket.close();
}
}
客户端代码:
public class Client {
public static void main(String[] args) throws Exception {
Socket socket = new Socket(InetAddress.getLocalHost(),6666);
String filePath = "d:\\线程状态.bmp";
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(filePath));
byte[] bytes = StreamUtils.streamToByteArray(bufferedInputStream);
OutputStream outputStream = socket.getOutputStream();
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream);
bufferedOutputStream.write(bytes);
socket.shutdownOutput();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line ="";
while ((line = bufferedReader.readLine())!=null){
System.out.println(line);}
bufferedOutputStream.close();
bufferedInputStream.close();
bufferedReader.close();
socket.close();
System.out.println("客户端退出");
}
}
StreamUtils类:
public class StreamUtils {
/**
* 功能:将输入流转换成byte[], 即可以把文件的内容读入到byte[]
* @param is
* @return
* @throws Exception
*/
public static byte[] streamToByteArray(InputStream is) throws Exception{
ByteArrayOutputStream bos = new ByteArrayOutputStream();//创建输出流对象
byte[] b = new byte[1024];//字节数组
int len;
while((len=is.read(b))!=-1){//循环读取
bos.write(b, 0, len);//把读取到的数据,写入bos
}
byte[] array = bos.toByteArray();//然后将bos 转成字节数组
bos.close();
return array;
}
/**
* 功能:将InputStream转换成String
* @param is
* @return
* @throws Exception
*/
public static String streamToString(InputStream is) throws Exception{
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder builder= new StringBuilder();
String line;
while((line=reader.readLine())!=null){
builder.append(line+"\r\n");
}
return builder.toString();
}
}
netstat指令
netstat
是一个用于显示网络连接和路由表信息的命令行工具,它可以在不同的操作系统中使用,包括Windows、Linux和macOS。netstat
可以帮助你监视和诊断网络连接问题,了解网络活动情况。以下是 netstat
命令的基本介绍:
在命令行中执行 netstat
命令时,通常需要提供一些选项和参数以获取特定的网络信息。以下是一些常见的 netstat
选项和用法:
-
查看所有活动连接:
netstat -a
这会显示所有的活动网络连接,包括TCP和UDP连接,以及监听中的端口。
-
查看活动TCP连接:
netstat -at
这会显示所有的活动TCP连接,包括本地地址、远程地址、状态等信息。
-
查看活动UDP连接:
netstat -au
这会显示所有的活动UDP连接,包括本地地址、远程地址等信息。
-
显示监听端口:
netstat -l
这会显示当前正在监听的端口,包括TCP和UDP。
-
显示路由表信息:
netstat -r
这会显示操作系统的路由表信息,包括网络地址、网关、接口等。
-
显示统计信息:
netstat -s
这会显示各种协议的统计信息,如TCP、UDP、ICMP等。
-
显示PID和程序名:
netstat -b
这会显示每个连接的相关进程的PID和程序名。在Windows系统中,需要以管理员权限运行才能查看程序名。
-
显示数字格式:
netstat -n
这会以数字格式显示网络地址和端口,而不是尝试解析主机名和服务名。
这只是 netstat
命令的一些常见选项和用法。根据你的需求,你可以根据操作系统和情况选择不同的选项以查看所需的网络信息。netstat
是网络管理和故障排除的有用工具,它可以帮助你了解网络连接、端口使用情况、路由信息等,以便更好地管理和维护计算机网络。
UDP编程
UDP(User Datagram Protocol,用户数据报协议)是一种无连接的、不可靠的网络通信协议,它用于在计算机网络上进行数据传输。UDP通信的特点是速度快,但不保证数据的可靠性。以下是Java中UDP编程的基本介绍:
-
无连接性:UDP是无连接的协议,通信双方在发送和接收数据之前不需要建立连接。这使得UDP通信的速度较快,因为不需要进行连接和断开的握手过程。
-
不可靠性:UDP不保证数据的可靠性,数据包可能会丢失、乱序到达或重复到达。因此,如果需要可靠的数据传输,必须在应用层进行额外的处理。
-
面向数据报:UDP通信是面向数据报的,每个UDP数据包都是独立的,没有关联性。这使得应用程序可以发送和接收独立的消息。
-
低开销:由于UDP没有建立连接和维护状态的开销,因此它在某些情况下比TCP更高效。UDP通常用于实时音频、视频和在线游戏等需要快速传输数据的应用。
-
广播和多播:UDP支持广播和多播,允许一个主机向多个接收者发送相同的数据包,而无需建立多个连接。
在Java中,你可以使用java.net
包中的DatagramSocket
和DatagramPacket
类来实现UDP编程。以下是基本的UDP编程步骤:
发送端(客户端):
- 创建一个
DatagramSocket
对象,用于发送数据。 - 创建一个
DatagramPacket
对象,将要发送的数据放入其中,并指定目标主机的IP地址和端口号。 - 使用
DatagramSocket
的send()
方法发送数据包。 - 关闭
DatagramSocket
以释放资源。
接收端(服务器端):
- 创建一个
DatagramSocket
对象,用于接收数据,并指定监听的端口号。 - 创建一个
DatagramPacket
对象,用于接收数据。 - 使用
DatagramSocket
的receive()
方法接收数据包。 - 从接收到的数据包中提取数据。
- 关闭
DatagramSocket
以释放资源。
以下是一个简单的UDP发送端和接收端的示例:
发送端代码:
public class UDPSender {
public static void main(String[] args) throws IOException {
//1.创建 DatagramSocket 对象,准备在 9998 端口 接收数据
DatagramSocket socket = new DatagramSocket(9998);
//2. 将需要发送的数据,封装到 DatagramPacket 对象
byte[] data = "hello 明天吃火锅~".getBytes();
//说明: 封装的 DatagramPacket 对象 data 内容字节数组 , data.length , 主机(IP) , 端口
DatagramPacket packet = new DatagramPacket(data, data.length, InetAddress.getByName("172.21.10.13"), 9999);
socket.send(packet);
//3.=== 接收从 A 端回复的信息
//(1) 构建一个 DatagramPacket 对象,准备接收数据
// 在前面讲解 UDP 协议时,一个数据包最大 64k
byte[] data1 = new byte[1024];
DatagramPacket packet1 = new DatagramPacket(data1, data1.length);
//(2) 调用 接收方法, 将通过网络传输的 DatagramPacket 对象
// 填充到 packet 对象
//当有数据包发送到 本机的 9998 端口时,就会接收到数据
// 如果没有数据包发送到 本机的 9998 端口, 就会阻塞等待. socket.receive(packet);
socket.receive(packet1);
//(3) 可以把 packet 进行拆包,取出数据,并显示. int length = packet.getLength();//实际接收到的数据字节长度
int length = packet1.getLength();
data1 = packet1.getData();//接收到数据
String s = new String(data1, 0, length);
System.out.println(s);
//关闭资源
socket.close();
System.out.println("B 端退出");
}
}
接收端代码:
public class UDPReceiver {
public static void main(String[] args) throws IOException {
//1. 创建一个 DatagramSocket 对象,准备在 9999 接收数据
System.out.println("9999端口等待数据的请求");
DatagramSocket socket = new DatagramSocket(9999);
//2. 构建一个 DatagramPacket 对象,准备接收数据
// 在前面讲解 UDP 协议时,老师说过一个数据包最大 64k
byte[] buf = new byte[1024];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
//3. 调用 接收方法, 将通过网络传输的 DatagramPacket 对象
// 填充到 packet 对象
// 当有数据包发送到 本机的 9999 端口时,就会接收到数据
// 如果没有数据包发送到 本机的 9999 端口, 就会阻塞等待. System.out.println("接收端 A 等待接收数据..");
socket.receive(packet);
//4. 可以把 packet 进行拆包,取出数据,并显示. int length = packet.getLength();//实际接收到的数据字节长度
int length = packet.getLength();
byte[] data = packet.getData();//接收到数据
String s = new String(data, 0, length);
System.out.println(s);
//===回复信息给 B 端
//将需要发送的数据,封装到 DatagramPacket 对象
byte[] data1 = "好的, 明天见".getBytes();
//说明: 封装的 DatagramPacket 对象 data 内容字节数组 , data.length , 主机(IP) , 端口
DatagramPacket packet1 = new DatagramPacket(data1, data1.length, InetAddress.getByName("172.21.10.13"), 9998);
socket.send(packet1);//发送
//5. 关闭资源
socket.close();
System.out.println("A 端退出...");
}
}
UDP通信适用于需要快速传输数据且可以容忍一些数据丢失的场景,例如实时音视频传输和在线游戏。然而,由于UDP不保证数据的可靠性,因此在应用中需要考虑如何处理丢失的数据或保证数据的顺序。