java网络编程
https://www.bilibili.com/video/BV1LJ411z7vY狂神说Java
网络编程
“网络编程很简单,重点是给你们回顾IO呢”
网络类比
信件 包packet
对方邮编 对方地址
自己地址 自己地址
打电话——连接——通信 TCP
发短信——发送——接收 UDP
网络里的概念,在Java中都有对应的类、对象
概念
太抽象的概念没必要记,善用百度
计算机网络是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。
网络编程的目的:(像无线电台)传播交流信息,数据交换,通信
javaweb是网页编程,B/S架构
网络编程这里学的TCP/IP和C/S架构
网络通信的要素
自动驾驶技术已经挺成熟了,之所以没大规模上线是因为有电车难题
如何实现网络的通信?
通信双方地址:
- ip
- 端口号
规则:网络通信的协议
TCP/IP协议
网络编程的重点在于传输层,对TCP和UDP进行编程,下面的我们就管不着了,以后再学就是应用层
应用层好比QQ微信,网络层就是数据封包,数据链路层不是专业的话很难学,也就是考试用得到
小结:
-
网络编程中有两个主要的问题
- 如何准确地定位到网络上的一台或多台主机
- 找到主机之后如何进行通信
-
网络编程中的要素
- IP 和 端口号
- 网络通信协议
-
万物皆对象
IP类,UDP类,TCP类
IP
java.lang.Object
java.net.InetAddress
此类表示Internet协议(IP)地址。没有构造器
-
唯一定位一台网络上的计算机
-
127.0.0.1:本机 localhost(没网也能ping通)
-
ip地址的分类
-
ipv4/ipv6
- IPV4 如127.0.0.1,4个字节组成,0~255,42亿,其中30亿都在北美,亚洲只有4亿,2011年就用尽了
- IPV6 (据说可以给地球上每一粒沙子分配一个IP地址)fe80::d8d5:c7a7:4a75:7cdd%2,128位,8个无符号整数
-
公网(互联网)-私网(局域网)
- ABCDE类地址
-
-
192.168.xx.xx,专门给组织内部使用的
-
域名:IP不好记,阿里云上可以买卖域名
以后在公司里、社会上有的领导不愿意让你见世面、不让你学新东西、不愿意把他知道的告诉你
InetAddress.getByname(域名或IP地址);
会抛出UnknownHostException,返回的是域名/IP地址
要获得本机地址可以有三种方法:
InetAddress.getByname("127.0.0.1");
InetAddress.getByname("localhost");
InetAddress.getLocalHost();
getByName()也就是因为InetAddress没有构造方法,又需要把字符串变成IP地址
还有:
- getHostAddress() 获得主机ip
- getHostName() 获得主机域名
API里说的多,但是能用上的就这些
端口
端口表示计算机上的一个程序的进程
- 不同的进程有不同的端口号,用来区分软件
- 端口号被规定为0~65535
- TCP端口和UDP端口是分开算的,单个协议下端口号不能冲突
- 端口分类
- 公有端口:0~1023(尽量不要用,内置程序会使用)
- HTTP:80
- HTTPS:443
- FTP:21
- Telnet:23
- 程序注册端口:1024~49151,分配给用户或者程序(不用硬记,慢慢用多了就会了)
- Tomcat:8080
- MySQL:3306
- Oracle:1521
- 动态、私有端口:49152~65535(也不建议用)
- IDEA测试HTML:63342
- 公有端口:0~1023(尽量不要用,内置程序会使用)
nestat -ano#查看所有的端口
nestat -ano|findstr "端口号"#|是管道符,过滤得到指定的端口。管道符来自linux
tasklist|finstr "端口号"#用端口户号查进程
ctrl+shift+esc 任务管理器快捷键
弄到一个变量可以在后面加个.sout自动补全输出
InetSocketAddress就是IP+端口号,有构造方法
inetSocketAddress.getAddress();
inetSocketAddress.getHostName();
inetSocketAddress.getPort();
其中hostName可以在C:\Windows\System32\drivers\etc\hosts文件里设置,不过这两天用不到
分别在两台机器上的进程要通信,发信方要知道对方的IP、找到对方的端口号,对方也得有对应的接口处理程序才能实现
当时微软有MSN,OICQ出来的时候微软想要不要制止一下,但是心软,又有些法律问题,就把QQ养起来了,QQ现在各种收购别人
通信协议
协议:约定
网络通信协议:速率,传输码率,代码结构,传输控制......
问题:非常的复杂,通信工程学的就是这些(弹幕说通信基本做的是物理层)
我们要大事化小,分层地学
TCP/IP协议簇:实际上是一组协议
重要:
- TCP协议:用户传输协议
- UDP协议:用户数据报协议
出名的协议:
- TCP
- IP:网络互连协议
TCP UDP对比(用TCP UDP可以写简单的应用如聊天室,也可以写HTTP写比如Tomcat但是对新手个人说太麻烦是个开源项目)
TCP:打电话
- 连接,稳定
- “三次握手”,“四次挥手”
- 客户端、服务端
- 传输完成,释放连接
UDP:发短信
- 不连接,不稳定
- 客户端和服务端没有明确的界限
- 不管有没有准备好都可以发给你
- 导弹
- DDOS:洪水攻击(饱和攻击)
其实以后不会写这么多,但是“学一学总没有坏处”
TCP
客户端
- 要知道服务器的IP地址+端口号
- 用IP+端口号创建一个socket连接(在这步和服务器“三次握手”)
- 发送消息(用IO流,socket.getOutputStream)
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
public class TcpClient {
public static void main(String[] args) throws UnknownHostException {
int port = 9999;
InetAddress inetAddress = InetAddress.getByName("127.0.0.1");
try(Socket socket = new Socket(inetAddress, port);
OutputStream outputStream = socket.getOutputStream();) {
outputStream.write("你好,我是客户端".getBytes());
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务器
socket是套接字,整个java网络编程也称为套接字编程。(socket原意插座)
Socket是客户端的socket,ServerSocket是服务器端的socket,TCP中的套接字叫流套接字。
- 建立服务的端口
- 等待来自客户端的连接accept
- socket.getInputStream读取客户端的消息
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class TcpServer {
public static void main(String[] args) {
while(true)
try(ServerSocket serverSocket = new ServerSocket(9999);
Socket socket = serverSocket.accept();
InputStream inputStream = socket.getInputStream();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();){
byte[] bytes = new byte[1024];
int len;
while((len=inputStream.read(bytes))!=-1)
byteArrayOutputStream.write(bytes,0,len);
System.out.println(byteArrayOutputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 发现try-with-resources报错,问题出在它要求资源变量都是事实final,而把定义声明写在try()外面又会有问题,所以解决办法是把所有资源的定义声明全写在try后括号()里。
- 狂神介绍了一种新流叫ByteArrayOutputStream,有缓冲作用,也可以避免字符被拆散。至于有没有之前学的流好用就不知道了。看来是write的都存起来,最后用sout可以输出到控制台上。
- 服务器端加了while(true)就可以持续等待连接、提供服务了
- 发现用OutputStream.write(bytes,0,len);是有必要的,如果只写bytes不加off和len的话会把整个byte数组写进去,包括初始没有覆盖的0或者已经输出过而没被再覆盖的废数据
上传文件、传输完毕的通知与反馈
package 网络编程;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
public class TcpClient2 {
public static void main(String[] args) throws UnknownHostException {
int port = 9999;
InetAddress inetAddress = InetAddress.getByName("127.0.0.1");
try(
Socket socket = new Socket(inetAddress, port);
OutputStream outputStream = socket.getOutputStream();
FileInputStream fileInputStream = new FileInputStream("Hello.java");
InputStream inputStream = socket.getInputStream();
) {
byte[] bytes = new byte[1024];
int len;
while((len=fileInputStream.read(bytes))!=-1)
outputStream.write(bytes,0,len);
socket.shutdownOutput();//通知我已经传输完了,让服务器的“while ((len = inputStream.read(bytes)) != -1) {”循环结束
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
bytes = new byte[1024];
while((len=inputStream.read(bytes))!=-1)
byteArrayOutputStream.write(bytes,0,len);
System.out.println(byteArrayOutputStream.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
}
package 网络编程;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class TcpServer2 {
public static void main(String[] args) {
while (true)
try (ServerSocket serverSocket = new ServerSocket(9999);
Socket socket = serverSocket.accept();
InputStream inputStream = socket.getInputStream();
FileOutputStream fileOutputStream = new FileOutputStream("H.java");
OutputStream outputStream = socket.getOutputStream();
) {
System.out.println("接收了来自IP地址为"+socket.getInetAddress()+",端口号是"+socket.getPort()+"的连接");
System.out.println("本地IP地址:"+serverSocket.getInetAddress()+" 端口:"+serverSocket.getLocalPort());
byte[] bytes = new byte[1024];
int len;
while ((len = inputStream.read(bytes)) != -1) {
fileOutputStream.write(bytes,0,len);
System.out.println(new String(bytes,0,len));
}
outputStream.write("我接收完毕了,你可以断开了".getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
}
Tomcat
服务端
- 自定义S
- Tomcat服务器(也是用Java写的)S
客户端
- 自定义C
- 浏览器B
Tomcat也是java写的,lib文件夹里全是jar,bin文件夹里startup是开启,shutdown是.bat就是Windows的,.sh就是Linux的
发现很奇怪地关了startup黑窗口还是能在http:localhost:8080访问到,tasklist | findstr竟然找不到,netstat -ano才找到,发现了一个不知道怎么自动开启的tomcat.exe,关掉以后正常了
关闭开机自启:搜索栏搜服务,找Apache Tomcat
startup后黑窗口里出的是“日志”
可以自己写服务器但没必要,重点在于后台开发,用别人的服务器
UDP
发短信:不用连接,但需要知道对方的地址
DatagramPacket
数据报包类
DatagramSocket
数据报套接字,用数据报套接字可以向指定的IP、端口发送数据报包。对方不存在也不会报错。
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UdpClient {
public static void main(String[] args) throws Exception {
//新建数据报包
DatagramSocket datagramSocket = new DatagramSocket();
String msg = "你好啊,服务器!";
InetAddress localhost = InetAddress.getByName("localhost");
int port = 9090;
//
DatagramPacket datagramPacket = new DatagramPacket(msg.getBytes(), 0, msg.getBytes().length, localhost, port);
datagramSocket.send(datagramPacket);
datagramSocket.close();
}
}
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UdpServer {
public static void main(String[] args) throws IOException {
DatagramSocket datagramSocket = new DatagramSocket(9090);
byte[] bytes = new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(bytes, 0, bytes.length);
datagramSocket.receive(datagramPacket);//阻塞接收
datagramSocket.close();
System.out.println(datagramPacket.getAddress().getHostAddress());
System.out.println(new String(datagramPacket.getData()));
}
}
UDP聊天
可以用到多线程
循环接收消息代码:
package 网络编程.chat;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UdpReceiverDemo01 {
public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket(6666);
while (true) {
byte[] container = new byte[1024];
DatagramPacket packet = new DatagramPacket(container, 0, container.length);
socket.receive(packet);
byte[] data = packet.getData();
System.out.println(data.length);
System.out.println(packet.getLength());
String receiveData = new String(data,0,data.length);
receiveData = new String(data,0, packet.getLength());
System.out.println(receiveData);
if(receiveData.trim().equals("bye"))
break;
socket.receive(packet);
}
System.out.println("---------聊天结束---------");
socket.close();
}
}
package 网络编程.chat;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
public class UdpSenderDemo01 {
public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket(8888);
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
while (true) {
String s = reader.readLine();
byte[] data = s.getBytes();
DatagramPacket packet = new DatagramPacket(data, 0, data.length, new InetSocketAddress("localhost", 6666));
socket.send(packet);
if(s.equals("bye"))
break;
}
socket.close();
}
}
遇到一个问题:发送端发出了“bye”,接收端却没关闭,receiveData判断的时候需要加个trim(),trim()的作用是删除字符串的头尾空白符,看来这样输入会有尾部空白符。
问题出在packet.getData()上,byte data[] = packet.getData()
,查看源码发现该函数直接返回DatagramPacket的buf,其长度不是有效数据的长度,有效数据的长度得另用getLength()获得。
双向聊天改造:
package 网络编程.chat;
public class TalkTeacher {
public static void main(String[] args) {
new Thread(new TalkSend("localhost",8899,5555)).start();
new Thread(new TalkReceive(9966,"学生")).start();
}
}
package 网络编程.chat;
public class TalkStudent {
public static void main(String[] args) {
new Thread(new TalkSend("localhost",9966,7777)).start();
new Thread(new TalkReceive(8899,"老师")).start();
}
}
package 网络编程.chat;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class TalkReceive implements Runnable{
DatagramSocket socket = null;
private int port;
private String msgFrom;
public TalkReceive(int port, String msgFrom) {
this.msgFrom = msgFrom;
this.port = port;
try {
socket = new DatagramSocket(port);
} catch (SocketException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while (true) {
byte[] container = new byte[1024];
DatagramPacket packet = new DatagramPacket(container, 0, container.length);
try {
socket.receive(packet);
} catch (IOException e) {
e.printStackTrace();
}
byte[] data = packet.getData();
String receiveData = new String(data,0, packet.getLength());
System.out.println(msgFrom+":"+receiveData);
try {
socket.receive(packet);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
package 网络编程.chat;
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 String toIP;
private int toPort;
private int fromPort;
public TalkSend(String toIP, int toPort, int fromPort) {
this.toIP = toIP;
this.toPort = toPort;
this.fromPort = fromPort;
try {
this.socket = new DatagramSocket(fromPort);
} catch (SocketException e) {
e.printStackTrace();
}
reader = new BufferedReader(new InputStreamReader(System.in));
}
@Override
public void run() {
while (true) {
String s = null;
try {
s = reader.readLine();
} catch (IOException e) {
e.printStackTrace();
}
byte[] data = s.getBytes();
DatagramPacket packet = new DatagramPacket(data, 0, data.length, new InetSocketAddress(this.toIP, this.toPort));
try {
socket.send(packet);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
老师和学生各有一个听进程、一个说进程,这边的听进程的socket要创成那边说进程的toPort
URL
浏览器里的、文件资源管理器里的都叫URL
DNS域名解析的功能:把域名解析成IP
协议://IP地址:端口号/项目名/资源?参数
public class URLDemo {
public static void main(String[] args) throws MalformedURLException {
URL url = new URL("http://localhost:8080/helloworld/index.jsp?username=ks&password=123");
System.out.println(url.getPath());// /helloworld/index.jsp
System.out.println(url.getProtocol());// http
System.out.println(url.getHost());// localhost
System.out.println(url.getPort());// 8080
System.out.println(url.getFile());// /helloworld/index.jsp?username=ks&password=123
System.out.println(url.getQuery());// username=ks&password=123
}
}
下载网络上的文件(爬):
public class URLDemo {
public static void main(String[] args) throws MalformedURLException {
URL url = new URL("http://localhost:8080/helloworld/index.jsp?username=ks&password=123");
System.out.println(url.getPath());// /helloworld/index.jsp
System.out.println(url.getProtocol());// http
System.out.println(url.getHost());// localhost
System.out.println(url.getPort());// 8080
System.out.println(url.getFile());// /helloworld/index.jsp?username=ks&password=123
System.out.println(url.getQuery());// username=ks&password=123
}
}
有的VIP音乐可以下载下来,也许可以试试会员电影什么的