黑马程序员Java基础第十一章----网络编程

来源:互联网 发布:淘宝董事长 编辑:程序博客网 时间:2024/05/18 07:51

------<a href="http://www.itheima.com" target="blank">Java培训、Android培训、iOS培训、.Net培训</a>、期待与您交流! -------

一.网络基础

1.协议:设备之间进行数据传输和处理而约定的规则,就叫做协议。

2.协议分为自定义协议和国际标准协议两种。

3.自定义协议:个人或者团队之间为了特定的功能而私下或者内部使用的规则。

4.国际标准协议:规则被国际化组织承认,并且被大多数的设备厂商所遵循的规则叫做国际标准协议。

5.常见的协议有:232串口协议,485协议,TCP/IP协议,FTP协议,HTTP协议等。

6.同种设备间能够进行的通信和数据处理必须要遵循相同的协议,只有遵循了相同的协议,设备间发送的数据才能被识别和处理。

7.用于互联网通信的协议叫做TCP/IP协议。

7.各种上网设备能够互联互通就是遵循了相同的网络协议,但是用于互联的不同的设备厂商的实现方式各不相同,所以这是一件极其复杂的事情。为了不同的设备都能够应用于互联网中,国际化标准组织对网络做出了分层。上层不管下层的是怎么实现的只要下层提供上来的数据符合上层的处理规则,上层就可以处理数据。因此,简化了TCP/IP协议的复杂度,但是有了更多的协议,这些协议组成了网络通信协议族,每个协议只负责特定的功能。

8.TCP/IP分层常用的有两种:5层结构和7层结构。

5层结构从下往上:物理层,数据链路层,网络层,运输层,应用层。

7层结构从下往上:物理层,数据链路层,网络层,会话层,表示层,应用层。

二.TCP/IP协议(5层)

1.5层协议包括:物理层,数据链路层,网络层,运输层,应用层。

2.向外发送数据时,各层所做的事情:

1).应用层:将程序中的数据按照使用的协议的规则封装成数据。

2).运输层:将应用层封装的数据加上特定的头部和尾部(数据长度,数据校验,网络延时等相关)。

3).网络层:将运输层下来的数据加上特定的头部和尾部(目的ip和端口)。

4).数据链路层:将网络层的数据封装成帧

5).物理层:实实在在的网络传输线路,将链路层的数据变以二进制数据0和1,发到网络中

3.接收数据时,各层所做的事情:

1)物理层:从传输线路上接收数据

2)数据链路层:将物理层接收的数据封装成帧,并去掉链路层用的数据,向上提交

3)网络层:去掉链路层的帧数据中的网络层使用的数据,并向上提交

4)运输层:去掉网络层的数据中的运输层使用的数据,并交给对应的端口的引用程序

5)应用层:将对应端口中读出的数据,按照处理规则进行处理

4.各层常用的协议:

1)应用层:HTTP协议,FTP协议,SMTP协议,POP3协议,DNS协议

2)运输层协议:TCP协议,UDP协议

3)网络层协议:IP协议,ARP协议,RARP协议

5.常用协议的介绍:

1).HTTP协议:是一个基于请求响应机制的协议,底层使用的是socket。

2).SMTP协议:是一个邮件协议,用于发送邮件

3).POP3协议:也是一个邮件协议,用于从邮件服务器取邮件的协议

4).DNS协议:是一个从域名到ip地址转换的协议。进行网络通信时,先到域名服务器中查询域名对应的ip地址,再拿着地址去访问对应的主机。

5).IP协议:处于互联网中的每个终端都有一个全球唯一的标识,ip地址。为了解决ip地址不够用的问题,同一个局域网中的设备对外只使用一个公网ip,在内部使用局域网ip进行区分。

6).ARP协议:是硬件地址到ip地址的转换协议。

6.InetAddress类:这是一个IP地址的封装类,还可能封装了主机名(如果用主机名进行构造)。

1)这个类没有对外提供构造函数,只能通过类本身提供的方法获取本类的对象。

2)获取本机的IP地址:现获取本机的IP地址地址:String ip = InetAddress().getLocalHost().getHostAddress();

3)通过主机名称获取IP地址对象:InetAddress inetAddress = InetAddress().getByName();

4)通过主机名获取多个IP地址对象:InetAddress[] inetAddrs = InetAddress().getAllByName();因为可能一个域名对应了多个IP地址,比如说百度不止一台主机。

7.同一台主机中有多个网络通信的程序,我们为了能够正确的区分达到主机的消息是给哪个程序使用的,就给每个需要网络通信的程序都分配一个逻辑的端口来标识。不能多个程序同时绑定同一个端口,因为会发生冲突。端口号 的范围在0~65535之间,其中0~1024一般是留给系统程序使用的,建议尽量使用10000以后的端口,避免端口冲突。

7.InetSocketAddress类:IP套接字(ip地址+端口号)地址。在构造函数中可以使用主机名和端口号来构造,也可以使用ip地址和端口号来构造。例如:

ip地址和端口号构造:InetSocketAddress isa = new InetSocketAddress(inetAddress,port);

主机名和端口号构造:InetSocketAddress isa = new InetSocketAddress(hostName,port);

三.UDP协议

1.UDP协议的特点:

1)它是一个面向无连接的协议。

2)因为面向无连接的通信,所以效率高,速度快。

3)不保证可靠传输,不管通信的对方还在不在,本端点一直发数据,直到数据发送完毕,不管对方有没有收到数据。

4)一次传输的数据量小于64K

2.UDP的使用条件:UDP协议使用在那些不要求保证可靠传输,丢写数据也无所谓的地方。

3.UDP协议的数据传输方式:UDP协议将数据封装成数据包,然后将数据包发送到目的地。大数据量大于64K时,UDP自动将数据分成多个包进行封装发送。

4.UDP协议的应用场景:聊天软件,网络视频会议,在线视频等。

5.DatagramPacket类:这个类是数据包类。有两种用法:

1)将字节数组数据封装到数据包中,并且带上目的地的IP地址和端口号,使用UDP协议将数据发送到目的地。

示例:DatagramPacket packet = new DatagramPacket(buf,buf.length,inetAddress,port);//此时buf数组中装的是要通过数据包发送出去的数据

2)将空的字节数组封装成数据包,用来接收发送过来的数据包。

示例:DatagramPacket pcket = new DatagramPacket(buf,buf.length);//此时buf是一个空的字节数组,用于接收数据

3)获取数据缓冲区的数据:byte[] buf = packet.getData();

4)获取缓冲区中数据的长度:int len = packet.getLength();

5)获取本地或者远程主机的ip地址对象:InetAddress inetAddr = packet.getInetAddress();

6)获取远程主机的端口号:int port = packet.getPort();

6.DatagramSocket类:此类表示一个用于发送和接受数据包的套接字。

发送数据包:send(packet);将封装好的数据包发送到目的地。

接受数据包:receive(packet);从远端接收数据包放到空的字节数组中。这个方法是一个阻塞的方法。在接收到数据包前一直阻塞。length字段代表信息的长度,当信息的长度大于数据包的大小时信息将被截短。

示例程序:从键盘输入字母,发送给服务端,服务端返回大写字母给客户端。

import java.net.*;import java.io.*;class UdpClient{public static void main(String[] args){DatagramSocket ds = null ;BufferedReader br = null ;try{//创建用于发送和接收数据包的UDPds = new DatagramSocket(10000);//创建键盘读取流,并使用缓冲包装br = new BufferedReader(new InputStreamReader(System.in));String line = null ;while((line=br.readLine())!=null){//定义结束标记if("over".equals(line))break;byte[] data = line.getBytes() ;//将读取到的键盘数据封装成数据包,并在报上注明要发送到的地址和端口DatagramPacket dpS =                                                                                                 new DatagramPacket(data,data.length,InetAddress.getByName("192.168.2.66"),10002);//发送Udp数据包ds.send(dpS);byte[] buf = new byte[1024] ;//定义一个空的字节数组,用于接收数据,并将接收到到的数据封装成数据包对象,便于获取属性DatagramPacket dpR = new DatagramPacket(buf,buf.length);//获取数据,是一个阻塞的方法。没有数据就等待,直到读取到数据ds.receive(dpR);String msg = new String(dpR.getData(),0,dpR.getLength()) ;System.out.println("server:"+msg);}}catch (IOException e){throw new RuntimeException("获取键盘数据或者发送错误");}finally{try{if(br!=null)br.close();}catch (IOException e){throw new RuntimeException("关闭输入流失败");}try{if(ds!=null)ds.close();}catch (Exception e){throw new RuntimeException("关闭套接字是失败");}}}}class UdpServer {public static void main(String[] args) {DatagramSocket ds = null ;try{                                                                                                                          //监听10002端口,客户端发送数据到这个端口上,服务端才能收到ds = new DatagramSocket(10002);while(true){byte[] buf = new byte[1024] ;DatagramPacket dpR = new DatagramPacket(buf,buf.length);ds.receive(dpR);String msg = new String(dpR.getData(),0,dpR.getLength());System.out.println("client:"+dpR.getAddress().getHostAddress()+":"+msg);byte[] data = msg.toUpperCase().getBytes();DatagramPacket dpS =                                                                                                 new DatagramPacket(data,data.length,InetAddress.getByName("192.168.2.66"),10000);ds.send(dpS);}}catch (IOException e){throw new RuntimeException("接收或者发送数据失败");}finally{try{if(ds!=null)ds.close();}catch (Exception e){throw new RuntimeException("关闭套接字失败");}}}}

运行结果:


示例程序二:UDP多线程聊天程序

import java.io.*;import java.net.*;class Send implements Runnable{private DatagramSocket ds ;Send(DatagramSocket ds){this.ds = ds ;}public void run(){BufferedReader br = null ;try{br = new BufferedReader(new InputStreamReader(System.in));//创建Socket地址对象  ip+端口号  255代表的是网段的广播地址InetSocketAddress socketAddr =                                                                                                       new InetSocketAddress(InetAddress.getByName("192.168.2.255"),10006);String line = null ;while((line=br.readLine())!=null)//接收键盘的数据发送到广播地址{byte[] data = line.getBytes();DatagramPacket dp = new DatagramPacket(data,data.length,socketAddr);ds.send(dp);}}catch (IOException e){throw new RuntimeException("发送失败");}finally{try{if(ds!=null)ds.close();}catch (Exception e){throw new RuntimeException("关闭套接字失败");}try{if(br!=null)br.close();}catch (IOException e){throw new RuntimeException("关闭输入流失败");}}}}class Recv implements Runnable{private DatagramSocket ds ;Recv(DatagramSocket ds){this.ds = ds ;}public void run(){try{while(true){byte[] buf = new byte[1024] ;DatagramPacket dp = new DatagramPacket(buf,buf.length);ds.receive(dp);String msg = new String(dp.getData(),0,dp.getLength());String ip = dp.getAddress().getHostAddress();System.out.println(ip+":"+msg);}}catch (IOException e){throw new RuntimeException("接收数据失败");}finally{try{if(ds!=null)ds.close();}catch (Exception e){throw new RuntimeException("关闭套接字失败");}}}}class UdpChart{public static void main(String[] args){try{DatagramSocket send = new DatagramSocket();//发送数据线程socketDatagramSocket recv = new DatagramSocket(10006);//接收数据线程socket监听10006端口new Thread(new Send(send)).start();//发送消息线程new Thread(new Recv(recv)).start();//接收数据线程}catch (IOException e){throw new RuntimeException("创建套接字失败");}}}
四.TCP通信

1.TCP通信的特点:

1)面向连接的通信,要先经过三次握手,建立连接后才能通信,在连接通道内通信。

2)速度比UDP要慢,效率比UDP要低

3)是可靠的通信方式,保证接收端收到数据,有丢数据重传机制

2.TCP的使用条件:数据的传输必须保证可靠性,不能丢失数据。

3.TCP数据传输方式:建立连接后,在连接通道内进行数据传输,以流的形式。

4.TCP的使用场景:下载,在线升级等。

5.Socket类:

1)它是一个客户端的Socket,创建时要指明连接的地址和端口。如:

            Socket s = new Socket("192.168.2.66",10002);

     这句代码执行后,就和服务端建立了通信连接。连接建立后就有了Socket流,有输入流和输出流。

6.ServerSocket类:

1)这是一个服务端的Socket,创建时要指定服务端的Socket要监听哪一个端口,它只能处理这个端口来的请求和数据。

          ServerSocket  ss = new ServerSocket(10002);

2)服务端Socket在监听的端口检测到请求接受请求后就已经建立了通信连接,并且服务端会返回一个客户端Socket对象。

         Socket  s = ss.accept();//这是一个阻塞的方法

3)服务器端没有流对象,服务器拿着客户端的流和客户端进行通信。

         InputStream  in = s.getInputStream();//服务端从输入流接收客户端发过来的数据

         OutputStream out = s.getOutputStream();//服务端向输出流中发送数据到客户端

7.程序示例:写一个将文件上传到客户端保存的程序

import java.net.*;import java.io.*;class TcpClient{public static void main(String[] args){Socket s = null ;BufferedReader br = null ;BufferedWriter bw = null ;try{s = new Socket("192.168.2.66",10006);InputStream in = s.getInputStream();bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));br = new BufferedReader(new FileReader("g:\\1.txt"));String line = null ;while((line=br.readLine())!=null){bw.write(line);bw.newLine();bw.flush();}//告诉服务端数据已经上传完了,其实就是向流中写了一个-1s.shutdownOutput();byte[] buf = new byte[1024] ;int len = in.read(buf);System.out.println(new String(buf,0,len));}catch (IOException e){throw new RuntimeException("上传文件失败");}finally{try{if(br!=null)br.close();}catch (IOException e){throw new RuntimeException("关闭输入流失败");}try{if(s!=null)s.close();}catch (Exception e){throw new RuntimeException("关闭套接字失败");}}}}class TcpServer{public static void main(String[] args){ServerSocket ss = null ;Socket s = null ;BufferedReader br = null ;BufferedWriter bw = null ;try{ss = new ServerSocket(10006);s = ss.accept();OutputStream out = s.getOutputStream();br = new BufferedReader(new InputStreamReader(s.getInputStream()));bw = new BufferedWriter(new FileWriter("g:\\2.txt"));String line = null ;while((line=br.readLine())!= null){bw.write(line);bw.newLine();bw.flush();}byte[] data = "上传成功".getBytes();out.write(data,0,data.length);}catch (IOException e){throw new RuntimeException("存储文件失败");}finally{try{if(bw!=null)bw.close();}catch (IOException e){throw new RuntimeException("关闭输出流失败");}try{if(s!=null)s.close();}catch (Exception e){throw new RuntimeException("关闭套接字失败");}try{if(ss!=null)ss.close();}catch (Exception e){throw new RuntimeException("关闭套接字失败");}}}}

8.TCP通信的总结

1).建立了连接,就有了socket流,有输入流和输出流。

2).关闭资源只需要关闭socket即可,因为流是属于socket的,关闭了套接字,流也就关闭了。

3).服务端没有流对象,服务端拿着客户端的流与客户端通信。

4).服务端的ServerSocket的关闭时可选的。但在服务端要关闭客户端的流对象,避免浪费服务端的资源。

5).小心TCP中通信时的互相等待问题。read,readLine,accept都是阻塞的方法。UDP中的receive也是阻塞的方法。

6).对于read方法,没有读到数据会一直等待,直到有数据。所以一次通信一个来回可以结束,不会等待。但是传文件使用循环,发送端发完数据,接收端还要循环一次读取数据,读不到数据就阻塞。

7)对于readLine方法,没有读到回车符就一直阻塞,直到有回车符。所以一次通信一个来回,客户端不带回车符,服务端结束不了,客户端也接受不到服务端发送过来的数据致使客户端也等待。对于使用循环传文件,即使客户端带上回车符,客户端发送完数据,服务端也不知道客户端发送完了数据,因而造成了服务端的等待,客户端也因为接收不到服务端的数据而等待。

8)对于以上的情况我们要客户端通知服务端数据已经发送完毕。可以使用shutdownOutput和shutdownInpt方法。

四.URL和HTTP

1.URI:统一资源标识符。

2.URL:统一资源定位符,范围比URI要小。

3.我们上网输入的网址中就带有了URL。其实在浏览网页的过程中,浏览器通过HTTP请求了网络上的资源,底层走的还是socket,只不过应用层的Http协议对它进行了封装。

3.Http请求和接受都要发送一些额外的数据,这些数据不是给用户看的,是客户端浏览器和服务器交互用的,包括客户端能够接处理的类型,能够识别的语言,连接服务器的方式,请求那个资源,使用何种方式请求等。

4.Http协议中将发送给服务器请求中的附带信息叫做Http请求头,而发送到服务器中的数据叫做请求正文。

5.Http协议中将从服务器端获得的响应信息中附带的信息叫做Http响应头,而接收到的响应的数据叫做响应正文。

6.头和正文之间要有一个空行隔开,这是HTTP协议的规定。只有这样写的数据才能被HTTP解析。

7.当我们不是用HTTP协议,自定义一个浏览器使用传输层协议TCP来获取远程资源数据的方式,我们要显示的发送请求头,返回来的数据中又带有了响应头。但是,运输层协议的封装类Socket无法解析这些信息。所以就直接全部提交给了应用程序。应用程序在获取这些头中有用的信息很复杂,而用户却看到了这些自己看不懂的信息。例如:

import java.net.*;import java.io.*;class MyIE{public static void main(String[] args) throws Exception{Socket s = new Socket("192.168.2.66",80);PrintWriter out = new PrintWriter(s.getOutputStream(),true);//手动发送请求头信息out.println("GET /myweb/hello.html HTTP/1.1");out.println("Accept-Language: */*");out.println("Host: 192.168.2.66:80");out.println("Connection: Closed");out.println();out.println();System.out.println("发送成功");BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));String line = null ;while((line=br.readLine())!=null){System.out.println(line);}s.close();}}
8.为了能够更好的设置请求头和获取响应头数据,也为了提升用户体验,Java对它们进行了封装,提供了极为方便的方法来获取这些数据。封装类为URLConnection。也为了能够更好的处理资源定位符URL,而封装了URL类。

8.URL类:URL url = new URL(urlpath); 当传入的字符串无法解析时抛出异常,MalformedException 。

示例程序:获取url中的信息。

import java.net.*;import java.io.*;class URLTest {public static void main(String[] args) throws IOException{URL url = new URL("http://192.168.2.66:80/myweb/hello.html");System.out.println(url.getProtocol());//获取url所使用的协议System.out.println(url.getHost());//获取主机System.out.println(url.getPort());//获取端口System.out.println(url.getPath());//获取路径System.out.println(url.getFile());//获取文件,如果后面有请求参数信息,如: ?name=lili .包括参数信息System.out.println(url.getQuery());//获取请求参数信息,没有时返回null}}

9.URLConnection类:是一个到远程URL资源的连接,可以设置请求头信息和解析响应头信息。这个类是一个抽象类,不可以使用new来创建对象。它有一个子类HttpURLConnection。

获取连接对象可以使用:URLConnection conn = url.openConnection();来获取这个连接对象。

实际上底层得到的是它的子类HttpURLConnection对象,然后向上转型成了URLConnection对象,并将URLConnection返回。

所以我们也可以采用:HttpURLConnection  conn = (HttpURLConnection)url.openConnection();强制将对象转换回来成子类对象。

获取这个对象后就表示拿到了一个到远程的连接。使用这两个类我们获取远程数据有两种方式:

第一种:不调用connect(),直接获取头信息和响应体数据(底层会默认去连接connect(),使用的是默认的请求参数):

获取响应头数据: 使用使用conn对象的get方法获取

获取响应体数据:InputStream in = conn.getInputStream();

第二种:可以使用conn连接对象先使用set方法设置请求参数信息,在进行连接connect(),连接后在使用conn对象的get方法获取数据。

设置信息:使用set设置请求信息。

进行远程连接:调用connect方法连接远端

获取头信息:使用get方式获取

获取响应体数据:InputStream in = conn.getInputStream();

实例程序:

import java.net.*;import java.io.*;class URLTest {public static void main(String[] args) throws IOException{URL url = new URL("http://192.168.2.66:80/myweb/hello.html");                //获取远程连接对象,不进行参数设置,底层会默认调用connect()方法去连接URLConnection conn = url.openConnection();               InputStream in = conn.getInputStream();byte[] buf = new byte[1024] ;int len = 0 ;        //读取响应体数据while((len=in.read(buf))!=-1){System.out.println(new String(buf,0,len));}                //获取响应头信息中的contentLength字段的值System.out.println("length:"+conn.getContentLength());}}


0 0
原创粉丝点击