1、服务器介绍
-
服务器就是一台配置很高的电脑
-
服务器常见的场景
-
通过网址访问网站
-
登陆
-
注册
-
外卖软件
-
打车软件
-
...
-
只要涉及到网络的操作, 都需要连接服务器
-
2、互联网架构分类
BS架构 :
-
Browser / Server
优点:不需要下载客户端,使用起来非常方便。
缺点:用户体验比较差
CS架构 :
-
Client / Server
缺点:需要下载客户端,而且每一次要更新内容的时候,都要更新客户端,甚至要重新下载,非常麻烦。
优点:画面非常精美,用户体验比较好。
3、网络编程三要素
1)IP地址
设备在网络中的唯一标识
2)端口
程序在设备中的唯一标识
3)协议
通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规则一样。在计算机网络中,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换。常见的协议有 UDP协议 和 TCP协议
4、IP地址
IP(Internet Protocol):全称 【互联网协议地址】,是分配给上网设备的唯一标志。
-
常见的IP分类为:IPv4 和 IPv6
IPV4 :
IPV6 :
IP的分类
5、InetAddress
常用方法:
getByName :确定主机名称的IP地址。主机名称可以是机器名称,也可以是IP地址
getHostName:获取主机名
getHostAddress :获取IP
//1.获取一个IP地址(在网络中计算机的对象)
InetAddress address = InetAddress.getByName("127.0.0.1");
//2.获取主机名获取ip地址
//细节:如果能获取到主机名返回的就是主机名
//但是如果有的情况下,获取不到,返回的就是ip地址
String hostName = address.getHostName();
System.out.println(hostName);
//3.获取IP
String ip = address.getHostAddress();
System.out.println(ip);
6、端口号
-
端口:应用程序在设备中唯一的标识。
-
端口号:用两个字节表示的整数,它的取值范围是0~65535。
其中0~1023之间的端口号用于一些知名的网络服务或者应用。
我们自己使用1024以上的端口号就可以了。
-
常见的端口号
-
8080 :tomcat 服务器
-
3306 :MySQL 数据库
-
-
-
注意:写代码的时候, 注意端口绑定, 不要出现端口冲突
7、UDP协议和TCP协议介绍
UDP :
-
用户数据报协议(User Datagram Protocol)
-
UDP是面向无连接通信协议。
速度快,有大小限制一次最多发送64K,数据不安全,易丢失数据
TCP :
-
传输控制协议TCP(Transmission Control Protocol)
-
TCP协议是面向连接的通信协议。
速度慢,没有大小限制,数据安全
8、UDP协议收发数据
发送数据
-
创建发送端对象 : DatagramSocket ds = new DatagramSocket();
-
创建数据包 : DatagramPacket dp = new DatagramPacket(字节数组, 数据个数, 接收端IP, 接收端端口);
-
发送数据 : ds.send(dp);
-
释放资源 : ds.close();
package com.itheima.udp;
import java.net.*;
public class Client {
/*
DatagramPacket(byte[] buf, int length, InetAddress address, int port)
参数1: 将传输的数据, 转换为字节, 并存入一个数组
参数2: 数组的长度
参数3: InetAddress(IP对象)
参数4: 端口号
*/
public static void main(String[] args) throws Exception {
// 1. 创建DatagramSocket对象 (码头)
DatagramSocket socket = new DatagramSocket();
// 2. 创建DatagramPacket对象 (包裹对象)
String content = "你可能说不出哪里好";
byte[] bytes = content.getBytes();
DatagramPacket packet =
new DatagramPacket
(bytes, bytes.length,
InetAddress.getByName("127.0.0.1"),
8888);
// 3. 调用码头对象的发送方法, 将包裹对象发送出去
socket.send(packet);
// 4. 关闭流释放资源
socket.close();
}
}
接收数据
-
创建接收端对象 : DatagramSocket ds = new DatagramSocket(端口);
-
创建数据包 : DatagramPacket dp = new DatagramPacket(字节数组, 数组的长度);
-
接收数据 : ds.receive(dp);
-
使用接收的数据 : System.out.println(new String(bys, 0, dp.getLength()));
-
释放资源 : ds.close();
package com.itheima.udp;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class Server {
public static void main(String[] args) throws IOException {
// 1. 创建DatagramSocket对象(码头), 绑定端口为8888
DatagramSocket socket = new DatagramSocket(8888);
// 2. 创建DatagramPacket对象(包裹对象)
byte[] bys = new byte[1024];
DatagramPacket packet = new DatagramPacket(bys, bys.length);
// 3. 调用码头对象的接受方法, 将数据, 接收到自己的包裹当中
socket.receive(packet);
System.out.println(packet.getAddress());
String s = new String(bys, 0, packet.getLength());
System.out.println(s);
// 4. 关闭流释放资源
socket.close();
}
}
练习
UDP发送数据:数据来自于键盘录入,直到输入的数据是886,发送数据结束
UDP接收数据:因为接收端不知道发送端什么时候停止发送,故采用死循环接收
发送数据
package com.lyl.net;
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class Client {
/*
客户端发送数据
*/
public static void main(String[] args) throws IOException {
//创建DatagramSocket对象,随机绑定端口
DatagramSocket socket = new DatagramSocket();
Scanner sc = new Scanner(System.in);
System.out.println("请输入要发送的数据:");
while (true){
String msg = sc.nextLine();
if ("886".equals(msg)){
socket.close();
break;
}
//准备要发送的数据字节数组
byte[] bytes = msg.getBytes();
//数据打包
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getByName("192.168.15.49"), 8888);
//发送
socket.send(packet);
}
}
}
接收数据
package com.lyl.net;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class Server {
/*
服务端接收数据
*/
public static void main(String[] args) throws IOException {
//创建DatagramSocket对象,绑定8888端口
DatagramSocket socket = new DatagramSocket(8888);
//创建包
while (true) {
byte[] bytes = new byte[1024];
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
//接受
socket.receive(packet);
//从包裹中取出数据
byte[] data = packet.getData();
String msg = new String(data,0, packet.getLength());
//打印数据
System.out.println(msg);
}
}
}
操作细节:
需要把发送端运行多次。
IDEA 默认只能运行一次,所以需要进行配置。
需要保证第一个红色箭头是当前要运行多次的类,再点击下面的Edit Configurations,再按照下面的图解进行设置即可。
9、TCP协议收发数据
-
Java中的TCP通信
Java对基于TCP协议的的网络提供了良好的封装,使用Socket对象来代表两端的通信端口,并通过Socket产生IO流来进行网络通信。
Java为客户端制提供了Socket类,为服务器端提供了ServerSocket类
构造方法
方法名 | 说明 |
---|---|
Socket(InetAddress address,int port) | 创建流套接字并将其连接到指定IP指定端口号 |
Socket(String host, int port) | 创建流套接字并将其连接到指定主机上的指定端口号 |
相关方法
方法名 | 说明 |
---|---|
InputStream getInputStream() | 返回此套接字的输入流 |
OutputStream getOutputStream() | 返回此套接字的输出流 |
示例代码
public class ClientDemo {
public static void main(String[] args) throws IOException {
//创建客户端的Socket对象(Socket)
//Socket(String host, int port) 创建流套接字并将其连接到指定主机上的指定端口号
Socket s = new Socket("127.0.0.1",10000);
//获取输出流,写数据
//OutputStream getOutputStream() 返回此套接字的输出流
OutputStream os = s.getOutputStream();
os.write("hello,tcp,我来了".getBytes());
//释放资源
os.close();
s.close();
}
}
构造方法
方法名 | 说明 |
---|---|
ServletSocket(int port) | 创建绑定到指定端口的服务器套接字 |
相关方法
方法名 | 说明 |
---|---|
Socket accept() | 监听要连接到此的套接字并接受它 |
示例代码
public class ServerDemo {
public static void main(String[] args) throws IOException {
//创建服务器端的Socket对象(ServerSocket)
//ServerSocket(int port) 创建绑定到指定端口的服务器套接字
ServerSocket ss = new ServerSocket(10000);
//Socket accept() 侦听要连接到此套接字并接受它
Socket s = ss.accept();
//获取输入流,读数据,并把数据显示在控制台
InputStream is = s.getInputStream();
byte[] bys = new byte[1024];
int len = is.read(bys);
String data = new String(bys,0,len);
System.out.println("数据是:" + data);
//释放资源
s.close();
ss.close();
}
}
注意事项
accept方法是阻塞的,作用就是等待客户端连接
客户端创建对象并连接服务器,此时是通过三次握手协议,保证跟服务器之间的连接
针对客户端来讲,是往外写的,所以是输出流;针对服务器来讲,是往里读的,所以是输入流
read方法也是阻塞的
客户端在关流的时候,还多了一个往服务器写结束标记的动作
最后一步断开连接,通过四次挥手协议保证连接终止
三次握手和四次挥手
-
UDP : 面向无连接, 数据不安全, 速度快
-
有传输大小限制 , 一次最多只能发送64k
-
-
TCP : 面向连接, 数据安全, 速度相对来说较慢
-
没有传输大小限制
-
-
三次握手
-
四次挥手
案例—文件上传
-
客户端代码
package com.lyl.file_tcp;
import java.io.*;
import java.net.Socket;
public class Client {
public static void main(String[] args) throws IOException {
System.out.println("开始连接服务器...");
//创建对象,参数一为目标IP,参数二为目标端口
Socket socket = new Socket("127.0.0.1", 9988);
System.out.println("连接成功...");
//创建网络流对象
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
//将要上传的文件封装为file对象
File file = new File("D:\\test.png");
//将文件名写入到服务器
bw.write(file.getName());
bw.newLine();
bw.flush();
//读取服务器上传信号
String state = br.readLine();
if ("同意上传".equals(state)) {
//创建本地输入流,读取文件
FileInputStream fis = new FileInputStream(file);
byte[] bytes = new byte[8192];
int len;
while ((len = fis.read(bytes)) != -1) {
//写入网络流
os.write(bytes,0,len);
}
//读取本地文件完毕,关闭输入流
fis.close();
//给服务器一个结束标记,结束传输
socket.shutdownOutput();
//读取上传结果
String result = br.readLine();
System.out.println(result);
}
//关流
socket.close();
bw.close();
br.close();
}
}
-
服务器端代码
package com.lyl.file_tcp;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Server {
public static void main(String[] args) throws IOException {
//创建对象,绑定端口号
ServerSocket server = new ServerSocket(9988);
System.out.println("服务器启动...等待客户端连接...");
//创建线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
1,5,10,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
while (true) {
//接收客户端
Socket socket = server.accept();
//将任务提交到线程池
threadPoolExecutor.submit(new SubmitFileTask(socket));
}
}
}
-
SubmitFileTask任务处理类
package com.lyl.file_tcp;
import java.io.*;
import java.net.Socket;
import java.util.UUID;
public class SubmitFileTask implements Runnable{
private Socket socket;
public SubmitFileTask(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
//创建网络流对象
try {
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
System.out.println("响应客户端请求成功...");
//读取客户端发来的文件名
String fileName = br.readLine();
//获取客户端IP
String hostAddress = socket.getInetAddress().getHostAddress();
//以客户端IP创建文件夹
File floder = new File("E:\\"+hostAddress+"\\");
floder.mkdirs();
//创建file对象
//创建UUID对象调用方法加随机ID,防止文件覆盖
File file = new File(floder,UUID.randomUUID().toString()+fileName);
//给出同意上传信号
bw.write("同意上传");
bw.newLine();
bw.flush();
//写出客户端发来的文件到本地
FileOutputStream fos = new FileOutputStream(file);
byte[] bytes = new byte[8192];
int len;
while ((len = is.read(bytes))!=-1){
fos.write(bytes,0,len);
}
System.out.println("下载成功...");
//关闭输出流
fos.close();
//响应给客户端上传结果
bw.write("上传成功!");
bw.newLine();
bw.flush();
//关流
socket.close();
bw.close();
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}