Java是 Internet 上的语言,它从语言级上提供了对网络应用程序的支持,程序员能够很容易开发常见的网络应用程序
Java提供的网络类库,可以实现无痛的网络连接,联网的底层细节被隐藏在 Java 的本机安装系统里,由 JVM 进行控制。并且 Java 实现了一个跨平台的网络库,程序员面对的是一个统一的网络编程环境
🌔 1、与网络相关的软件架构包括哪些?
C/S架构(Client / Server):指客户端和服务器结构 【QQ、美团等】
B/S架构(Browser / Server):指浏览器和服务器结构【IE、谷歌等】
网络编程的作用就是 在一定的协议下,实现两台计算机通信的程序
上面的两种架构都离不开网络的支持
🌔 2、什么是计算机网络?
🌔 3、网络编程的目的是什么?
🌔 4、网络编程中有三个主要的问题?
🌔 1、什么是IP地址呢?
🌔 2、IP地址的第一种分类方式:
- IP地址 = 网络地址 + 主机地址
- 网络地址:标识计算机或网络设备所在的网段
- 主机地址:标识特定主机或网络设备
🌔 3、IP地址的第二种分类方式:
🌔 4、介绍几个最基础的常用命令:
ping IP地址
可以检测本机和目标地址之间是否可以连通🌔 5、有两个特殊的IP地址
🌔 6、在Internet上的主机有两种方式表示地址:
🌔 7、什么是域名解析呢?
在浏览器里解析一个域名的完整流程
网络的通信,本质上是两个进程(应用程序)的通信。每台计算机都有很多的进程,那么在网络通信时,如何区分这些进程呢?
端口号:用两个字节表示的整数,它的取值范围是0-65535
如果端口号被另一个服务或应用程序所占用,会导致当前程序启动失败
网络通信协议:在计算机网络中,这些连接和通信的规则被称为网络通信协议
如何实现如此复杂的网络协议呢?
有两套参考模型:
应用层:决定了向用户提供应用服务时通信的活动 【HTTP协议、FTP协议、SNMP(简单网络管理协议)、SMTP(简单邮件传输协议)和POP3】
传输层:主要使网络程序进行通信,在进行网络通信时,可以采用TCP协议或UDP协议
网络层:是协议的核心,支持网间互联的数据通信,主要用于将传输的数据进行分组,将分组数据发送到目标计算机或网络
物理+数据链路层:链路层是用于定义物理传输通道,通常是对某些网络连接设备的驱动协议 【光纤、网线提供的驱动】
🌔 1、我们需要知道这两个协议分别代表什么?
TCP协议:
TCP协议进行通信的两个应用进程:客户端、服务端
使用TCP协议之前,需要先建立TCP连接,形成基于字节流的数据传输通道
传输前,采用“三次握手”方式,点对点通信,是可靠的传输
在连接中可进行 大数据量的传输
传输完毕,需要释放已建立的连接,效率较低 【打电话】
UDP协议:
🌔 2、上面提及的TCP三次握手指的是什么?
1、客户端会随机一个初始序列号seq=x,设置SYN=1 ,表示这是SYN握手报文。然后就可以把这个 SYN 报文发送给服务端了,表示向服务端发起连接,之后客户端处于同步已发送状态。
2、服务端收到客户端的 SYN 报文后,也随机一个初始序列号(seq=y),设置ack=x+1,表示收到了客户端的x之前的数据,希望客户端下次发送的数据从x+1开始。
设置 SYN=1 和 ACK=1。表示这是一个SYN握手和ACK确认应答报文。最后把该报文发给客户端,该报文也不包含应用层数据,之后服务端处于同步已接收状态。
3、客户端收到服务端报文后,还要向服务端回应最后一个应答报文,将ACK置为 1 ,表示这是一个应答报文
ack=y+1 ,表示收到了服务器的y之前的数据,希望服务器下次发送的数据从y+1开始。
最后把报文发送给服务端,这次报文可以携带数据,之后客户端处于 连接已建立 状态。服务器收到客户端的应答报文后,也进入连接已建立状态。
🌔 3、TCP的四次握手代表什么呢?
1、客户端打算断开连接,向服务器发送FIN报文(FIN标记位被设置为1,1表示为FIN,0表示不是),FIN报文中会指定一个序列号,之后客户端进入FIN_WAIT_1状态。也就是客户端发出连接释放报文段(FIN报文),指定序列号seq = u,主动关闭TCP连接,等待服务器的确认。
2、服务器收到连接释放报文段(FIN报文)后,就向客户端发送ACK应答报文,以客户端的FIN报文的序列号 seq+1 作为ACK应答报文段的确认序列号ack = seq+1 = u + 1。接着服务器进入CLOSE_WAIT(等待关闭)状态,此时的TCP处于半关闭状态(下面会说什么是半关闭状态),客户端到服务器的连接释放。客户端收到来自服务器的ACK应答报文段后,进入FIN_WAIT_2状态。
3、服务器也打算断开连接,向客户端发送连接释放(FIN)报文段,之后服务器进入LASK_ACK(最后确认)状态,等待客户端的确认。服务器的连接释放(FIN)报文段的FIN=1,ACK=1,序列号seq=m,确认序列号ack=u+1。
4、客户端收到来自服务器的连接释放(FIN)报文段后,会向服务器发送一个ACK应答报文段,以连接释放(FIN)报文段的确认序号 ack 作为ACK应答报文段的序列号 seq,以连接释放(FIN)报文段的序列号 seq+1作为确认序号ack。
之后客户端进入TIME_WAIT(时间等待)状态,服务器收到ACK应答报文段后,服务器就进入CLOSE(关闭)状态,到此服务器的连接已经完成关闭。客户端处于TIME_WAIT状态时,此时的TCP还未释放掉,需要等待2MSL后,客户端才进入CLOSE状态。
🌔 1、InetAddress类
该类表示IP地址,有两个子类:Inet4Address、Inet6Address
该类没有提供公共的构造器,而是提供了几个静态方法来获取 InetAddress 实例
该类提供了几个常用的方法:
我们通过代码来演示如何通过这几个静态方法获取到InetAddress实例
package com.zwh.shangguigu.netcoding_;import java.net.InetAddress;
import java.net.UnknownHostException;/*** @author Bonbons* @version 1.0* 演示获取InetAddress实例的几种方式*/
public class InetAddressTest {public static void main(String[] args) {try{//1、获取本地主机InetAddress localHost = InetAddress.getLocalHost();System.out.println("本地主机: " + localHost);//2、根据域名获取InetAddress baidu = InetAddress.getByName("www.baidu.com");System.out.println("baidu: " + baidu);//3、根据地址获取InetAddress baidu2 = InetAddress.getByAddress(new byte[]{(byte)202, 108, 22, 5});System.out.println("baidu2: " + baidu2);}catch (UnknownHostException e){e.printStackTrace();}}
}
🌔 2、Socket类
流套接字(steam socket):使用TCP提供可以来的字节流服务
数据报套接(datagram socket):使用UDP提供尽力而为的数据报服务
🌔 3、服务器套接字的相关API
🌔 4、Socket类的相关API 【客户端套接字】
(1)Socket类的常用构造方法:【区别在于一个利用IP地址,一个利用域名】
(2)Socket 类的常用方法
我们需要知道:
🌔 4、DatagramSocket类的常用方法
public DatagramSocket(int port):创建数据报套接字并将其绑定到本地主机上的指定端口
public DatagramSocket(int port, InetAddress laddr):创建数据报套接字,将其绑定到指定的本地地址
public void close(): 关闭此数据报套接字
public void sent(DatagramPacket p):从此套接字发送数据报包
public void receive(DatagramPacket p): 从套接字接受数据报包,当此方法返回时,DatagramPacket 的缓冲区填充了接受的数据
public InetAddress getLocalAddress(): 获取套接字绑定的本地地址
public int getLocalPort(): 返回此套接字绑定的本地主机上的端口号
public InetAddress getInetAddress(): 返回此套接字连接的地址【未连接返回 null】
public int getPort(): 返回此套接字的端口,未连接返回-1
🌔 5、DatagramPacket类的常用方法
🌔 1、首先先介绍一下TCP编程的通信模型:
🌔 2、客户端程序和服务器程序的开发步骤:
(1)客户端程序的开发步骤:
(2)服务器程序的开发步骤:
🌔 3、案例一:演示单个客户端与服务器单次通信
需求:客户端连接服务器,连接成功后给服务器发送"lalala",服务器收到消息后,给客户端返回"欢迎登录",客户端收到消息后,断开链接
package com.zwh.shangguigu.netcoding_;import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;/*** @author Bonbons* @version 1.0* 单个客户端与服务器单次通信演示*/
//服务器端代码
public class Server {public static void main(String[] args) {try{//1、创建ServerSocket对象,绑定到8888端口ServerSocket server = new ServerSocket(8888);System.out.println("等待连接...");//2、在8888端口监听客户端的连接请求,该方法是个阻塞方法,没有客户端连接一直处于阻塞状态Socket socket = server.accept();InetAddress inetAddress = socket.getInetAddress();System.out.println(inetAddress.getHostAddress() + "客户端连接成功!");//3、获取输入流,用来接受客户端发送给服务器的数据InputStream input = socket.getInputStream();//字节数组暂存数据byte [] data = new byte[1024];StringBuilder s = new StringBuilder();int len;while((len = input.read(data)) != -1){s.append(new String(data, 0, len));}System.out.println(inetAddress.getHostAddress() + "客户端发送的消息是: " + s);//4、获取输出流,用来发送数据给该客户端OutputStream out = socket.getOutputStream();//发送数据[转换成字节,因为用的是字节输出流]out.write("欢迎登录".getBytes());//刷新缓存out.flush();//5、关闭socket,不再与客户端通信,socket的输入、输出流也一起关闭了socket.close();//6、如果不再接受任何客户端通信,可以关闭服务器端程序server.close();}catch (IOException e){e.printStackTrace();}}
}//客户端代码
class Client{public static void main(String[] args) {try{//1、准备Socket,连接服务器,需要指定服务器的IP地址和端口号Socket socket = new Socket("127.0.0.1", 8888);//2、获取输出流,用来发送数据给服务器OutputStream out = socket.getOutputStream();//发送数据out.write("lalala".getBytes());//要在流末尾写一个 "流的末尾" 的标记,对方才能读到-1,否则对方的读取方法会一直阻塞socket.shutdownOutput();//3、获取输入流,用来接收服务器发送给客户端的数据InputStream input = socket.getInputStream();//接收数据byte [] data = new byte[1024];StringBuilder s = new StringBuilder();int len;while((len = input.read(data)) != -1){s.append(new String(data, 0, len));}System.out.println("服务器返回的消息是: " + s);//4、关闭socket,不再与服务器通信socket.close();}catch (IOException e){e.printStackTrace();}}
}
🌔 4、演示多个客户端与服务器之间的多次通信
通常情况下,服务器不应该只接受一个客户端请求,而应该不断地接受来自客户端的所有请求,所以Java程序通常会通过循环,不断地调用ServerSocket的accept()方法
如果服务器端要“同时”处理多个客户端的请求,就要为每个客户端单独分配一个进程来处理,否则无法实现“同时”
装饰者设计模式:不管底层IO流是怎样的节点流(文件流、网路Socket产生的流等),程序都可以将其包装成处理流,甚至可以多层包装,从而提供更多方便的处理
案例需求:多个客户端连接服务器,并进行多次通信
- 每一个客户端连接成功后,从键盘输入英文单词或中国成语,并发送给服务器
- 服务器收到客户端的消息后,把词语“反转”后给客户端
- 客户端接收服务器返回的"词语",打印显示
- 当客户端输入 "stop" 时断开与服务器的连接
- 多个客户端可以同时给服务器发送“词语”,服务器可以“同时”处理多个客户端的请求
package com.zwh.shangguigu.netcoding_;import java.lang.String;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;/*** @author Bonbons* @version 1.0* 演示多个客户端与服务器之间的多次通信*/
public class Server2 {public static void main(String[] args) throws IOException {//1、准备ServerSocketServerSocket server = new ServerSocket(8888);System.out.println("等待连接...");int count = 0;while(true){//2、监听一个客户端的连接Socket socket = server.accept();System.out.println("第" + ++count + "个客户端" + socket.getInetAddress().getHostAddress() + "连接成功!");ClientHandlerThread ct = new ClientHandlerThread(socket);ct.start();}//永远监听是否有客户端的请求}static class ClientHandlerThread extends Thread{private Socket socket;private String ip;public ClientHandlerThread(Socket socket){super();this.socket = socket;ip = socket.getInetAddress().getHostAddress();}@Overridepublic void run() {try{//(1)获取输入流,用来接受该客户端发送给服务器的数据BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));//(2)获取输出流,用来发送数据给该客户端PrintStream ps = new PrintStream(socket.getOutputStream());//(3)接收数据String str;while((str = br.readLine()) != null){//反转服务器得到的"成语"StringBuilder word = new StringBuilder(str);word.reverse();//返回给客户端ps.println(word);}System.out.println("客户端" + ip + "正常退出");}catch (IOException e){System.out.println("客户端" + ip + "意外退出");
// e.printStackTrace();}finally {try{//(6)断开连接释放资源socket.close();}catch (IOException e){e.printStackTrace();}}}}
}
//客户端程序
class Client2{public static void main(String[] args) throws IOException{//1、准备Socket,连接服务器,需要指定服务器的IP地址和端口号Socket socket = new Socket("127.0.0.1", 8888);//2、获取输出流,用来发送数据给服务器OutputStream out = socket.getOutputStream();PrintStream ps = new PrintStream(out);//3、获取输入流,用来接受服务器发送给客户端的数据InputStream input = socket.getInputStream();BufferedReader br;if(args != null && args.length > 0){String encoding = args[0];//下面encoding这里报错,无法解析,但是我换成指定的字符集就没问题 >> 解决了,是我原来导入的String不对br = new BufferedReader(new InputStreamReader(input, encoding));}else{br = new BufferedReader(new InputStreamReader(input));}Scanner scanner = new Scanner(System.in);while(true){System.out.println("输入发送给服务器的单词或成语: ");String message = scanner.nextLine();if(message.equals("stop")){socket.shutdownOutput();break;}//4、发送数据ps.println(message);//接收数据String feedback = br.readLine();System.out.println("从服务器收到的反馈是: " + feedback);}//5、关闭socket,断开与服务器的连接scanner.close();socket.close();}
}
🌔 5、TCP网络编程经典案例:聊天室
package com.zwh.shangguigu.netcoding_;import com.sun.xml.internal.ws.api.handler.MessageHandler;import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Scanner;/*** @author Bonbons* @version 1.0*/
public class TestChatServer {//存储所有在线的客户端static ArrayList online = new ArrayList<>();public static void main(String[] args) throws IOException {//1、启动服务器,绑定端口号ServerSocket server = new ServerSocket(8989);//2、接收多个客户端同时连接while(true){Socket accept = server.accept();//把新连接的客户端添加到online列表中online.add(accept);//自定义的线程,用来给每个客户端分配一个线程,为了同时处理请求MessageHandler mh = new MessageHandler(accept);mh.start();}}static class MessageHandler extends Thread{private Socket socket;private String ip;public MessageHandler(Socket socket){super();this.socket = socket;}@Overridepublic void run() {try{//获取客户端的ipip = socket.getInetAddress().getHostAddress();//给其他客户转发"我上线了"sendToOther(ip + "上线了");//接收客户端发送的消息InputStream input = socket.getInputStream();//字符流到字节流的桥梁InputStreamReader reader = new InputStreamReader(input);BufferedReader br = new BufferedReader(reader);String str;while((str = br.readLine()) != null){sendToOther(ip + ":" + str);}sendToOther(ip + "下线了");}catch (IOException e){try{sendToOther(ip + "掉线了");}catch (IOException e){e.printStackTrace();}}finally {online.remove(socket);}}//我们封装一个方法用于给给其他客户端转发xxx信息public void sendToOther(String message) throws IOException{//遍历所有在线的客户端,一一转发for(Socket on : online){OutputStream every = on.getOutputStream();//使用PrintStream的目的是为了使用它的println方法按行打印PrintStream ps = new PrintStream(every);ps.println(message);}}}
}//客户端
class TestChatClient{public static void main(String [] args) throws Exception{//1、连接服务器Socket socket = new Socket("127.0.0.1", 8989);//2、开启两个线程//(1)线程1负责接收转发的消息[xxx上线、掉线、下线]Receive receive = new Receive(socket);receive.start();//(2)线程2负责发送自己的消息Send send = new Send(socket);send.start();//等待我发送线程结束了才会结束整个程序 [join() 等待结束]send.join();socket.close();}
}//接收消息
class Receive extends Thread{private Socket socket;public Receive(Socket socket){super();this.socket = socket;}@Overridepublic void run() {try{InputStream inputStream = socket.getInputStream();Scanner input = new Scanner(inputStream);while(input.hasNextLine()){String line = input.nextLine();System.out.println(line);}}catch (IOException e){e.printStackTrace();}}
}//发送消息
class Send extends Thread{private Socket socket;public Send(Socket socket){super();this.socket = socket;}@Overridepublic void run() {try{OutputStream outputStream = socket.getOutputStream();PrintStream ps = new PrintStream(outputStream);Scanner input = new Scanner(System.in);while(true){System.out.println("自己的话: ");String str = input.nextLine();if("bye".equals(str)){break;}ps.println(str);}input.close();}catch (IOException e){e.printStackTrace();}}
}
🌔 1、UDP网络编程的通信模型是什么?
UDP协议是一种面向非连接的协议,指的是在正式通信前不比与对方先建立连接,不管对方状态就直接发送,也不在乎对方是否接收到了完整的数据,UDP协议无法控制,是一种不可靠的协议
UDP协议是面向数据报文的信息传送服务,在发送端没有缓冲区 。对于应用层交付下来的报文在添加了首部之后就直接交付于ip层,不会进行合并,也不会进行拆分,而是一次交付一个完整的报文
UDP协议没有拥塞控制,所以当网络出现的拥塞不会导致主机发送数据的速率降低。虽然UDP的接收端有缓冲区,但是这个缓冲区只负责接收,并不会保证UDP报文的到达顺序是否和发送的顺序一致。因为网络传输的时候,由于网络拥塞的存在是很大的可能导致先发的报文比后发的报文晚到达。如果此时缓冲区满了,后面到达的报文将直接被丢弃
🌔 2、UDP网络编程的开发步骤:
(1)发送端程序的开发步骤
(2)接收端程序的开发步骤:
🌔 3、演示使用UDP网络编程发送和接收消息
基于UDP协议的网络编程仍然需要在通信实例的两端各建立一个Socket,但这两个Socket之间并没有虚拟链路,这两个Socket只是发送、接收数据报的对象,Java提供了DatagramSocket对象作为基于UDP协议的Socket,使用DatagramPacket代表DatagramSocket发送、接收的数据报
(1)最基础的发送端和接收端核心代码
DatagramSocket ds = null;
try{ds = new DatagramSocket();byte [] by = "hello,www.baidu.com".getBytes();DatagramPacket dp = new Datagram(by, 0, by.length, InetAddress.getByName("127.0.0.1"), 10000);ds.send(dp);
}catch(Exception e){e.printStackTrace();
}finally{if(ds != null){ds.close();}
}
DatagramSocket ds = null;
try{ds = new DatagramSocket(10000); byte [] by = new byte[1024 * 64];DatagramPacket dp = new DatagramPacket(dp.getData(), 0, dp.getLength());System.out.println(str + "--" + dp.getAddress());
}catch(Exception e){e.printStackTrace();
}finally{if(ds != null){ds.close();}
}
(2)案例二:完整代码
package com.atguigu.udp;import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.ArrayList;public class Send {public static void main(String[] args)throws Exception {
// 1、建立发送端的DatagramSocketDatagramSocket ds = new DatagramSocket();//要发送的数据ArrayList all = new ArrayList();all.add("尚硅谷让天下没有难学的技术!");all.add("学高端前沿的IT技术来尚硅谷!");all.add("尚硅谷让你的梦想变得更具体!");all.add("尚硅谷让你的努力更有价值!");//接收方的IP地址InetAddress ip = InetAddress.getByName("127.0.0.1");//接收方的监听端口号int port = 9999;//发送多个数据报for (int i = 0; i < all.size(); i++) {
// 2、建立数据包DatagramPacketbyte[] data = all.get(i).getBytes();DatagramPacket dp = new DatagramPacket(data, 0, data.length, ip, port);
// 3、调用Socket的发送方法ds.send(dp);}// 4、关闭Socketds.close();}
}
package com.atguigu.udp;import java.net.DatagramPacket;
import java.net.DatagramSocket;public class Receive {public static void main(String[] args) throws Exception {
// 1、建立接收端的DatagramSocket,需要指定本端的监听端口号DatagramSocket ds = new DatagramSocket(9999);//一直监听数据while(true){//2、建立数据包DatagramPacketbyte[] buffer = new byte[1024*64];DatagramPacket dp = new DatagramPacket(buffer,buffer.length);//3、调用Socket的接收方法ds.receive(dp);//4、拆封数据String str = new String(dp.getData(),0,dp.getLength());System.out.println(str);}// ds.close();}
}
URL(Uniform Resource Locator):统一资源定位符,表示 Internet 上某一资源的地址
通过URL我们可以访问互联网上的各种资源【www, ftp站点】
URL 的基本结构由5部分组成:
传输协议
主机名
端口号
文件名
片段名?参数列表
<传输协议>://<主机名>:<端口号>/<文件名>#片段名?参数列表
例如:http://192.168.1.100:8080/helloworld/index.jsp#a?username=shkstart&password=123
传输协议为:http
主机名为:192.168.1.100
端口号:8080
文件名:helloworld/index.jsp
片段名:a
参数列表:username=shkstart&password=123 【参数名=参数值】
Java中 java.net 中实现了类URL,可以通过构造器初始化URL对象
Url url = new Url("http://www.baidu.com")
URL downloadUrl = new URL(url, "download.html");
URL url = new URL("http", "www.atguigu.com", “download. html");
URL gamelan = new URL("http", "www.atguigu.com", 80, “download.html");
URL类的构造器都声明抛出非运行时异常,必须要对这一异常进行处理,通常是用 try-catch 语句进行捕获。
🌔 1、URL类的常用方法:
🌔 2、针对HTTP协议的URLConnection类
URL的方法 openStream():能从网络上读取数据
若希望输出数据,例如向服务器端的 CGI (公共网关接口-Common Gateway Interface-的简称,是用户浏览器和服务器端的应用程序进行连接的接口)程序发送一些数据,则必须先与URL建立连接,然后才能对其进行读写,此时需要使用 URLConnection
URLConnection:表示到URL所引用的远程对象的连接。当与一个URL建立连接时,首先要在一个 URL 对象上通过方法 openConnection() 生成对应的 URLConnection 对象。如果连接过程失败,将产生IOException.
通过URLConnection对象获取的输入流和输出流,即可以与现有的CGI程序进行交互。
总结:
位于网络中的计算机具有唯一的IP地址,这样不同的主机可以互相区分。
客户端-服务器是一种最常见的网络应用程序模型。服务器是一个为其客户端提供某种特定服务的硬件或软件。客户机是一个用户应用程序,用于访问某台服务器提供的服务。端口号是对一个服务的访问场所,它用于区分同一物理计算机上的多个服务。套接字用于连接客户端和服务器,客户端和服务器之间的每个通信会话使用一个不同的套接字。TCP协议用于实现面向连接的会话。
Java 中有关网络方面的功能都定义在 java.net 程序包中。Java 用 InetAddress 对象表示 IP 地址,该对象里有两个字段:主机名(String) 和 IP 地址(int)。
类 Socket 和 ServerSocket 实现了基于TCP协议的客户端-服务器程序。Socket是客户端和服务器之间的一个连接,连接创建的细节被隐藏了。这个连接提供了一个安全的数据传输通道,这是因为 TCP 协议可以解决数据在传送过程中的丢失、损坏、重复、乱序以及网络拥挤等问题,它保证数据可靠的传送。
类 URL 和 URLConnection 提供了最高级网络应用。URL 的网络资源的位置来同一表示 Internet 上各种网络资源。通过URL对象可以创建当前应用程序和 URL 表示的网络资源之间的连接,这样当前程序就可以读取网络资源数据,或者把自己的数据传送到网络上去