Java网络编程

Java网络编程

计算机网络就是通过传输介质、通信设施和网络协议,把分散在不同地点的计算设备互连起来,实现资 源共享和数据传输的系统。

TCP/IP协议簇

TCP/IP协议栈是一系列网络协议的总和,是构成网络通信的核心骨架。

分层模型

TCP/IP协议栈的分层模型常见的有2个,分别是TCP/IP参考模型和ISO组织提出的OSI参考模型。在 TCP/IP参考模型中将网络分为网络访问层【数据链路层】、互联网层【网络层】、传输层、应用层共4 层,OSI参考模型分为物理层、数据链路层、网络层、传输层、会话层、表示层、应用层共7个层。

请添加图片描述

OSI参考模型是一个开放的通信系统互联参考模型

TCP/IP参考模型

TCP/IP协议采用4层架构,从上向下分为应用层、传输层、网络层和链路层,每一层都可以使用其下一层 的协议完成自己的需求,不允许下层访问上层

当通过http协议发起一个请求时,从上往下依次通过应用层、传输层、网络层和链路层,每一层相关协 议都依次对数据包进行处理,并携带响应的首部,最终在链路层生成以太网数据包,通过物理介质进行 传输,传送到对方主机后,对方主机再依次从下向上使用响应协议进行拆包,最终经应用层数据交给应 用程序进行处理

配送车就是物理介质、配送站就是网关、快递员就是路由器、收货地址就是IP地址、联系电话就是MAC地址

三次握手

TCP是面向连接的协议,连接连接需要有3个阶段:连接建立、数据传送和连接释放。其中连接建立需要 经历3个步骤,通常称为三次握手

请添加图片描述

1、第一次握手,客户端发起请求

2、第二次握手,服务器端回传确认

3、第三次握手,客户端回传确认

四次挥手

由于TCP连接是双工的,所以每个方向都必须单独进行关闭

请添加图片描述

粘包

多个数据包存储在缓存中,对数据包的处理由于无法确认边界,所以经常采用估测值大小进行数据的读 写,如果发送和接收数据的双方size不一致时,会使用发送方发送的若干个包数据到接受方接收时粘成 一个包

原因

既可以是发送方造成的,也可能是接收方造成。粘包并不是TCP协议造成的,出现是因为应用层设计的 缺陷 Nagle算法通过减少数据包数量的方式提供TCP传输性能

解决方案

应用层协议自己划分消息边界,常见的方案有基于长度或者基于终结符号

拥塞控制

防止过多的数据注入网络,以避免使网络中的路由器或者链路过载

拥塞控制前提是网络能够承受现有的网络负荷

不同于流量控制,流量控制就是拟制发送方发送数据的速率,以便使接收方能够来得及接收数据

拥塞控制的机制

慢开始、拥塞避免、快重传和快恢复

  • 慢开始就是当主机发送数据时,先进行探测,可以由小到大逐渐增加发送窗口
  • 拥塞避免是让拥塞窗口缓慢增大,不是加倍,而是加1
  • 不使用快重传就是当发送方并没有在规定的时间内收到确认,则拥塞窗口减少到1,并执行慢开始 算法;快重传要求结束方每收到一个乱序的报文后立即确认
  • 和快重传机制一起使用的是快恢复,将拥塞窗口的大小设置为慢开始的上限值的一半

IP地址

在网络中定位一个机器需要通过IP地址,IP协议可以分为IPv4和IPv6两种,IPv4采用的是点分十进制的 计法,例如192.168.1.8

Java中提供了一个InetAddress实现对IP地址的封装,子类Inet4Address和Inet6Address,这个类一般 会和Socket一起使用

InetAddress没有公共的构造方法,必须通过使用静态方法获取对应的实例

ping www.baidu.com

InetAddress ia=InetAddress.getByName("www.baidu.com");//依赖DNS
System.out.println(ia);//输出格式为www.baidu.com/14.215.177.39
System.out.println(ia.getHostName());//获取主机名称www.baidu.com
System.out.println(ia.getHostAddress());//获取主机对应的IP地址14.215.177.39
InetAddress ia2=InetAddress.getLocalHost();//获取当前主机的IP地址

特殊方法isReachable用于测试是否可以到达指定的地址,防火墙或者服务器配置可能会阻塞请求,使得 访问指定地址时处于不用达状态

InetAddress ia = InetAddress.getByName("14.215.177.254");//参数既可以是主机名称,也可以是IP地址
//参数int类型,表示超时时间,单位ms
boolean bb=ia.isReachable(2000);
System.out.println(bb);

URL编程

java.net.URL对象用于代表一个网络环境的资源,资源可以是简单的文件或者目录,也可以是复杂对象 的引用,例如数据库或者搜索引擎的查询。URL使用协议名、主机名、端口号和资源组成,基本格式为 protocol://host:port/resource,例如http://www.yan.com:80/index.php,由于不同的协议有对应的标 准端口号,如果使用标准端口,这个端口号可以省略,http协议的标准端口号为80

  • URL统一资源定位器,实际上就是一个资源的指针
  • URI统一资源标识符,实际上就是一个URL的名称
  • 目前考虑到http协议缺少安全机制,很容易被监听;所以引入https协议。https=http+SSL安全套 接层,可以实现传输数据的加密,默认端口号443
try {
URL url = new URL("http://campus.51job.com/ssjkq/images/banner.jpg");
InputStream is = url.openStream();//获取服务器返回图片的响应字节流
OutputStream os=new FileOutputStream("d:/banner.jpg");
byte[] buffer=new byte[8192];
int len=0;
while((len=is.read(buffer))>0){
os.write(buffer,0,len);
}
is.close();
os.close();
} catch (IOException e) {
e.printStackTrace();
}

可以通过URL对象获取访问相关的属性

  • String getFile()获取资源名
  • String getHost()获取主机名
  • String getPath()获取路径部分的名称
  • int getPort()获取端口号,如果不能获取则返回-1
URL url = new URL("http://campus.51job.com:80/ssjkq/images/banner.jpg");
System.out.println(url.getFile()); //资源文件名/ssjkq/images/banner.jpg
System.out.println(url.getHost()); //主机名称campus.51job.com
System.out.println(url.getPath()); //路径名/ssjkq/images/banner.jpg
System.out.println(url.getPort()); //端口号,注意地址中必须包含端口号,否则-1

可以使用字符串解析获取相应部分的内容

String ss = "http://campus.51job.com:80/ssjkq/images/banner.jpg";
String fileName = ss.substring(ss.lastIndexOf("/") + 1);
System.out.println(fileName);
int pos1 = ss.indexOf("http://") + "http://".length();
int pos2 = ss.indexOf("/", pos1);
String hostName = ss.substring(pos1, pos2);
System.out.println(hostName);
String pathName = ss.substring(pos2);
System.out.println(pathName);
int pos3 = ss.lastIndexOf(":");
if (pos3 != -1) {
int pos4=ss.indexOf("/",pos3);
String port=ss.substring(pos3+1,pos4);
System.out.println(port);
}

重要方法

  • openConnection():URLConnection可以获取输入、输出流
http协议依靠的是全双工的TCP协议
  • openStream():InputStream直接获取服务器的响应输出流
URL url = new
URL("https://news.cctv.com/2022/04/10/ARTIWLj3f0W2lIKwP8lp7HTb220410.shtml")
;
InputStream is = url.openStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
PrintWriter pw = new PrintWriter(new BufferedWriter(new
FileWriter("d:/bb.html")));
String tmp="";
while((tmp=br.readLine())!=null){
System.out.println(tmp);
pw.println(tmp);
}
br.close();
pw.close();

URL vs URLConnection

从语义的角度上来说:URL代表一个资源的位置,URLConnection代表的是连接

Java中提供了两种读取数据的方法:1、通过URL对象直接获取相关的网络信息。2、先获取一个 URLConnection实例,然后再得到响应的InputSteam和OutputStream,实现数据的读写

URL是一种简单直接的方法,但是缺乏灵活性,并且只能读取只读性质的信息;URLConnection提供了 非常灵活有效的方法来读取网络资源

URL url = new
URL("https://news.cctv.com/2022/04/10/ARTIWLj3f0W2lIKwP8lp7HTb220410.shtml")
;
URLConnection connection = url.openConnection();
InputStream is = connection.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
PrintWriter pw = new PrintWriter(new BufferedWriter(new
FileWriter("d:/bb.html")));
String tmp = "";
while ((tmp = br.readLine()) != null) {
System.out.println(tmp);
pw.println(tmp);
}
br.close();
pw.close();

TCP编程

TCP是一种面向虚电路连接的端对端的保证可靠传输的协议,使用TCP协议可以得到一个顺序的无差错的 数据流

UDP是一种不保证数据的可靠性,但是协议简单、传输速度块。一般用于视频或者音频的传输,不需要很高的可靠性,可以容忍偶尔的丢帧

在具体编程中发送方和接收方必须成对的使用socket建立连接,在tcp协议的基础上进行通信

Socket

socket套接字就是两个进行通信的主机之间逻辑连接的端点。socket编程实现主要涉及到客户端和服务 器端两方面。首先在服务器端创建一个服务器套接字ServerSocket,并将其附加到一个端口上,服务器 可以通过这个端口监听客户端的连接请求。端口号是int类型,取值范围为0到65535,但是一般0到1024 属于特殊保留的端口。

服务器和客户端建立连接时,需要服务器的域名或者IP地址,加上端口号,可以打开一个套接字。当服 务器接收到客户端的连接请求后,服务器和客户端之间的通信实际上就是一种输出输入流的操作

典型的网络编程模型

请添加图片描述

ServerSocket类

java.net.ServerSocket用于表示一个服务器端套接字,主要功能就是监听客户端的连接请求,并将照客 户端的连接请求存入到请求队列中,默认请求队列大小为50

  • ServerSocket()创建非绑定服务器指定端口的套接字
  • ServerSocket(int)创建绑定到服务器指定算口的套接字,其中的int类型参数就是对应的监听端口 号,取值范围为0-65535,一般不建议使用1024以下的端口号。其中0表示使用任意的没有被占用 的端口。如果当前指定端口已经被占用,则出异常
for(int i=0; i<=65535;i++){
ServerSocket ss=null;
try{
ss=new ServerSocket(i);//尝试以i作为端口号来打开监听端口,如果成功则正常运
行,如果报错则表示该端口已经占用
} catch(Exception e){
System.out.println(i+"端口已经被占用")
} finally{
if(ss!=null)
ss.close(); //关闭监听端口 netstat
}
}

客户端通过构建Socket对象实现连接请求

  • Socket(InetAddress,int) InetAddress就是需要连接的服务器,int就是服务器的监听端口号
  • Socket(String,int) String就是服务器的名称或者IP地址。如果构建Socket对象成功,则连接创建, 否则ConnectException

简单的C/S编程

要求:客户端发送hello信息,服务器端接收到信息后,添加一个日期信息,然后回传给客户端,客户端 在控制台上打印显示

服务器端程序

ServerSocket ss=new ServerSocket(9000);//服务器打开监听端口,注意int默认取值范围为
0-65535,不建议使用1024以下的端口
Socket socket=ss.accept(); //阻塞当前线程,并等待客户端的连接请求。如果客户端发起了连接请求,同时连接成功,则返回一个Socket对象
//通过socket对象就可以获取从网络中读取数据或者向网络输出数据的输入输出流
InputStream is=socket.getInputStream();
OutputStream os=socket.getOutputStream();
//具体的数据的接收
BufferedReader br=new BufferedReader(new InputStreamReader(is));
String str=br.readLine(); //这里使用的是BIO,所以当客户端的数据没有到达服务器时,则阻塞等待
System.out.println("服务器接收到客户端的数据:"+str);
// 服务器端回传数据
PrintStream ps=new PrintStream(os);
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String sdate = df.format(new Date());//LocalDate
str+=("[server:]"+sdate); //服务器端的回传内容
ps.println(str);
ps.flush();
//最后发送完毕,执行关闭操作,释放所占用的系统资源,一般建议使用try/finally结构
ps.close();
br.close();
socket.close();

具体的编码流程:

1、创建ServerSocket对象,绑定监听端口

2、通过accept方法阻塞程序执行,并等待监听客户端的连接请求

3、连接建立后,通过Socket对象获取输入输出流

4、通过输入输出流读取客户端发送的数据或者向客户端发送数据

5、关闭相应的资源

客户端程序

Socket socket=new Socket("localhost",9000);//参数1是服务器的地址,可以使用机器名,也可以使用IP地址;参数2就是服务器的监听端口号。也就是说客户端是向服务器的监听端口发起连接请求,但是真正连接所使用的端口号并不是这个监听端口号。参数1也可以使用127.0.0.1回绕地址。如果连接成功则返回一个Socket对象,否则异常
//通过socket对象就可以获取从网络中读取数据或者向网络输出数据的输入输出流
InputStream is=socket.getInputStream();
OutputStream os=socket.getOutputStream();
//客户端发送hello server!字符串
PrintStream ps=new PrintStream(os);
ps.println("Hello Server!"); //向服务器发送字符串hello server!
// 客户端接收服务器的响应信息
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String str = br.readLine();// 注意阻塞
System.out.println("客户端接收到服务器的响应信息:" + str);
// 最后操作完毕,执行关闭操作,释放所占用的系统资源,一般建议使用try/finally结构
ps.close();
br.close();
socket.close();

体的编码流程:

1、创建Socket对象,指明需要连接的服务器地址和对应的监听端口号

2、连接建立后,通过Socket对象获取输入输出流

3、通过输入输出流读取服务器端发送的数据或者向服务器发送数据

4、关闭相应的资源

服务器端编程

请添加图片描述

主线程一直处于阻塞等待状态,一旦连接成功则启动一个线程对外提供服务。主线程并不处理响应逻 辑,响应处理是由子线程负责

ServerSocket ss=new ServerSocket(9000);//监听端口只负责连接的创建,创建完成后监听端口并不会被占用
while(true){
Socket socket=ss.accept();
new Thread(new MyRunnable(socket)).start();
}

启动工作线程时主线程会将创建好的Socket对象传递过去,在工作线程的run方法中直接使用,并执行 对应的处理逻辑

public class MyRunnable implements Runnable {
	private Socket socket;
	public MyRunnable(Socket socket){
		this.socket=socket;
	}
	public void run(){
		BufferedReader br=null;
		PrintStream ps=null;
		try{
			InputStream is=socket.getInputStream();
			OutputStream os=socket.getOutputStream();
			br=new BufferedReader(new InputStreamReader(is));
			ps=new PrintStream(os);
			String str=br.readLine();
			System.out.println(Thread.currentThread()+":"+str);
			Date now=new Date();
			DateFormat df=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
			String sdate=df.format(now);
			ps.println(sdate);
			ps.flush();
		} catch(Exception e){
			e.printStackTrace();
		} finally{
			if (br != null)
				try {
					br.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			if (ps != null)
				ps.close();
			if (socket != null)
				try {
					socket.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
		}
	}	
}

系统结构

客户机/服务器结构,简称C/S,胖客户端应用,是软件系统体系结构中的一种,基本上用于企业内部网 络的应用系统,大部分的应用逻辑都集中在客户端中,而服务器一般只提供数据的存储支持。

浏览器/服务器结构,简称B/S,瘦客户端应用,是目前主流的软件系统体系结构,主要逻辑集中在服务 器端,客户端只负责一些简单的显示逻辑。

UDP编程

UDP是用户数据报协议的简称,是一种无连接的协议,每个数据报都是一个独立信息,包括完成的源地 址和目标地址,它能够在网络上以任何可能的路径传送到目的地,因此是否能够到达目的地、到达目的 地的时间以及内容的正确性都是不能保证的

UDP vs TCP

UDP

  • 每个数据报中都由完整的地址信息,因此无需在发送方和接收方之间建立连接
  • UDP传输数据时是有大小限制的,每个传输的数据报应该在64KB之内
  • UDP是一个不可靠的协议,所以发送方发送的数据报并不一定以相同的顺序到达接收方
  • UDP操作简单,一般只需要少量的监控,常用于局域网高可靠的分散系统的网络环境中,例如视频 会议

TCP

  • 面向虚电路连接的协议,在socket之间进行数据传输之前必须协商建立连接,所以在TCP中需要有 连接时间
  • 理论上来说TCP传送数据大小没有限制
  • TCP是一个可靠的协议,能够保证正确的传送和接收
  • TCP为了保证数据的可靠传送是需要付出代价的,对数据内容的校验必然占据计算机的处理时间和 网络带宽,所以TCP的传送效率不如UDP

UDP通信过程

1、使用DatagramSocket创建一个数据报套接字

2、使用DatagramPacket(byte[] data数据,int offset偏移量指定下标,int length长度,InetAddress接收方 地址,port接收方端口号) 创建一个要发送的数据报

3、使用DatagramSocket的send方法就可以发送数据报

//构建用于发送数据报的套接字,一般都没有参数,表示使用任意空闲接口发送数据
DatagramSocket socket=new DatagramSocket();
//需要发送的信息
String str="小胖回家吃饭!";
byte[] data=str.getBytes(); //将字符串转换为字节数组
//将需要发送的信息打包成用户数据报,其中需要包括目标地址和端口号
DatagramPacket dp=new
DatagramPacket(data,data.length,InetAddress.getLocalHost(),9999);
//调用datagramSocket中的send方法发送数据报发送
socket.send(dp);
//关闭释放资源
socket.close();

接收数据报的过程:

  • 1、使用DatagramSocket创建一个数据报套接字,绑定到指定端口上
  • 2、使用DatagramPacket(byte[]数据,int 长度)创建一个字节数组用于接收数据报
  • 3、使用DatagramSocket的receive方法接收UDP数据报
//构建用于接收数据报的套接字,一般都应该有参数,用于绑定在指定的端口上。这个端口号应该提前
约定
DatagramSocket socket=new DatagramSocket(9999);
//创建用于接收客户端传送数据的空的用户数据报
byte[] buffer=new byte[30];
DatagramPacket dp=new DatagramPacket(buffer,buffer.length); //接收的数据会自动放入byte数组中,可以接收的最大长度为length。和数组中实际存储的内容无关
//调用用户数据报套接字中的方法接收客户端提交的UDP数据
socket.receive(dp);//自动将接收到的数据写入到字节数组中,如果数据没有到达会阻塞等待
String res = new String(dp.getData(), 0, dp.getLength());
//研究1个重要问题
//刚开始以为创建用户数据报的byte数组没用?
/* 接收数据报中的报文是通过datagramPaket的getData方法进行获取 */
System.out.println(dp.getData()==buffer);//true
System.out.println(buffer);
/*实际上的接收流程是将数据填充到byte[]数组中。如果数组的长度不足,则只能获取数据报中的一部
分数据*/
//获取发送方的地址
InetAddress ia=dp.getAddress();
//获取发送方的端口号
int port=dp.getPort();
System.out.println(ia+"--"+port);
//关闭释放资源
socket.close();
//输出接收到的数据
System.out.println("接收到的数据:"+res)

ing res = new String(dp.getData(), 0, dp.getLength());
//研究1个重要问题
//刚开始以为创建用户数据报的byte数组没用?
/* 接收数据报中的报文是通过datagramPaket的getData方法进行获取 */
System.out.println(dp.getData()==buffer);//true
System.out.println(buffer);
/实际上的接收流程是将数据填充到byte[]数组中。如果数组的长度不足,则只能获取数据报中的一部
分数据
/
//获取发送方的地址
InetAddress ia=dp.getAddress();
//获取发送方的端口号
int port=dp.getPort();
System.out.println(ia+“–”+port);
//关闭释放资源
socket.close();
//输出接收到的数据
System.out.println(“接收到的数据:”+res)


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值