JavaSE高级部分之网络编程

本文详细介绍了网络编程的三要素:协议、IP地址和端口号,并通过实例演示了UDP和TCP协议的区别及使用。讲解了UDP的不可靠连接特性及其高效执行,以及TCP的可靠连接和三次握手、四次挥手过程。同时,展示了UDP发送端和接收端的实现步骤,以及TCP客户端与服务器端的基本交互流程。还探讨了如何在UDP中实现客户端与服务器端的持续通信以及TCP文件传输的操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

网络编程的三要素

协议 ip地址 端口号
举例:
1)找到美女---->ip地址
2)对它说话 ----(耳朵说)----端口号
3)找到她了,对她说: ---->协议
i love you(比如:不懂英语,说中文)

网络协议 UDP和TCP

UDP和TCP协议的区别:
1)是否需要建立连接通道
UDP:不需要建立通道 (QQ聊天,发短信)
TCP:需要建立连接通道(打电话…)

2)是否是可靠连接(是否安全)
UDP:是一种不可靠连接,不安全–执行效率高
TCP:是一种可靠连接,服务器端一直阻塞状态(同步的—安全性),执行效率低
三次握手,四次挥手!
3)共同点
UDP/TCP —两端都需要有Socket(Socket编程)

应用协议

http协议
https协议(比上http协议)

第一要素: ip地址

192.168.138.1:	使用点分十进制法
A类IP地址:第一段号码为网络号码,剩下的三段号码为本地计算机的号码  (政府部门)
	B类IP地址:前二段号码为网络号码,剩下的二段号码为本地计算机的号码 (大学校园)
	C类IP地址:前三段号码为网络号码,剩下的一段号码为本地计算机的号码(私人地址)
	
	127.0.0.1:回环地址:表示本机	---- 域名:localhost
	
	xxx.xxx.xxx.255 广播地址

第二个要素:端口号

使用360软件—查看当前计算机中每个软件 的端口号
有效端口号:0-65535
0-1024:保留端口号

http://www.baidu.com
http://192.168.25.1:80(可以不写)/xx
一般:80端口号:是不写的(省略)

常见端口号

tomcat: 8080
redis:6575.. (数据库---非关系型数据库  key-value )
mysql软件:3306

java.net.InetAddress类:互联网ip地址统称

这个类没有构造方法,不能直接创建对象!,提供一些成员方法使用:静态的
Runtime类: 单例模式

  • 提供静态方法,返回该类本身
  • public static InetAddress getByName(String host):
  • 参数为:主机名称:
  • 成员方法
  • public String getHostAddress()返回 IP 地址字符串(以文本表现形式)。
  • public String getHostName():获取主机名
public class InetAddressDemo {
	
	public static void main(String[] args) throws UnknownHostException {
		//获取ip地址:字符串形式
		//public static InetAddress getByName(String host)
//		InetAddress inetAddress = InetAddress.getByName("LAPTOP-BHL76J0S") ;
		InetAddress inetAddress = InetAddress.getByName("10.12.156.36") ;
		
		// * public String getHostAddress()
		String ip = inetAddress.getHostAddress() ;
		System.out.println(ip);//10.12.156.36
		
		//public String getHostName()
		String name = inetAddress.getHostName() ;
		System.out.println(name);//APTOP-BHL76J0S
		
		
	}
}

实例:使用UDP实现客户端和服务端

UDP协议发送端的步骤

  • 1)创建发送端的Socket对象
  • 2)数据数据报包对象:DatagramPacket
  • 3)发送数据
  • 4)关闭资源
public class SendDemo {
	
	public static void main(String[] args) throws IOException {
		
		//1)创建发送端的Socket对象
		//DatagramSocket
		//此类表示用来发送和接收数据报包的套接字。
		//public DatagramSocket()
		DatagramSocket ds = new DatagramSocket() ;
		
		//2)创建数据数据报包对象:DatagramPacket
		//数据报包用来实现无连接包投递服务
		//public DatagramPacket(byte[] buf,
      //  int length,
        //InetAddress address,
        //int port)
		//参数1:当前发送数据的字节数组
		//参数2:当前数据的实际长度
		//参数3:ip地址对象
		//参数4:端口号:0-1024保留端口 (0-65535)
		
		String s = "hello,udp,我来了" ;
		byte[] bytes = s.getBytes() ;
		int length = bytes.length ;
		DatagramPacket dp = new DatagramPacket(bytes, length,
				InetAddress.getByName("10.12.156.36"), 10086) ;
		
		//3)发送数据报包 
		//public void send(DatagramPacket p)

		ds.send(dp);
		
		//释放资源
		ds.close();
	}
}

UDP协议接收端的实现步骤

  • 1)创建接收端的Socket对象,绑定端口号
  • 2)创建一个数据报包—DatagramPacket:当前接收容器
  • public DatagramPacket(byte[] buf,int length)
  • 3)接收数据
  • 4)从接收容器中解析实际数据
  • 5)展示数据
public class ReceiveDemo {
	
	public static void main(String[] args)  throws IOException{
		//1)创建接收端的Socket对象,绑定端口号
//		public DatagramSocket(int port)
		DatagramSocket ds = new DatagramSocket(10086) ;
		
		//2)创建一个数据报包---DatagramPacket:当前接收容器
		 // public DatagramPacket(byte[] buf,int length)
		byte[] bytes = new byte[1024] ;
		int length = bytes.length ;
		DatagramPacket dp = new DatagramPacket(bytes, length) ;
		
		//3)接收数据
//		public void receive(DatagramPacket p)
		ds.receive(dp);
		
		//4)解析当前接收容器中的实际数据
		//public byte[] getData():获取缓冲区数据中实际字节数组
		//public int getLength():获取缓冲区中实际长度
		byte[] buf = dp.getData() ;
		int length2 = dp.getLength() ; 
		
		//获取接收端发送的数据:ip地址
//		public InetAddress getAddress()
		InetAddress address = dp.getAddress() ;
		String ip = address.getHostAddress() ;
		
		//展示数据
		String s = new String(buf,0,length2) ;
		System.out.println("data is: "+s+",from  "+ip);
		
		//关闭资源
		ds.close();
		
	}
}

客户端与服务器端升级版:

需求

  • UDP
  • 发送端键盘录入数据,接收端不断接收数据(不关闭)

分析

键盘录入数据

  • Scanner
  • 使用字符流的方式
  • BufferedReader(new InputStreamReader(System.in))

接收端不断接收收据,并解析

public class ReceiveDemo {

	public static void main(String[] args) throws IOException {
		
		//创建一个接收端的Socket
		DatagramSocket ds = new DatagramSocket(10000) ;
		
		//不断接收数据
		while(true) {
			//创建接收容器
			byte[] bytes = new byte[1024] ;
			int length = bytes.length ;
			DatagramPacket dp = new DatagramPacket(bytes , length) ;
			
			//接收
			ds.receive(dp);
			
			//解析真实数据
			String str = new String(dp.getData(), 0, dp.getLength()) ;
			//获取ip地址
			String ip = dp.getAddress().getHostAddress() ;
			
			//展示数据
			System.out.println("data is :"+str+"from "+ip);
		}
		//接收端不关闭
	}
}

发送端

public class SendDemo {
	
	public static void main(String[] args) throws IOException {
		
		//1)创建发送端的Socket对象
		DatagramSocket ds = new DatagramSocket() ;
		
		//2)键盘录入数据
		//创建bufferedReader类对象
		BufferedReader br = new BufferedReader(
				new InputStreamReader(System.in)) ;
		//一次读取一行内容
		String line = null ;
		while((line=br.readLine())!=null) {
			//自定义结束条件
			if(line.equals("886")) {
				break ;
			}
			
			
			//line:发送的数据
			byte[] bytes = line.getBytes() ;
			int length = bytes.length ;
			//创建数据报包对象
			DatagramPacket dp = new DatagramPacket(
					bytes, 
					length, 
					InetAddress.getByName("10.12.156.36"),
					10000) ;
			
			//发送数据
			ds.send(dp);
			
		}
		
		//释放资源
		ds.close();
		
		
	}
}

UDP和线程

需要在一个窗口下进行聊天,发送和接收数据

  • 发送端会开启发送端的线程!
  • 接收端开启接收端的线程!

多线程的实现方式:

  • 1)Thread类:继承自它
  • 2)实现Runnable接口(静态代理)
  • 3)线程池

分析:

  • 1)发送端和接收端都需要有Socket 都需要在当前用户线程(main)中创建出来
  • 2)多线程实现方式2
  • SendThread /ReceiveThread 实现Runnable接口重写run方法 (这两个作为资源类)
  • 这两个资源类对象需要将上面Socket对象传递进来
  • 3)创建Thread类对象,将上面的SendThread和ReceiveThread作为参数传递
  • 4)启动线程

接收端的资源类

public class ReceiveThread implements Runnable {
	
	private DatagramSocket ds ;
	
	public ReceiveThread(DatagramSocket ds) {
		this.ds = ds ;
	}

	@Override
	public void run() {

		
		try {
			// 不断接收数据
			while (true) {
				// 创建接收容器
				byte[] bytes = new byte[1024];
				int length = bytes.length;
				DatagramPacket dp = new DatagramPacket(bytes, length);

				// 接收
				ds.receive(dp);

				// 解析真实数据
				String str = new String(dp.getData(), 0, dp.getLength());
				// 获取ip地址
				String ip = dp.getAddress().getHostAddress();

				// 展示数据
				System.out.println("data is :" + str + "from " + ip);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}

发送端的资源类

public class SendThread implements Runnable {
	private DatagramSocket ds ; 
	
	public SendThread(DatagramSocket ds) {
		this.ds  = ds;
	}

	@Override
	public void run() {
		try {
				// 键盘录入数据
				// 创建bufferedReader类对象
				BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
				// 一次读取一行内容
				String line = null;
				while ((line = br.readLine()) != null) {
					// 自定义结束条件
					if (line.equals("886")) {
						break;
					}
	
					// line:发送的数据
					byte[] bytes = line.getBytes();
					int length = bytes.length;
					// 创建数据报包对象
					DatagramPacket dp = new DatagramPacket(bytes, length, 
							InetAddress.getByName("10.12.156.36"), 12306);
	
					// 发送数据
					ds.send(dp);

			}
		} catch (IOException e) {
			e.printStackTrace();
		}finally {
			if(ds!=null) {
				ds.close();
			}
		}

	}

}

测试类

public class ChatRoom {
	
	public static void main(String[] args) {
		try {
			//发送端的Scoket
			DatagramSocket sendDs = new DatagramSocket() ;
			//接收端Socket
			DatagramSocket receDs = new DatagramSocket(12306) ;
			
			//创建资源类对象:多线程的方式2
			SendThread st = new SendThread(sendDs) ; 
			ReceiveThread rt = new ReceiveThread(receDs) ;
			
			//创建Thread类对象
			Thread t1 = new Thread(st) ;
			Thread t2 = new Thread(rt) ;
			t1.start();
			t2.start();
			
		} catch (SocketException e) {
			e.printStackTrace();
		}
		
	}
}

TCP协议

基本使用:

客户端的实现步骤

  • 1)创建客户端的Socket对象 java.net.Socket(套接字)
  • public Socket(String host,int port)
  • 2)获取客户端通道内输出流,写入内容
  • 3)关闭资源
public class ClientDemo {
	
	public static void main(String[] args) throws  IOException {
		
		//创建客户端的Socket对象
		//public Socket(String host,int port)
		Socket s = new Socket("10.12.156.36", 8888) ;
		
		//获取客户端通道内输出流,写入内容
		//public OutputStream getOutputStream()

		OutputStream out = s.getOutputStream() ;
		out.write("hello,TCP,我来了".getBytes());
		
		//释放资源
		s.close();
	}
}

服务器端基本使用

步骤

  • 1)创建服务器端的Socket对象,绑定端口
  • 2)进入阻塞状态,监听客户端连接!
  • 3)获取动态内输入流,读数据
  • 4)展示数据
  • 5)释放资源
public class ServerDemo {
	
	public static void main(String[] args) throws IOException {
		//1)创建服务器端的Socket对象,绑定端口
		//public ServerSocket(int port)
		ServerSocket ss = new ServerSocket(8888) ;
		
		//2)进入阻塞状态,监听客户端连接!
		//public Socket accept()
		Socket s = ss.accept() ;
		
		//3)获取动态内输入流,读数据
		//public InputStream getInputStream()
		InputStream in = s.getInputStream() ;
		//读数据
		//一次读取一个字节数组
		byte[] bytes = new byte[1024] ;
		int len = in.read(bytes) ;
		//展示数据
		String clienStr = new String(bytes,0,len) ;
		//public InetAddress getInetAddress()
		String ip = s.getInetAddress().getHostAddress() ;
		//输出
		System.out.println("data is:"+clienStr+",from :"+ip);
		//释放资源
		ss.close();
	}
}

加入反馈

客户端发送数据"hello,Server,我来了"

  • 服务器端将数据读取出来,展示出来
  • 服务器端还需要加入反馈操作,“我收到了”
  • 客户端需要将反馈信息读取 出来
public class ClientDemo {
	
	public static void main(String[] args) throws IOException {
		
		//创建客户端的socket
		Socket socket = new Socket("10.12.156.36",6666) ;
 	
		//获取通道内的输出流
		OutputStream out = socket.getOutputStream() ;
		out.write("hello,Server,我来了".getBytes());
		
		
		//读取服务器端的反馈数据
		//获取通道内的输入流对象
		InputStream in = socket.getInputStream() ;
		//一次读取一个字节数组
		byte[] bytes = new byte[1024] ;
		int len = in.read(bytes) ;
		//展示数据
		String serverStr = new String(bytes, 0, len) ;
		System.out.println(serverStr);
		
		
		//释放资源
		socket.close();
	}
}

服务器端还需要加入反馈操作,“我收到了”

  • 客户端需要将反馈信息读取 出来
  • 注意事项:
  • 服务器端不要开启多次,就会出现BindException:绑定异常: 端口号被占用!
public class ServerDemo {
	
	public static void main(String[] args) throws IOException {
		
		//创建ServerSocket对象
		ServerSocket ss = new ServerSocket(6666) ;
		
		//监听客户端的链接
		Socket socket = ss.accept() ;
		
		//获取通道输入流,读取数据
		InputStream in = socket.getInputStream() ;
		//一次读取一个字节数组
		byte[] bytes = new byte[1024] ;
		int len = in.read(bytes) ;
		//展示数据:客户端发来的数据
		String clientStr = new String(bytes, 0, len) ;
		System.out.println(clientStr);
		
		
		//服务器端反馈给客户端
		//获取通道内的输出流对象
		OutputStream out = socket.getOutputStream() ;
		out.write("数据已经收到了".getBytes());
		
		
		
		//释放资源
		ss.close();
	}
}

加入键盘输入

需求

  • 键盘录入:BufferedReader
  • 1)客户端不断键盘录入数据,服务器端不断将数据展示在控制台上
public class ClientTest {

	public static void main(String[] args) throws IOException {
		
		//创建客户端Socket
		Socket socket = new Socket("10.12.156.36", 2222) ;
		//)客户端不断键盘录入数据
		//创建字符输入流:BufferedReader
		BufferedReader br =
				new BufferedReader(new InputStreamReader(System.in)) ;
		
		//OutputStream getOutputStream():通道内的字节输出流
		//输出流需要和BufferedReader对应:  BufferedWriter:字符输出流
		//将节输出流封装成BuffferedWriter   :字符流通向字节流的桥梁 OutputStreamWriter
		BufferedWriter bw = new BufferedWriter(
				new OutputStreamWriter(socket.getOutputStream())) ;
		
		//一次读取一行数据:键盘录入 数据,写入到BufferedWriter中
		String line = null ;
		while((line=br.readLine())!=null) {
			
			//结束条件
			if(line.equals("over")) {
				break ;
			}
			
			//录入一行,写入到bw流中
			bw.write(line);
			bw.newLine();
			bw.flush();
		}
		
		//释放资源
		socket.close();	
	}
}

服务器端不断将数据展示在控制台上

public class ServerTest {

	public static void main(String[] args) throws IOException {
		
		//创建服务器端的Socket对象
		ServerSocket ss = new ServerSocket(2222) ;
		
		//监听客户端连接
		Socket socket = ss.accept() ;
		
		//不断的去读取数据
		//获取通道内输入流:将封装 成BufferedReader
		BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())) ;
		String line = null ;
		while((line=br.readLine())!=null) {
			//输出数据
			System.out.println(line);
		}
			
		//服务器端不关闭
	}
}

文件操作

客户端将当前项目下ReceiveDemo.java文件 写入到通道内的流中
服务器端将客户端的文本进行复制:到当前项目Copy.java文件中

加入服务器端的反馈?

问题:

服务器端和客户端程序没有结束,但是文件已经复制完毕!

String readLine() :返回为null,仅仅表示文件已经读完了,
但是服务器端不知道客户端的文件是否已经写入到通道流中(是否应写入完毕),就等待着客户端告诉服务器端"已经写入完毕"

当前客户端没有告诉服务器端,是否写入完毕,那么客户端也一直等待着服务器的反馈,就出现互相等待了!

解决方案

1)在客户端自定义一个结束标记
通道输出流(BufferedWriter)写入"over",服务器端读取到"over",直接结束!

2)在客户端Socket中:结束的方法 “告诉服务器,这里面没有内容写入”

public void shutdownOutput()

public class UploadClient {
	
	public static void main(String[] args) throws IOException {
		
		//创建Scoket对象
		Socket s = new Socket("10.12.156.36", 5555) ;
		
		//创建字符输入流BufferedReader
		BufferedReader br = new BufferedReader
				(new FileReader("ReceiveDemo.java")) ;
		
		//封装通道内的字节输出流
		BufferedWriter bw =
					new BufferedWriter(
							new OutputStreamWriter(s.getOutputStream())) ;
		
		//将文件的内容写到bw流对象中
		String line = null ;
		while((line=br.readLine())!=null) { //readLine():阻塞式方法
			//写入
			bw.write(line);
			bw.newLine();
			
			bw.flush();
		}
		
		/**
		 * 方案1
		 * //自定义一个结束标记
		bw.write("over");
		bw.newLine();
		bw.flush();
		 */
		
		//方案2
//		public void shutdownOutput()://禁用此套接字的输出流:告诉服务器端,没有内容写入到通道流中
		s.shutdownOutput();
		
		
		//获取通道内的字节输入流,读取服务器端的反馈
		//将字节输入流---封装BufferedReader
		BufferedReader br2 = new BufferedReader
				(new InputStreamReader(s.getInputStream())) ;
		//readLine()
		String serverMsg = br2.readLine() ;
		System.out.println(serverMsg);
		
		
		
		//关闭
		br.close();
		s.close();
	}
}
public class UploadServer {
	
	public static void main(String[] args)  throws IOException{
		
		//创建ServerSocket对象
		ServerSocket ss = new ServerSocket(5555) ;
		
		//监听链接
		Socket socket = ss.accept() ;
		
		//封装通道内的字节输入流
		BufferedReader br = new BufferedReader
				(new InputStreamReader(socket.getInputStream())) ;
		
		//创建BufferedWriter将流中数据进行复制
		BufferedWriter bw = new BufferedWriter(new FileWriter("Copy.java")) ;
		
		String line = null ;
		while((line=br.readLine())!=null) {//阻塞式方法
			/*
			//读取客户端的自定义标记
			if("over".equals(line)) {
				break ;
			}
			*/
			
			bw.write(line);
			bw.newLine();
			bw.flush();
		}
		
		
		//客户端的文本文件,服务器端输出到另一个文件中,如果复制完毕
		//加入服务器端的反馈?
		//获取通道内的字节输出流---封装成BufferedWriter
		BufferedWriter bw2 = new BufferedWriter(
				new OutputStreamWriter(socket.getOutputStream())) ;
		bw2.write("文件复制完毕");
		bw2.newLine();
		bw2.flush();
		
		//关闭
		bw.close();
		socket.close();
	}
}

图片的复制:BufferedInputStream/BufferedOutputStream

一次读取一个字节数组

  • 客户端的一个图片文件:当前项目下xxx.jpg
  • 服务器端将图片文件:进行复制:mv.jpg
  • 加入服务器端的反馈
  • 发现问题:
  • 图片文件:没有复制完整,图片文件缺失(少字节数)
  • 图片文件本身在内存中:缓存数据!
  • 字节缓冲输出流中:public void flush():强制将缓冲的字节数输出到流中!
public class UploadImageClient {
	
	public static void main(String[] args) throws IOException {
		
		//创建Scoket
		Socket s = new Socket("10.12.156.36",6666) ;
		
		//创建字节输入流:封装图片文件
		BufferedInputStream bis = 
				new BufferedInputStream(new FileInputStream("高圆圆.jpg")) ;
		//封装通过的字节输出流
		BufferedOutputStream bos = new BufferedOutputStream(s.getOutputStream()) ;
		
		//一次读取一个字节数组
		byte[] bytes = new byte[1024] ;
		int len = 0 ;
		while((len=bis.read(bytes))!=-1) {
			bos.write(bytes, 0, len);
			
			//刷新
			bos.flush();
		}
		
		//告诉服务器端,图片文件已经全部写入到输出流中,不要等待了
		s.shutdownOutput();
		
		
		//读取服务端反馈数据
		//获取通道内的字节输入流
		InputStream in = s.getInputStream() ;
		//一次读取一个字节数组
		byte[] bytes2 = new byte[1024] ;
		int len2 = in.read(bytes2) ;
		String fkMsg = new String(bytes2, 0, len2) ;
		System.out.println("fkMsg:"+fkMsg);
		
		//释放资源
		
		bis.close();
		
		s.close();
	}

}
public class UploadImageServer {
	
	public static void main(String[] args) throws IOException {
		
		ServerSocket ss = new ServerSocket(6666) ;
		
		Socket socket = ss.accept() ;
		
		//封装通道内在字节输入流
		BufferedInputStream bis = new BufferedInputStream(socket.getInputStream()) ;
		//输出到指定文件
		BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("mv.jpg")) ;
		
		//一次读取一个字节数组
		byte[] bytes = new byte[1024] ;
		int len = 0  ;
		while((len = bis.read(bytes))!=-1) {
			//写入
			bos.write(bytes, 0, len);
			
			//强制刷新
			bos.flush();
		}
		
		//加入反馈
		//获取通道内的输出流
		OutputStream out = socket.getOutputStream() ;
		out.write("图片已经复制完毕".getBytes());
		//刷新
		out.flush();
		
		
		//释放资源
		bos.close();
		socket.close();
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值