1 网络编程
1.1 网络协议
http协议的里程:
http0.9版本:短链接、只支持GET请求、响应的数据格式只能是html的字符串
http1.0版本:短链接、可支持GET请求、POST请求,增加了状态码、响应的数据格式多元化(数字、字符串、图片、视频等)
http1.1版本:长链接、可支持GET、POST、DELETE、PUT等请求,增加100状态码等
面试题:tcp/ip和udp/ip协议的区别
1.udp是无状态传输协议,不保证安全,容易丢包
2.tcp协议在传输之前会先进行三次握手确认,保证数据的安全性
3.tcp传输慢,效率低,udp效率高,传输快
1.2 InetAddress和InetSocketAddress
InetAddress是Java对IP地址相关信息的封装,代表互联网协议(IP)地址,是构建UDP和TCP协议的低级协议.
public static void main(String[] args) throws UnknownHostException {
//0 获取InetAddress对象
InetAddress address = InetAddress.getLocalHost();
System.out.println("主机名:"+address.getHostName());
System.out.println("主机地址:"+address.getHostAddress());
InetAddress address2 = InetAddress.getByName("WIN10-008051646");
System.out.println("主机名:"+address2.getHostName());
System.out.println("主机地址:"+address2.getHostAddress());
InetAddress address3 = InetAddress.getByName("172.16.6.100");
System.out.println("主机名:"+address3.getHostName());
System.out.println("主机地址:"+address3.getHostAddress());
}
思考:一台主机上会有很多不同的网络服务(多个web服务、ftp服务等),主机要如何进行区分呢?
通常是通过IP地址+端口号进行区分
端口号范畴:
取值范围为 0 ~ 65535 ,其中0~1023
的端口号为系统所保留,例如http
服务的端口号为80
,telnet
服务的端口号为21
,ftp
服务的端口号为23
;
InetSocketAddress是SocketAddress的子类,用于实现IP套接字地址(IP+端口号),不依赖任何协议;
public static void main(String[] args) throws UnknownHostException {
InetSocketAddress socketAddress = new InetSocketAddress("172.16.6.100",8899);
System.out.println("主机名:"+socketAddress.getHostName());
System.out.println("端口号:"+socketAddress.getPort());
//0 转换为InetAddress用来获取主机地址
InetAddress addr = socketAddress.getAddress();
System.out.println("主机地址:"+addr.getHostAddress());
}
两者间的区别:
属性 | InetAddress | InetSocketAddress |
---|---|---|
描述对象 | IP地址 | Socket地址(IP地址+端口) |
描述 | IP和主机对象名称 | IP和主机的对象名称,并包括端口号 |
解决问题 | IP到主机名称,主机名称到IP | IP到主机名称,主机名称到IP,可以包含端口 |
获取对象 | InetAddress.getLocalhost(); InetAddress.getByName(String); InetAddress.getByAddress(String); | InetSocketAddress.createUnresolved(String, port); |
1.3Socket编程
Socket(套接字)可以看成是两个网络应用程序进行通信时,各自通信连接中的端点,这是一个逻辑上的概念
实现socket编程需要有客户端(client)及服务端(server)
数据传输其实就是客户端到服务端,服务端到客户端的一个过程,数据是以流的形式进行传输的
示例1:使用Socket实现聊天功能
创建服务端
public class Server {
public static void main(String[] args) throws IOException {
//定义一个端口号作为标识 0-65535,1023以下的不建议使用
int port = 8888;
//创建一个服务端的对象--用于接收客户端传递的信息
ServerSocket serverSocket = new ServerSocket(port);
Socket socket = null;
InputStream inputStream = null;
InputStreamReader isr = null;
BufferedReader br = null;
try {
while(true) {
//监听客户端发送的请求
socket = serverSocket.accept();
//读取传递的数据,需要有流对象
inputStream = socket.getInputStream();
//转换为字符流对象再包装成字符缓冲流对象
isr = new InputStreamReader(inputStream);
br = new BufferedReader(isr);
String line = null;
while((line=br.readLine())!=null) {
System.out.println("接收到客户端信息:"+line);
if(line.equals("over")) {
break;
}
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if(br!=null) {
br.close();
}
if(isr!=null) {
isr.close();
}
if(inputStream!=null) {
inputStream.close();
}
if(socket!=null) {
socket.close();
}
}
}
}
//创建客户端
public class Client {
public static void main(String[] args) {
int port = 8888;
String host = "127.0.0.1";
//创建Socket对象指定请求的地址及端口号
Socket socket = null;
OutputStream os = null;
OutputStreamWriter osw = null;
try {
while(true) {
socket = new Socket(host, port);
//通过socket获取输出流对象
os = socket.getOutputStream();
//转换为字符流
osw = new OutputStreamWriter(os);
//从控制台获取用户输入的内容
Scanner scanner = new Scanner(System.in);
String content = scanner.nextLine();
//以流的形式进行写动作
osw.write(content);
osw.flush();
socket.shutdownOutput();
if(content.equals("under line")) {
break;
}
}
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if(osw!=null) {
osw.close();
}
if(os!=null) {
os.close();
}
if(socket!=null) {
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
示例2:使用Socket实现文件传输功能
思路:
//客户端
private static final String SERVER_IP = "127.0.0.1"; // 服务端IP
private static final int SERVER_PORT = 8899; // 服务端端口
public static void main(String[] args) throws IOException {
Socket client = null;
FileInputStream fis = null;
DataOutputStream dos = null;
DecimalFormat format = new DecimalFormat("#.00");
try {
client = new Socket(SERVER_IP, SERVER_PORT);
File file = new File("E:\\Java课堂\\课堂资料\\Java\\录屏\\day01\\1、java环境配置.mp4");
if (file.exists()) {
fis = new FileInputStream(file);
dos = new DataOutputStream(client.getOutputStream());
// 文件名和长度
dos.writeUTF(file.getName());
dos.flush();
dos.writeLong(file.length());
dos.flush();
// 开始传输文件
System.out.println("======== 开始传输文件 ========");
byte[] bytes = new byte[1024 * 1024];
int length = 0;
long progress = 0;
while ((length = fis.read(bytes, 0, bytes.length)) != -1) {
dos.write(bytes, 0, length);
dos.flush();
progress += length;
System.out.println((100 * progress / file.length()) + "%");
Thread.sleep(100);
}
System.out.println();
System.out.println("======== 文件传输成功 ========");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fis != null)
fis.close();
if (dos != null)
dos.close();
client.close();
}
}
//服务端
public class Server extends ServerSocket{
private static final int SERVER_PORT = 8899; // 服务端端口
private static DecimalFormat df = null;
static {
// 设置数字格式,保留一位有效小数
df = new DecimalFormat("#0.0");
df.setRoundingMode(RoundingMode.HALF_UP);
df.setMinimumFractionDigits(1);
df.setMaximumFractionDigits(1);
}
public Server() throws Exception {
super(SERVER_PORT);
}
/**
* 使用线程处理每个客户端传输的文件
* @throws Exception
*/
public void load() throws Exception {
while (true) {
// server尝试接收其他Socket的连接请求,server的accept方法是阻塞式的
Socket socket = this.accept();
/**
* 我们的服务端处理客户端的连接请求是同步进行的, 每次接收到来自客户端的连接请求后,
* 都要先跟当前的客户端通信完之后才能再处理下一个连接请求。 这在并发比较多的情况下会严重影响程序的性能,
* 为此,我们可以把它改为如下这种异步处理与客户端通信的方式
*/
// 每接收到一个Socket就建立一个新的线程来处理它
new Thread(new Task(socket)).start();
}
}
/**
* 处理客户端传输过来的文件线程类
*/
class Task implements Runnable {
private Socket socket;
private DataInputStream dis;
private FileOutputStream fos;
public Task(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
dis = new DataInputStream(socket.getInputStream());
// 文件名和长度
String fileName = dis.readUTF();
long fileLength = dis.readLong();
File directory = new File("E:\\Cache");
if(!directory.exists()) {
directory.mkdir();
}
File file = new File(directory.getAbsolutePath() + File.separatorChar + fileName);
fos = new FileOutputStream(file);
// 开始接收文件
byte[] bytes = new byte[1024];
int length = 0;
while((length = dis.read(bytes, 0, bytes.length)) != -1) {
fos.write(bytes, 0, length);
fos.flush();
}
System.out.println("======== 文件接收成功 [File Name:" + fileName + "] [Size:" + getFormatFileSize(fileLength) + "] ========");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if(fos != null)
fos.close();
if(dis != null)
dis.close();
socket.close();
} catch (Exception e) {}
}
}
}
/**
* 格式化文件大小
* @param length
* @return
*/
private String getFormatFileSize(long length) {
double size = ((double) length) / (1 << 30);
if(size >= 1) {
return df.format(size) + "GB";
}
size = ((double) length) / (1 << 20);
if(size >= 1) {
return df.format(size) + "MB";
}
size = ((double) length) / (1 << 10);
if(size >= 1) {
return df.format(size) + "KB";
}
return length + "B";
}
/**
* 入口
* @param args
*/
public static void main(String[] args) {
try {
Server server = new Server(); // 启动服务端
server.load();
} catch (Exception e) {
e.printStackTrace();
}
}
}