Java网络编程
一、网络基本概念
2.1、URL统一资源定位符
URL(Uniform Resource Locator)
- 统一资源定位符,由4部分组成:协议、存放资源的主机域名、端口号和资源文件名
- URL是指向互联网“资源”的指针
2.2、Socket套接字
- Socket套接字是传输层供给应用层的编程接口,实质就是应用层与传输层之间的桥梁
- 传输层在网络层的基础上提供了进程到进程间的逻辑通道,应用层的进程利用传输层向另一台主机的某一个进程通信
- 案例:写信
2.3、相关类
- InetAddress类——只包括IP
public class TestInetAddress {
public static void main(String[] args) throws UnknownHostException {
//获取本机的IP地址
InetAddress i1 = InetAddress.getLocalHost();
System.out.println(i1.toString());
//HostName 主机名
System.out.println("i1.getHostName() = " + i1.getHostName());
//HostAddress 主机地址
System.out.println("i1.getHostAddress() = " + i1.getHostAddress());
//Address 地址,字节数组的形式
System.out.println("i1.getAddress() = " + Arrays.toString(i1.getAddress()));
//获取百度的IP地址
InetAddress i2 = InetAddress.getByName("www.baidu.com");
System.out.println(i2.toString());
System.out.println("i2.getHostName() = " + i2.getHostName());
System.out.println("i2.getHostAddress() = " + i2.getHostAddress());
System.out.println("i2.getAddress() = " + Arrays.toString(i2.getAddress()));
}
}
- InetSocketAddress类——包括IP+Socket
public class TestInetSocketAddress {
public static void main(String[] args) throws UnknownHostException {
//第一种构造方式
// InetAddress b1 = InetAddress.getByName("www.baidu.com");
// InetSocketAddress is2 = new InetSocketAddress(b1, 8907);
// System.out.println("is2 = " + is2);
//第二种构造方式
InetSocketAddress is1 = new InetSocketAddress("www.baidu.com",8907);
//网址/IP地址:端口号
System.out.println("is1 = " + is1);
//网址/IP地址
System.out.println("is1.getAddress() = " + is1.getAddress());
//网址
System.out.println("is1.getHostName() = " + is1.getHostName());
//端口号
System.out.println("is1.getPort() = " + is1.getPort());
}
}
- URL类
public class TestURL {
public static void main(String[] args) throws MalformedURLException {
URL url = new URL("https://www.baidu.com:12345/s?wd=nihao&inputT=1849#cloths");
//url
System.out.println("url = " + url);
//协议
System.out.println("url.getProtocol() = " + url.getProtocol());
//主机/域名
System.out.println("url.getHost() = " + url.getHost());
//默认的端口号
System.out.println("url.getDefaultPort() = " + url.getDefaultPort());
//设置的端口号
System.out.println("url.getPort() = " + url.getPort());
//域名后,请求参数前的部分
System.out.println("url.getPath() = " + url.getPath());
//域名后的部分
System.out.println("url.getFile() = " + url.getFile());
//cloths
System.out.println("url.getRef() = " + url.getRef());
//请求参数的部分
System.out.println("url.getQuery() = " + url.getQuery());
}
}
/*
url = https://www.baidu.com:12345/s?wd=nihao&inputT=1849#cloths
url.getProtocol() = https
url.getHost() = www.baidu.com
url.getDefaultPort() = 443
url.getPort() = 12345
url.getPath() = /s
url.getFile() = /s?wd=nihao&inputT=1849
url.getRef() = cloths
url.getQuery() = wd=nihao&inputT=1849
二、TCP编程
2.1、TCP编程——一次单向通信
功能:实现类似QQ、微信、邮箱、商城的网络登录功能,可以多个用户同时登录。为了便于理解,进行功能分解和迭代,分为一次单向通信、一次双向通信、传输对象、引入多线程来实现
//服务端
//迭代1:客户端向服务器发起请求,服务器端获取请求,获取请求数据并在服务器输出
public class LoginServer {
public static void main(String[] args) throws IOException {
//1.创建一个ServerSocket,负责监听客户端请求
ServerSocket serverSocket = new ServerSocket(8888);
//2.使用ServerSocket进行监听,如果请求没有收到,代码就会阻塞
Socket socket = serverSocket.accept();
//3.处理用户的请求
InputStream is = socket.getInputStream();
DataInputStream dis = new DataInputStream(is);
String str = dis.readUTF();
System.out.println("str = " + str);
//4.关闭资源
dis.close();
socket.close();
System.out.println("serverSocket.isClosed() = " + serverSocket.isClosed());
serverSocket.close();
System.out.println("serverSocket.isClosed() = " + serverSocket.isClosed());
}
}
//客户端
public class LoginClient {
public static void main(String[] args) throws IOException {
//1.创建一个Socket,指明服务器端的IP地址和端口号
//Socket socket = new Socket(InetAddress.getLocalHost(),8888);
//Socket socket = new Socket(InetAddress.getByName("localhost"),8888);
Socket socket = new Socket(InetAddress.getByName("192.168.140.1"),8888);
//2.向服务器端发起一个新的请求
OutputStream os = socket.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
dos.writeUTF("username=doublew2w&password=doublew2w");
3.关闭资源
System.out.println("socket.isClosed() = " + socket.isClosed());
dos.close();
System.out.println("socket.isClosed() = " + socket.isClosed());
}
}
注意事项:
- 服务器端的要手动关闭
- 客户端在关闭IO流的时候,就顺便把客户端关闭了。
2.2、TCP编程-一次双向通信
双向通信相当于服务器端返回响应信息给客户端。之所以能正确地发送客户端,那是因为IP地址和端口号等信息都封装在Socket socket = serverSocket.accept();
和 Socket socket = new Socket();
//服务器端
//迭代2:客户端向服务器发起请求,服务器端获取请求,获取请求数据并在服务器输出,服务器端返回响应信息给客户端
public class LoginServer2 {
public static void main(String[] args) throws IOException {
//1.创建一个ServerSocket,负责监听客户端请求
ServerSocket serverSocket = new ServerSocket(8888);
//2.使用ServerSocket进行监听,如果请求没有收到,代码就会阻塞
Socket socket = serverSocket.accept();
// InputStream is = socket.getInputStream();
DataInputStream dis = new DataInputStream(socket.getInputStream());
// OutputStream os = socket.getOutputStream();
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
//3.处理用户的请求
String str = dis.readUTF();
System.out.println("str = " + str);
//4.给客户端返回响应信息
dos.writeUTF("用户登录成功,欢迎你");
//5.关闭资源
dis.close();
dos.close();
serverSocket.close();
}
}
public class LoginClient2 {
public static void main(String[] args) throws IOException {
//1.创建一个Socket,指明服务器端的IP地址和端口号
//Socket socket = new Socket(InetAddress.getLocalHost(),8888);
//Socket socket = new Socket(InetAddress.getByName("localhost"),8888);
Socket socket = new Socket(InetAddress.getByName("192.168.140.1"),8888);
// OutputStream os = socket.getOutputStream();
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
// InputStream is = socket.getInputStream();
DataInputStream dis = new DataInputStream(socket.getInputStream());
//2.向服务器端发起一个新的请求
dos.writeUTF("username=doublew2w&password=doublew2w");
//3.接收服务器端发送过来的响应信息
String str = dis.readUTF();
System.out.println("str = " + str);
//4.关闭资源
dos.close();
dis.close();
}
}
2.3、TCP编程-传输对象
在一次双向通信基础上进步优化,在客户端输入用户名和密码,并封装到User对象中。如何在TCP编程中传输对象呢?
User类
public class User implements Serializable {
private String userId;
private String password;
public User() {
}
public User(String userId, String password){
this.userId = userId;
this.password = password;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"userId='" + userId + '\'' +
", password='" + password + '\'' +
'}';
}
}
客户端
public class LoginClient3 {
public static void main(String[] args) throws IOException {
//1.创建一个Socket,指明服务器端的IP地址和端口号
//Socket socket = new Socket(InetAddress.getLocalHost(),8888);
//Socket socket = new Socket(InetAddress.getByName("localhost"),8888);
Socket socket = new Socket(InetAddress.getByName("192.168.140.1"),8888);
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
DataInputStream dis = new DataInputStream(socket.getInputStream());
//2.向服务器端发起一个新的请求
Scanner scanner = new Scanner(System.in);
System.out.println("请输入用户名");
String userId = scanner.next();
System.out.println("请输入密码");
String password = scanner.next();
User user = new User(userId, password);
oos.writeObject(user);
//3.接收服务器端发送过来的响应信息
String str = dis.readUTF();
System.out.println("这里是客户端:" + str);
//4.关闭资源
oos.close();
dis.close();
}
}
服务器端
public class LoginServer3 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//1.创建一个ServerSocket,负责监听客户端请求
ServerSocket serverSocket = new ServerSocket(8888);
//2.使用ServerSocket进行监听,如果请求没有收到,代码就会阻塞
Socket socket = serverSocket.accept();
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
//3.处理用户的请求
User user = (User)ois.readObject();
System.out.println("这里是服务器端:"+user);
//4.给客户端返回响应信息
if("doublew2w".equals(user.getUserId())){
dos.writeUTF("您好,登录成功,欢迎您");
}else{
dos.writeUTF("您好,登录失败,请重新输入");
}
//5.关闭资源
ois.close();
dos.close();
serverSocket.close();//手动关闭
}
}
使用TCP实现登录功能总结:
- 服务器创建
ServerSocket
,在指定端口监听并并处理请求; - ServletSocket通过
accept()
接收用户请求并返回对应的Socket,否则一种处于监听等待状态,线程也被阻塞 - 客户端创建
Socket
,需要指定服务器的ip和端口号,向服务器发送和接收响应 - 客户端发送数据需输出流(写),客户端获取反馈数据需输入流(读)
- 服务端反馈数据需输出流(写),服务端获取请求数据需输入流(读)
- 一旦使用ServerSocket和Socket建立了网络连接后,网络通信和普通IO流操作并没有太大区别
- 网络通信输出流建议使用
DataOutputStream
和ObjectOutputStream
,与平台无关,输入流相应使用DataIntputStream
和ObjectInputStream
- 如果是字符串通信也可以使用
BufferedReader
和PrintWriter
,简单方便
2.5、TCP编程——引入多线程
- 如何让服务器端一直处于运行状态,并且会出现多个用户同时登陆的情况,需要服务器端进行处理
- 方法1:只引入while ,不可以
-
此时服务器端只做两件事: 接受请求(1秒)+ 处理请求(9秒)
-
必须处理完一个请求后,才能处理下一个请求——串行
-
- 方法2:引入while+多线程
-
接收用户的请求 + 每个请求创建一个线程来处理请求
-
LoginThread类——多线程处理请求
public class LoginThread extends Thread{
private Socket socket;
public LoginThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try(
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
){
//3.处理用户的请求
User user = (User)ois.readObject();
System.out.println("这里是服务器端:"+user);
//4.给客户端返回响应信息
if("doublew2w".equals(user.getUserId())){
dos.writeUTF("您好,登录成功,欢迎您");
}else{
dos.writeUTF("您好,登录失败,请重新输入");
}
}catch(ClassNotFoundException | IOException e){
e.printStackTrace();
}
}
}
客户端(不改变)——
public class LoginClient4 {
public static void main(String[] args) throws IOException {
//1.创建一个Socket,指明服务器端的IP地址和端口号
//Socket socket = new Socket(InetAddress.getLocalHost(),8888);
//Socket socket = new Socket(InetAddress.getByName("localhost"),8888);
Socket socket = new Socket(InetAddress.getByName("192.168.140.1"),8888);
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
DataInputStream dis = new DataInputStream(socket.getInputStream());
//2.向服务器端发起一个新的请求
Scanner scanner = new Scanner(System.in);
System.out.println("请输入用户名");
String userId = scanner.next();
System.out.println("请输入密码");
String password = scanner.next();
User user = new User(userId, password);
oos.writeObject(user);
//3.接收服务器端发送过来的响应信息
String str = dis.readUTF();
System.out.println("这里是客户端:" + str);
//4.关闭资源
oos.close();
dis.close();
}
}
服务器端
public class LoginServer4 {
public static void main(String[] args) throws Exception {
//1.创建一个ServerSocket,负责监听客户端请求
ServerSocket serverSocket = new ServerSocket(8888);
int i=1;
while(true){
//2.使用ServerSocket进行监听,如果请求没有收到,代码就会阻塞
Socket socket = serverSocket.accept();
//3.创建一个线程处理请求
new LoginThread(socket).start();
//4.输出请求数量与客户端的IP地址和端口号
System.out.println("这是第"+ (i++)+"个请求" +
"对方的IP地址是"+socket.getInetAddress()+
"对方的端口号是"+socket.getPort());
}
}
}
三、UDP编程
需求:完成在线咨询功能
分析:
- 使用基于UDP协议的Socket网络编程实现
- 不需要利用IO流实现数据的传输
- 每个数据发送单元被统一封装成数据包的方式,发送方将数据包发送到网络中,数据包在网络中去寻找他的目的地
UDP基本概念
- DatagramSocket:用于发送或接收数据包
- DatagramPacket:数据包
3.1、UDP编程——一次单向通信
服务器端,被动接受请求
public class AskServer {
public static void main(String[] args) throws IOException {
//1.创建一个DatagramSocket,可以指定接受数据的端口(也可以不指定,会自动分配)
DatagramSocket socket = new DatagramSocket(8888);
//2.接受数据包
byte[] bytes = new byte[100];
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
System.out.println(packet);
socket.receive(packet);
System.out.println(packet);
System.out.println("packet.getData() = " + Arrays.toString(packet.getData()));
System.out.println("new String(packet.getData()) = " + new String(packet.getData(),0, packet.getLength()));
System.out.println("packet.getLength() = " + packet.getLength());
System.out.println("packet.getAddress() = " + packet.getAddress());
System.out.println("packet.getPort() = " + packet.getPort());
// socket.receive(packet);
//3.关闭资源
socket.close();
}
}
/**
java.net.DatagramPacket@677327b6
java.net.DatagramPacket@677327b6
packet.getData() = [-28, -70, -78, -17, -68, -116, -27, -100, -88, -27, -112, -105, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
new String(packet.getData()) = 亲,在吗
packet.getLength() = 12
packet.getAddress() = /127.0.0.1
packet.getPort() = 9999
客户端——主动发送请求
public class AskClient {
public static void main(String[] args) throws IOException {
//1.创建一个DatagramSocket,可以指定接受数据的端口(也可以不指定,会自动分配)
//同一台电脑 同一个端口不能又发送又接受
DatagramSocket socket = new DatagramSocket(9999);
//2.发送数据包
String str = "亲,在吗";
byte[] buf = str.getBytes();
int length = buf.length;
InetAddress address = InetAddress.getByName("localhost");
int port = 8888;
DatagramPacket packet = new DatagramPacket(buf, length, address, port);
socket.send(packet);
//3.关闭资源
socket.close();
}
}
3.2、UDP编程-一次双向通信
服务器端
public class AskServer {
public static void main(String[] args) throws IOException {
//1.创建一个DatagramSocket,可以指定接受数据的端口(也可以不指定,会自动分配)
DatagramSocket socket = new DatagramSocket(8888);
//2.接受数据包
byte[] bytes = new byte[100];
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
socket.receive(packet);
System.out.println("这里是服务器端:" + new String(packet.getData(),0, packet.getLength()));
//3.给出客户端响应
String str = "亲,在的";
byte[] bytes1 = str.getBytes();
InetAddress address = InetAddress.getByName("localhost");
int port = 9999;
DatagramPacket packetSend = new DatagramPacket(bytes1, 0, bytes1.length, address, port);
socket.send(packetSend);
//4.关闭资源
socket.close();
}
}
客户端
public class AskClient {
public static void main(String[] args) throws IOException {
//1.创建一个DatagramSocket,可以指定接受数据的端口(也可以不指定,会自动分配)
//同一台电脑 同一个端口不能又发送又接受
DatagramSocket socket = new DatagramSocket(9999);
//2.发送数据包
String str = "亲,在吗";
byte[] buf = str.getBytes();
int length = buf.length;
InetAddress address = InetAddress.getByName("localhost");
int port = 8888;
DatagramPacket packet = new DatagramPacket(buf, length, address, port);
socket.send(packet);
//3.接受服务端响应
byte[] bytes = new byte[1024];
DatagramPacket packetReceive = new DatagramPacket(bytes, bytes.length);
socket.receive(packetReceive);
System.out.println(" 这里是客户端:" + new String(packetReceive.getData(),0, packet.getLength()));
//4.关闭资源
socket.close();
}
}
总结:
- 服务器端——接收请求
DatagramPacket packet(字节数组,字节数组长度)
socket.receive(packet);
new String(packet.getData(),0, packet.getLength())
转换数据
- 服务器端——发送响应
byte[] bytes1 = str.getBytes();
DatagramPacket(字节数组, 0, bytes1.length, address, port);
socket.send(packetSend);
3.3、UDP编程-多次双向通信
服务器端
public class AskServer {
public static void main(String[] args) throws IOException {
//1.创建一个Socket,用来发送和接收数据包
DatagramSocket socket = new DatagramSocket(8888);
Scanner input = new Scanner(System.in);
while(true){
//2.使用socket接收一个数据包并输出
byte [] buf = new byte[128];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
socket.receive(packet);
String info = new String(packet.getData(),0,packet.getLength());
System.out.println(info);
if("bye".equals(info)){
break;
}
//3.使用socket发送一个响应
String str =input.nextLine();
byte[] buf2 = str.getBytes();
DatagramPacket packet2
= new DatagramPacket(buf2,buf2.length, packet.getAddress(), packet.getPort());
socket.send(packet2);
}
//3.关闭socket
socket.close();
}
}
客户端
public class AskClient {
public static void main(String[] args) throws IOException {
//1.创建一个Socket,用来发送和接收数据包
//服务器返回数据给客户端,
DatagramSocket socket = new DatagramSocket(9999);
Scanner input = new Scanner(System.in);
while(true ){
//2.使用socket发送一个数据包
String str = input.nextLine();
byte [] buf = str.getBytes();
InetAddress address = InetAddress.getLocalHost();
int port = 8888;
DatagramPacket packet = new DatagramPacket(buf,buf.length ,address ,port );
socket.send(packet);
if("bye".equals(str)){
break;
}
//3.接收服务器端的反馈
byte [] buf2 = new byte[128];
DatagramPacket packet2 = new DatagramPacket(buf2, buf2.length);
socket.receive(packet2);
System.out.println(new String(packet2.getData(),0,packet2.getLength()));
}
//4.关闭socket
socket.close();
}
四、TCP编程实现文件上传功能
- 思路:进行两次文件复制
- 客户端将文件从本地复制到网络;服务端将文件从网络复制到本地
客户端实现把源文件赋值到服务器端的监听端口
public class UploadClient {
public static void main(String[] args) throws IOException {
//源文件是本地
String sourceFilename = "e:/readme.txt";
//目的文件是服务器的监听端口
Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
//创建输入流和输出流
try(BufferedInputStream bis = new BufferedInputStream(new FileInputStream(sourceFilename));
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
){
//使用输入流和输出流完成文件赋值
//2.1 准备一个中转站(一个字节)
by】;te [] buf = new byte[1024];
int len;
while((len = bis.read(buf)) != -1) { // ==-1,读到了文件的末尾
//写字节数组到文件
bos.write(buf, 0, len);
len = bis.read(buf);
}
}catch(FileNotFoundException e){
e.printStackTrace();
}
}
}
服务器端实现把端口中的文件复制到某个具体位置
public class UploadServer {
public static void main(String[] args) throws IOException {
//源文件是服务器端的端口
ServerSocket serverSocket = new ServerSocket(8888);
Socket socket = serverSocket.accept();
//目的文件是服务器
String destFilename = "e:/readme2.txt";
//创建输入流和输出流
try(BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFilename));
){
//使用输入流和输出流完成文件赋值
//2.1 准备一个中转站(一个字节)
byte [] buf = new byte[1024];
int len;
while((len = bis.read(buf)) != -1) { // ==-1,读到了文件的末尾
//写字节数组到文件
bos.write(buf, 0, len);
len = bis.read(buf);
}
}catch(FileNotFoundException e){
e.printStackTrace();
}
}
}