JavaSE总结(四)网络编程

JavaSE总结(四)网络编程

一:网络编程概述

1.什么是网络:

分存在不同区域计算机使用专业通信线路连接起来,实现资源共享;

网络编程目的:通过通讯协议实现数据传输

在这里插入图片描述

2.通讯协议的约定

TCP:传输控制协议

UDP:用户数据报协议

2.通讯要素(三要素:ip、端口、协议)
1.ip地址和端口

ip:Internet网络唯一标识,端口:计算机中运行的一个进程

InetAddress类:此类表示Internet协议(ip)地址

常用方法:

方法作用
getByname(String host)确定主机名称的ip地址;//解析域名得到ip
getAddress()返回InetAddress对象的原始ip地址,是InetAddress类型的
getLocalHost()返回本机主机的地址
getHostName()获取此ip地址的主机名
getHostAddress()获得字符串类型的ip地址

端口号:0~65535 系统预留:0~1023 自定义:1024~65535

如:mysql默认端口号:3306

@Test
	public void test1() throws UnknownHostException {
             //确定主机名称的ip地址
		InetAddress in=InetAddress.getByName("www.baidu.com");
		//InetAddress in2=InetAddress.getByName("182.");
		System.out.println(in);
		System.out.println(in.getHostName());
		System.out.println(in.getHostAddress());
		//本机  localhost  127.0.0回环地址(用于测试本机)
		//ipconfig----查询本机ip
		//ping   ip-----检查当前网络是否畅通
	    in=InetAddress.getByName("127.0.0");
	    System.out.println(in.getHostName());
	    System.out.println(in.getHostAddress());
	    
	    //本机端口号
	    InetAddress in3=InetAddress.getLocalHost();
	    //获取本机名
	    System.out.println(in3.getHostName());
	    //获取本机ip地址
	    System.out.println(in3.getHostAddress());
	}
2.TCP通讯协议

在这里插入图片描述

TCP有6种标示:

  • SYN(建立联机)
  • ACK(确认)
  • PSH(传送)
  • FIN(结束)
  • RST(重置)
  • URG(紧急)
1)三次握手

在这里插入图片描述

第一次握手

客户端向服务器发出连接请求报文,这时报文首部中的同部位SYN=1,同时随机生成初始序列号 seq=x,此时,TCP客户端进程进入了 SYN-SENT(同步已发送状态)状态。TCP规定,SYN报文段(SYN=1的报文段)不能携带数据,但需要消耗掉一个序号。这个三次握手中的开始。表示客户端想要和服务端建立连接。

第二次握手

TCP服务器收到请求报文后,如果同意连接,则发出确认报文。确认报文中应该 ACK=1,SYN=1,确认号是ack=x+1,同时也要为自己随机初始化一个序列号 seq=y,此时,TCP服务器进程进入了SYN-RCVD(同步收到)状态。这个报文也不能携带数据,但是同样要消耗一个序号。这个报文带有SYN(建立连接)和ACK(确认)标志,询问客户端是否准备好。

第三次握手

TCP客户进程收到确认后,还要向服务器给出确认。确认报文的ACK=1,ack=y+1,此时,TCP连接建立,客户端进入ESTABLISHED(已建立连接)状态。

TCP规定,ACK报文段可以携带数据,但是如果不携带数据则不消耗序号。这里客户端表示我已经准备好。

2)四次挥手

在这里插入图片描述

第一次挥手

TCP客户端发送一个FIN(结束),用来关闭客户到服务端的连接。客户端进程发出连接释放报文,并且停止发送数据。

释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。

第二次挥手

服务端收到这个FIN,他发回一个ACK(确认),确认收到序号为收到序号+1,和SYN一样,一个FIN将占用一个序号。

服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。

TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。

客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。

第三次挥手

服务端发送一个FIN(结束)到客户端,服务端关闭客户端的连接。

服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。

第四次挥手

客户端发送ACK(确认)报文确认,并将确认的序号+1,这样关闭完成。

客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。

服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。

3.UDP协议传输

Internet 协议集支持一个无连接的传输协议,该协议称为用户数据报协议UDP,User Datagram Protocol)。UDP 为应用程序提供了一种无需建立连接就可以发送封装的 IP 数据报的方法。

二:Java中基于TCP的编程

传递的是socket套接字(ip地址和端口组合socket)

网络通信其实就是Socket间的通信,以流的方式进行传输

一般主动发去通信应用程序的叫客户端,等待接收数据的叫服务器

客户端:Socket

  • Socket(InteAddress address,int port):创建字节流套接字,并且将其连接到指定的ip地址的指定端口号
  • getInputStream():得到套接字里面的输入流
  • getOutputStream():得到套接字里面的输出流

服务端:ServerSocket

  • ServerSocket(int port):创建指定端口的服务器套接字
  • Socket=accept():侦听连接到次套接字并且接受它
  • getInputStream():得到次套接字的输入流
  • getOutputStream():得到次套接字的输出流

在这里插入图片描述

public class TestTcp2 {
	@Test
	/**
	 * 客户端
	 */
	public void client() throws UnknownHostException, IOException {
		//创建客户端Socket对象
		Socket socket=new Socket(InetAddress.getByName("127.0.0.1"),8888);
		//获取输出流
		OutputStream out=socket.getOutputStream();
		out.write("你好,我是客户端".getBytes());
		//获取输入流
		InputStream input=socket.getInputStream();
		int size=input.available();
		byte[] array=new byte[size];
		input.read(array);
		String client=new String(array);
		System.out.println(client);
		//关闭资源
		socket.close();
	}
	/**
	 * 服务器端
	 */
	@Test
	public void server() throws IOException {
		//创建ServerSock对象
		ServerSocket ssocket=new ServerSocket(8888);
		
		//监听客户端的连接
		Socket mon=ssocket.accept();
		
		//创建输出流
		OutputStream out=mon.getOutputStream();
		out.write("你好,我是服务端".getBytes());
		
		//获取输入流
		InputStream input=mon.getInputStream();
		int size=input.available();//估算流的长度
		byte[] array=new byte[size];//获得固定长度的字节数组
		input.read(array);
		String server=new String(array);
		System.out.println(server);
		//关闭资源
		ssocket.close();
	}
}
实践一:实现客户端和服务端的通信:

踩坑:1.读取字节流要用到byte[]数组,一般可以使用available(0方法估测流的长度,从而动态创建数组长度;但是在这里却不行,因为read()是一个阻塞式方法,如果读不到数据就会阻塞线程

//服务端
public class server {
	public static void main(String[] args) throws IOException {
		//建立连接
		ServerSocket ss=new ServerSocket(9000);
		//对客户端连接进行监听,有连接就会获取
		Socket socket=ss.accept();
		//创建输入流具体任务,传入套接字
		ReciveImpl res=new ReciveImpl(socket);
		//分配任务,开启输入流线程
		new Thread(res).start();
		
		//创建输出流任务,传入套接字
		SendImpl sen=new SendImpl(socket);
		//分配任务,开启输出流线程
		new Thread(sen).start();
	}
}
//客户端
public class client {
	public static void main(String[] args) throws UnknownHostException, IOException {
		//建立连接
		Socket socket=new Socket(InetAddress.getLocalHost(),9000);
		//创建输入流任务
		ReciveImpl rec=new ReciveImpl(socket);
		//分配并且启动输入流任务
		new Thread(rec).start();
		//创建输出流任务
		SendImpl  sen=new SendImpl(socket);
		//分配并且启动输出流任务
		new Thread(sen).start();
	}
}
//输入流的线程类
public class ReciveImpl implements Runnable{
	Socket socket;
	public ReciveImpl(Socket socket) {
		this.socket=socket;
	}
	@Override
	public void run() {
		while(true) {
			try {
				InputStream is=socket.getInputStream();
				byte[] array=new byte[1024];
				//第一种读取方式:
//				is.read(array);
//				String str=new String(array);
//				System.out.println("收到消息:"+str);
				//第二种读取方式:
				int len = is.read(array);
				System.out.println("收到的消息:"+new String(array, 0 ,len));
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}	
	}
}
//输出流的线程类
public class SendImpl implements Runnable{
	Socket socket;
	public SendImpl(Socket socket) {
		this.socket=socket;
	}
	@Override
	public void run() {
		System.out.println("请输入信息:");
		Scanner input=new Scanner(System.in);
			while(true) {
				try {
					OutputStream os = socket.getOutputStream();
					String line=input.nextLine();
					os.write(line.getBytes());
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
	}
}
实践二:实现图片的上传和下载
//服务器端
public class UpLoadServer {
	public static void main(String[] args) throws IOException {
		//建立连接
		ServerSocket se=new ServerSocket(10088);
		//监听
		Socket s=se.accept();
		//获取输入流
		InputStream in=s.getInputStream();
		//处理输入流
		BufferedInputStream bi=new BufferedInputStream(in);
		
		//创建输出流
		BufferedOutputStream  bu=new BufferedOutputStream(new FileOutputStream("C:\\Users\\Administrator\\Desktop\\gg.jpg"));
		
		byte[] byt=new byte[bi.available()];
		bi.read(byt);
		bu.write(byt);
		
		BufferedWriter out=new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
		out.write("文件上传成功");
		out.newLine();//写一行分隔符
		out.flush();
//		OutputStream out=s.getOutputStream();
//		byte[] byts="00".getBytes();
//		out.write(byts);
		bu.close();
		s.close();
	}
}
//客户端
ublic class UpLoadClien {
	public static void main(String[] args) throws UnknownHostException, IOException {
		//创建连接
		Socket se=new Socket(InetAddress.getLocalHost(), 10088);
		//获取要上传图片的地址
		BufferedInputStream bu=new BufferedInputStream(new FileInputStream("C:\\Users\\Administrator\\Desktop\\picture.jpg"));
		//获取连接通道中的输出流
		BufferedOutputStream bo=new BufferedOutputStream(se.getOutputStream());
		
		//将输入流中的数据写到输出流中
		byte[] byt=new byte[bu.available()];
		bu.read(byt);
		bo.write(byt);
		
		
		//获取服务端的反馈
		BufferedReader inp=new BufferedReader(new InputStreamReader(se.getInputStream()));
		String str=inp.readLine();
		System.out.println("马上读");
		System.out.println(str);
		System.out.println("读了");
//		InputStream inp=se.getInputStream();
//		byte[] byts=new byte[inp.available()];
//		inp.read(byts);
//		String str=new String(byts);
//		System.out.println("马上读");
//		System.out.println(str);
//		System.out.println("读了");
		
		//关闭资源和连接
		bu.close();
		se.close();
	}
}

三:Java中基于UDP的编程

DatagramSocket(数据报套接字):此类表示用于发送和接收数据包套接字

DatagramPacket(数据报包):封装UDP数据包,数据包中包含了发送端的ip和端口号,UDP协议中给出了完整的地址信息,因此不需要像TCP协议一样建立连接

注:DatagramSocket的接收数据包的方法receive()是一个阻塞式的方法:没有接收到数据,就会阻塞主线程;

接收端:
public class ReceiveDemo {
	public static void main(String[] args) throws Exception {
		//创建接收端Socket对象
		DatagramSocket ds=new DatagramSocket(10086);
		
		//创建一个数据包(接收容器)
		byte[] bys=new byte[1024];
		int len=bys.length;
		DatagramPacket  dp=new DatagramPacket(bys, len);
		//调用Socket对象的接收方法接收数据
		ds.receive(dp);//阻塞式
		System.out.println("你好");
		//获取接受的数据包的发送端ip
		InetAddress address=dp.getAddress();
		String ip=address.getHostAddress();
		//解析数据包
		byte[] bys2=dp.getData();
		int len2=dp.getLength();
		String s=new String(bys2, 0, len2);
		System.out.println(ip+"====="+s);
		ds.close();
	}
}
发送端:
public class SendDemo {
	public static void main(String[] args) throws IOException {
		//创建发送端Socket对象
		DatagramSocket ds=new DatagramSocket();
		
		//创建数据,并且把数据打包
		byte[] bys="你好,世界,我是UDP协议传输的".getBytes();
		//数据长度
		int len=bys.length;
		//Ip地址对象
		InetAddress address=InetAddress.getLocalHost();
          //等价于InetAddress address=InetAddress.getByName("192.168.10.22")
		//端口
		int part=10086;
		
		//打包数据
		DatagramPacket dp=new DatagramPacket(bys, len, address,part);
		
		//调用Socket对象的发送方法将数据包发送
		ds.send(dp);
		
		//关闭连接
		ds.close();
	}
}
实践一:实现键盘录入,在线聊天(多线程)
//发送端线程
public class SendThread implements Runnable{
	DatagramSocket ds;
	public SendThread(DatagramSocket ds) {
		this.ds=ds;
	}
	//发送端线程
	@Override
	public void run() {
		while(true) {
			try {
				
				//构建数据包所需要的数据
				Scanner  input=new Scanner(System.in);
				String buf=input.nextLine();
				int len=buf.length();
				int part=10066;
				//创建数据包
				DatagramPacket dp=new DatagramPacket(buf.getBytes(), len,InetAddress.getLocalHost(), part);
				
				//将数据包发出
				ds.send(dp);
			} catch (UnknownHostException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}
//接收端线程
public class ReceiveThread implements Runnable{
	//创建DatagramSocket对象,建立连接
	DatagramSocket ds;
	public ReceiveThread(DatagramSocket ds) {
		this.ds=ds;
	}
	@Override
	public void run() {
		while(true) {
			try {
				//创建一个合适大小的接收容器
				byte[] byt=new byte[1024];//只起到一个缓存的作用
				int len=byt.length;
				DatagramPacket dp=new DatagramPacket(byt, len);
				//接收数据包到容器中
				ds.receive(dp);
				//调用getData()方法获取解析这个数据包
				byte[] bty2=dp.getData();
				int len2=bty2.length;
				String s=new String(bty2, 0, len2);
				//得到数据包中的ip
				String ip=(dp.getAddress()).getHostAddress();
				System.out.println(ip+"========"+s);
			} catch (SocketException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}
//控制类
public class InterFace {
	public static void main(String[] args) throws SocketException {
		DatagramSocket dssend=new DatagramSocket();
		DatagramSocket dsreceive=new DatagramSocket(10066);
		SendThread send=new SendThread(dssend);
		ReceiveThread receive=new ReceiveThread(dsreceive);
		new Thread(send).start();
		new Thread(receive).start();
	}
}
实践一升级:使用UDP协议传输实现接收端反馈

实现反馈,又重新定义了一个端口号,否则会报错,端口号被占用;

在这里插入图片描述

//接收端
public class ReceiveDemo {
	public static void main(String[] args) throws Exception {
		//创建接收端Socket对象
		DatagramSocket ds=new DatagramSocket(10086);
		
		//创建一个数据包(接收容器)
		byte[] bys=new byte[1024];
		int len=bys.length;
		DatagramPacket  dp=new DatagramPacket(bys, len);
		//调用Socket对象的接收方法接收数据
		ds.receive(dp);//阻塞式
		System.out.println("你好");
		//获取接受的数据包的发送端ip
		InetAddress address=dp.getAddress();
		String ip=address.getHostAddress();
		//解析数据包
		byte[] bys2=dp.getData();
		int len2=dp.getLength();
		String s=new String(bys2, 0, len2);
		System.out.println(ip+"====="+s);
		ds.close();
	}
	
}
//发送端
public class SendThread implements Runnable{
	DatagramSocket ds1;
	DatagramSocket ds2;
	public SendThread(DatagramSocket ds1,DatagramSocket ds2) {
		this.ds1=ds1;
		this.ds2=ds2;
	}
	//发送端线程
	@Override
	public void run() {
		while(true) {
			try {
				
				//构建数据包所需要的数据
				System.out.println("小帅:");
				Scanner  input=new Scanner(System.in);
				String buf1=input.nextLine();
				byte[] buf=buf1.getBytes();
				int len=buf.length;
				int part=10066;
				//创建数据包
				DatagramPacket dp=new DatagramPacket(buf, len,InetAddress.getLocalHost(), part);
				//将数据包发出
				ds1.send(dp);
				//==================================
				//接收接收端的反馈信息
				DatagramPacket at=new DatagramPacket(new byte[1024], new byte[1024].length);
				ds2.receive(at);//阻塞
				System.out.println("小帅:");
				byte[] byts=at.getData();
				String s=new String(byts, 0, byts.length);
				System.out.println(s);
			} catch (UnknownHostException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
		}
		
	}
}
//控制类
public class InterFace {
	public static void main(String[] args) throws SocketException {
		DatagramSocket dssend=new DatagramSocket();
		DatagramSocket dssend1=new DatagramSocket(10010);
		DatagramSocket dsreceive=new DatagramSocket(10066);
		DatagramSocket dsreceive1=new DatagramSocket();
		SendThread send=new SendThread(dssend,dssend1);
		ReceiveThread receive=new ReceiveThread(dsreceive,dsreceive1);
		new Thread(send).start();
		new Thread(receive).start();
	}
}

四:Java中基于URL的编程

url"统一资源定位符

组成:<传输协议>://<主机名>:<端口号 http 默认80>/<文件名>

URL类:需要给定具体传输协议为参数

URL类的openStream()方法:返回一个输入流

实践一:开启Tomcat服务器:读取examples/app.txt文件
public static void main(String[] args) throws IOException {
	URL url=new URL("http://127.0.0.1:8080/examples/app.txt");
	InputStream is=url.openStream();
	BufferedInputStream bs=new BufferedInputStream(is);
	byte[] byt=new byte[1024];
	int len;
	while((len=bs.read(byt))!=-1) {
		String str=new String(byt,0,len);
		System.out.println(str);
	}
实践二:读取网页上文件,存入本地内存
public static void main(String[] args) throws IOException {
		URL url=new URL("http://pic51.nipic.com/file/20141025/8649940_220505558734_2.jpg");
		InputStream is=url.openStream();
		BufferedInputStream buf=new BufferedInputStream(is);
		BufferedOutputStream out=new BufferedOutputStream(new FileOutputStream(new File("C:\\Users\\Administrator\\Desktop\\a.jpg")));
		//方式一读取
//		byte[] byt=new byte[buf.available()];
//		buf.read(byt);
//		out.write(byt);
//		is.close();
//		buf.close();
//		out.close();
		//方式二读取
		byte[] byt=new byte[1024];
		int len;
		while((len=buf.read(byt))!=-1) {
			out.write(byt, 0, len);
		}
	}

方式一读取,文件会失真,所以在读取非文本文件最好还是使用第二种方式读取

在这里插入图片描述

五:总结

网络通信总结:

一:基于TCP协议:
1.客户端和服务端单程通信:
客户端:
  1. 创建Socket()对象,要写入ip地址和端口号
  2. 通过Socket()的getOutputStream()方法获取输出流对象;
  3. 将数据转成字节数组(或者字符数组),调用write()方法,将数据写入输入流
  4. 通过Socket()的getInputStream()方法获取输入流对象;
  5. 解析输入流,获得数据并且输出;
  6. 释放连接
服务端:
  1. 创建ServerSocket()对象,写入端口号;
  2. 调用ServerSocket()的accept()方法对连接到服务器的客户端进行监听(accept是一个阻塞式方法,如果没有监听到连接,就会阻塞主线程);
  3. 通过Socket()的getOutputStream()方法获取输出流对象;
  4. 将数据转成字节数组(或者字符数组),调用write()方法,将数据写入输入流
  5. 通过Socket()的getInputStream()方法获取输入流对象;
  6. 解析输入流,获得数据并且输出;
  7. 释放连接
2.基于多线程的服务端客户端实时通信

输入流线程:输入流线程任务,构造器要传入Socket()实例对象,需要端口号

输出流线程:输出流线程任务,构造器要传入Socket socket=ss.accept()的实例对象,需要端口号和ip地址;

客户端:创建Socket()套接字对象,开启Thread

服务端:创建ServerSocket()套接字,并且调用accept()方法监听连接,开启Thread

二:基于UDP协议:
1.接收端向发送端发送数据
接收端:
  1. 创建DatagramSocket()对象,需要传入端口号(这里的端口号其实就是一个 标识)
  2. 创建一个数据报包DatagramSocket()用来缓存,DatagramSocket()有两个参数,一个是字节数组,一个是长度;这个需要自己确定;
  3. 调用数据包套接字DatagramSocket()的receive()方法,用来将数据报中的数据读取到缓存容器中,(为什么要有一个缓存区呢?这是因为我们读取数据只能在数据报中读取,而不是在数据包套接字中读取,所以需要将数据报套接字的数据通过receive()方法保存到数据报包中)
  4. 解析数据包,通过getData()方法获取数据报包缓存容器中的数据;
  5. 关闭连接;
发送端:
  1. 创建一个DatagramSocket()对象,不需要传入任何参数;
  2. 创建一个数据报包,DatagramSocket(),需要传入参数byte[]数组(存放数据)、数据长度、接收端ip、端口号;
  3. 调用DatagramSocket()的send()方法将数据报包发送出去;
  4. 关闭连接
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值