day23(上)_网络编程(UDP传输与TCP传输)
1.网络概述:
/* 两台主机之间通过QQ或飞秋进行通信: 1.找到对方IP 2.数据发送到对方指定的应用程序上.为了标识这些 应用程序,所以给这些网络应用程序用数字进行标识 为了方便称呼这个数字,叫做端口(逻辑端口) 例如: QQ ------->QQ 4000(数字标识:0~65535,0~1024被系统程序保留) FeiQ------->FeiQ 2900 类比:先找到具体几号楼(IP),在找楼里对应的教室(端口) 3.定义通信规则,通讯规则称为协议 例如:两人面对面交谈,约定使用共同语言(英语,汉语....) 国际组织定义了通用协议:TCP/IP 4.网络模型 (示意图) */OSI参考模型(七层)与TCP/IP参考模型(四层)
2.IP地址:
/* IP地址: 网络中设备的标识 不易记忆,可用主机名 本地回环地址:127.0.0.1,主机名:localhost 127.0.0.1的作用:(百度百科) 一是测试本机的网络配置, 能PING通127.0.0.1说明本机的网卡和IP协议安装都没有问题; 另一个作用是某些SERVER/CLIENT的应用程序在运行时需调用服务器上的资源, 一般要指定SERVER的IP地址,但当该程序要在同一台机器上运行而没有别的SERVER时就可以把SERVER的资源装在本机, SERVER的IP地址设为127.0.0.1同样也可以运行。 */ package ipdemo; import java.net.InetAddress;//此类表示互联网协议 (IP) 地址。 //IP 地址是IP使用的 32 位或 128 位无符号数字,它是一种低级协议,UDP 和 TCP 协议都是在它的基础上构建的。 import java.util.Arrays; class IPDemo{ public static void main(String[] args)throws Exception{ InetAddress ia=InetAddress.getLocalHost(); System.out.println(ia.toString());//UnknownHostException:指示主机 IP 地址无法确定而抛出的异常。 //返回InetAddress对象封装本地主机名+本地主机IP System.out.println(ia.getHostAddress()+"\n"+ia.getHostName()); } }
3.UDP,TCP,Socket概述:
/* 传输协议: 一.UDP:(User Datagram Protocol,用户数据报协议) 1.将数据及源和目的封装成数据包中,不需要建立连接 2.每个数据报的大小限制在64K内 3.因无连接,是不可靠协议(对方可能不在,源依然再发,数据丢失) 4.不需要建立连接(面向无连接),速度快 现实例子:1.寄包裹 我去邮局寄一本书-->把书打包,写明寄往的地方(地址),某某人收(端口) -->但是寄往的地方(地址)和该人(端口)都不一定存在-->丢掉该包 2.聊天/视频会议 UDP优点:传输速度快 缺点:可能丢包 二.TCP:(Transmission Control Protocol,传输控制协议) 1.建立连接,形成传输数据通道 2.在连接中进行大数据传输 3.通过三次握手完成连接,是可靠协议 4.必须建立连接,效率会稍低 例如:打电话 三.Socket(直译:插座,空,套接字,端) 通俗理解: Socket理解为港口,两个港口(A,B)想要进行通信 通过船在A装货(数据),运送到B卸货.先有的港口,再有的船,最后进行通信. 在通信之前必须两端先有Socket,有了Socket才能进行连接,之后有了数据传输的通路 1.Socket就是为网络服务提供的一种机制 2.通信的两端都有Socket 3.网络通信其实就是Socket间的通信 4.数据在两个Socket间通过IO传输 */
4.UDP传输:
/* UDP传输:DatagramSocket与DatagramPacket DatagramSocket: 此类表示用来发送和接收数据报包的套接字(Socket)。 在 DatagramSocket 上总是启用 UDP 广播发送。 DatagramPacket: 此类表示数据报包. 封装了源地址,目的地址,源端口,目的端口 */ /* 通过UDP传输方式,将一段文字数据发送出去 1.建立udpscoket服务(类比先找邮局) 2.提供数据,并将数据封装到数据包中(邮寄的东西,目的地址等等打包) 3.通过scoket服务发送功能,将数据包发送出去 4.关闭资源.(会用到底层系统资源,需要释放) */客户端:
package udp; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.DatagramPacket; import java.net.DatagramSocket; class UDPSend{ public static void main(String[] args)throws Exception{//在创建服务,发送都可能出现问题 //1.创建UDP服务,通过DatagramSocket对象 DatagramSocket ds=new DatagramSocket(); //2.提供数据,并封成数据包:DatagramPacket(byte[] buf, int length, InetAddress address, int port) byte[] buf="UDP".getBytes();//UDP以GBK编码 InetAddress ia=InetAddress.getLocalHost();//发送到本地主机 DatagramPacket dp=new DatagramPacket(buf,buf.length,ia,10000); //3.通过Socket将已有数据包发送出去 ds.send(dp); //4.关闭资源 ds.close(); } }服务端:
class UDPReceive{ public static void main(String[] args)throws Exception{ //创建UDP Socket,建立端点 DatagramSocket ds=new DatagramSocket(10000); //2.定义数据包,用于存储数据 byte[] buf=new byte[1024]; DatagramPacket dp=new DatagramPacket(buf,buf.length); //3.通过服务的receive方法收到数据存入数据包中 ds.receive(dp); //4.通过数据包的方法获取其中的数据 System.out.println(dp.getAddress().getHostAddress()+" "+ new String(buf,0,dp.getLength())+" "+ //将有效字节数以GBK解码成字符串,getLength在这里返回接收到的字节数据长度 dp.getPort());//发送方的主机IP地址+发送方发送的数据+发送方的端口 //5.关闭资源 ds.close(); } }/* 注意: 1. 发送应用程序: DatagramSocket ds=new DatagramSocket();//系统随机分配一个端口给接收应用程序 接收应用程序: DatagramSocket ds=new DatagramSocket(10000);//在这里给接收方应用程序一个标识10000 //创建数据报套接字并将其绑定到本地主机上的指定端口 //如果不指定数字标识(端口),它将接收不到,因为发送方指定了10000端口的应用程序去接收处理. //即使不指定,接收应用程序和发送应用程序均有数字标识(系统随机分配的) 2.ds.receive(dp); 从此套接字接收数据报包。 当此方法返回时,DatagramPacket 的缓冲区填充了接收的数据。 数据报包也包含发送方的 IP 地址和发送方机器上的端口号。 此方法在接收到数据报前一直阻塞。 数据报包对象的 length 字段包含所接收信息的长度。 如果信息比包的长度长,该信息将被截短。 3. 先运行发送端:发送端直接发送数据出去,可能丢包(接收方没运行),接收方接收到 先运行接收端:如果没有收到数据,将一直堵塞. 4. new DatagramSocket(10000); new DatagramSocket(10000); 不能把两个socket服务对象绑定到一个端口上 否则引发Exception in thread "main" java.net.BindException */需要在两个cmd下运行,因为发送端和接收端都是两个独立的可执行的程序,先运行哪一端均可,但是如果先运行发送端,它直接发送数据,这时候在开启接收端,接收不到已发送的数据,因此先运行接收端,在运行发送端即可:
示意图:
5.将发送数据改为键盘录入:
/* 将发送的数据改为键盘录入 接收方改为循环接收 */ package udp; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.IOException; import java.net.DatagramSocket; import java.net.DatagramPacket; import java.net.InetAddress; class UDPSend2{ public static void main(String[] args)throws Exception{ DatagramSocket ds=new DatagramSocket(); BufferedReader bfr=new BufferedReader(new InputStreamReader(System.in)); String data=null; while((data=bfr.readLine())!=null){ if(data.equals("886")) break; byte[] buf=data.getBytes(); DatagramPacket dp=new DatagramPacket(buf,buf.length,InetAddress.getByName("192.168.1.255"),10001);//主机IP地址:192.168.1.1~192.168.1.254都将收到 ds.send(dp); } ds.close(); } }class UDPReceive2{ public static void main(String[] args)throws Exception{ DatagramSocket ds=new DatagramSocket(10001); byte[] buf=new byte[1024];//发送的数据<=64K,也就是64*1024 while(true){ DatagramPacket dp=new DatagramPacket(buf,buf.length); ds.receive(dp); System.out.println(dp.getAddress().getHostAddress()+" "+dp.getPort()+" " +new String(buf,0,dp.getLength())); } } }
6.采用多线程将发送端与接收端在一个程序中运行:
/* 编写一个聊天程序 有收数据的部分,和发数据的部分 这两部分需要并发执行 ->多线程 一个线程控制收,一个控制发 未使用多线程,启动了两个cmd命令窗口,两个进程, 这两个进程里面至少有一个线程,控制进程执行. */ /*对刚才代码,封装到线程里面*/ package udp; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.IOException; import java.net.DatagramSocket; import java.net.DatagramPacket; import java.net.InetAddress; class Send implements Runnable{ private DatagramSocket ds; public Send(DatagramSocket ds){ this.ds=ds; } public void run(){ try{ BufferedReader bfr=new BufferedReader(new InputStreamReader(System.in)); String data=null; while((data=bfr.readLine())!=null){ if(data.equals("886")) break; byte[] buf=data.getBytes(); DatagramPacket dp=new DatagramPacket(buf,buf.length,InetAddress.getByName("192.168.1.101"), 10002);ds.send(dp); } ds.close(); } catch(Exception e){ throw new RuntimeException("发送失败"); } } } class Receive implements Runnable{ private DatagramSocket ds; public Receive(DatagramSocket ds){ this.ds=ds; } public void run(){ try{ byte[] buf=new byte[1024];//发送的数据<=64K,也就是64*1024 while(true){ DatagramPacket dp=new DatagramPacket(buf,buf.length); ds.receive(dp); System.out.println(dp.getAddress().getHostAddress()+" "+dp.getPort()+" " +new String(buf,0,dp.getLength())); } } catch(Exception e){ throw new RuntimeException("接收失败"); } } } class UDPTalk{ public static void main(String[] args)throws Exception{ new Thread(new Send(new DatagramSocket())).start(); new Thread(new Receive(new DatagramSocket(10002))).start(); } }
7.TCP传输
/* TCP传输: Socket和ServerSocket 1.建立客户端和服务器端 2.建立连接后,通过Socket中的IO流进行数据的传输 3.关闭Socket 4.同样,客户端与服务端是两个独立的应用程序 Socket: 此类实现客户端套接字(也可以就叫“套接字”)。套接字是两台机器间通信的端点。 ServerSocket: 此类实现服务器套接字。服务器套接字等待请求通过网络传入。 它基于该请求执行某些操作,然后可能向请求者返回结果。 */示例:
客户端:
package tcp; import java.net.Socket; import java.net.ServerSocket; import java.io.InputStream; import java.io.OutputStream; class Client{ public static void main(String[] args)throws Exception{ //1.创建客户端的scoket服务,指定目的的主机和端口 Socket s=new Socket("192.168.1.101",10002);// Socket(String host, int port) /* 因为TCP是面向连接的,所以在建立socket服务时, 就要有服务端存在,并连接成功.形成通路后,在该通道进行数据传输 */ //2.为了发送数据,应该获取socket流中的输出流.OutputStream os=s.getOutputStream(); os.write("TCP".getBytes()); s.close(); } }服务端:
/* 为了避免冲突(服务端把应该发送A的数据发送给了B) 在连接成功后,服务端通过客户端的流对象进行通信 1.建立服务端的Socekt服务,ServerSocket(). 2.获取连接过来的客户端对象 通过ServerSocket的accept方法,没有建立连接,此方法将一直堵塞 3.客户端如果发过来数据,那么服务端要使用对应的客户端对象,并获取到该客户端 对象的读取流读取发过来的数据 4.关闭服务端(一般不关闭,在一个客户端连接结束,还有其它客户端) */ class Server{ public static void main(String[] args)throws Exception{ //1.建立服务端的Socket服务 ServerSocket ss=new ServerSocket(10002);//绑定10002端口 //2.通过accept方法获取连接过来的客户端对象 Socket s=ss.accept(); String ip=s.getInetAddress().getHostAddress();//此套接字连接到的远程 IP 地址 即 客户端主机的IP地址 int port=s.getPort();//返回此套接字连接到的远程端口 即 客户端的端口 //3.获取客户端发送过来的数据,那么要使用客户端对象的读取流来读取数据 InputStream is=s.getInputStream(); byte[] buf=new byte[1024]; int bytes=is.read(buf); System.out.println(ip+" "+port+" "+new String(buf,0,bytes)); ss.close();//如果仅进行一次通讯,关闭服务端 s.close();//关闭连接的客户端,不关浪费服务端资源 } }/*
注意:
必须先运行服务端程序,在运行客户端程序
这是因为TCP通信之前,必须建立连接成功,如果不先开启服务端,客户端无法连接成功*/
示意图:
8.服务端反馈信息:
/* 客户端给服务端发送数据,服务端收到后,给客户端反馈信息 */ package tcp; import java.net.Socket; import java.net.ServerSocket; import java.io.InputStream; import java.io.OutputStream; import java.io.*; class Client2{ public static void main(String[] args)throws Exception{ Socket s=new Socket("192.168.1.101",10002); //客户端向服务端发送数据 OutputStream os=s.getOutputStream(); os.write("服务端你好".getBytes()); //客户端接收服务端发送(反馈)数据 InputStream is=s.getInputStream(); byte[] buf=new byte[1024]; int bytes=is.read(buf);//read()为堵塞方法,如果流中没有数据,该方法一致等待, //也就是说服务端未发送数据,一直堵塞 System.out.println(new String(buf,0,bytes)); s.close(); } }class Server2{ public static void main(String[] args)throws Exception{ ServerSocket ss=new ServerSocket(10002); //服务端接收客户端发来的数据 Socket s=ss.accept(); String ip=s.getInetAddress().getHostAddress(); InputStream is=s.getInputStream(); byte[] buf=new byte[1024]; int bytes=is.read(buf); System.out.println(ip+" "+new String(buf,0,bytes)); //服务端向客户端发送数据 OutputStream os=s.getOutputStream(); os.write("我已收到".getBytes()); s.close();//关闭客户端,防止浪费服务端资源 ss.close();//关闭服务端 } }
9.TCP练习:
/* 将客户端发送的小写字母在服务端转换成大写后反馈给客户端 客户端: 源:键盘录入 目的:Socket输出流 操作的是文本数据,使用字符流操作方便 服务端: 源:Socket读取流 目的:Socket写入流 操作的是文本数据,使用字符流操作方便 */ package tcp; import java.net.Socket; import java.net.ServerSocket; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.InputStreamReader; import java.io.OutputStreamWriter; class TransClient{ public static void main(String[] args)throws Exception{ Socket s=new Socket("192.168.1.101",10003); BufferedReader bfr=new BufferedReader(new InputStreamReader(System.in)); BufferedWriter bfwOut=new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));//对Socket的输出流使用缓冲技术 BufferedReader bfrIn=new BufferedReader(new InputStreamReader(s.getInputStream()));//读取服务器的反馈信息 String line=null; while((line=bfr.readLine())!=null){ if(line.equals("over")) break; bfwOut.write(line); bfwOut.newLine();//如果不写入行终止符,导致服务端的bfrIn.readLine()一直堵塞 bfwOut.flush();//写入缓冲区的数据需要刷新System.out.println(bfrIn.readLine());//打印服务端发回的反馈信息 } bfr.close(); s.close(); } }class TransServer{ public static void main(String[] args)throws Exception{ ServerSocket ss=new ServerSocket(10003); Socket s=ss.accept(); String ip=s.getInetAddress().getHostAddress(); System.out.println(ip+" Connect Success"); BufferedReader bfrIn=new BufferedReader(new InputStreamReader(s.getInputStream())); BufferedWriter bfwOut=new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); String line=null; while((line=bfrIn.readLine())!=null){ System.out.println(line);//打印客户端发过来的信息 bfwOut.write(line.toUpperCase()); bfwOut.newLine();//如果不写入行终止符,导致客户端的bfrIn.readLine()方法一直堵塞bfwOut.flush(); } s.close(); ss.close(); } }
/* 注意: 1.为什么客户端输入over,服务器端也结束? 客户端的s.close();在Socket流中加了一个-1(结束标记) 服务端的bfrIn.readLine()底层调用read方法,读到结束标记 表示读到流末尾,循环结束 2.使用PrintWriter/PrintStream简化代码 BufferedWriter bfwOut=new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));//对Socket的输出流使用缓冲技术 bfwOut.write(line); bfwOut.newLine();//如果不写入行终止符,导致服务端的bfrIn.readLine()一直堵塞 bfwOut.flush();//写入缓冲区的数据需要刷新 PrintWriter pw=new PrintWriter(Socket.getOutputStream(),true)//自动刷新 pw.println(line.toUpperCase()); */
10.TCP上传文件:
package tcp; import java.net.Socket; import java.net.ServerSocket; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.PrintWriter; import java.io.InputStreamReader; import java.io.FileReader; import java.io.FileWriter; class TCPUpLoadClient{ public static void main(String[] args)throws Exception{ Socket s=new Socket("192.168.1.101",10003); BufferedReader buf=new BufferedReader(new FileReader("C:\\Users\\ZhangHongQ\\Desktop\\Client.txt")); PrintWriter pw=new PrintWriter(s.getOutputStream(),true); String line=null; while((line=buf.readLine())!=null) pw.println(line); s.shutdownOutput(); BufferedReader bufIn=new BufferedReader(new InputStreamReader(s.getInputStream())); System.out.println(bufIn.readLine()); buf.close(); s.close(); } }class TCPUpLoadServer{ public static void main(String[] args)throws Exception{ ServerSocket ss=new ServerSocket(10003); Socket s=ss.accept(); PrintWriter pw=new PrintWriter(new FileWriter("C:\\Users\\ZhangHongQ\\Desktop\\Server.txt"),true); BufferedReader bufIn=new BufferedReader(new InputStreamReader(s.getInputStream())); String line=null; while((line=bufIn.readLine())!=null) pw.println(line); pw=new PrintWriter(s.getOutputStream(),true); pw.println("上传成功"); s.close(); ss.close(); } }/* 以上程序运行发现客户端和服务端均等待: 客户端在bufIn.readLine()等待, 而服务端由于客户端pw.println(line);并未写入任何结束标记 导致bufIn.readLine()一直等待读入下一行 解决:在客户端读完文件后,加入s.shutdownOutput();语句:关闭客户端输出流 相当于在OutputStream流末尾加入结束标记(-1) */