网络编程
一、概述
1.计算机网络
计算机网络是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。
2.网络编程的目的
传播、交流信息,数据交换,通信。
3.网络通信的要素
如何实现网络的通信?
了解通信双方地址:
- ip
- 端口号
- 192.168.16.124(ip) : 5900(端口号)
设定通信规则:网络通信的协议
TCP/IP参考模型:
网络编程主要在传输层!
4.总结
A.网络编程中有两个主要的问题
- 如何准确的定位到网络上的一台或者多台主机
- 找到主机之后如何进行通信
B.网络编程中的要素
- IP 和 端口号
- 网络通信协议 UDP,TCP
C.万物皆对象
通过万物皆对象可知,Java中肯定有相关的类。
二、IP
ip地址:学习InetAddress类
-
ip用于唯一定位网络上的一台计算机
-
本机 (localhost)ip : 127.0.0.1
-
ip地址的分类
-
域名:为了解决记忆IP而提出!
- IP: www.baidu.com 这就是一个域名
- 记住域名,就不用记IP地址了。
ABCDE类地址(在课本里找到的)
根据不同的网络规模和用途,定义了5类地址空间。其中A、B、C类用于不同规模下普通网络的单播通信,D类地址用于IP组播通信,E类地址保留供实验使用。
为了便于确定一个IP地址属于哪个类型,分类编址方案采用了“首字母规则”,即通过IP地址的第一个字节中的标志位区分地址的类型。如果IP地址首字节的第一个比特为0,则为A类地址,依次类推。通过“首字母规则”,路由器可以快速地确定一个IP地址的网络前缀,提高分组转发效率。
简单示意图
代码
//InetAddress类没有构造器,我们可以通过静态方法来用
public class TestInetAddress {
public static void main(String[] args) throws UnknownHostException {
//这里会出现异常,抛出或try{}catch(){}解决
//得到指定字符串的IP
InetAddress inetAddress1 = InetAddress.getByName("127.0.0.1");
System.out.println(inetAddress1);
InetAddress inetAddress2 = InetAddress.getLocalHost();
System.out.println(inetAddress2);
InetAddress inetAddress3 = InetAddress.getByName("localhost");
System.out.println(inetAddress3);
//这里会有点慢
InetAddress inetAddress4 = InetAddress.getByName("www.baidu.com");
System.out.println(inetAddress4);
System.out.println(inetAddress4.getCanonicalHostName());//得到规范的ip
System.out.println(inetAddress4.getHostAddress());//ip
System.out.println(inetAddress4.getHostName());//得到域名,或自己主机的名字
}
}
三、端口
端口表示计算机上的一个程序的进程;
-
不同的进程有不同的端口号!用来区分软件!
-
范围被规定为 0~65535 之间
-
TCP,UDP : 65535 * 2(TCP:65535; UDP:65535)
单个协议下,端口号不能冲突
UDP : 80
TCP : 80
这两个端口不会冲突,因为不在同一个协议下 -
端口分类
- 公有端口 0~1023
- HTTP : 80
- HTTPS : 443
- FTP : 21
- Telent : 23
- 程序注册端口:1024~49151, 分配用户或者程序
- Tomcat : 8080
- MySQL : 3306
- Oracle :1521
- 动态、私有端口:49152~ 65535
- 常见窗口命令:
- 公有端口 0~1023
netstat -ano #查看所有的端口
netstat -ano|findstr "5900" # 查看指定的端口
tasklist|findstr "8696" #查看指定端口的进程
Ctrl+ shift + ESC #打开任务管理器
代码
学习InetSocketAddress类
public class TestInetSocketAddress {
public static void main(String[] args) {
InetSocketAddress socketAddress = new InetSocketAddress("127.0.0.1", 8080);
InetSocketAddress socketAddress2 = new InetSocketAddress("localhost", 8080);
System.out.println(socketAddress);//ip : 端口
System.out.println(socketAddress2);
System.out.println(socketAddress.getAddress());//ip
System.out.println(socketAddress.getHostName()); //地址
System.out.println(socketAddress.getPort()); //端口
}
}
四、通信协议
协议: 约定,就好比人与人之间的沟通语言的统一
网络通信协议:
TCP/IP协议簇:实际上是一组协议
重要:
- TCP : 用户传输协议
- UDP : 用户数据报协议
出名的协议:
- TCP:用户传输协议
- IP : 网络互连协议
TCP 与 UDP 对比
TCP : 如同打电话一样
-
必须建立连接,但是很稳定
-
三次握手
四次挥手
最少需要三次,保证稳定连接! A:你瞅啥? B: 瞅你咋地? A:干一场! A:我要走了! B:你真的要走了吗? B:你真的真的要走了吗? A:我的真的要走了!
-
客户端、服务端有明确的界限
-
传输完成,释放连接,效率低
UDP : 如同发短信一样
- 不用连接,但是不稳定(例如:生活中有些短信发送了,但是未被接收到)
- 客户端、服务端:没有明确的界限
- 不管有没有准备好,都可以发给你
- 就好比一场突如其来的大雨。
五、TCP——以牺牲效率为代价,换取高可靠的服务
1.服务器端接收用户端的消息
客户端
- 连接服务器 Socket
- 发送消息
public class TcpClientDemo01 {
public static void main(String[] args) {
Socket socket = null;
OutputStream os = null;
try {
//1.要知道服务器的地址
InetAddress ipAddress = InetAddress.getByName("127.0.0.1");
//2.要知道一个端口
int port = 9900;
//3.创建一个Socket,将地址和端口传进去
socket = new Socket(ipAddress, port);
//4.发送消息 需要IO流
os = socket.getOutputStream();
os.write("学习TCP".getBytes());
} catch (Exception e) {
e.printStackTrace();
}finally {
//关闭各种流
//先开后关
if(socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
服务端
- 建立服务的端口 ServerSocket
- 等待用户端的链接 accept
- 接收用户端的消息
public class TcpServerDemo01 {
public static void main(String[] args) {
ServerSocket serverSocket = null;
Socket socket = null;
InputStream is = null;
ByteArrayOutputStream outputStream = null;
try {
//1.我需要一个地址
serverSocket = new ServerSocket(9900);
//2.等待客户端连接过来
//这里的socket 与 TcpClientDemo01 中的socket一样;
socket = serverSocket.accept();
//3.读取客户端的消息
is = socket.getInputStream();
//3.1需要一个管道流
outputStream = new ByteArrayOutputStream();
//3.2 需要一个字节数组
byte[] bytes = new byte[1024];
int len;
//3.2 一般默认读取流中的数据并写人管道流;
while((len = is.read(bytes)) != -1) {
outputStream.write(bytes, 0, len);
}
System.out.println(outputStream.toString());
} catch (Exception e) {
e.printStackTrace();
}finally {
//关闭各种流
//按照先开后关的原则
if(outputStream != null) {
try {
outputStream.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();
}
}
}
}
}
注意:
1.先启动服务端,再启动用户端;
2.提升作用域;
3.不要觉的处理异常很麻烦,这是必要的。
由上图可以看出来,如果不提升各种流的作用域,在finally作用域中不存在这些流,因此需要提升作用域,才能关闭各种流。
2.服务端一直接收客户端的消息
用户端不变
服务端
public class TcpServerDemo01 {
public static void main(String[] args) {
ServerSocket serverSocket = null;
Socket socket = null;
InputStream is = null;
ByteArrayOutputStream outputStream = null;
try {
//1.我需要一个地址
serverSocket = new ServerSocket(9900);
while(true) {
//2.等待客户端连接过来
//这里的socket 与 TcpClientDemo01 中的socket一样;
socket = serverSocket.accept();
//3.读取客户端的消息
is = socket.getInputStream();
//3.1需要一个管道流
outputStream = new ByteArrayOutputStream();
//3.2 需要一个字节数组
byte[] bytes = new byte[1024];
int len;
//3.2 一般默认读取流中的数据并写人管道流;
while((len = is.read(bytes)) != -1) {
outputStream.write(bytes, 0, len);
}
System.out.println(outputStream.toString());
}
} catch (Exception e) {
e.printStackTrace();
}finally {
//关闭各种流
//按照先开后关的原则
if(outputStream != null) {
try {
outputStream.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();
}
}
}
}
}
先将服务端只启动一次,然后将用户端多次启动,如下图:
文件上传
服务端
public class TcpServerDemo02 {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(9900);
//阻塞式接收(会一直等待客户端的连接):
//就好比Scanner输入的scanner.next()一样,一直等待用户输入
Socket socket = serverSocket.accept();
//获取输入流
InputStream socketInputStream = socket.getInputStream();
//获取文件输出流
FileOutputStream fileOutputStream = new FileOutputStream(new File("receive.jpg"));
byte[] bytes = new byte[1024];
int len;
while((len = socketInputStream.read(bytes)) != -1) {
fileOutputStream.write(bytes, 0, len);
}
fileOutputStream.close();
socketInputStream.close();
serverSocket.close();
}
}
用户端
public class TcpClientDemo02 {
public static void main(String[] args) throws Exception {
//1.创建一个Socket连接
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 9900);
//2.创建一个输出流
OutputStream socketOutputStream = socket.getOutputStream();
//3.创建一个文件输入流
FileInputStream fileInputStream = new FileInputStream(new File("pic.jpg"));
//读出文件
byte[] bytes = new byte[1024];
int len;
while((len = fileInputStream.read(bytes)) != -1) {
socketOutputStream.write(bytes, 0, len);
}
fileInputStream.close();
socketOutputStream.close();
socket.close();
}
}
注意:一定要把要上传的文件放在当前项目底下,否则程序将会报错(说 xxx文件没找到)
改进
服务端
public class TcpServerDemo02 {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(9900);
//阻塞式接收(会一直等待客户端的连接):
//就好比Scanner输入的scanner.next()一样,一直等待用户输入
Socket socket = serverSocket.accept();
//获取输入流
InputStream socketInputStream = socket.getInputStream();
//获取文件输出流
FileOutputStream fileOutputStream = new FileOutputStream(new File("receive.jpg"));
byte[] bytes = new byte[1024];
int len;
while((len = socketInputStream.read(bytes)) != -1) {
fileOutputStream.write(bytes, 0, len);
}
//通知客户端:我接收完毕了
OutputStream socketOutputStream = socket.getOutputStream();
socketOutputStream.write("我接收完毕了,你可以断开连接了。".getBytes());
//关闭资源(各种流)
fileOutputStream.close();
socketInputStream.close();
serverSocket.close();
}
}
用户端
public class TcpClientDemo02 {
public static void main(String[] args) throws Exception {
//1.创建一个Socket连接
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 9900);
//2.创建一个输出流
OutputStream socketOutputStream = socket.getOutputStream();
//3.创建一个文件输入流
FileInputStream fileInputStream = new FileInputStream(new File("pic.jpg"));
//读出文件
byte[] bytes = new byte[1024];
int len;
while((len = fileInputStream.read(bytes)) != -1) {
socketOutputStream.write(bytes, 0, len);
}
//通知服务器:我发送完毕了
socket.shutdownOutput();//意思就是告诉服务器我已经发送完了
//接收服务端的信息
InputStream socketInputStream = socket.getInputStream();
//需要一个管道流
ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream();
byte[] bytes2 = new byte[1024];
int len2;
while((len2 = socketInputStream.read(bytes2)) != -1) {
arrayOutputStream.write(bytes2, 0, len2);
}
System.out.println(arrayOutputStream.toString());
//关闭资源(各种流)
arrayOutputStream.close();
socketInputStream.close();
fileInputStream.close();
socketOutputStream.close();
socket.close();
}
}
运行现象:
当用户端收到“我接收完毕了,你可以断开连接了。”这句话时,文件就上传成功了。如下图:
六、UDP——无连接的高效数据报传输
发短信:不用连接,但是需要知道对方的地址!
发送消息
发送端
//DatagramSocket类
//DatagramPacket类
public class UdpClientDemo1 {
public static void main(String[] args) throws Exception {
//建立一个socket
DatagramSocket socket = new DatagramSocket();
//设置要发送的信息
String msg = "你好!服务器";
//建立IP和端口
InetAddress localhost = InetAddress.getByName("localhost");
int port = 9900;
//建立一个包
//参数依次是:数据(字节数组类型),数据起始位置,数据的结束位置,发送给谁(目标),端口
DatagramPacket packet = new DatagramPacket(msg.getBytes(), 0, msg.getBytes().length, localhost, port);
//发送包
socket.send(packet);
//关闭流
socket.close();
}
}
接收端
public class UdpServerDemo1 {
public static void main(String[] args) throws Exception {
//开放端口
DatagramSocket socket = new DatagramSocket(9900);
//接收数据包
byte[] buffer = new byte[1024];
//参数:容器,起始位置,末位置
DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length);
socket.receive(packet);//阻塞接收
System.out.println(packet.getAddress().getHostAddress());
System.out.println(new String(packet.getData(),0,packet.getLength()));
//关闭连接
socket.close();
}
}
先启动接收端,否则,发送端将会报错(说要访问的端口不能隐藏)。正确运行结果如下:
咨询
这里不分服务器端和用户端,只区分发送端和接收端
单向单咨询
发送端
public class UdpSenderDemo1 {
public static void main(String[] args) throws Exception {
//准备一个socket
DatagramSocket socket = new DatagramSocket(9999);
//控制台读取数据
BufferedReader datas = new BufferedReader(new InputStreamReader(System.in));
//按行读取数据
String data = datas.readLine();
//参数:前三个不变,后面为一个InetSocketAddress类的对象实例
DatagramPacket packet = new DatagramPacket(data.getBytes(),
0, data.getBytes().length,
new InetSocketAddress("localhost", 9900));
socket.send(packet);
socket.close();
}
}
接收端
public class UdpReceiveDemo1 {
public static void main(String[] args) throws Exception {
//开放端口
DatagramSocket socket = new DatagramSocket(9900);
//准备一个字节数组
byte[] bytes = new byte[1024];
//建包
DatagramPacket packet = new DatagramPacket(bytes, 0, bytes.length);
socket.receive(packet);//阻塞式接收包裹
byte[] data = packet.getData();
String receiveData = new String(data, 0, data.length);
//打印接收的数据
System.out.println(receiveData);
socket.close();;
}
}
单向可多次咨询
发送端
public class UdpSenderDemo2 {
public static void main(String[] args) throws Exception {
//准备一个socket
DatagramSocket socket = new DatagramSocket(9999);
//控制台读取数据
BufferedReader datas = new BufferedReader(new InputStreamReader(System.in));
while(true) {
//按行读取数据
String data = datas.readLine();
//参数:前三个不变,后面为一个InetSocketAddress类的对象实例
DatagramPacket packet = new DatagramPacket(data.getBytes(),
0, data.getBytes().length,
new InetSocketAddress("localhost", 9900));
socket.send(packet);
if(data.equals("bye")) {
break;
}
}
socket.close();
}
}
接收端
public class UdpReceiveDemo2 {
public static void main(String[] args) throws Exception {
//开放端口
DatagramSocket socket = new DatagramSocket(9900);
while(true) {
//准备一个字节数组
byte[] bytes = new byte[1024];
//建包
DatagramPacket packet = new DatagramPacket(bytes, 0, bytes.length);
socket.receive(packet);//阻塞式接收包裹
byte[] data = packet.getData();
String receiveData = new String(data, 0, data.length);
//打印接收的数据
System.out.println(receiveData);
if (receiveData.equals("bye")){
break;
}
}
socket.close();;
}
}
实现双方相互咨询(需要用到多线程)
发送端
//需要提升作用域
public class ChatSender implements Runnable {
DatagramSocket socket = null;
BufferedReader datas = null;
private int myPort;
private int toPort;
private String localHost;
public ChatSender(int myPort, int toPort, String localHost) {
this.myPort = myPort;
this.toPort = toPort;
this.localHost = localHost;
try {
socket = new DatagramSocket(this.myPort);
datas = new BufferedReader(new InputStreamReader(System.in));
} catch (SocketException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while(true) {
//按行读取数据
String data = null;
try {
data = datas.readLine();
//参数:前三个不变,后面为一个InetSocketAddress类的对象实例
DatagramPacket packet = new DatagramPacket(data.getBytes(),
0, data.getBytes().length,
new InetSocketAddress(this.localHost , this.toPort));
socket.send(packet);
if(data.equals("bye")) {
break;
}
} catch (IOException e) {
e.printStackTrace();
}
}
socket.close();
}
}
接收端
//需要提升作用域
public class ChatReceiver implements Runnable {
DatagramSocket socket = null;
private int port;
private String name;
public ChatReceiver(int fromPort, String name) {
this.port = fromPort;
this.name = name;
try {
socket = new DatagramSocket(this.port);
} catch (SocketException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while(true) {
try {
//准备一个字节数组
byte[] bytes = new byte[1024];
//建包
DatagramPacket packet = new DatagramPacket(bytes, 0, bytes.length);
socket.receive(packet);//阻塞式接收包裹
byte[] data = packet.getData();
String receiveData = new String(data, 0, data.length);
//打印接收的数据
System.out.println(this.name + ":" + receiveData);
if (receiveData.equals("bye")){
break;
}
} catch (IOException e) {
e.printStackTrace();
}
}
socket.close();
}
}
测试
学生端
//学生给老师发送消息
public class Student {
public static void main(String[] args) {
new Thread(new ChatSender(4444, 9900, "localhost")).start();
new Thread(new ChatReceiver(5555, "老师")).start();
}
}
老师端
//老师给学生发送消息
public class Teacher {
public static void main(String[] args) {
new Thread(new ChatSender(1111, 5555, "localhost")).start();
new Thread(new ChatReceiver(9900, "学生")).start();
}
}
注意:
1.学生端的接收端口要与老师端的发送端口一致;
2.老师端的接收端口要与学生端的发送端口一致;
3.学生端和老师端可以任意规定自身从哪个端口发送信息。
七、URL
统一资源定位符:定位资源的,定位互联网上的某一个资源
格式:
协议://ip地址:端口/项目名/资源
如:
"http://localhost:8080/helloworld/index.jsp?username=kuangshen&password=123"
测试代码(很简单,会用就好)
import java.net.MalformedURLException;
import java.net.URL;
import java.time.Year;
public class URLDemo01 {
public static void main(String[] args) throws MalformedURLException {
//传递一个完整的域名就好
URL url = new URL("http://localhost:8080/helloworld/index.jsp?username=kuangshen&password=123");
System.out.println(url.getProtocol()); //协议
System.out.println(url.getHost()); //主机ip
System.out.println(url.getPort()); //端口
System.out.println(url.getPath()); //文件
System.out.println(url.getFile()); //全路径
System.out.println(url.getQuery()); //参数
}
}
下载资源
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public class UrlDown {
public static void main(String[] args) throws Exception {
//1. 下载地址(随便写一个完整地址)
URL url = new URL("https://m10.music.126.net/20191201174818/c09b1932384617e535421702c26ccc5c/yyaac/0708/0652/0508/0b9b6827b718aa223af92bd52aa2424f.m4a");
//2. 连接到这个资源 HTTP
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
//得到一个可以读取的文件流
InputStream inputStream = urlConnection.getInputStream();
//得到一个可以写入保存的文件流
FileOutputStream fos = new FileOutputStream("f.m4a");
byte[] buffer = new byte[1024];
int len;
while ((len=inputStream.read(buffer))!=-1){
fos.write(buffer,0,len); //写出这个数据
}
fos.close();
inputStream.close();
urlConnection.disconnect(); //断开连接
}
}