黑马程序员——网络编程


 

网络编程

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,自动刷新!

原文地址:https://www.cnblogs.com/dengzhenyu/p/4842668.html