Java网络编程
# 网络编程
# 网络编程概述
网络编程是指编写运行在多个设备(计算机)的程序,这些设备都通过网络连接起来。
java.net 包中 J2SE 的 API 包含有类和接口,它们提供低层次的通信细节。你可以直接使用这些类和接口,来专注于解决问题,而不用关注通信细节。
java.net 包中提供了两种常见的网络协议的支持:
- TCP:TCP(英语:Transmission Control Protocol,传输控制协议) 是一种面向连接的、可靠的、基于字节流的传输层通信协议,TCP 层是位于 IP 层之上,应用层之下的中间层。TCP 保障了两个应用程序之间的可靠通信。通常用于互联网协议,被称 TCP / IP。
- UDP:UDP (英语:User Datagram Protocol,用户数据报协议),位于 OSI 模型的传输层。一个无连接的协议。提供了应用程序之间要发送数据的数据报。由于UDP缺乏可靠性且属于无连接协议,所以应用程序通常必须容许一些丢失、错误或重复的数据包。
本教程主要讲解以下两个主题。
- Socket 编程:这是使用最广泛的网络概念,它已被解释地非常详细。
- URL 处理:这部分会在另外的篇幅里讲,点击这里更详细地了解在 Java 语言中的 URL 处理 (opens new window)。
# Socket 编程
套接字使用TCP提供了两台计算机之间的通信机制。 客户端程序创建一个套接字,并尝试连接服务器的套接字。
当连接建立时,服务器会创建一个 Socket 对象。客户端和服务器现在可以通过对 Socket 对象的写入和读取来进行通信。
java.net.Socket 类代表一个套接字,并且 java.net.ServerSocket 类为服务器程序提供了一种来监听客户端,并与他们建立连接的机制。
以下步骤在两台计算机之间使用套接字建立TCP连接时会出现:
- 服务器实例化一个 ServerSocket 对象,表示通过服务器上的端口通信。
- 服务器调用 ServerSocket 类的 accept() 方法,该方法将一直等待,直到客户端连接到服务器上给定的端口。
- 服务器正在等待时,一个客户端实例化一个 Socket 对象,指定服务器名称和端口号来请求连接。
- Socket 类的构造函数试图将客户端连接到指定的服务器和端口号。如果通信被建立,则在客户端创建一个 Socket 对象能够与服务器进行通信。
- 在服务器端,accept() 方法返回服务器上一个新的 socket 引用,该 socket 连接到客户端的 socket。
连接建立后,通过使用 I/O 流在进行通信,每一个socket都有一个输出流和一个输入流,客户端的输出流连接到服务器端的输入流,而客户端的输入流连接到服务器端的输出流。
TCP 是一个双向的通信协议,因此数据可以通过两个数据流在同一时间发送.以下是一些类提供的一套完整的有用的方法来实现 socket。
# UDP通信
# 快速入门
UDP协议的特点
- UDP是一种
无连接、不可靠传输的协议。
- 将数据源IP、目的地IP和端口以及数据封装成数据包,大小限制在64KB内,直接发送出去即可。
# 方法介绍
DatagramPacket:数据包对象
构造器 | 说明 |
---|---|
public DatagramPacket(byte[] buf, int length, InetAddress address, int port) | 创建发送端数据包对象 buf:要发送的内容,字节数组 length:要发送内容的字节长度 address:接收端的IP地址对象 port:接收端的端口号 |
public DatagramPacket(byte[] buf, int length) | 创建接收端的数据包对象 buf:用来存储接收的内容 length:能够接收内容的长度 |
DatagramPacket常用方法
方法 | 说明 |
---|---|
public int getLength() | 获得实际接收到的字节个数 |
DatagramSocket:发送端和接收端对象(人)
构造器 | 说明 |
---|---|
public DatagramSocket() | 创建发送端的Socket对象,系统会随机分配一个端口号。 |
public DatagramSocket(int port) | 创建接收端的Socket对象并指定端口号 |
DatagramSocket类成员方法
方法 | 说明 |
---|---|
public void send(DatagramPacket dp) | 发送数据包 |
public void receive(DatagramPacket p) | 接收数据包 |
InetAddress 的使用
此类表示Internet协议(IP)地址。
名称 | 说明 |
---|---|
public static InetAddress getLocalHost() | 返回本主机的地址对象 |
public static InetAddress getByName(String host) | 得到指定主机的IP地址对象,参数是域名或者IP地址 |
public String getHostName() | 获取此IP地址的主机名 |
public String getHostAddress() | 返回IP地址字符串 |
public boolean isReachable(int timeout) | 在指定毫秒内连通该IP地址对应的主机,连通返回true |
使用UDP通信实现:发送消息、接收消息
需求:客户端实现步骤
①创建DatagramSocket对象(发送端对象) 扔韭菜的人
②创建DatagramPacket对象封装需要发送的数据(数据包对象) 韭菜盘子
③使用DatagramSocket对象的send方法传入DatagramPacket对象 开始抛出韭菜
④释放资源
package com.itheima.d4_udp;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
public class ClientDemo1 {
public static void main(String[] args) throws Exception {
// 创建发送端对象:发送端口自带默认的端口号
System.out.println("===客户端启动===");
DatagramSocket socket = new DatagramSocket();
// 创建一个数据包对象封装数据(韭菜盘子)
/*
DatagramPacket(byte buf[], int length,
InetAddress address, int port)
参数一: 封装要发送的数据
参数二: 发送数据的大小
参数三: 服务端的主机IP地址
参数四: 服务端的端口
*/
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请输入发送内容:");
String msg = sc.nextLine();
if ("exit".equals(msg)){
System.out.println("离线成功");
socket.close();
break;
}
byte[] buffer = msg.getBytes();
DatagramPacket packet = new DatagramPacket(buffer,buffer.length, InetAddress.getByName("255.255.255.255"),9999);
// 发送数据出去
socket.send(packet);
}
}
}
需求:接收端实现步骤
①创建DatagramSocket对象并指定端口(接收端对象) 接韭菜的人
②创建DatagramPacket对象接收数据(数据包对象) 韭菜盘子
③使用DatagramSocket对象的receive方法传入DatagramPacket对象 开始接收韭菜
④释放资源
package com.itheima.d4_udp;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class ServerDemo1 {
public static void main(String[] args) throws Exception{
System.out.println("===服务端启动了===");
// 创建接收端对象: 注册端口(人)
DatagramSocket socket = new DatagramSocket(9999);
// 创建一个数据包对象接收数据
byte[] buffer = new byte[1024 * 64];
DatagramPacket packet = new DatagramPacket(buffer,buffer.length);
while (true) {
// 接收数据即可
socket.receive(packet);
// 取出数据即可
int len = packet.getLength();
String rs = new String(buffer,0,len);
System.out.println("收到了来自:" + packet.getSocketAddress() + "对方端口是:" + packet.getPort() + "的消息:" + rs);
}
}
}
总结:
1.UDP发送端和接收端的对象是哪个?
public DatagramSocket()
:创建发送端的Socket对象public DatagramSocket(int port)
:创建接收端的Socket对象
2.数据包对象是哪个?
DatagramPacket
3.如何发送、接收数据包
- 使用DatagramSocket的如下方法:
public void send(DatagramPacket dp)
:发送数据包public void receive(DatagramPacket dp)
:接收数据包
# UDP的三种通信方式
- 单播:单台主机与单台主机之间的通信。
- 广播:当前主机与所在网络中的所有主机通信。
- 组播:当前主机与选定的一组主机的通信。
UDP如何实现广播
使用广播地址:255.255.255.255
具体操作:
①发送端发送的数据包的目的地写的是广播地址、且指定端口。 (255.255.255.255 , 9999)
②本机所在网段的其他主机的程序只要注册对应端口就可以收到消息了。(9999)
UDP如何实现组播
使用组播地址:224.0.0.0 ~ 239.255.255.255
具体操作:
①发送端的数据包的目的地是组播IP (例如:224.0.1.1, 端口:9999)
②接收端必须绑定该组播IP(224.0.1.1),端口还要注册发送端的目的端口9999 ,这样即可接收该组播消息。
③DatagramSocket
的子类MulticastSocket
可以在接收端绑定组播IP。
# TCP通信
# 快速入门
TCP是一种面向连接,安全、可靠的传输数据的协议
传输前,采用“三次握手”方式,点对点通信,是可靠的
在连接中可进行大数据量的传输
Socket
构造器 | 说明 |
---|---|
public Socket(String host , int port) | 创建发送端的Socket对象与服务端连接,参数为服务端程序的ip和端口。 |
Socket类成员方法
方法 | 说明 |
---|---|
OutputStream getOutputStream() | 获得字节输出流对象 |
InputStream getInputStream() | 获得字节输入流对象 |
ServerSocket(服务端)
构造器 | 说明 |
---|---|
public ServerSocket(int port) | 注册服务端端口 |
ServerSocket类成员方法
方法 | 说明 |
---|---|
public Socket accept() | 等待接收客户端的Socket通信连接 连接成功返回Socket对象与客户端建立端到端通信 |
# 服务端实现接收消息
需求:服务端实现步骤
①创建ServerSocket对象,注册服务端端口。
②调用ServerSocket对象的accept()方法,等待客户端的连接,并得到Socket管道对象。
③通过Socket对象调用getInputStream()方法得到字节输入流、完成数据的接收。
④释放资源:关闭socket管道
服务端的实现:
package com.itheima.d5_demo1;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerDemo1 {
public static void main(String[] args) {
try {
System.out.println("===服务端启动===");
// 注册端口
ServerSocket serverSocket = new ServerSocket(7777);
while (true) {
// 必须调用accept方法,等待接收客户端的Socket连接请求,建立Socket通信管道
Socket socket = serverSocket.accept();
// 从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
// 把字节输入流包装成缓冲字符输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 按照行读取消息
String msg;
while ((msg = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + "发送了消息,内容为:" + msg);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
客户端实现:
package com.itheima.d5_demo1;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
public class ClientDemo1 {
public static void main(String[] args) {
try {
System.out.println("===客户端启动===");
// 创建Socket通信管道请求的服务端连接
// public Socket(String host, int port)
// 参数一: 服务端IP地址
// 参数二: 服务端的端口
Socket socket = new Socket("127.0.0.1",7777);
// 从socket通信管道中得到一个字节输出流 负责发送数据
OutputStream os = socket.getOutputStream();
// 把低级的字节流包装成打印流
PrintStream ps = new PrintStream(os);
Scanner sc = new Scanner(System.in);
while (true){
System.out.println("请输入你需要发送的话:");
String msg = sc.nextLine();
// 发送消息
ps.println(msg);
ps.flush();
}
// 不建议关闭资源
// socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
总结:
1.TCP通信服务端用的代表类?
- ServerSocket类,注册端口。
- 调用accept()方法阻塞等待接收客户端连接。得到Socket对象。
2.TCP通信的基本原理?
- 客户端怎么发,服务端就应该怎么收。
- 客户端如果没有消息,服务端会进入阻塞等待。
- Socket一方关闭或者出现异常、对方Socket也会失效或者出错。
# 多发多收消息
需求:使用TCP通信方式实现:多发多收消息。
具体要求:
①可以使用死循环控制服务端收完消息继续等待接收下一个消息。
②客户端也可以使用死循环等待用户不断输入消息。
③客户端一旦输入了exit,则关闭客户端程序,并释放资源。
修改上面的服务端:
package com.itheima.d5_demo1;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;
public class ServerDemo1 {
// 定义一个线程池
public static ExecutorService pool = new ThreadPoolExecutor(3,
5,6, TimeUnit.SECONDS,new ArrayBlockingQueue<>(2)
, Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
public static void main(String[] args) {
try {
System.out.println("===服务端启动===");
// 注册端口
ServerSocket serverSocket = new ServerSocket(7777);
// 定义一个死循环由主线程负责不断的接收客户端的Socket的管道连接
while (true) {
// 必须调用accept方法,等待接收客户端的Socket连接请求,建立Socket通信管道
Socket socket = serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress() + "上线了");
// 调用线程
Runnable taget = new ServerReadThread(socket);
pool.execute(taget);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 定义一个多线程
class ServerReadThread implements Runnable{
private Socket socket;
public ServerReadThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
// 从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
// 把字节输入流包装成缓冲字符输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 按照行读取消息
String msg;
while ((msg = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + "发送了消息,内容为:" + msg);
}
} catch (Exception e) {
System.out.println(socket.getRemoteSocketAddress() + "下线了");
}
}
}
总结:
1.本次多发多收是如何实现的
- 客户端使用循环反复地发送消息。
- 服务端使用循环反复地接收消息。
2.现在服务端为什么不可以同时接收多个客户端的消息。
- 目前服务端是单线程的,每次只能处理一个客户端的消息。
3.本次是如何实现服务端接收多个客户端的消息的。
- 主线程定义了循环负责接收客户端Socket管道连接
- 每接收到一个Socket通信管道后分配一个独立的线程负责处理它。