1. 网络基本含义
1.1. 计算机网络
- 计算机网络按照规模分类
- 局域网LAN
- 城域网MAN
- 广域网WAN
- 计算机网络按照传输介质分类
- 同轴电缆网络
- 双绞线网络
- 光纤网络
- 卫星网络
- 计算机网络按照拓扑结构分类
- 星形网络
- 总线网络
- 环状网络
1.2. 网络通信协议
- 数据的封装与拆封
- 封装-发送数据
- 拆封-接受数据
- 封装-发送数据
- TCP/IP协议栈
- 网络层主要协议IP协议
- 传输层主要协议TCP和UDP
1.3. TCP协议和UDP协议
- TCP:一种面向连接的、可靠的、基于字节流的运输层通信协议
- 特点
- 面向连接
- 点到点的通信
- 高可靠性:三次握手
- 占用系统资源多、效率低
- 生活案例:打电话
- 应用案例:HTTP、FTP、TELNET、SMTP
- 特点
- UDP:一种无连接传输层协议,提供面向事物的简单不可靠信息传送服务
- 特点
- 非面向连接,传输不可靠,可能丢失
- 发送不管对方是否准备好,接收方收到也不确认
- 可以广播发送
- 非常简单的协议,开销小
- 生活案例:发送短信,发电报
- 应用案例:DNS、SNMP
- 特点
1.4. IP地址和端口
- IP地址
- 分类
- IPV4: 32位地址,以点分十进制表示,如192.168.0.1
- IPV6: 128位,写成8个16位的无符号整数,每个整数用四个16进制位表示,数之间用冒号:分开
- 特殊的IP地址
- 127.0.0.1:本机地址
- 192.168.0.0–192.168.255.255:私有地址,属于非注册地址,专门为组织机构内部使用
- 分类
- 端口port
- 分类
- 公认端口:0-1023,比如80端口分配给www,21端口分配给FTP
- 注册端口:1024-49151,分配给用户进程或应用程序
- 动态/私有端口:49152–65535
- IP和端口API
- InetAddress类:封装计算机的IP地址,没有端口
- InetSocketAddress类:包含端口,用于socket通信
- 分类
- IP和端口的关系
- 必须同时指定IP地址和端口号才能正确的发送数据
- IP地址好比电话号码,端口号好比分机号
public class TestInetAddress {
public static void main(String[] args) throws UnknownHostException {
// 认识本机的IP地址
InetAddress ia = InetAddress.getLocalHost();
System.out.println(ia);
System.out.println(ia.getHostName());
System.out.println(ia.getHostAddress()); // 127.0.0.1
byte[] buf = ia.getAddress();
System.out.println(buf.length); // 4
System.out.println(Arrays.toString(buf)); // [127, 0, 0, 1]
InetAddress ia2 = InetAddress.getByName("www.baidu.com");
System.out.println(ia2); // www.baidu.com/198.18.3.73
System.out.println(ia2.getHostAddress()); // 198.18.3.73
System.out.println(Arrays.toString(ia2.getAddress())); // [-58, 18, 3, 73]
}
}
public class TestInetSocketAddress {
public static void main(String[] args) throws UnknownHostException {
InetAddress addr = InetAddress.getByName("www.baidu.com");
int port = 4321;
InetSocketAddress isa = new InetSocketAddress(addr, port);
System.out.println(isa); // www.baidu.com/198.18.3.73:4321
System.out.println(isa.getAddress()); // www.baidu.com/198.18.3.73
System.out.println(isa.getPort()); // 4321
}
}
2. TCP编程
2.1. URL统一资源定位符
- URL:统一资源定位符,由4部分组成:协议、存放资源的主机域名、端口号和资源文件名
- URL是指向互联网资源的指针
- 资源可以是简单的文件或目录,也可以是对更为复杂的对象的引用,例如对数据库或搜索引擎的查询
public class TestURL {
public static void main(String[] args) throws IOException {
URL url = new URL("https://www.google.com/#/api?name=wyb&");
System.out.println(url.getProtocol()); // https
System.out.println(url.getHost()); // www.google.com
System.out.println(url.getDefaultPort()); // 443
System.out.println(url.getPath()); // /
System.out.println(url.getQuery()); // null
System.out.println(url.getRef()); // /api?name=wyb&
System.out.println(url.getAuthority()); // www.google.com
System.out.println(url.getContent()); // sun.net.www.protocol.http.HttpURLConnection$HttpInputStream@2f490758
System.out.println(url.getFile()); // /
}
}
2.2. Socket套接字
我们开发的网络应用程序都位于应用层,TCP和UDP属于传输层协议,在应用层和传输层之间,使用套接字来进行分离
套接字就像传输层为应用层开的一个小口,应用程序通过这个小口向远程发送数据,或接收远程发来的数据;数据进入这个小口之后,如何传输,是属于网络其他层次的工作
- Socket实际是传输层供给应用层的编程接口。传输层则在网络层的基础上提供进程到进程间的逻辑通道,而应用层的进程则利用传输层向另一台主机的某一进程通信。Socket就是应用层与传输层之间的桥梁
- 使用Socket编程可以开发客户机和服务器应用程序。可以在本地网络上进行通信,也可通过Internet在全球范围内通信
2.3. TCP编程-一次单向通信
功能:实现多个用户同时登陆,分为一次单向通信、一次双向通信、传输对象、引入多线程来实现
public class LoginServer {
public static void main(String[] args) throws IOException {
// 1. 创建一个ServerSocket,负责监听客户端请求
ServerSocket serverSocket = new ServerSocket(8800);
// 2. 使用ServerSocket进行监听
Socket socket = serverSocket.accept(); // 请求未到,在此阻塞
// 3. 处理用户的请求
InputStream is = socket.getInputStream();
DataInputStream dis = new DataInputStream(is);
String result = dis.readUTF();
System.out.println("这里是服务端:" + result);
// 4. 关闭资源
dis.close();
// is.close();
// socket.close();
// serverSocket.close();
}
}
public class LoginClient {
public static void main(String[] args) throws IOException {
// 1. 创建一个Sokcet,指明服务端的IP地址和端口号
Socket socket = new Socket(InetAddress.getByName("198.18.3.73"), 8800);
// 2. 向服务器端发起一个新的请求
OutputStream os = socket.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
dos.writeUTF("username=wyb&password=xz");
// 3. 关闭资源
dos.close();
// os.close();
// socket.close();
}
}
2.4. TCP编程-一次双向通信
public class LoginServer1 {
public static void main(String[] args) throws IOException {
// 1. 创建一个ServerSocket,负责监听客户端请求
ServerSocket serverSocket = new ServerSocket(8800);
// 2. 使用serverSocket进行监听
Socket socket = serverSocket.accept(); // 请求未到,在此阻塞
// 3. 处理用户请求
InputStream is = socket.getInputStream();
DataInputStream dis = new DataInputStream(is);
String result = dis.readUTF();
System.out.println("这里是服务端:" + result);
// 4. 给出客户端响应
OutputStream os = socket.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
dos.writeUTF("您好,登陆成功");
// 5. 关闭资源
dis.close();
dos.close();
}
}
public class LoginClient1 {
public static void main(String[] args) throws IOException {
// 1. 创建一个Socket,指明服务器端的IP地址和端口号
Socket socket = new Socket(InetAddress.getByName("198.18.3.73"), 8800);
// 2. 向服务器端发起一个新的请求
OutputStream os = socket.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
dos.writeUTF("username=wyb&password=xz");
// 3. 接受来自服务器端的响应
InputStream is = socket.getInputStream();
DataInputStream dis = new DataInputStream(is);
String result = dis.readUTF();
System.out.println("这里是客户端:" + result);
// 3. 关闭资源
dis.close();
dos.close();
}
}
3. TCP编程
3.1. 编程-传输对象
在一次双向通信基础上进一步优化,在客户端输入用户名和密码,并封状到User对象中
public class User implements Serializable {
private String userId;
private String password;
public String getUserId() {
return userId;
}
}
public class LoginServer {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 1. 创建一个ServerSocket,负责监听客户端请求
ServerSocket serverSocket = new ServerSocket(8800);
// 2. 使用ServerSocket进行监听
Socket socket = serverSocket.accept(); // 请求未到,在此阻塞
// 3. 处理用户的请求
InputStream is = socket.getInputStream();
ObjectInputStream ois = new ObjectInputStream(is);
User user = (User)ois.readObject();
System.out.println("这里是服务端:" + user);
// 4. 给出客户端响应
OutputStream os = socket.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
if(user.getUserId().indexOf("wyb") >= 0) {
dos.writeUTF("您好,登陆成功");
}else {
dos.writeUTF("您好,登陆失败,请重新输入");
}
// 5. 关闭资源
ois.close();
dos.close();
}
}
public class LoginClient2 {
public static void main(String[] args) throws IOException {
// 1. 创建一个Socket,指明服务器端的IP地址和端口号
Socket socket = new Socket(InetAddress.getByName("198.18.3.73"), 8800);
// 2. 从键盘输入用户名和密码,封装到User对象中
Scanner input = new Scanner(System.in);
System.out.println("请输入用户名:");
String userId = input.next();
System.out.println("请输入密码:");
String password = input.next();
User user = new User(userId, password);
// 3. 向服务器发起一个新的请求
OutputStream os = socket.getOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeObject(user);
// 4. 接收来自服务器端的响应
InputStream is = socket.getInputStream();
DataInputStream dis = new DataInputStream(is);
String result = dis.readUTF();
System.out.println("这里是客户端:" + result);
// 5. 关闭资源
dis.close();
oos.close();
}
}
- 使用TCP实现网络登录功能总结
- 服务器创建ServerSocket,在指定端口监听并处理请求
- ServerSocket通过accept(),接收用户请求并返回对应的Socket,否则一直处于监听等待状态,线程也被阻塞】
- 客户端创建Socket,需要指定服务器的ip和端口号,向服务器发送和接收响应
- 客户端发送数据需输出流(写),客户端获取反馈数据需输入流(读)
- 服务端反馈数据需输出流(写),服务端获取请求数据需输入流(读)
- 一旦使用ServerSocket和Socket建立了网络连接后,网络通信和普通IO流操作并没有太大区别
- 网络通信输出流建议使用DataOutputStream和ObjectOutputStream,与平台无关,输入流相应使用DataInputStream和ObjectInputStream
- 如果是字符串通信,也可以使用BufferedReader和PrintWriter,简单方便
3.2. TCP编程-引入多线程
- 实际应用中,服务端一直处于运行状态,并且会出现多个用户同时登录的情况,需要服务端进行处理
- 引入多线程,服务端收到请求后,开辟一个新的线程,实现对用户请求的处理
public class LoginThread extends Thread {
private Socket socket;
public LoginThread(Socket socket) {
this.socket = socket;
}
public void run() {
ObjectInputStream ois = null;
DataOutputStream dos = null;
try {
ois = new ObjectInputStream(socket.getInputStream());
dos = new DataOutputStream(socket.getOutputStream());
// 1. 处理用户的请求
User user = (User)ois.readObject();
// 2. 给出客户端响应
if(user.getUserId().indexOf("wyb") >= 0) {
dos.writeUTF("您好,登陆成功");
}else {
dos.writeUTF("您好,登陆失败,请重新登陆");
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
// 3. 关闭资源
try {
if(ois != null) {
ois.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if(dos != null) {
dos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public class LoginServer3 {
public static void main(String[] args) throws IOException {
// 1. 创建一个ServerSocket,负责监听客户端请求
ServerSocket serverSocket = new ServerSocket(8800);
int i = 0;
while (true) {
// 2. 使用ServerSocket进行监听
Socket socket = serverSocket.accept();
// 3. 创建一个新的线程来处理请求
Thread thread = new LoginThread(socket);
thread.start();
// 4. 输出请求的次数,客户端的ip地址
String ip = socket.getInetAddress().getHostAddress();
System.out.println("这是第" + (++i) + "次访问,对方的ip地址是:" + ip);
}
}
}
4. UDP编程
- 需求:完成在线咨询功能,客户和咨询师在线交流
- 分析
- 使用基于UDP协议的Socket网络编程实现
- 不需要利用IO流实现数据的传输
- 每个数据发送单元被统一封装成数据包的方式,发送方将数据包发送到网络中,数据包在网络中去寻找他的目的地
- UDP基本概念
- DatagramSocket:用于发送或接收数据包
- DatagramPacket:封装要发送的数据和目的地的SocketAddress
4.1. UDP编程-一次单向通信
public class AskServer {
public static void main(String[] args) throws IOException {
// 创建一个Socket,指明接收数据的端口
DatagramSocket socket = new DatagramSocket(8888);
// 2. 接收数据并输出
byte[] buf = new byte[1024];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
socket.receive(packet);
System.out.println(new String(packet.getData(), 0, packet.getLength()));
// 数据长度
System.out.println(packet.getLength());
// 客户端的ip
System.out.println(packet.getAddress());
// 客户端接收数据的端口
System.out.println(packet.getPort());
// 关闭socket
socket.close();
}
}
public class AskClient {
public static void main(String[] args) throws IOException {
// 1. 创建一个Socket,【可以不指明接收数据的端口,会默认分配】
DatagramSocket socket = new DatagramSocket();
// 2. 封装数据并发送
String str = "在吗?";
byte[] buf = str.getBytes();
InetAddress address = InetAddress.getByName("198.18.3.73");
int port = 8888;
DatagramPacket packet = new DatagramPacket(buf, buf.length, address, port);
socket.send(packet);
socket.close();
}
}
4.2. UDP编程-一次双向通信
public class AskServer1 {
public static void main(String[] args) throws IOException {
// 创建一个Socket,指明接收数据的端口
DatagramSocket socket = new DatagramSocket(8888);
// 2. 接收数据并输出
byte[] buf = new byte[1024];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
socket.receive(packet);
System.out.println(new String(packet.getData(), 0, packet.getLength()));
// 3. 发送响应消息给客户端
String str = "在的";
byte[] buf1 = str.getBytes();
InetAddress address = packet.getAddress();
int port = packet.getPort();
DatagramPacket packet1 = new DatagramPacket(buf1, buf1.length, address, port);
socket.send(packet1);
// 关闭socket
socket.close();
}
}
public class AskClient1 {
public static void main(String[] args) throws IOException {
// 1. 创建一个Socket,【可以不指明接收数据的端口,会默认分配】
DatagramSocket socket = new DatagramSocket();
// 2. 封装数据并发送
String str = "在吗?";
byte[] buf = str.getBytes();
InetAddress address = InetAddress.getByName("198.18.3.73");
int port = 8888;
DatagramPacket packet = new DatagramPacket(buf, buf.length, address, port);
socket.send(packet);
// 3. 接收服务端的响应信息
byte[] buf1 = new byte[1024];
DatagramPacket packet1 = new DatagramPacket(buf1, buf1.length);
socket.receive(packet1);
System.out.println(new String(packet1.getData(), 0, packet1.getLength()));
socket.close();
}
}
4.3. UDP编程-多次双向通信
public class AskClient1 {
public static void main(String[] args) throws IOException {
// 1. 创建一个Socket,【可以不指明接收数据的端口,会默认分配】
DatagramSocket socket = new DatagramSocket();
// 2. 封装数据并发送
String str = "在吗?";
byte[] buf = str.getBytes();
InetAddress address = InetAddress.getByName("198.18.3.73");
int port = 8888;
DatagramPacket packet = new DatagramPacket(buf, buf.length, address, port);
socket.send(packet);
// 3. 接收服务端的响应信息
byte[] buf1 = new byte[1024];
DatagramPacket packet1 = new DatagramPacket(buf1, buf1.length);
socket.receive(packet1);
System.out.println(new String(packet1.getData(), 0, packet1.getLength()));
socket.close();
}
}
public class AskClient2 {
public static void main(String[] args) throws IOException {
// 1. 创建一个Socket,【可以不指明接收数据的端口,会默认分配】
DatagramSocket socket = new DatagramSocket();
Scanner input = new Scanner(System.in);
while (true) {
String str = input.nextLine();
byte[] buf = str.getBytes();
InetAddress address = InetAddress.getByName("198.18.3.73");
int port = 8888;
DatagramPacket packet = new DatagramPacket(buf, buf.length, address, port);
socket.send(packet);
if("bye".equals(str)) break;
byte[] buf1 = new byte[1024];
DatagramPacket packet1 = new DatagramPacket(buf1, buf1.length);
socket.receive(packet1);
System.out.println(new String(packet1.getData(), 0, packet1.getLength()));
}
socket.close();
}
}
public class UploadClient {
public static void main(String[] args) throws IOException {
// 创建一个Socket,指明访问哪个服务器的哪个端口
Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
// 使用Socket向服务器端上传文件
// 1. 创建一个输入流和输出流
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
bis = new BufferedInputStream(new FileInputStream(new File("/d:readme.txt")));
bos = new BufferedOutputStream(socket.getOutputStream());
// 2. 使用输入流和输出流完成文件复制
// 2.1. 准备一个中转站(水杯)
byte[] buf = new byte[1024];
int len = bis.read(buf);
while (len != -1) {
bos.write(buf, 0, len);
len = bis.read(buf);
}
// 3. 关闭输入流和输出流
}
}
文件上传–服务端
public class UploadServer {
public static void main(String[] args) throws IOException {
// 创建一个ServerSocket,可以在某个端口监听
ServerSocket serverSocket = new ServerSocket(8888);
// 使用创建的serverSocket在指定的端口监听
Socket socket = serverSocket.accept();
// 接收来自客户端的文件流,并完成将文件保存到本地
// 1. 创建一个输入流和输出流
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
bis = new BufferedInputStream(socket.getInputStream());
bos = new BufferedOutputStream(new FileOutputStream("d: readme1.txt"));
// 2. 使用输入流和输出流完成文件复制
// 2.1. 准备一个中转站(水杯)
byte[] buf = new byte[1024];
int len = bis.read(buf);
while (len != -1) {
bos.write(buf, 0, len);
len = bis.read(buf);
}
// 3. 关闭输入流和输出流
}
}