• 黑马程序员——网络编程



     

    网络编程

    1、网络参考模型

      网络参考模型是控制网络的规范,所有的公司使用这个相同的规范来控制网络,就能互联了。最流行的参考模型有:OSI参考模型和TCP/IP参考模型。

      两种网络参考模型的结构示意图如下:

      

      网络中的数据通信是通过层与层之间封包、解封包的过程。

      

      A机器的数据经过逐层封装,传到B机器,再经过逐层解封,B拿到了最终传输的数据。

      OSI参考模型虽然设计精细,但过于麻烦,效率不高,因此才产生了简化版的TCP/IP参考模型。

    2、网络通讯要素

      网络通讯有三要素:IP地址,端口号,传输协议

      一、IP地址,对应java中的InetAddress类。

        1、ip地址是网络中设备的标识。不易记忆,可用主机名。

        2、在没有连接互联网的情况,为了让访问本机方便,所以分配了一个默认的IP地址,也就是本地回环。本地回环地址:127.0.0.1 主机名:localhost。

        3、通过ping 127.0.0.1可以测试网络是不是通,如果不通,可能是网卡出问题了。

        InetAddress类无构造函数,可通过getLocalHost()方法获取InetAddress对象,此方法是静态的,返回本类对象。

                      InetAddress i = InetAddress.getLocalHost();

          常用方法:

                    1)static InetAddress getByName(String host):获取指定主机的IP和主机名。(最好用ip地址去获取,主机名需要解析)

                    2)static InetAddress[] getAllByName(String host):在给定主机名的情况下,根据系统上配置的名称服务返回IP地址所组成的数组。返回对象不唯一时,用此方法。

                    3)String getHostAddress():返回IP地址字符串文本形式,以IP地址为主。

                    4)String getHostName():返回IP地址主机名。

      二、端口号

        端口号:用于标识进程(应用程序)的逻辑地址,不同进程的标识。有效端口:0~65535,其中0~1024系统使用或保留端口。

      三、传输协议

        传输协议就是通讯的规则。常见协议:UDP、TCP。

        UDP:

          1、将数据及源和目的封装成数据包中,不需要建立连接。

          2、每个数据报的大小在限制在64k内。

          3、因无连接,是不可靠协议。

          4、不需要建立连接,速度快。

          应用案例:QQ、FeiQ聊天、在线视频用的都是UDP传输协议。

        TCP:

          1、建立连接,形成传输数据的通道。

          2、在连接中进行大数据量传输。

          3、通过三次握手完成连接,是可靠协议

          4、必须建立连接,效率会稍低。

          应用案例:FTP,File Transfer Protocol(文件传输协议) 。

      关于域名解析的小知识:域名解析,最先走是本地的hosts(C:WINDOWSsystem32driversetchosts)文件,解析失败了,才去访问DNS服务器解析、获取IP地址。通过hosts文件可以屏蔽垃圾网站内容弹出,例如:在hosts文件中添加,127.0.0.1 www.game18.com。

      Socket就是为网络服务提供的一种机制。通信的两端都有Socket。网络通信其实就是Socket间的通信。数据在两个Socket间通过IO传输。

    3、UDP协议

      ①:只要是网络传输,必须有socket 。

      ②:数据一定要封装到数据包中,数据包中包括目的地址、端口、数据等信息。

      直接操作UDP很复杂,java语言将UDP封装成对象,易于我们的使用,这个对象就是DatagramSocket. 封装了UDP传输协议的socket对象。 因为数据包中包含的信息较多,为了操作这些信息方便,也一样会将其封装成对象。这个数据包对象就是:DatagramPacket.通过这个对象中的方法,就可以获取到数据包中的各种信息。 DatagramSocket具备发送和接受功能,在进行UDP传输时,需要明确一个是发送端,一个是接收端。

      UDP的发送端:

        ①:建立udp的socket服务,创建对象时如果没有明确端口,系统会自动分配一个未被使用的端口。

        ②:明确要发送的具体数据。

        ③:将数据封装成了数据包。

        ④:用socket服务的send方法将数据包发送出去。

        ⑤:关闭资源。

      UDP的接收端:

        ①:创建udp的socket服务,必须要明确一个端口,作用在于,只有发送到这个端口的数据才是这个接收端可以处理的数据。

        ②:定义数据包,用于存储接收到数据。

        ③:通过socket服务的接收方法将收到的数据存储到数据包中。

        ④:通过数据包的方法获取数据包中的具体数据内容,比如ip、端口、数据等等。

        ⑤:关闭资源。

     1 import java.net.*;
     2 //UDP发送端
     3 class UDPDemoSend 
     4 {
     5     public static void main(String[] args) throws Exception
     6     {
     7         
     8         System.out.println("发送端启动!");
     9         DatagramSocket ds=new DatagramSocket();//创建UDP服务
    10         String str="Hello!UDP";
    11         //构建数据包
    12         DatagramPacket dp=new DatagramPacket(str.getBytes(),str.length(),InetAddress.getByName("192.168.1.103"),10000);
    13         //发送数据包
    14         ds.send(dp);
    15         //关闭socket服务
    16         ds.close();
    17     }
    18 }
    19 //UDP接收端
    20 class UDPDemoReceive 
    21 {
    22     public static void main(String[] args) throws Exception
    23     {
    24         System.out.println("接收端启动!");
    25         DatagramSocket ds=new DatagramSocket(10000);//创建UDP服务,接收端要指定端口
    26         byte[] buf=new byte[1024];
    27         DatagramPacket dp=new DatagramPacket(buf,buf.length);//创建数据包,接收端不用指定具体的接收端信息
    28         ds.receive(dp);//接收数据。
    29         String ip=dp.getAddress().getHostAddress();//获取IP
    30         int port =dp.getPort();//获取端口
    31         String data=new String(dp.getData(),0,dp.getLength());//获取数据
    32         System.out.println(ip+":"+port+":"+data);
    33         ds.close();//关闭数据流
    34     }
    35 }

    运行结果如图所示:

      由于UDP协议传输数据,只管发送数据,而不管接收端是否能够接收到数据。因此,应该首先启动接收端程序,再启动发送端程序。

      下面写一个复杂一点的UDP程序,通过线程来做接收和发送,在同一个窗口中实现聊天。

     1 import java.net.*;
     2 import java.io.*;
     3 //发送线程
     4 class UDPSend implements Runnable
     5 {
     6     private DatagramSocket ds;
     7     UDPSend(DatagramSocket ds)
     8     {
     9         this.ds=ds;
    10     }
    11     public void run()
    12     {
    13         DatagramPacket dp=null;
    14         try
    15         {
    16             //创建控制台录入的流
    17             BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
    18             for(String line=null;(line=br.readLine())!=null;)
    19             {
    20                 //创建发送的数据包
    21                 dp=new DatagramPacket(line.getBytes(),line.length(),InetAddress.getByName("192.168.1.103"),10001);
    22                 ds.send(dp);
    23                 if(line.equals("886"))
    24                     break;
    25             }
    26             br.close();
    27             ds.close();//记得关流
    28         }
    29         catch (Exception e)
    30         {
    31             throw new RuntimeException();
    32         }
    33         
    34     }
    35 }
    36 //接收线程
    37 class UDPReceive implements Runnable
    38 {
    39     private DatagramSocket ds;
    40     UDPReceive(DatagramSocket ds)
    41     {
    42         this.ds=ds;
    43     }
    44     public void run()
    45     {
    46         DatagramPacket dp=null;
    47         try
    48         {
    49             while(true)
    50             {
    51                 //BufferedReader br=new BufferedReader(new InputStream(System.in));
    52                 byte[] buf=new byte[1024];
    53                 dp=new DatagramPacket(buf,buf.length);
    54                 ds.receive(dp);
    55                 String ip=dp.getAddress().getHostAddress();
    56                 int port =dp.getPort();
    57                 String data=new String(dp.getData(),0,dp.getLength());
    58                 System.out.println(ip+":"+port+"-"+data);
    59                 if(data.equals("886"))
    60                     System.out.println(ip+"退出聊天室!");//数据解析,如果是886就表明退出。
    61             }
    62             //ds.close();
    63         }
    64         catch (Exception e)
    65         {
    66             throw new RuntimeException();
    67         }
    68         
    69     }
    70 }
    71 class UDPDemo 
    72 {
    73     public static void main(String[] args) throws Exception
    74     {
    75         DatagramSocket dsSend=new DatagramSocket();
    76         DatagramSocket dsReceive=new DatagramSocket(10001);
    77 
    78         new Thread(new UDPReceive(dsReceive)).start();
    79         new Thread(new UDPSend(dsSend)).start();
    80 
    81 
    82 
    83     }
    84 }

    运行结果如下:

    4、TCP协议

       TCP传输

      两个端点的建立连接后会有一个传输数据的通道,这通道称为流,而且是建立在网络基础上的流,称之为socket流。该流中既有读取,也有写入。

      tcp的两个端点:一个是客户端,一个是服务端。

        客户端:对应的对象,Socket

        服务端:对应的对象,ServerSocket

      TCP客户端:

        ①:建立tcp的socket服务,最好明确具体的地址和端口。这个对象在创建时,就已经可以对指定ip和端口进行连接(三次握手)。

        ②:如果连接成功,就意味着通道建立了,socket流就已经产生了。只要获取到socket流中的读取流和写入流即可,只要通过getInputStream和getOutputStream就可以获取两个流对象。

        ③:关闭资源。

      TCP服务端:

        服务端需要明确它要处理的数据是从哪个端口进入的。

        当有客户端访问时,要明确是哪个客户端,可通过accept()获取已连接的客户端对象,并通过该对象与客户端通过IO流进行数据传输。

        当该客户端访问结束,关闭该客户端。

    接下来简单的演示一下客户端连接服务端的代码:

     1 import java.net.*;
     2 import java.io.*;
     3 class TCPdemoClient 
     4 {
     5     public static void main(String[] args) throws Exception
     6     {
     7         //创建socket服务
     8         Socket s=new Socket("192.168.1.103",10002);
     9         //获取输出流
    10         OutputStream out=s.getOutputStream();
    11         out.write("哥们来了!".getBytes());
    12         //关闭socket,也同时关闭了流。
    13         s.close();
    14     }
    15 }
    16 class TCPdemoServer
    17 {
    18     public static void main(String[] args) throws Exception
    19     {
    20         ServerSocket ss=new ServerSocket(10002);
    21         Socket s=ss.accept();//获取连接到服务器的socket对象
    22         String ip=s.getInetAddress().getHostAddress();
    23         System.out.println(ip+" connected!");
    24         InputStream in =s.getInputStream();
    25         byte[] buf =new byte[1024];
    26         int len=in.read(buf);//读取输入的数据
    27         String data=new String(buf,0,len);
    28         System.out.println(ip+" : "+data);
    29         s.close();//关流
    30         ss.close();
    31     }
    32 }

    结果如下:

      注意:TCP协议传输数据必须先开服务端,再开客户端。否则,客户端根本连接不上服务端。

    接下来写一个客户端和服务器端交互的例子,服务器端将客户端发送过来的数据转换成大写再发送回去,

     1 import java.net.*;
     2 import java.io.*;
     3 class TCPdemoClient 
     4 {
     5     public static void main(String[] args) throws Exception
     6     {
     7         //创建socket服务
     8         Socket s=new Socket("192.168.1.103",10003);
     9         //获取输出流,在tcp传输中注意结束标记和刷新流,所以用打印流很方便
    10         PrintWriter out=new PrintWriter(s.getOutputStream(),true);
    11         BufferedReader brInput=new BufferedReader(
    12             new InputStreamReader(s.getInputStream()));
    13         BufferedReader br=new BufferedReader(
    14             new InputStreamReader(System.in));
    15         for(String line=null;(line=br.readLine())!=null;)
    16         {
    17             out.println(line);
    18             if(line.equals("886"))
    19                 break;
    20             String result=brInput.readLine();
    21             System.out.println(result);
    22         }
    23         br.close();
    24         //关闭socket,也同时关闭了流。
    25         s.close();
    26     }
    27 }
    28 class TCPdemoServer
    29 {
    30     public static void main(String[] args) throws Exception
    31     {
    32         ServerSocket ss=new ServerSocket(10003);
    33         Socket s=ss.accept();//获取连接到服务器的socket对象
    34         String ip=s.getInetAddress().getHostAddress();
    35         System.out.println(ip+" connected!");
    36         BufferedReader br =new BufferedReader(
    37             new InputStreamReader(s.getInputStream()));
    38         //用打印流,自动刷新和换行,比较方便
    39         PrintWriter pw =new PrintWriter(s.getOutputStream(),true);
    40         for(String line=null;(line=br.readLine())!=null;)
    41         {
    42             System.out.println(ip+": "+line);
    43             pw.println(line.toUpperCase());
    44             if(line.equals("886"))
    45                 System.out.println(ip+"退出聊天!");
    46         }
    47         s.close();//关流
    48         ss.close();
    49     }
    50 }

    运行结果如下图

      1、上面练习中之所以客户端结束后,服务端也随之结束的原因在于:客户端的socket关闭后,服务端获取的客户端socket读取流也关闭了,因此读取不到数据,line = bufIn.readLine()为null,循环结束,ServerSocket的close方法也就执行关闭了。

      2、上面练习中的客户端和服务端的PrintWriter对象out获取到数据后,一定要刷新,否则对方(服务端或客户端)就获取不到数据,程序便无法正常执行。刷新操作可以通过PrintWriter类的println()方法实现,也可以通过PrintWriter类的flush()方法实现。但是,由于获取数据的方法是BufferedReader对象bufIn的readLine()方法(阻塞式方法),此方法只有遇到“ ”标记时,才认为数据读取完毕,赋值给String对象line。所以,使用PrintWriter类的flush()方法刷新数据时一定要记得追加“ ”,标志行结束。

      注意,在客户端向服务器端上传文件的时候,在上传文件完成的时候,客户端要给服务器一个信号文件传输完毕,否则只要流没有断开,服务器一直在阻塞在读流数据的函数中,这个结束标记可以是自定义的结束符(如:over),时间戳标记,最好的方法是s.shutdownOutput();调用socket方法关闭output流通知服务器传输完成。

      现实中服务器都是通过多线程来响应不同的客户端的请求的,接下来就模拟一个客户端图片上传服务器多线程方法:

     1 import java.io.*;
     2 import java.net.*;
     3 class UploadThread implements Runnable
     4 {
     5     private Socket s;
     6     UploadThread(Socket s)
     7     {
     8         this.s=s;
     9     }
    10     public void run()
    11     {
    12         int count=0;
    13         String ip=s.getInetAddress().getHostAddress();
    14         System.out.println(ip+"connected!");
    15         try
    16         {
    17             File f=new File(ip+".jpg");
    18             //如果文件已经存在
    19             while(f.exists())
    20             {
    21                 f=new File(ip+"("+(++count)+").jpg");
    22             }
    23             InputStream is=s.getInputStream();
    24             FileOutputStream fos=new FileOutputStream(f);
    25             byte[] buf=new byte[1024];
    26             for(int len=0;(len=is.read(buf))!=-1;)
    27             {
    28                 fos.write(buf,0,len);
    29             }
    30             OutputStream out=s.getOutputStream();
    31             out.write("上传成功".getBytes());
    32             fos.close();
    33             s.close();
    34         }
    35         catch (Exception e)
    36         {
    37             throw new RuntimeException();
    38         }
    39     }
    40 }
    41 class PicUploadServer
    42 {
    43     public static void main(String[] args) throws Exception
    44     {
    45         ServerSocket ss=new ServerSocket(10004);
    46         while(true)
    47         {
    48             Socket s=ss.accept();
    49             new Thread(new UploadThread(s)).start();
    50         }
    51     }
    52 }
    53 class UploadClient
    54 {
    55     public static void main(String[] args) throws Exception
    56     {
    57         Socket s=new Socket("192.168.1.103",10004);
    58         FileInputStream fis=new FileInputStream("d:\1.jpg");
    59         //获取输出流,给服务器发送字节数据
    60         OutputStream os=s.getOutputStream();
    61         byte[] buf=new byte[1024];
    62         for(int len;(len=fis.read(buf))!=-1;)
    63         {
    64             os.write(buf,0,len);
    65         }
    66         //告诉服务器数据发送完毕
    67         s.shutdownOutput();
    68         //接收服务器的返回
    69         InputStream is=s.getInputStream();
    70         byte[] bufIn=new byte[1024];
    71         int len = is.read(bufIn);        
    72         System.out.println(new String(bufIn,0,len));
    73         fis.close();
    74         s.close();
    75     }
    76 }

    图片上传成功了!

     

      Socket类的构造函数中,有一个空参数的构造函数:

            Socket()//通过系统默认类型的 SocketImpl创建未连接套接字对象

           可以通过connect(SocketAddress endpoint)方法来连接服务器。而SocketAddress是一个抽象类,它的子类InetSocketAddress实现了IP套接字地址(IP地址+端口号)。所以就可以连接到服务器了。

    5、URL和URLConnection

      URL:统一资源定位符,也就是说根据URL能够定位到网络上的某个资源,它是指向互联网“资源”的指针。 

      方法:

            1)构造函数:URL(String protocol,String host,int port,String file);//根据指定 protocol、host、port号和 file 创建 URL对象。

            2)String getProtocol();//获取协议名称

            3)String getHost();//获取主机名

            4)int getPort();//获取端口号

            5)String getFile();//获取URL文件名

            6)String getPath();//获取此URL的路径部分

            7)String getQuery();//获取此URL的查询部,客户端传输的特定信息 

      URLConnection方法:

            1)URLConnection openConnection();//用URL调用此方法,返回一个 URLConnection 对象,它表示到 URL 所引用的远程对象的连接。

            2)InputStream getInputStream();//获取输入流

            3)OutputStream getOutputStream();//获取输出流

     

     1 import java.net.*;
     2 import java.io.*;
     3 class URLDemo 
     4 {
     5     public static void main(String[] args)throws Exception 
     6     {
     7         String strURL="http://192.168.1.100:8080/myweb/hello.html?name=admin";
     8         URL u=new URL(strURL);
     9         System.out.println("getProtocol: "+u.getProtocol());
    10         System.out.println("getHost: "+u.getHost());
    11         System.out.println("getPort: "+u.getPort());
    12         System.out.println("getFile: "+u.getFile());
    13         System.out.println("getPath: "+u.getPath());
    14         System.out.println("getQuery: "+u.getQuery());
    15         URLConnection uc=u.openConnection();
    16         InputStream is=uc.getInputStream();//等价于InputStream is=u.openStream();
    17         byte[] buf=new byte[1024];
    18         int len=is.read(buf);
    19         System.out.println(new String(buf,0,len));
    20         is.close();
    21     }
    22 }

    输出结果为:

      通过URL获取的流,接收到服务器的响应信息,URLConnection对象已经把响应头给解析了,只取到了消息体的数据,如果需要消息头的信息,URLConnection.getHeaderField(“具体的属性名”);来获取。

      网络编程内容总结到这里就结束了,对于网络编程而言,重要的是理解其步骤,按照步骤的需要,一步步搭建根基! 客户端和服务端需要交互,那么就要构建相对应的流,供其输入和输出! 对于阻塞式方法,一定要注意,提供停止标签! 对于PrintWriter ,记得用println而不是write;不要忘了加上true,自动刷新!

  • 相关阅读:
    jQuery tablesort插件推荐
    更改firefox默认搜索引擎
    Chrome Firefox 自定义背景色
    python 基础之列表切片内置方法
    python 基础之while无限循环
    python 基础之for循环有限循环
    python 基础之格式化输出
    nginx之HTTP模块配置
    Kubernetes之pod的属性
    nginx的工作流程
  • 原文地址:https://www.cnblogs.com/dengzhenyu/p/4842668.html
Copyright © 2020-2023  润新知