这是我学习慕课网的这个视频整理的学习笔记,在这个过程中,也查阅了其他的资料,如下所示:
笔记中的代码我都保证自己全部理解而且运行后得到了正确的结果才敢贴出来,所以正确率是有保证的。如果你在阅读过程中发现有错误,欢迎指出,大家相互交流,共同进步。
一.网络基础简介
- 网络中的计算机通过唯一的IP标识作为自己的”身份证”;
计算机之间通过协议(TCP/UDP等等)进行交流,就好像人类社会的”语言”;
TCP/IP协议
- TCP/IP是目前世界上应用最为广泛的协议,是以TCP和IP为基础的不同层次上多个协议的集合,也称:TCP/IP协议族 或 TCP/IP协议栈;
- TCP: Transmission Control Protocol (传输控制协议);
- IP: Internet Protocol (互联网协议);
- 特点:面向连接、面向字节流、全双工通信、可靠
重点解释一下面向字节流:
1.流,指的是流入到进程或从进程流出的字节或字符序列。
2.简单来说,虽然有时候要传输的数据流太大,TCP报文长度有限制,不能一次传输完,要把它分为好几个数据块,但是由于可靠性保证,接收方可以按顺序接收数据块然后重新组成分块之前的数据流,所以TCP看起来就像直接互相传输字节流一样,面向字节流。
二.Java中网络相关的API
InetAddress类
InetAddress类用于标识网络上的硬件资源,表示互联网协议(IP)地址;
InetAddrees类没有构造方法,可通过一些静态方法来获取InetAddress的实例;
一个示例:
/**
* InetAddress类
* */
public class test01 {
public static void main(String[] args) throws UnknownHostException {
//获取本机的InetAddress实例
InetAddress address = InetAddress.getLocalHost();
System.out.println("计算机名(本机):" + address.getHostName());
System.out.println("IP地址(本机): " + address.getHostAddress());
//获取字节数组形式的IP地址
byte[] bytes = address.getAddress();
System.out.println("字节数组形式的IP: " + Arrays.toString(bytes));
//直接输出InetAddress对象
System.out.println("直接输出InetAddress对象\n" + address);
//根据主机名获取InetAddress实例
InetAddress address2 = InetAddress.getByName("DESKTOP-UFEHPPP");
System.out.println("计算机名(根据主机名获得):" + address2.getHostName());
System.out.println("IP地址(根据主机名获得): " + address2.getHostAddress());
//根据IP地址获取InetAddress实例
InetAddress address3 = InetAddress.getByName("192.168.153.2");
System.out.println("计算机名(根据IP地址获得):" + address3.getHostName());
System.out.println("IP地址: " + address3.getHostAddress());
}
}
运行结果:
URL类
URL(Uniform Resource Locator)统一资源定位符,表示Internet上某一资源的地址
URL由两部分组成:协议名称和资源名称,中间用冒号隔开
在java.net包中,提供了URL类来表示URL
一个示例:
import java.net.MalformedURLException;
import java.net.URL;
/**
* URL常用方法
* */
public class test02 {
public static void main(String[] args) {
try {
//创建一个URL实例
URL google = new URL("http://www.google.com");
//根据已存在的URL创建一个新的URL
//?后面表示参数,#后面表示锚点
URL url = new URL(google,"/index.html?username=tom#test");
System.out.println("协议: " + url.getProtocol());
System.out.println("主机: " + url.getHost());
//如果未指定端口号,则使用默认的端口号,此时getport()方法返回-1
System.out.println("端口: " + url.getPort());
System.out.println("文件路径: " + url.getPath());
System.out.println("文件名: " + url.getFile());
System.out.println("相对路径: " + url.getRef());
System.out.println("查询字符串: " + url.getQuery());
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
}
运行结果:
使用URL读取网页内容
通过URL对象的
openStream()
方法可以得到指定资源的输入流通过输入流可以读取、访问网络上的数据
一个示例:
/**
* 使用URL读取网页内容
* */
public class test03 {
public static void main(String[] args) {
//创建一个URL实例
try {
URL url = new URL("http://www.baidu.com");
//通过URL的openStream()方法获取URL对象所表示的资源的字节输入流
InputStream is = url.openStream();
//将字节输入流转换为字符输入流
InputStreamReader isr = new InputStreamReader(is,"utf-8");//指定转换的编码为"utf-8"
//为字符输入流添加缓冲
BufferedReader br = new BufferedReader(isr);
String data = br.readLine();//读取数据
while(data != null) {//循环读取数据
System.out.println(data);//输出数据
data = br.readLine();
}
br.close();
isr.close();
is.close();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行结果:
这里返回的是百度的首页,我只截取了一部分。在InputStreamReader
中指定了gbk
之类的编码时,“百度一下,你就知道”等中文字符会出现乱码。
三.通过 Socket 实现编程
3-1 Socket 简介
与服务器的通信方式主要有两种,一是Http通信,一是Socket通信。
两者的最大差异在于,http连接使用的是“请求—响应方式”,即在请求时建立连接通道,当客户端向服务器发送请求后,服务器端才能向客户端返回数据。而Socket通信则是在双方建立起连接后就可以直接进行数据的传输,在连接时可实现信息的主动推送,而不需要每次由客户端向服务器发送请求。
什么是Socket?Socket又称套接字,在程序内部提供了与外界通信的端口,即端口通信。通过建立socket连接,可为通信双方的数据传输提供通道。socket的主要特点有数据丢失率低,使用简单且易于移植。
简单来说,Socket提供了程序内部与外界通信的端口并为通信双方的数据传输提供通道。
3-2 基于TCP的Socket编程
- Socket提供了两个套接字,一个连接客户端(通过Socket类),一个连接服务器(通过ServerSocket类)。
- Socket通信模型:
1.在服务器端建立ServerSocket,绑定相应的端口,并在指定的端口进行侦听;
2.在客户端创建Socket并向服务器端发送请求;
3.服务器端收到请求,接收客户端的请求信息,创建一个连接Socket来与客户端进行通信。通信过程涉及到InputStream
和OutputStream
;
4.结束通信后,服务器端和客户端都要关闭Socket及相关资源。
3-2-1 编程实现:基于TCP的Socket编程之服务器端
步骤:
1. 创建ServerSocket对象,绑定监听接口
2. 通过 accept()
方法监听客户端请求
3. 连接建立后,通过输入流读取客户端发送的请求消息
4. 通过输出流向客户端发送响应信息
5. 关闭相应资源
public class Server {
public static void main(String[] args) {
try {
//1.创建一个服务器端Socket,即ServerSocket,指定绑定的端口,并监听此端口
ServerSocket serverSocket = new ServerSocket(8888);//指定的端口建议为1024~65535
//2.调用 accept() 方法开始监听,等待客户端的连接
System.out.println("***服务器即将启动,等待客户端的连接***");
Socket socket = serverSocket.accept();//accept()获取客户端发来的Socket
//3.获取一个输入流,用来读取客户端发送的登陆信息
InputStream is = socket.getInputStream();//字节输入流
InputStreamReader isr = new InputStreamReader(is);//将字节流转换为字符流
//以缓冲的方式读取输入流
BufferedReader br = new BufferedReader(isr);//为输入流添加缓冲
String info = null;
while ((info = br.readLine()) != null) {//循环读取客户端的信息
System.out.println("我是服务器,客户端说: " + info);
}
socket.shutdownInput();//关闭输入流
//4.关闭资源
serverSocket.close();
socket.close();
is.close();
isr.close();
br.close();
} catch(IOException e) {
e.printStackTrace();
}
}
}
3-2-2 编程实现:基于TCP的Socket编程之客户端
步骤:
1. 创建Socket对象,指定服务器地址和端口
2. 获取输出流,向服务器端发送消息
3. 获取输入流,用来读取服务器端的响应
4. 关闭相应资源
public class Client {
/**客户端*/
public static void main(String[] args) {
try {
//1.创建客户端Socket,指定服务器地址和端口
Socket socket = new Socket("localhost",8888);
//2.获取输出流,向服务器端发送消息
OutputStream os = socket.getOutputStream();//获取字节输出流
PrintWriter pw = new PrintWriter(os);//将输出流包装为打印流
pw.write("用户名: admin;密码: 123" + "hahaha");
pw.flush();//刷新缓存,向服务器端发送信息
socket.shutdownOutput();//关闭输出流
//3.关闭资源
socket.close();
os.close();
pw.close();
} catch(UnknownHostException e) {
e.printStackTrace();
} catch(IOException e) {
e.printStackTrace();
}
}
}
- 运行查看效果时,要注意:服务器端必须早于客户端启动
3-2-3 完善用户登陆之服务器响应客户端
- 服务器获取客户端的数据采用输入流;
- 服务器响应客户端采用输出流,此时客户端会有一个输入流对象来接收服务器的输出流。
服务器端新建输出流来响应客户端:
public class Server {
public static void main(String[] args) {
try {
......
//4.获取输出流,响应客户端的请求
OutputStream os = socket.getOutputStream();
PrintWriter pw = new PrintWriter(os);
pw.write("欢迎您! ");
pw.flush();//调用flush()方法将缓冲输出
//5.关闭资源
......
os.close();
pw.close();
} catch(IOException e) {
e.printStackTrace();
}
}
}
客户端新建输入流来获取服务端的响应:
public class Client {
public static void main(String[] args) {
try {
......
//3.获取输入流,用来读取服务器端的响应
InputStream is = sokcet.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String infor = null;
//循环读取服务器端的信息
while ((info = br.readLine()) != null) {
System.out.println("我是客户端,服务器说: " + info);
}
//4.关闭资源
......
is.close();
br.close();
} catch(UnKnownHostException e) {
e.printStackTrace();
} catch(IOException e) {
e.printStackTrace();
}
}
}
3-2-4 使用多线程实现多客户端同服务器的通信(TCP)
基本步骤:
1.服务器端创建ServerSocket,循环调用accpet()
等待客户端连接
2.客户端创建一个Socket并请求和服务器端连接
3.服务器端接收客户端请求,创建Socket与该客户端建立专线连接
4.建立连接的两个Socket在一个单独的线程上对话
5.服务器端继续等待新的连接
在代码中看吧:
- 服务器端:
public class Server {
public static void main(String[] args) {
try {
//1.创建一个服务器端Socket,即ServerSocket,指定绑定的端口,并监听此端口
ServerSocket serverSocket = new ServerSocket(10000);//指定的端口建议为1024~65535
Socket socket = null;
int count = 0;//记录与服务器相连的客户端的个数
//2.调用 accept() 方法开始监听,等待客户端的连接
System.out.println("***服务器即将启动,等待客户端的连接***");
while(true) { //循环接收客户端发来的消息
socket = serverSocket.accept();//accept()获取客户端发来的Socket
//接收消息后,开启线程
ServerThread serverThread = new ServerThread(socket);
serverThread.start();
count ++;
System.out.println("客户端的数量:" + count);
InetAddress address = socket.getInetAddress();
System.out.println("当前客户端的 ip: " + address.getHostAddress());
}
} catch(IOException e) {
e.printStackTrace();
}
}
}
- 服务器端线程处理类:
public class ServerThread extends Thread {
//和本线程相关的Socket
Socket socket = null;
public ServerThread(Socket socket) {
this.socket = socket;
}
//线程执行的操作,响应客户端的请求
public void run() {
InputStream is = null;
InputStreamReader isr = null;
BufferedReader br = null;
OutputStream os = null;
PrintWriter pw = null;
try {
//获取一个输入流,并读取客户端消息
is = socket.getInputStream();//字节输入流
isr = new InputStreamReader(is);//将字节流转换为字符流
//以缓冲的方式读取输入流
br = new BufferedReader(isr);
String info = null;
while ((info = br.readLine()) != null) {//循环读取客户端的信息
System.out.println("我是服务器,客户端说: " + info);
}
socket.shutdownInput();//关闭输入流
//4.获取输出流,响应客户端的请求
os = socket.getOutputStream();
pw = new PrintWriter(os);
pw.write("欢迎您! ");
pw.flush();//调用flush()方法将缓冲输出
} catch(IOException e) {
e.printStackTrace();
} finally {
//5.关闭资源
try {
if(socket!=null)
socket.close();
if(os!=null)
os.close();
if(pw!=null)
pw.close();
if(isr!=null)
isr.close();
if(is!=null)
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
- 客户端不变,只需要修改一下端口号,同时,服务器端的端口号也要修改,因为在做这个笔记时,我的代码中前一个例子我没有终止,端口被占用了。
运行结果:
3-3 基于UDP的Socket编程
关于UDP协议:
UDP协议(用户数据报协议)是无连接、不可靠的、无序的
UDP以数据报作为数据传输的载体
进行数据传输时,首先需要将要传输的数据定义成数据报(Datagram),在数据报中指明数据所要达到的Socket(主机地址和端口号),然后再将数据报发送出去。
需要用到的类:
- DatagramPacket:表示数据报包
DatagramSocket:进行端到端通信的类
3-3-1 基于UDP的Socket编程之服务器端
实现步骤:
1.创建 DatagramSocket并指定端口号
2.创建 DatagramPacket
3.接收客户端发送的数据信息
4.读取数据
/**服务器端,实现基于UDP的用户登陆*/
public class UDPServer {
public static void main(String[] args) throws IOException {
//1.创建服务器端DatagramSocket,指定端口
DatagramSocket socket = new DatagramSocket(8800);
//2.创建数据报,用于接收客户端发送的数据
byte[] data = new byte[1024];//数据报中的数据保存在字节数组当中
DatagramPacket packet = new DatagramPacket(data,data.length);
//3.接收客户端发送的数据
System.out.println("服务器端已经启动,等待客户端发送数据");
socket.receive(packet);//此方法在接收到数据报之前会一直阻塞
//4.读取数据
String info = new String(data,0,packet.getLength());
System.out.println("我是服务器,客户端说: " + info);
/**向客户端响应数据*/
//1.定义客户端的地址、端口号、数据
InetAddress address = packet.getAddress();//从客户端传过来的packet中获取客户端的地址和端口
int port = packet.getPort();
byte[] data2 = "欢迎您!".getBytes();//这里直接将字符串转换为字节数组
//2.创建数据报
DatagramPacket packet2 =
new DatagramPacket(data2,data2.length,address,port);
//3.响应客户端
socket.send(packet2);
//4.关闭资源
socket.close();
}
}
3-3-2 基于UDP的Socket编程之客户端
实现步骤:
1.定义客户端的地址、端口、数据
2.创建DatagramPacket,包含将要发送的信息
3.创建DatagramSocket
4.发送数据
public class UDPClient {
public static void main(String[] args) throws IOException {
//1.定义客户端的地址、端口号、数据
InetAddress address = InetAddress.getByName("localhost");
int port = 8800;
byte[] data = "用户名:admin;密码:123".getBytes();
//2.创建数据报,包含发送的数据信息
DatagramPacket packet =
new DatagramPacket(data,data.length,address,port);
//3.创建DatagramSocket对象
DatagramSocket socket = new DatagramSocket();
//4.向服务器端发送数据
socket.send(packet);
/**接收服务器端的响应*/
//1.创建数据报,用于接收服务器响应的数据
byte[] data2 = new byte[1024];
DatagramPacket packet2 = new DatagramPacket(data2,data2.length);
//2.接收服务器响应的数据
socket.receive(packet2);
//3.读取数据
String reply = new String(data2,0,packet2.getLength());
System.out.println("我是客户端,服务器说:" + reply);
//4.关闭资源
socket.close();
}
}
运行结果:
- 服务器端:
- 客户端:
3-3-2 使用多线程实现多客户端同服务器的通信(UDP)
四. 总结
1.重点是Socket通信原理和基于TCP的Socket通信
2.多线程的优先级:
ServerThread serverThread = new ServerThread(socket);
//设置线程的优先级,范围为[1,10],默认为5
serverThread.setPriority(4);
serverThread.start();//启动线程
在实际开发中,未设置优先级可能会导致运行时速度非常慢,可降低优先级
3.对于同一个Socket,如果关闭了输出流,则与该输出流关联的Socket也会关闭,所以一般不用关闭流,直接关闭Socket即可
4.使用TCP通信传输对象
使用ObjectOutputStream
流:
5.Socket编程传递文件