@w460461339
2016-11-15T23:10:15.000000Z
字数 10116
阅读 960
Java基础
今天,哦不,是这几天断断续续把Java的网络编程给学习了= - 效率太低了。Anyway,开始吧。
一共有七层,从上到下分别为:
应用层
表示层
会话层
传输层
网络层
数据链入口层
物理层
我们主要是在应用层层面进行编程。
网络通信主要有三要素,分别为:IP地址,端口号以及协议。
IP地址是在网络中确认电脑的唯一地址,它帮助我们找到电脑;
端口号是应用程序在该电脑上的地址,它帮助我们在某电脑上找到特定的应用程序;
协议则是传输数据的方式,帮助我们的应用程序确认以哪种方式来理解数据。
网络中计算机的唯一标识。
计算机只能识别二进制的数据,所以我们的IP地址应该是一个二进制的数据。
但是呢,我们配置的IP地址确不是二进制的,为什么呢?
IP:192.168.1.100
换算:11000000 10101000 00000001 01100100
假如真是:11000000 10101000 00000001 01100100的话。
我们如果每次再上课的时候要配置该IP地址,记忆起来就比较的麻烦。
所以,为了方便表示IP地址,我们就把IP地址的每一个字节上的数据换算成十进制,然后用.分开来表示:
"点分十进制"
IP地址的组成:网络号段+主机号段
A类:第一号段为网络号段+后三段的主机号段
一个网络号:256*256*256 = 16777216
B类:前二号段为网络号段+后二段的主机号段
一个网络号:256*256 = 65536
C类:前三号段为网络号段+后一段的主机号段
一个网络号:256
IP地址的分类:
A类 1.0.0.1---127.255.255.254 (1)10.X.X.X是私有地址(私有地址就是在互联网上不使用,而被用在局域网络中的地址) (2)127.X.X.X是保留地址,用做循环测试用的。
B类 128.0.0.1---191.255.255.254 172.16.0.0---172.31.255.255是私有地址。169.254.X.X是保留地址。
C类 192.0.0.1---223.255.255.254 192.168.X.X是私有地址
D类 224.0.0.1---239.255.255.254
E类 240.0.0.1---247.255.255.254
两个DOS命令:
ipconfig 查看本机ip地址
ping 后面跟ip地址。测试本机与指定的ip地址间的通信是否有问题
特殊的IP地址:
127.0.0.1 回环地址(表示本机)
x.x.x.255 广播地址
x.x.x.0 网络地址
InetAddress获取本机IP地址
import java.io.IOException;import java.net.InetAddress;public class MyInetDemo {/*** @param args*/public static void main(String[] args) throws IOException{//以主机名字获取其InetAddress对象。//注意,它没有构造方法,也不是单一实例模式//仅能通过调用它的静态方法来创建它的实例InetAddress id=InetAddress.getByName("WuZifan");//获取主机的名字String name=id.getHostName();//获取主机IP地址String ipadd=id.getHostAddress();//输出System.out.println(name+"--"+ipadd);}}
正在运行的程序的标识。
有效端口:0~65535,其中0~1024系统使用或保留端口。
通信的规则
UDP:
把数据打包
数据有限制
不建立连接
速度快
不可靠
TCP:
建立连接通道
数据无限制
速度慢
可靠
举例:
UDP:发短信
TCP:打电话
首先,想清楚发送和接收数据需要确认的内容:
数据以及数据长度
IP地址以及端口号
在UDP中,发送和接收都需要通过DatagraSocket来进行(相当于电台);而数据都需要通过DatagramPacket来打包(相当于信封)。
为了方便理解,先写发送端
发送端
package net_02;import java.io.IOException;import java.net.DatagramPacket;import java.net.DatagramSocket;import java.net.InetAddress;public class MyUDPSend {//要抛IOEceptionpublic static void main(String[] args) throws IOException{//创建UDP的Socket对象DatagramSocket ds=new DatagramSocket();//封装要打包的数据//用这条语句:DatagramPacket(byte[] buf, int length, InetAddress address, int port)//要打包的数据byte[] bys="hello,world".getBytes();//获取长度int len=bys.length;//封装目标主机的IPInetAddress id=InetAddress.getByName("WuZifan");//封装端口int port=22222;//创建DatagramPacket对象DatagramPacket dp=new DatagramPacket(bys, len, id, port);//发送数据ds.send(dp);//关闭ds.close();}}
接收端
package net_02;import java.io.IOException;import java.net.DatagramPacket;import java.net.DatagramSocket;public class MyUDPRec {public static void main(String[] args)throws IOException {//创建接收的Socket对象,并指定端口(这里和发送端不一样了!)DatagramSocket ds=new DatagramSocket(22222);//创建接收的Datagrampacket对象byte[] bys=new byte[1024];int len=bys.length;DatagramPacket dp=new DatagramPacket(bys, len);//接收数据//注意此时数据并不会放到之前的bys和len中,需要另外创建//字节数组和int变量,通过getData和getLength方法啦获得ds.receive(dp);//显示数据byte[] buf=dp.getData();int length=dp.getLength();String data=new String(buf,0,length);System.out.println(data);//关闭ds.close();}}
基本思想使用线程将发送和接收端封装,使得可以进行持续的发送和接收
测试类
package net_05;import java.io.IOException;import java.net.DatagramSocket;public class MyChat {/*** 任务:* 创建聊天室* 实现:* 创建发送端和接收端的datagramsocket对象* 创建多线程* 打开多线程*/public static void main(String[] args) throws IOException{//创建datagramsocket对象//注意这里传给dsReve的端口号要和st里面的端口号一样DatagramSocket dsSend=new DatagramSocket();DatagramSocket dsReve=new DatagramSocket(22222);//创建多线程SendThread st=new SendThread(dsSend);ReveThread rt=new ReveThread(dsReve);Thread send=new Thread(st);Thread reve=new Thread(rt);//打开线程send.start();reve.start();}}
发送端
package net_05;import java.net.DatagramPacket;import java.net.DatagramSocket;import java.net.InetAddress;import java.util.Scanner;public class SendThread implements Runnable{//创建发送端DatagramSocket对象private DatagramSocket ds;public SendThread(DatagramSocket ds){this.ds=ds;}//多线程要运行的程序@Overridepublic void run() {//从键盘录入数据,并为打包进DatagramPacket做准备Scanner in=new Scanner(System.in);byte[] bys=new byte[1024];int len=0;while(true){//这里选择利用try-catch来处理异常try{String str=in.nextLine();//自定义结束符号if(str.equals("bye")){break;}else{//读取数据以及长度bys=str.getBytes();len=bys.length;//打包DatagramPacket dp=new DatagramPacket(bys, len,InetAddress.getByName("WuZifan"),22222);//发送ds.send(dp);}}catch(Exception e){e.printStackTrace();}}//注意这个关闭要放在while外面ds.close();}}
接收端
package net_05;import java.net.DatagramPacket;import java.net.DatagramSocket;public class ReveThread implements Runnable{//创建接收端DatagramSocket对象private DatagramSocket ds;public ReveThread(DatagramSocket ds){this.ds=ds;}@Overridepublic void run() {//创建接收端接收数据需要的包byte[] bys=new byte[1024];int len=bys.length;DatagramPacket dp=new DatagramPacket(bys, len);while(true){try{//获取数据包ds.receive(dp);//获取数据以及长度,并转为Stringbyte[] bys2=dp.getData();int len2=dp.getLength();String str=new String(bys2, 0, len2);//输出发送主机名以及数据System.out.println(dp.getAddress().getHostName()+"--"+str);}catch(Exception e){e.printStackTrace();}}}}
这里需要注意的一点是,因为接收端不会关闭,所以在运行测试类的时候,哪怕输入了bye这个自定义结束符号,测试类也不会关闭,因为接收端的线程还在一直开启。
ICP和UDP除了是否需要确定接收以外,在代码层面上最大的不同就是,UDP利用DatagramPacket封装了数据,并直接利用DatagramSocket的send和receive方法来封装了发送和接收的方法,让我们不知道底层是什么;而ICP则很明确的告诉我们,他们是利用IO流来进行数据传输的。
这次我们先写发送端
发送端
package net_03;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.net.ServerSocket;import java.net.Socket;public class MyICPServer {/*** 服务器端创建步骤:* A:创建服务器端Socket对象(seversocket)* B:获取输入流,输出* C:关闭*/public static void main(String[] args)throws IOException {// TODO Auto-generated method stub//创建服务器端socket对象ServerSocket ss=new ServerSocket(22222);//事件监听,在这一步程序阻塞,直到有数据传来Socket sk=ss.accept();//获取输入流,这里用高效的字符流获取BufferedReader br=new BufferedReader(new InputStreamReader(sk.getInputStream()));String str=br.readLine();System.out.println(str);//字节流接收并打印到控制台//byte[] bys=new byte[1024];//int len=0;//InputStream is=sk.getInputStream();//len=is.read(bys);//String str=new String(bys,0,len);//System.out.println(str);//关闭ss.close();}}
客户端
package net_03;import java.io.BufferedWriter;import java.io.IOException;import java.io.OutputStream;import java.io.OutputStreamWriter;import java.net.InetAddress;import java.net.Socket;public class MyICPClient {/*** 客户端创建步骤:* A:创建发送端的Socket对象* B:获取输出流,写入数据* C:释放资源*/public static void main(String[] args) throws IOException {// TODO Auto-generated method stub//创建发送端Socket对象,指定IP,以及端口号InetAddress id=InetAddress.getByName("WuZifan");Socket sc=new Socket(id,22222);//获取输出流对象(这里用高效的字符流接收)//创建方式:高效字符流对象=高效字符流(字符字节转换流(字节流))BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(sc.getOutputStream()));//写入数据bw.write("Hello,ICP");bw.flush();//这个刷新一定不要忘了,不然就读不到了= -//字节流方法写入数据//OutputStream os=sc.getOutputStream();//os.write("hello,ICP".getBytes());//关闭,因为bw依赖sc,直接关闭sc就好sc.close();}}
这里的关键是如何处理多次录入的多条数据;在客户端较好解决,添加自定义结束字符,每次一行行的发送就好;在客户端,也是每次一行行读即可。
但是需要理解这个,就需要理解两个阻塞式方法,一个是ServerSocket的accept方法,一个就是字符/字节流的read或者readLine方法。accept()阻塞是为了等待连接信号,一旦连通了,就往下走;read()或者readLine()阻塞是为了等待数据的进入,一旦有数据进来,程序就往下走。
就是这样
客户端
package net_04;import java.io.BufferedWriter;import java.io.IOException;import java.io.OutputStreamWriter;import java.net.InetAddress;import java.net.Socket;import java.util.Scanner;public class MyICPClient {public static void main(String[] args) throws IOException{//创建客户端Socket对象Socket sk=new Socket(InetAddress.getByName("WuZifan"),22222);//键盘输入Scanner in=new Scanner(System.in);//封装输入流BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(sk.getOutputStream()));String str=null;//持续输入数据while(true){str=in.nextLine();//直到输入bye时停止输入if(str.equals("bye")){break;}else{//注意write,newLine以及flush,三者缺一不可。bw.write(str);bw.newLine();bw.flush();}}//关闭sk.close();}}
服务器
package net_04;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.net.ServerSocket;import java.net.Socket;public class MyICPServer {public static void main(String[] args) throws IOException{//创建服务器端Socket对象ServerSocket ss=new ServerSocket(22222);//监听//阻塞式方法,这个方法判断是否有客户端连接//一旦连接上,就往下继续进行(连接上和有数据过来是两个概念)Socket sk=ss.accept();//封装输入数据BufferedReader br=new BufferedReader(new InputStreamReader(sk.getInputStream()));String str=null;//这个readLine或者read在这里都是阻塞式方法//在没有数据过来时一直停着,当数据过来后才继续往下//当客户端输入bye时,没有数据继续录入,传入一个空行,结束while((str=br.readLine())!=null){System.out.println(str);}//关闭sk.close();}}
上传文件需要注意的有两点:
1、最后记得刷新,以免数据丢失
2、记得shutdown输出流,告诉服务器上传结束
反馈主要就是服务器通过输入流写入,客户端通过输出流读取
客户端
package net_06;import java.io.BufferedInputStream;import java.io.BufferedOutputStream;import java.io.BufferedReader;import java.io.FileInputStream;import java.io.IOException;import java.io.InputStreamReader;import java.net.InetAddress;import java.net.Socket;public class MyClientDemoR {/*** @param args*/public static void main(String[] args) throws IOException{//创建客户端处Socket对象Socket sk=new Socket(InetAddress.getByName("WuZifan"), 22222);//获取输出流BufferedOutputStream bis=new BufferedOutputStream(sk.getOutputStream());//获取图片文件,并写入(这个需要再复习一下啊)//FileinputStream是InputStream的子类BufferedInputStream mypic=new BufferedInputStream(new FileInputStream("my.jpg"));byte[] bys=new byte[1024];int len=0;while((len=mypic.read(bys))!=-1){bis.write(bys,0,len);}//当之前读入到最后时,还有一部分数据在缓冲区,没有刷进流中//需要强制刷新一下,让最后的数据进入流bis.flush();//在读入数据时可以通过len是否为-1来判断读入是否结束//但在服务器端接收时却无法读到这个最后的信息。需要人为给一个信息,告诉服务器端我读完了//通过shutdown方法来告诉服务器,上传完成了sk.shutdownOutput();//获取输入流,并显示反馈BufferedReader br=new BufferedReader(new InputStreamReader(sk.getInputStream()));String str=br.readLine();//阻塞式System.out.println(str);//关闭mypic.close();sk.close();}}
服务器
package net_06;import java.io.BufferedInputStream;import java.io.BufferedOutputStream;import java.io.BufferedWriter;import java.io.FileOutputStream;import java.io.IOException;import java.io.OutputStreamWriter;import java.net.ServerSocket;import java.net.Socket;public class MySeverDemoR {/*** @param args*/public static void main(String[] args) throws IOException {//创建服务器端Socket对象ServerSocket ss=new ServerSocket(22222);Socket sk=ss.accept();//创建被输出对象(多复习啊!!)BufferedOutputStream newpic=new BufferedOutputStream(new FileOutputStream("new.jpg"));//获取输入流,并显示BufferedInputStream bis=new BufferedInputStream(sk.getInputStream());//读取,并写入文件byte[] bys=new byte[1024];int len=0;while((len=bis.read(bys))!=-1){//结束标志是-1,不是0!newpic.write(bys, 0, len);}//获取输出流,并给与回馈BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(sk.getOutputStream()));bw.write("收到了");bw.newLine();bw.flush();//关闭newpic.close();sk.close();}}