java 网络编程
网络编程
一、概述
网络编程是指编写运行在多个设备(计算机)的程序,这些设备都通过网络连接起来。
计算机网络
把分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规模大,功能强的网络系统, 从而使众多的计算机可以方便地互相传递信息,共享硬件,软件,数据信息等资源。
网络编程目的
-
直接或间接地通过网络协议与其他计算机实现数据交换,
-
进行通讯。
网络编程中主要问题
- 如何准确的定位网络上的一台或多台主机,定位主机上的特定的应用?
- 找到主机后如何可靠高效地进行数据传输?
二、网络通信要素
- ip + 端口号
- 通信协议
IP
-
'网际协议'
-
是 TCP/IP 协议族中最重要的协议之一,也是最重要的互联网协议之一。这里的 IP 指 IPV4
-
IP 地址分为五类,主要是前三类
- A类:0~127
- B类:128~191
- C类:192~223
-
ip 地址由 4个十进制数组成,以点隔开
例如:
192.168.10.102
端口号
-
识别计算机上的进程。因为一台计算机上有很多的软件,不同的软件都有不同的进程号。即识别计算机上的软件
-
范围:0~65535
-
常用端口号:
-
下述为服务端端口号,范围:0~1023
-
HTTP DNS HTTPS FTP 80 53 443 21
-
-
客户端端口号:49152~65535
端口号与IP地址的组合,得出一个网络套接字:Socket,所以说一些网络编程也被称为Socket编程
通信协议
说协议前需要了解网络体系结构。
网络体系结构
以前是OSI参考模型,但由于模型过于理想化,未能在因特网上进行广泛推广。现在国际上的标准是 TCP/IP 参考模型。
网络体系结构:
而网络通信主要在 传输层。所以需要了解的通信协议也就是:TCP 与 UDP
TCP
- 每一条 TCP 连接都会有两个端口
- TCP是面向连接的。因为在进行 TCP 通信前需要进行 ”三次握手“ 与。通信结束后需要进行 ”四次挥手“
- TCP协议进行通信的两个应用进程:客户端,服务端。
- 在连接中可进行大数据量的传输 传输完毕,需要释放已建立的连接,效率低 举例:打电话
UDP
- 每一条 UDP 连接都会有两个端口
- 面向无连接的。进行 UDP通信时,源端不需要确认你是否在线,它只管发送。
- 将数据,源,目的封装成数据包,不需要建立连接 每个数据报的大小限制在64K内 发送方不管对方是否准备好,接收方收到也不确认,故是不可靠的
- 例如:发短信。
三、TCP网络编程
1、通过上述可知,网络编程也可为 socket 编程;而 socket 是套接字。即将 端口号拼接到 ip 地址构成的。
2、套接字使用TCP提供了两台计算机之间的通信机制。 客户端程序创建一个套接字,并尝试连接服务器的套接字。当连接建立时,服务器会创建一个 Socket 对象。客户端和服务器现在可以通过对 Socket 对象的写入和读取来进行通信。java.net.Socket 类代表一个套接字,并且 java.net.ServerSocket 类为服务器程序提供了一种来监听客户端,并与他们建立连接的机制。
3、通过如下步骤即可在两台计算机之间使用套接字建立 TCP 连接:
- 服务器实例化一个 ServerSocket 对象,表示通过服务器上的端口通信。
- 服务器调用 ServerSocket 类的 accept() 方法,该方法将一直等待,直到客户端连接到服务器上给定的端口。
- 服务器正在等待时,一个客户端实例化一个 Socket 对象,指定服务器名称和端口号来请求连接。
- Socket 类的构造函数试图将客户端连接到指定的服务器和端口号。如果通信被建立,则在客户端创建一个 Socket 对象能够与服务器进行通信。
- 在服务器端,accept() 方法返回服务器上一个新的 socket 引用,该 socket 连接到客户端的 socket。
当连接建立后,即可通过 I/O 流进行通信。
- 每一个socket都有一个输出流和一个输入流
- 客户端的输出流连接到服务器端的输入流,而客户端的输入流连接到服务器端的输出流。
在 java 中提供了一个类用于获取一个端口,并且侦听客户端请求。如下
ServerSocket 类
服务器应用程序通过使用 java.net.ServerSocket 类以获取一个端口,并且侦听客户端请求。
构造方法如下:
public ServerSocket() //创建非绑定服务器套接字
public ServerSocket(int port) //创建绑定到特定端口的服务器套接字。
public ServerSocket(int port, int backlog) //利用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号。
public ServerSocket(int port, int backlog, InetAddress bindAddr) //使用指定的端口、侦听 backlog 和要绑定到的本地 IP 地址创建服务器。
常用方法:
public int getLocalPort() //返回此套接字在其上侦听的端口。
public Socket accept() //侦听并接受到此套接字的连接。
public void setSoTimeout(int timeout) //通过指定超时值启用/禁用 SO_TIMEOUT,以毫秒为单位
public void bind(SocketAddress host, int backlog) //将 ServerSocket 绑定到特定地址(IP 地址和端口号)。
Socket 类
java.net.Socket 类代表客户端和服务器都用来互相沟通的套接字。客户端要获取一个 Socket 对象通过实例化 ,
而 服务器获得一个 Socket 对象则通过 accept() 方法的返回值。
构造方法:
public Socket(String host, int port) //创建一个流套接字并将其连接到指定主机上的指定端口号。
public Socket(InetAddress host, int port) //创建一个流套接字并将其连接到指定 IP 地址的指定端口号
public Socket(String host, int port, InetAddress localAddress, int localPort) //创建一个套接字并将其连接到指定远程主机上的指定远程端口。
public Socket(InetAddress host, int port, InetAddress localAddress, int localPort) //创建一个套接字并将其连接到指定远程地址上的指定远程端口
public Socket() //通过系统默认类型的 SocketImpl 创建未连接套接字
常用方法:
public void connect(SocketAddress host, int timeout) //将此套接字连接到服务器,并指定一个超时值。
public InetAddress getInetAddress() //返回套接字连接的地址。
public int getPort() //返回此套接字连接到的远程端口。
public int getLocalPort() //返回此套接字绑定到的本地端口。
public SocketAddress getRemoteSocketAddress() //返回此套接字连接的端点的地址,如果未连接则返回 null。
public InputStream getInputStream() //返回此套接字的输入流。
public OutputStream getOutputStream() //返回此套接字的输出流。
public void close() //关闭此套接字。
注意:客户端和服务器端都有一个 Socket 对象,所以无论客户端还是服务端都能够调用这些方法。
InetAddress 类
这个类表示互联网协议(IP)地址。类中是没有构造方法的,所以不能 new 。而可以通过如下方法来进行获取 ip 地址
static InetAddress getByAddress(byte[] addr) //在给定原始 IP 地址的情况下,返回 InetAddress 对象。
static InetAddress getByAddress(String host, byte[] addr) //根据提供的主机名和 IP 地址创建 InetAddress。
static InetAddress getByName(String host) //在给定主机名的情况下确定主机的 IP 地址。
String getHostAddress() //返回 IP 地址字符串(以文本表现形式)。
String getHostName() //获取此 IP 地址的主机名。
static InetAddress getLocalHost() //返回本地主机。
String toString() //将此 IP 地址转换为 String。
案例一:TCP 通信
服务端:
public class TcpServer {
public static void main(String[] args) {
ByteArrayOutputStream bos = null;
InputStream is = null;
Socket socket = null;
ServerSocket serverSocket = null;
try {
//1、设置服务端的地址与端口
serverSocket = new ServerSocket(9923);
//2、监听该端口的请求
socket = serverSocket.accept();
//3、读取信息,通过管道流
//获取输入流
is = socket.getInputStream();
//获取字节缓冲数组流,用于输出
bos = new ByteArrayOutputStream();
//设置缓冲数组
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1){
bos.write(buffer, 0, len);
}
System.out.println(bos.toString());
} catch (IOException e) {
e.printStackTrace();
}finally {
//4、资源关闭
if (null != bos) {
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != is) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != socket) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != serverSocket) {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
客户端:
public class TcpClient {
public static void main(String[] args) {
OutputStream outputStream = null;
Socket socket = null;
try {
//1、设置ip地址
InetAddress inetAddress = InetAddress.getByName("127.0.0.1");
//2、设置端口
int port = 9923;
//3、创建交流的套子接口
socket = new Socket(inetAddress, port);
//4、进行交流
outputStream = socket.getOutputStream();
outputStream.write("你好,java网络编程".getBytes());
} catch (Exception e) {
e.printStackTrace();
}finally {
//5、进行资源关闭
if (null != outputStream) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != socket) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
案例二:TCP 文件
服务端:
public class TcpFileServer {
public static void main(String[] args) throws Exception {
//1、设置端口
ServerSocket serverSocket = new ServerSocket(9923);
//2、监听端口
Socket socket = serverSocket.accept();
//3、获取客户端发送的数据
InputStream inputStream = socket.getInputStream();
//将数据写入该路径下的文件
//FileOutputStream fileOutputStream = new FileOutputStream(new File("reseve.txt"));
FileOutputStream fileOutputStream = new FileOutputStream(new File("/opt/software/reseve.txt"));//虚拟机地址
byte[] buffer = new byte[1024];
int len;
while ((len = inputStream.read(buffer)) != -1){
fileOutputStream.write(buffer, 0, len);
}
//4、通知客户端传输完毕
OutputStream outputStream = socket.getOutputStream();
outputStream.write("已接收完毕".getBytes());
//5、关闭资源
outputStream.close();
fileOutputStream.close();
inputStream.close();
socket.close();
serverSocket.close();
}
}
客户端:
public class TcpFileClient {
public static void main(String[] args) throws Exception {
//1、获取端口与地址进行交互
Socket socket = new Socket("192.168.10.102", 9923);
//Socket socket = new Socket("127.0.0.1", 9923);
//2、获取输出流,进行输出文件
OutputStream outputStream = socket.getOutputStream();
//3、读取文件
FileInputStream fileInputStream = new FileInputStream(new File("20230702_18474242.png"));
//4、进行上传
byte[] buffer = new byte[1024];
int len;
while ((len = fileInputStream.read(buffer)) != -1){
outputStream.write(buffer, 0, len);
}
//5、进行断开连接判断
//通知服务器,已发送完毕
socket.shutdownOutput();;//已发送完
//获取输入流,获取服务端发送的数据
InputStream inputStream = socket.getInputStream();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] bytes = new byte[1024];
int len2;
while ((len2 = inputStream.read(bytes)) != -1){
bos.write(bytes, 0, len2);
}
System.out.println(bos.toString());
//6、关闭资源
bos.close();
inputStream.close();
fileInputStream.close();
outputStream.close();
socket.close();
}
}
注意:tcp 网络编程时,需要先运行服务端,在运行客户端
四、UDP网络编程
-
在 UDP 网络编程中,数据是以包发送的,所以需要将数据,目的主机,目的端口封装在数据包中进行发送。
-
DatagramSocket 和 DatagramPacket 两个类实现了基于UDP协议的网络程序。
-
UDP 数据报通过数据报套接字 DatagramSocket 发送和接收,系统不保证UDP数据报一定能够安全送到目的地,也不确定什么时候可以抵达。
-
DatagramPacket 对象封装了 UDP 数据报,在数据报中包含了发送端的IP地址和端口号以及接收端的IP地址和端口号。
-
UDP 协议中每个数据报都给出了完整的地址信息,因此无需建立发送方和接收方的连接。如同发快 递包裹一样。
案例一:UDP 通信
发送端:
public class UdpSend {
public static void main(String[] args) throws Exception {
//1、创建通信链接通道
DatagramSocket datagramSocket = new DatagramSocket();
//2、创建包
String msg = "你好";
InetAddress inetAddress = InetAddress.getByName("127.0.0.1");
int post = 9923;
DatagramPacket datagramPacket = new DatagramPacket(msg.getBytes(), 0, msg.getBytes().length, inetAddress, post);
//3、发送包
datagramSocket.send(datagramPacket);
//4、关闭资源
datagramSocket.close();
}
}
接收端:
public class UdpReceive {
public static void main(String[] args) throws IOException {
//1、创建通信链接通道
DatagramSocket datagramSocket = new DatagramSocket(9923);
//2、创建接收数据的包
byte[] buffer = new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(buffer, buffer.length);
//3、接收
datagramSocket.receive(datagramPacket);
System.out.println(new String(datagramPacket.getData()));//获取数据
System.out.println(datagramPacket.getPort());//获取发射器端口
System.out.println(datagramPacket.getSocketAddress());//获取包中的地址 加 发送器的端口
System.out.println(datagramPacket.getAddress());//获取包中的地址
//4、关闭资源
datagramSocket.close();
}
}
案例二:UDP 在线聊天
发送端:
public class UdpSend {
public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket();
//准备数据
BufferedReader dataPacket = new BufferedReader(new InputStreamReader(System.in));
while (true){
String data = dataPacket.readLine();
byte[] dataBytes = data.getBytes();
DatagramPacket packet = new DatagramPacket(dataBytes, 0, dataBytes.length, new InetSocketAddress("192.168.10.102", 9923));
//发送数据
socket.send(packet);
if (data.equalsIgnoreCase("bye")){
break;
}
}
//关闭资源
socket.close();
}
}
接收端:
public class UdpReceive {
public static void main(String[] args) throws IOException {
DatagramSocket socket = new DatagramSocket(9923);
//接收多次
while (true){
//准备容器接收数据
byte[] container = new byte[1024];
DatagramPacket packet = new DatagramPacket(container, 0, container.length);
//阻塞接收数据
socket.receive(packet);
//断开连接
byte[] data = packet.getData();
String receiveData = new String(data);
System.out.println(receiveData);
if (receiveData.equalsIgnoreCase("bye")){
break;
}
}
//关闭资源
socket.close();
}
}
案例三:案例二改进为多线程
发送端也是接收端。通过多线程来实现。
发送端:
public class TalkServiceSend implements Runnable{
private DatagramSocket socket = null;
private BufferedReader dataPacket = null;
/**
* 接收者的ip
*/
private String receiveIp;
/**
* 接收者端口
*/
private int receivePort;
public TalkServiceSend(String receiveIp, int receivePort) {
this.receiveIp = receiveIp;
this.receivePort = receivePort;
try {
socket = new DatagramSocket();
dataPacket = new BufferedReader(new InputStreamReader(System.in));
} catch (SocketException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while (true){
try {
String data = dataPacket.readLine();
byte[] dataBytes = data.getBytes();
DatagramPacket packet = new DatagramPacket(dataBytes, 0, dataBytes.length, new InetSocketAddress(this.receiveIp, this.receivePort));
//发送数据
socket.send(packet);
if (data.equalsIgnoreCase("bye")){
break;
}
} catch (IOException e) {
e.printStackTrace();
}
}
//关闭资源
socket.close();
}
}
接收端:
public class TalkServiceReceive implements Runnable{
DatagramSocket socket = null;
/**
* 端口
*/
private int port;
/**
* 发送者
*/
private String sender;
public TalkServiceReceive(int port, String sender) {
this.port = port;
this.sender = sender;
try {
socket = new DatagramSocket(this.port);
} catch (SocketException e) {
e.printStackTrace();
}
}
@Override
public void run() {
//接收多次
while (true){
try {
//准备容器接收数据
byte[] container = new byte[1024];
DatagramPacket packet = new DatagramPacket(container, 0, container.length);
//阻塞接收数据
socket.receive(packet);
//断开连接
byte[] data = packet.getData();
String receiveData = new String(data);
System.out.println(sender + " : " + receiveData);
if (receiveData.equalsIgnoreCase("bye")){
break;
}
} catch (IOException e) {
e.printStackTrace();
}
}
//关闭资源
socket.close();
}
}
测试:
public class TalkStudent {
public static void main(String[] args) {
//new Thread(new TalkServiceSend("127.0.0.1", 9923)).start();
new Thread(new TalkServiceSend("10.82.148.136", 9923)).start();//虚拟机地址
new Thread(new TalkServiceReceive(9924, "老师")).start();
}
}
public class TalkTeacher {
public static void main(String[] args) {
new Thread(new TalkServiceSend("192.168.10.102", 9924)).start();
new Thread(new TalkServiceReceive(9923, "学生")).start();
}
}
五、URL编程
- URL (Uniform Resource Locator): 统一资源定位符,它表示 internet 上某一资源的地址。
- 它是一种具体的URI,即URL可以用来标识一个资源,而且还指明了如何locate:定位这个资源。
- 通过URL 我们可以访问Internet上的各种网络资源
- URL 的 基本结构由 5部分组成: 传输协议://主机名:端口号/文件名 #片段名?参数列表
- 例如:http://localhost:8080/helloworld/index.jsp#a?username=kuangshen&password=123
举例:
public class UrlTest {
public static void main(String[] args) throws MalformedURLException {
URL url = new URL("https://www.jd.com/?cu=true&utm_source=www.baidu.com&utm_medium=tuiguang&utm_campaign=t_1003608409_");
System.out.println(url.getProtocol()); //获取URL的协议名
System.out.println(url.getHost()); //获取URL的主机名
System.out.println(url.getPort()); //获取URL的端口号
System.out.println(url.getPath()); //获取URL的文件路径
System.out.println(url.getFile()); //获取URL的文件名
System.out.println(url.getQuery()); //获取URL的查询名
}
}
案例一:下载文件
public class UrlTest {
public static void main(String[] args) throws Exception {
//1、定位到服务器上的资源位置
URL url = new URL("https://lmg.jj20.com/up/allimg/4k/s/02/2109250006343S5-0-lp.jpg");
//2、创建连接
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
//对于程序而言,写出去----》output/writer;读取---》input/reader
//3、获取输入流
InputStream stream = connection.getInputStream();
//4、写入文件
FileOutputStream outputStream = new FileOutputStream("山水.jpg");
byte[] bytes = new byte[1024];
int len;
while ((len = stream.read(bytes)) != -1){
outputStream.write(bytes, 0, len);
}
//5、关闭资源
outputStream.close();
stream.close();
connection.disconnect();//断开连接
}
}