下面我们来分别编写一个客户端程序和一个服务端程序,使用用户端给服务端发送一句消息,然后使用服务端接收用户端发送过来的这句消息并打印输出。
客户端:
Socket
类的实例对象,根据IP地址和端口连接服务器;Socket
类:该类实现客户端套接字。 套接字是两台机器之间通讯的端点,用于完成两个应用程序之间的数据传输(可以理解为IP+端口)。该类有很多种构造方法,这里我们使用其中一种参数为IP地址和端口号的构造方法:
Socket(InetAddress address, int port) //创建流套接字并将其连接到指定IP地址的指定端口号。
Socket
类中的getOutputStream()
方法,创建一个Socket
对象的字节输出流,使用write()
方法发送消息;TcpClientDemo01.java
package com.clown.net.tcp;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
//客户端
public class TcpClientDemo01 {
public static void main(String[] args) {
Socket socket = null;
OutputStream os = null;
try {
//1. 需要知道服务端的IP地址、端口号
InetAddress serverIP = InetAddress.getByName("127.0.0.1");
int port = 9999;
//2. 创建一个Socket连接
socket = new Socket(serverIP, port);
//3. 发送消息,字节输出流
os = socket.getOutputStream();
os.write("好好学习,天天向上".getBytes());
} catch (Exception e) {
e.printStackTrace();
} finally {
//4. 关闭资源
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
服务端:
ServerSocket
类的实例化对象,创建服务并设置端口号;ServerSocket
类:这个类实现了服务器套接字。服务器套接字等待通过网络进入的请求。 它根据该请求执行一些操作,然后可能将结果返回给请求者。 这里我们也使用它的构造方法之一来创建端口:
ServerSocket(int port) //创建绑定到指定端口的服务器套接字。
ServerSocket
类中的accept()
方法,监听客户端的连接,创建与客户端连接的Socket
对象;public Socket accept() throws IOException //从连接请求队列中取出一个客户的连接请求,然后创建与客户连接的Socket对象,并将它返回。如果队列中没有连接请求,accept()方法就会一直等待,直到接收到了连接请求才返回。
Socket
类中的getInputStream()
方法,创建一个Socket
对象的字节输入流,接收用户端发送过来的消息;ByteArrayOutputStream
和它的write(byte[] b, int off, int len)
方法,这样就能保证结果不会出现乱码。TcpServerDemo01.java
package com.clown.net.tcp;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
//服务端
public class TcpServerDemo01 {
public static void main(String[] args) {
ServerSocket serverSocket = null;
Socket socket = null;
InputStream is = null;
ByteArrayOutputStream baos = null;
try {
//1. 创建服务,需要建立一个端口
serverSocket = new ServerSocket(9999);
//2. 等待客户端连接过来
socket = serverSocket.accept(); //阻塞式监听,会一直等待客户端连接
//3. 读取客户端发送的消息,字节输入流
is = socket.getInputStream();
//管道流
//ByteArrayOutputStream: 该类实现了将数据写入字节数组的输出流。 当数据写入缓冲区时,缓冲区会自动增长。
baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = is.read(buffer)) != -1) {
//write(byte[] b, int off, int len); 从指定的字节数组写入 len个字节,从偏移量为 off开始,输出到这个字节数组输出流。
baos.write(buffer, 0 , len);
}
System.out.println(baos.toString());
} catch (IOException e) {
e.printStackTrace();
} finally {
//4. 关闭资源
if (baos != null) {
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
ServerSocket
类中的accept()
方法,所以服务端会阻塞,并一直等待客户端发送连接请求。这时,我们再启动客户端:galaxy.jpg
,我把它放在了我项目的根目录):try/catch/finally
语句捕获异常,而是直接抛出了异常:Socket
类的实例对象,根据IP地址和端口连接服务器;Socket
类中的getOutputStream()
方法,创建一个Socket
对象的字节输出流;write()
方法将文件上传给客户端;TcpClientDemo02.java
package com.clown.net.tcp;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
public class TcpClientDemo02 {
public static void main(String[] args) throws Exception {
//1. 创建一个与服务端连接的 Socket对象
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 9000);
//2. 创建一个 Socket对象的字节输出流
OutputStream os = socket.getOutputStream();
//3. 读取文件,创建一个文件字节输入流
FileInputStream fis = new FileInputStream(new File("galaxy.jpg"));
//4. 发送文件
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
//5. 发送完毕之后,禁用输出流
socket.shutdownOutput(); //我已经发送完了
//6. 确定服务器已接收完毕,才能断开连接
InputStream inputStream = socket.getInputStream();
//管道流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer2 = new byte[1024];
int len2;
while ((len2 = inputStream.read(buffer2))!= -1) {
baos.write(buffer2, 0, len2);
}
System.out.println(baos.toString());
//7. 关闭资源
fis.close();
os.close();
socket.close();
}
}
ServerSocket
类的实例化对象,创建服务并设置端口号;ServerSocket
类中的accept()
方法,监听客户端的连接,创建与客户端连接的Socket
对象;Socket
类中的getInputStream()
方法,创建一个Socket
对象的字节输入流,接收用户端上传过来的文件;TcpServerDemo02.java
package com.clown.net.tcp;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class TcpServerDemo02 {
public static void main(String[] args) throws Exception {
//1. 创建服务,设置端口
ServerSocket serverSocket = new ServerSocket(9000);
//2. 监听客户端的连接,创建一个与客户的连接的 Socket对象
Socket socket = serverSocket.accept(); //阻塞式监听,会一直等待客户端连接
//3. 接收文件,创建一个Socket对象的字节输入流
InputStream is = socket.getInputStream();
//4. 文件输出,文件字节输出流
FileOutputStream fos = new FileOutputStream(new File("receive.jpg"));
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
//5. 接收完毕之后,通知客户端:我已接收完毕,你可以断开连接了
OutputStream outputStream = socket.getOutputStream();
outputStream.write("我已接收完毕,你可以断开连接了".getBytes());
//6. 关闭资源
fos.close();
is.close();
socket.close();
serverSocket.close();
}
}
ServerSocket
类中的accept()
方法,所以服务端会阻塞,并一直等待客户端发送连接请求。这时,我们再启动客户端:UDP没有明确的客户端与服务端的概念,只有发送端和接收端。
下面,我们就来分别写一个发送端和一个接收端,实现消息的发送:
在正式开始写之前,我们需要先了解以下知识:
DatagramSocket
类:此类表示用于发送和接收数据报包的套接字。数据报套接字是分组传送服务的发送或接收点。
常用的构造方法:
DatagramSocket()
DatagramSocket(int port) //参数:port-要使用的端口
DatagramPacket
类:该类表示数据报包。
常用的构造方法:
DatagramPacket(byte buf[], int offset, int length, InetAddress address, int port)
//参数:buf-数据包数据; offset-分组数据偏移量; length-分组数据长度; address-目的地址; port-目的端口号。
DatagramPacket(byte buf[], int offset, int length, SocketAddress address)
//参数:buf-数据包数据; offset-分组数据偏移量; length-分组数据长度,address-目标套接字地址。
DatagramPacket(byte buf[], int offset, int length)
//参数:buf-数据包数据; offset-分组数据偏移量; length-分组数据长度。
常用方法:
send(DatagramPacket p) //从此套接字发送数据报包
receive(DatagramPacket p) //从此套接字接收数据报包。该方法阻塞,直到接收到数据报
getAddress() //返回该数据报发送或接收数据报的计算机的 IP地址
getData() //返回数据缓冲区
发送端:
DatagramSocket
类的实例对象;DatagramPacket
类的对象,来创建一个数据包,并把想要发送的数据和目的(接收端)IP地址及端口号作为参数,传入该数据包的构造方法;DatagramPacket
类中的send(DatagramPacket p)
方法发送数据;UdpSenderDemo01
package com.clown.net.udp;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
//UDP,不需要建立连接
//发送端
public class UdpSenderDemo01 {
public static void main(String[] args) throws Exception {
//1. 建立一个 DatagramSocket
//DatagramSocket: 此类表示用于发送和接收数据报套接字。
DatagramSocket socket = new DatagramSocket();
//2. 建立一个包
//要发送的消息
String msg = "你好啊,接收端!";
//要发送给谁
InetAddress localhost = InetAddress.getByName("localhost"); //IP地址
int port = 9090; //端口号
//DatagramPacket: 该类表示数据报包
//构造方法: DatagramPacket(byte buf[], int offset, int length, InetAddress address, int port);
//参数: buf-数据包数据; offset-分组数据偏移量; length-分组数据长度; address-目的地址; port-目的端口号。
DatagramPacket packet = new DatagramPacket(msg.getBytes(), 0, msg.getBytes().length, localhost, port);
//3. 发送包
//send(DatagramPacket p); 从此套接字发送数据报包
socket.send(packet);
//4. 关闭资源
socket.close();
}
}
DatagramSocket
类的实例对象,并设置端口号;DatagramPacket
),用于接收发送过来的数据;DatagramPacket
类中的receive(DatagramPacket p)
方法接收数据包,该方法为阻塞方法,它会一直等待,直到接收到发送端发来的数据包;UdpReceiverDemo01
package com.clown.net.udp;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
//还是需要一个阻塞方法,监听发送端发送数据
//接收端
public class UdpReceiverDemo01 {
public static void main(String[] args) throws Exception {
//1. 设置端口
DatagramSocket socket = new DatagramSocket(9090);
//2. 建立一个缓冲区并设置它的大小,根据该缓冲区建立一个包,用于接收发送过来的数据
byte[] buffer = new byte[1024];
//构造方法: DatagramPacket(byte buf[], int offset, int length)
//参数: buf-数据包数据; offset-分组数据偏移量; length-分组数据长度;
DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length);
//3. 接收数据包
//receive(DatagramPacket p); 从此套接字接收数据报包,该方法阻塞,直到接收到数据报
socket.receive(packet);
//getAddress(); 返回该数据报发送或接收数据报的计算机的 IP地址
System.out.println(packet.getAddress().getHostAddress()); //获取发送者的 IP地址
//getData(); 返回数据缓冲区
System.out.println(new String(packet.getData(), 0, packet.getLength())); //打印接收到的消息
//4. 关闭资源
socket.close();
}
}
DatagramPacket
类中的receive(DatagramPacket p)
方法,所以接收端会阻塞,并一直等待发送端发送数据包。这时,我们再启动发送端:UdpSenderDemo02
package com.clown.net.udp;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
public class UdpSenderDemo02 {
public static void main(String[] args) throws Exception {
//1. 创建 DatagramSocket对象,设置端口号
DatagramSocket socket = new DatagramSocket(8888);
//2. 准备数据,控制台读取:System.in
//桥转换流:InputStreamReader,将字符流转换为字节流
InputStreamReader isr = new InputStreamReader(System.in);
//字节缓冲流:BufferedReader
BufferedReader reader = new BufferedReader(isr);
//循环
while (true) {
String data = reader.readLine();
byte[] bytes = data.getBytes();
//3. 创建数据包
//InetSocketAddress: 该类实现 IP套接字地址(IP地址 + 端口号),它也可以是一对(主机名 + 端口号)
DatagramPacket packet = new DatagramPacket(bytes, 0, bytes.length, new InetSocketAddress("localhost", 6666));
//4. 发送数据包
socket.send(packet);
//设置终止循环的条件
if (data.equals("bye")) {
break;
}
}
//5. 关闭资源
socket.close();
}
}
UdpReceiverDemo02
package com.clown.net.udp;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UdpReceiverDemo02 {
public static void main(String[] args) throws Exception {
//1. 创建 DatagramSocket对象,设置端口号
DatagramSocket socket = new DatagramSocket(6666);
//循环
while (true) {
//2. 准备接收数据包
//创建一个缓冲区
byte[] buffer = new byte[1024];
//创建一个数据包,用于接收数据
DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length);
//设置终止循环的条件
byte[] bytes = packet.getData(); //读取接收到的数据包中的数据
String receiveStr = new String(bytes, 0, packet.getLength()); //将数据转换成字符串形式
//打印接收到的消息
System.out.println(receiveStr);
//3. 接收数据包
socket.receive(packet); //阻塞式
//当收到 "bye" 时,终止循环
if (receiveStr.equals("bye")) {
break;
}
}
//4. 关闭资源
socket.close();
}
}
bye
,终止程序:bye
后,发送端停止运行。bye
之后,也停止了运行。TalkSend.java
package com.clown.net.udp;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;
public class TalkSend implements Runnable {
DatagramSocket socket = null;
BufferedReader reader = null;
private int ownPort; //自身发送进程的端口号
private String destinationIP; //目的 IP地址
private int destinationPort; //目的接收进程的端口号
//构造器
public TalkSend(int ownPort, String destinationIP, int destinationPort) {
this.ownPort = ownPort;
this.destinationIP = destinationIP;
this.destinationPort = destinationPort;
try {
socket = new DatagramSocket(ownPort);
reader = new BufferedReader(new InputStreamReader(System.in));
} catch (SocketException e) {
e.printStackTrace();
}
}
@Override
public void run() {
//循环
while (true) {
try {
String data = reader.readLine(); //读取数据
byte[] bytes = data.getBytes(); //将数据转换成字节类型
//创建数据包
DatagramPacket packet = new DatagramPacket(bytes, 0, bytes.length, new InetSocketAddress(this.destinationIP, this.destinationPort));
//发送数据包
socket.send(packet);
//设置终止循环的条件
if (data.equals("bye")) {
break;
}
} catch (IOException e) {
e.printStackTrace();
}
}
//关闭资源
socket.close();
}
}
TalkReceive.java
package com.clown.net.udp;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class TalkReceive implements Runnable {
DatagramSocket socket = null;
String msgFrom = null; //谁发送的消息
private int ownPort; //自身接收进程的端口号
//构造器
public TalkReceive(int ownPort, String msgFrom) {
this.ownPort = ownPort;
this.msgFrom = msgFrom;
try {
socket = new DatagramSocket(ownPort);
} catch (SocketException e) {
e.printStackTrace();
}
}
@Override
public void run() {
//循环
while (true) {
try {
//准备接收数据包
//创建一个缓冲区
byte[] buffer = new byte[1024];
//创建一个数据包,用于接收数据
DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length);
//接收数据包
socket.receive(packet); //阻塞式
//设置终止循环的条件
byte[] bytes = packet.getData(); //读取接收到的数据包中的数据
String receiveData = new String(bytes, 0, packet.getLength()); //将数据转换成字符串形式
//打印接收到的消息
System.out.println(msgFrom + ": " + receiveData);
//当收到 "bye" 时,终止循环
if (receiveData.equals("bye")) {
break;
}
} catch (IOException e) {
e.printStackTrace();
}
}
//关闭资源
socket.close();
}
}
老师
和学生
- 他们都能接收对方发送的消息,也都能向对方发送消息,实现聊天。TalkTeacher.java
package com.clown.net.udp;
public class TalkTeacher {
public static void main(String[] args) {
//开启两个线程(发送、接收)
new Thread(new TalkSend(5555, "localhost", 8888)).start();
new Thread(new TalkReceive(9999, "学生")).start();
}
}
TalkStudent.java
package com.clown.net.udp;
public class TalkStudent {
public static void main(String[] args) {
//开启两个线程(发送、接收)
new Thread(new TalkSend(7777, "localhost", 9999)).start();
new Thread(new TalkReceive(8888, "老师")).start();
}
}
TalkTeacher.java
和TalkStudent.java
:TalkStudent.java
的控制台中输入一句消息,模拟学生向老师发送了一句话:TalkTeacher.java
的控制台中输入一句消息,模拟老师回复了学生的消息:bye
后,终止程序,结束聊天:https://www.baidu.com/
协议://IP地址:端口/项目名/资源
www.baidu.com
使用时会由DNS(域名系统)解析为IP地址:39.156.66.18
。/项目名/资源
这两个部分),但通常不会超过五个部分。URL
对象,调用URL
类中的一些常用方法来获取URL字符串中的各个组成部分:package com.clown.net.url;
import java.net.MalformedURLException;
import java.net.URL;
public class URLDemo01 {
public static void main(String[] args) throws MalformedURLException {
URL url = new URL("http://localhost:8080/helloworld/index.jsp?username=clown&password=123");
//getProtocol(); 获取此 URL的协议名
System.out.println(url.getProtocol());
//getHost(); 获取此 URL的主机名
System.out.println(url.getHost());
//getPort(); 获取此 URL的端口号
System.out.println(url.getPort());
//getPath(); 获取此 URL的文件路径部分
System.out.println(url.getPath());
//getFile(); 获取此 URL的文件名,返回的文件部分将与 getPath()相同,加上 getQuery()的返回值 (如果有)
System.out.println(url.getFile());
//getQuery(); 获取此 URL的查询部分
System.out.println(url.getQuery());
}
}
webapps
目录,在里面新建一个自己的文件夹(这里我新建的文件名为clown
),然后在新建的文件夹里添加文件(这里我添加了一个文本文件:ConfidentialFile.txt
):ANSI
(ANSI
:在不同的系统中,ANSI
表示不同的编码,因为我的电脑是简体中文系统的,所以ANSI
表示的编码方式为GBK
):bin
目录,双击startup.bat
启动Tomcat服务器:http://localhost:8080/clown/ConfidentialFile.txt
就能查看我们刚刚创建的文件了:
ConfidentialFile.txt
:package com.clown.net.url;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public class URLDownload {
public static void main(String[] args) throws Exception {
//1. 下载地址
URL url = new URL("http://localhost:8080/clown/ConfidentialFile.txt");
//2. 连接到这个资源 HTTP
//openConnection(); 打开连接,返回一个 URLConnection类的实例。
//URLConnection类: URLConnection 是一个抽象类,代表应用程序和 URL 之间的通信链接。它的实例可用于读取和写入此 URL 引用的资源。
//HttpURLConnection类: 支持 HTTP特定功能的 URLConnection。是 Java提供的发起 HTTP请求的基础类库,提供了 HTTP请求的基本能力
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); //强制转换为 HttpURLConnection类
//读取输入流
//getInputStream(); 返回从此打开的连接读取的输入流。
InputStream inputStream = urlConnection.getInputStream();
//创建文件输出流
FileOutputStream fos = new FileOutputStream("ConfidentialFile.txt");
byte[] buffer = new byte[1024];
int len;
while ((len = inputStream.read(buffer)) != -1) {
fos.write(buffer, 0, len); //写出数据
}
//关闭资源
fos.close();
inputStream.close();
//disconnect(); 关闭连接
urlConnection.disconnect();
}
}
package com.clown.net.url;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public class URLDownload {
public static void main(String[] args) throws Exception {
//1. 下载地址
URL url = new URL("https://m701.music.126.net/20230818212400/a087d364aa84bdf9360cdce0d896f386/jdyyaac/obj/w5rDlsOJwrLDjj7CmsOj/19668440665/bacd/3d83/588c/d0bbf5eb57a04d4b650f304bea6be713.m4a");
//2. 连接到这个资源 HTTP
//openConnection(); 打开连接,返回一个 URLConnection类的实例。
//URLConnection类: URLConnection 是一个抽象类,代表应用程序和 URL 之间的通信链接。它的实例可用于读取和写入此 URL 引用的资源。
//HttpURLConnection类: 支持 HTTP特定功能的 URLConnection。是 Java提供的发起 HTTP请求的基础类库,提供了 HTTP请求的基本能力
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); //强制转换为 HttpURLConnection类
//读取输入流
//getInputStream(); 返回从此打开的连接读取的输入流。
InputStream inputStream = urlConnection.getInputStream();
//创建文件输出流
FileOutputStream fos = new FileOutputStream("ThatIsUS.m4a");
byte[] buffer = new byte[1024];
int len;
while ((len = inputStream.read(buffer)) != -1) {
fos.write(buffer, 0, len); //写出数据
}
//关闭资源
fos.close();
inputStream.close();
//disconnect(); 关闭连接
urlConnection.disconnect();
}
}