Java学习笔记——网络编程

Java网络编程

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

TCP/IP协议簇

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

分层模型

TCP/IP协议栈的分层模型常见的有2个,分别是TCP/IP参考模型和ISO组织提出的OSI参考模型。

在TCP/IP参考模型中将网络分为网络访问层【数据链路层】、互联网层【网络层】、传输层、应用层共4层

OSI参考模型分为物理层、数据链路层、网络层、传输层、会话层、表示层、应用层共7个层。OSI参考模型是一个开放的通信系统互联参考模型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vIPzIg7M-1650970802502)(C:\Users\pon18\AppData\Roaming\Typora\typora-user-images\image-20220411165123404.png)]

TCP/IP参考模型

TCP/IP协议采用4层架构,从上向下分为应用层、传输层、网络层、链路层,每一层相关协议都依次对数据包进行处理,并携带相应的首部,最终在链路层生成以太网数据包,通过物理介质进行传输,传送到对方主机后,对方主机再依次从下向上使用相应协议进行拆包,最终经应用层数据交给应用程序进行处理

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

三次握手

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

  1. 第一次握手,客户端发送请求
  2. 第二次握手,服务器端回传确认
  3. 第三次握手,客户端回传确认

类比日常打电话:

A:喂,你听得到吗?

B:可以,你听得到我说话吗?

A:可以。我们聊天吧!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iWnROLUN-1650970802503)(C:\Users\pon18\AppData\Roaming\Typora\typora-user-images\image-20220411170610809.png)]

过程:

  1. A主动打开连接,B被动打开连接。一开始B的TCP服务器进程创建传输控制块TCB,处于LISTEN(收听)状态,等待用户进程的连接请求。A的TCP客户进程也是首先创建传输控制模块TCB。向B发送连接请求报文段,首部的同步位SYN=1,同时选择一个初始序号seq=x。TCP规定SYN报文段(即SYN=1的报文段)不能携带数据,但要消耗一个序号。这时TCP客户进程进入SYN-SENT(同步已发送)状态。
  2. B收到连接请求报文段后,如果同意建立连接,则向A发送确认。在确认报文段中应把SYN位和ACK位都置1,确认号是ack=x+1,同时为自己选择一个初始序号seq=y。这个报文段也不能携带数据,同样消耗一个序号。此时TCP服务器进程进入SYN-RCVD(同步收到)状态。
  3. TCP客户进程收到B的确认后,还要向B给出确认。确认报文段的ACK置1,确认号ack=y+1,而自己的序号seq=x+1。TCP标准规定,ACK报文可以携带数据,但如果不携带数据则不消耗序号,在这种情况下,下一个数据报文段的序号仍是seq=x+1。此时TCP已建立连接,A进入ESTABLISHED(已建立连接)状态。当B收到A的确认后,也进入到ESTABLISHED状态。

四次挥手

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

img

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uwgsFpaI-1650970802505)(C:\Users\pon18\AppData\Roaming\Typora\typora-user-images\image-20220411171117077.png)]

过程:

  1. 客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
  2. 服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
    • 客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
  3. 服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
  4. 客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
    • 服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。

【面试】为什么连接的时候是3次握手,而断开连接时是4次挥手?

答:因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,“你发的FIN报文我收到了”。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。

【面试】为什么不能用两次握手进行连接?

答:3次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。

现在把三次握手改成仅需要两次握手,死锁是可能发生的。作为例子,考虑计算机S和C之间的通信,假定C给S发送一个连接请求分组,S收到了这个分组,并发 送了确认应答分组。按照两次握手的协定,S认为连接已经成功地建立了,可以开始发送数据分组。可是,C在S的应答分组在传输中被丢失的情况下,将不知道S 是否已准备好,不知道S建立什么样的序列号,C甚至怀疑S是否收到自己的连接请求分组。在这种情况下,C认为连接还未建立成功,将忽略S发来的任何数据分 组,只等待连接确认应答分组。而S在发出的分组超时后,重复发送同样的分组。这样就形成了死锁

粘包

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

原因

既可以时发送方造成的,也可能是接收方造成。粘包并不是TCP协议造成的,出现是因为应用层设计的缺陷

Nagle算法通过减少数据报数量的方式提供TCP传输性能

解决方案

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

拥塞控制

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

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

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

拥塞控制的机制

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

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

在这里插入图片描述

在这里插入图片描述

IP地址

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

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

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

baidu.com:域名

www.baidu.com:主机名

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

//特殊方法isReachable用于测试是否可以到达指定的地址,防火墙或者服务器配置可能会阻塞请求,使得访问指定地址时处于不用达状态
InetAddress ia3 = InetAddress.getByName("192.168.56.100");//参数既可以是主机名称,也可以是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,由于不同的协议有对应的标准端口号,如果使用标准端口,这个端口号可以省略,http协议的标准端口号为80

  • URL统一资源定位器,实际上就是一个资源的指针
  • URL统一资源标识符,实际上就是一个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 (Exception 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);//banner.jpg
		int pos1 = ss.indexOf("http://") + "http://".length();
		int pos2 = ss.indexOf("/", pos1);
		String hostName = ss.substring(pos1, pos2);
		System.out.println(hostName);//campus.51job.com:80
		String pathName = ss.substring(pos2);
		System.out.println(pathName);// /ssjkq/images/banner.jpg
		int pos3 = ss.lastIndexOf(":");
		if (pos3 != -1) {
			int pos4 = ss.indexOf("/", pos3);
			String port = ss.substring(pos3 + 1, pos4);
			System.out.println(port);//80
		}

重要方法

  • 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实例,然后再得到响应的InputStream和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地址,加上端口号,可以打开一个套接字。当服务器接收到客户端的连接请求后,服务器和客户端之间的通信实际上就是一种输入输出流的操作

典型的网络编程模型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fEdgDZPg-1650970802506)(C:\Users\pon18\AppData\Roaming\Typora\typora-user-images\image-20220411193605962.png)]

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();//关闭监听端口  netstate
    }
}

Socket

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

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

服务器编程

  • ServerSocket ss = new ServerSocket(9999);//监听端口,不是具体的连接端口
  • Socket socket=ss.accept();//阻塞服务器线程,等待客户端的连接请求
  • InputStream is = socket.getInputStream();
  • OutputStream os = socket.getOutputStream();

InputStreamReader OutputStreamWriter

  • 关闭close

客户端编程

  • Socket socket=new Socket(“服务器的地址”,int 端口号);
  • InputStream is = socket.getInputStream();
  • OutputStream os = socket.getOutputStream();
  • 关闭close

简单地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());
str+=("[server:]"+sdate);
ps.println(str);
ps.flush();

//最后发送完毕,执行关闭操作,释放所占用的系统资源,一般建议使用try/finally结构 
ps.close(); 
br.close(); 
socket.close();

具体的编码流程:

  1. 创建ServerSocket对象,绑定监听端口
  2. 通过accept方法阻塞程序执行,并等待监听客户端的连接请求
  3. 连接建立后,通过Socket对象获取输入输出流
  4. 通过输入输出流读取客户端发送的数据或者向客户端发送数据
  5. 关闭相应的资源

客户端程序

注意:参数2服务器的监听端口号是客户端向服务器的监听端口发起连接请求,但真正连接所使用的端口号并不是这个监听端口号

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");

//客户端接收服务器的响应信息
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. 关闭相应的资源

服务器端编程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-02HuOFAe-1650970802507)(C:\Users\pon18\AppData\Roaming\Typora\typora-user-images\image-20220411194856382.png)]

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

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,瘦客户端应用,是目前主流的软件系统体系结构,主要逻辑集中在服务器端,客户端只负责一些简单的显示逻辑

HTTP协议

http协议

http超文本传输协议,建立在tcp协议之上的,属于应用层协议,是一种不保持连接的请求/响应式的协议。主要用于浏览器和web服务器之间的通信标准协议。目前有两个版本,1.0和1.1.1。1.0中采用的是非持续连接,1.1中采用的是持续连接,支持用户端和服务器持续的在这个连接上传送http报文

  • 浏览器首先通过DNS域名服务将主机名转换为IP地址

  • 默认情况下,客户端与打开80监听端口的服务器先建立一个TCP连接,URL还可以指定其他端口

  • 客户端进行服务器发送消息,指定请求的资源,这个资源一般包括一个头部、可选,后面还有一个空行,最后是这个请求传送的数据

  • 服务器处理请求后,向客户端回传响应信息。响应是以响应码开头,后面就是包含数据的响应头、一个空行以及所请求的文档信息或者错误信息

  • 服务器关闭连接

1个TCP连接除非主动关闭,可以供http协议多次使用;默认情况下chrome针对一个网站可以建立6个TCP连接

http VS https

https就是在http协议的基础上引入SSL安全套接层,建立以数据传输安全为目标的http通道,针对传送的数据进行加密。因为http这个协议是以明文的方式发送数据,不提供任何方式的数据加密,所以不适合传输敏感数据。SSL依靠证书验证服务器的身份,对传送数据进行加密解密处理

  • https协议需要申请证书,一般免费的证书较少,需要缴费
  • http信息是明文传输,https采用的是ssl加密数据传输
  • http协议使用80端口,https使用443端口
  • http连接是无状态的,https是具有ssl+http协议构建的可以进行加密传输、身份认证的网络协议,比http协议安全

UDP编程

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

UDP通信过程

发送数据报的过程:

  1. 使用DatagramSocket创建一个数据报套接字
  2. 使用DatagramPacket(byte[] data数据,int offset偏移量指定下标,int length长度,InetAddress接收方地址,port接收方端口号)创建一个要发送的数据报
  3. 使用DatagramSocket的send方法就可以发送数据报
- DatagramSocket socket=new DatagramSocket();//随机打开空闲端口
- DatagramPacket packet=new DataframPacket(byte[]具体的报文信息,int发送信息的长度,InetAddress接收方地址,int接收方的端口号)//准备发送的数据报
- socket.send(packet);//发送数据报
- socket.close();
//构建用于发送数据报的套接字socket,一般都没有参数,表示使用任意空闲接口发送数据
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);//这里是一个监听端口
- 声明一个空字节数组,用于存储接收到的数据,注意长度应该足够,否则有数据丢失
- DatagramPacket packet=new DatagramPacket(空数组,最大长度);
- socket.receive(packet);//接收发送方传送的数据
- packet.getAddress()/packet,getPort();//获取发送方的信息
- socket.close();
//构建用于接收数据报的套接字,一般都应该有参数,用于绑定在指定的端口上。这个端口号应该提前约定
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);

发送-接收-回传

客户端询问服务器时间,服务器回传具体当前时

		DatagramSocket socket=new DatagramSocket();
		//发送数据
		String str="几点了?";
		byte[] buffer=str.getBytes();
		DatagramPacket packet=new DatagramPacket(buffer, buffer.length,InetAddress.getLocalHost(),9999);
		socket.send(packet);
		
		//接收数据
		byte[] buffer2=new byte[8192];
		packet=new DatagramPacket(buffer2, buffer2.length);
		socket.receive(packet);
		str=new String(packet.getData(),0,packet.getLength());
		System.out.println("服务器时间为:"+str);
		socket.close();

服务器需要满足第一次请求信息接收,在具体的UDP编程中实际上是没有服务器和客户端之分的

		//接收数据
		DatagramSocket socket = new DatagramSocket(9999);
		byte[] buffer=new byte[8192];
		DatagramPacket packet=new DatagramPacket(buffer, buffer.length);
		socket.receive(packet);
		
		//生成相应内容
		String str = new String(packet.getData(),0,packet.getLength());
		System.out.println("客户端请求问:"+str);
		if ("几点了?".equals(str)) {
			DateFormat df = new SimpleDateFormat("yyyy年M月d日H点m分s秒");
			str=df.format(new Date());
		}
		//发送响应信息
		InetAddress sender = packet.getAddress();//获取发送方的IP地址
		int port=packet.getPort();//发送方的端口号
		byte[] data=str.getBytes();//发送的数据
		packet=new DatagramPacket(data, data.length,sender,port);//需要发送回传的数据报
		socket.send(packet);
		socket.close();

组播的实现

java.net.MulticastSocket可以将数据报以广播的形式发送给加入到指定组的所有客户端

接收方

public class Receiver {
	public static void main(String[] args) throws Exception {
		MulticastSocket ms = new MulticastSocket(5000);
		InetAddress group = InetAddress.getByName("225.0.0.1");
		ms.joinGroup(group);
		byte[] buffer = new byte[8192];
		DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
		Thread t = new Thread(() -> {
			while (true) {
				try {
					ms.receive(packet);
				} catch (IOException e) {
					e.printStackTrace();
				}
				String received = new String(packet.getData(), 0, packet.getLength());
				System.out.println(received);
			}
		});
		t.start();
		t.join();
	}
}

发送方

public class Sender {
	public static void main(String[] args) throws Exception {
		MulticastSocket ms = new MulticastSocket(5001);
		Scanner sc = new Scanner(System.in);
		InetAddress group = InetAddress.getByName("225.0.0.1");
		Random r = new Random();
		while (true) {
			int kk = r.nextInt(100);
			byte[] content = ("str_" + r.nextInt(10000)).getBytes();
			DatagramPacket packet = new DatagramPacket(content, content.length, group, 5000);
			ms.send(packet);
			if (kk < 30)
				break;
		}
		ms.close();
		sc.close();
	}
}

代理服务器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-evvPFLvV-1651291281075)(C:\Users\pon18\AppData\Roaming\Typora\typora-user-images\image-20220417211145491.png)]

代理服务器就是介于浏览器和服务器之间的一台服务器

  • 突破自身IP限制,对外隐藏自身IP地址
  • 提高访问速度

JDK1.5+提供了两个类Proxy和ProxySelector实现具体的代理访问

Proxy代表一个代理服务器,可以在可以URLConnection连接时灵活制定代理,也可以在创建Socket连接时指定代理。ProxySelector是一个代理选择器,提供对代理服务器更加灵活的控制

URL url = new URL("http://www.google.com");
Proxy proxy = new Proxy(Proxy.Type.HTTP,new InetSocketAddress("代理服务器的IP地址",代理服务器的端口号));//参数1表示代理服务器代理http协议,参数3为代理服务器的IP地址和端口号
URLConnection connection = url.openConnection(proxy);
connection.setConnectTimeout(3000);
		
//通过流直接获取数据或者发送数据
InputStream is = connection.getInputStream();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值