黑马程序员——8.网络编程(网络模型、网络地址、传输协议(UDP、TCP/IP)、客户端和服务器端、URL)

时间:2023-02-17 09:20:27

——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-

网络模型:

OSI(Open System Interconnection 开放系统互连)参考模型与TCP/IP 参考模型
黑马程序员——8.网络编程(网络模型、网络地址、传输协议(UDP、TCP/IP)、客户端和服务器端、URL)

OSI七层模型:

  1. 物理层:主要定义物理设备标准,如网线的接口类型、光纤的接口类型、各种传输介质的传输速率等。它的主要作用是传输比特流(就是由1、0转化为电流强弱来进行传输,到达目的地后再转化为1、0,也就是我们常说的数模转换与模数转换)。这一层的数据叫做比特。
  2. 数据链路层:主要将从物理层接收的数据进行MAC地址(网卡的地址)的封装与解封装。常把这一层的数据叫做帧。在这一层工作的设备是交换机,数据通过交换机来传输。
  3. 网络层:主要将下层接收到的数据进行IP地址(例,192.168.0.1)的封装与解封装。在这一层工作的设备是路由器,常把这一层的数据叫做数据包。
  4. 传输层:定义了一些传输数据的协议和端口号(WWW端口号80等),如:TCP(传输控制协议,传输效率低,可靠性强,用于传输可靠性要求高,数据量大的数据),UDP(用户数据报协议,与TCP特性恰恰相反,用于传输可靠性要求不高,数据量小的数据,如QQ聊天数据就是通过这种方式传输的)。主要是将从下层接收的数据进行分段和传输,到达目的地址后再进行重组。常常把这一层叫做段。
  5. 会话层:通过传输层(端口号:传输端口与接收端口)建立数据传输的通路。主要在你的系统之间发起会话或者接收会话请求(设备之间需要互相认识可以是IP也可以是MAC或者是主机名)。
  6. 表示层:主要是进行对接收的数据进行解释,加密与解密、压缩与解压缩等(也就是把计算机能够识别的东西转换成人能够识别的东西(如图片、声音等)。
  7. 应用层:主要是一些终端的应用,比如说FTP(各种文件下载)、WEB(IE浏览)、QQ之类的(可以把它理解成我们在电脑屏幕上可以看到的东西,就是终端应用)。

P.S.

  1. 每个网卡的MAC地址都是全球唯一的。
  2. 路由器实现将数据包发送到指定的地点。
  3. 应用软件之间通信的过程就是层与层之间封包、解封包的过程。

黑马程序员——8.网络编程(网络模型、网络地址、传输协议(UDP、TCP/IP)、客户端和服务器端、URL)

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

网络通讯要素:

IP地址:InetAddress
为网络中主机的真实地址,但是不易于记忆,可以使用主机名。

IPv4地址分4段,每段8个二进制位,即1个字节。

目前的IP分为以下几种:

  1. A类IP地址:由1字节的网络地址和3字节主机地址组成,第一个二进制位为0,地址范围从1.0.0.0 到126.255.255.255,即可用的A类网络有126个,每个网络能容纳1亿多个主机。
  2. B类IP地址:由2字节的网络地址和2字节的主机地址组成,网络地址的最高位必须是“10”,地址范围从128.0.0.0到191.255.255.255。可用的B类网络有16382个,每个网络能容纳6万多个主机 。
  3. C类IP地址:由3字节的网络地址和1字节的主机地址组成,网络地址的最高位必须是“110”,地址范围从192.0.0.0到223.255.255.255,可用的C类网络可达209万余个,每个网络能容纳254个主机。
  4. D类地址用于多点广播:D类IP地址第一个字节以“lll0”开始,它是一个专门保留的地址。它并不指向特定的网络,目前这一类地址被用在多点广播(Multicast)中。多点广播地址用来一次寻址一组计算机,它标识共享同一协议的一组计算机。
  5. E类IP地址:以“llll0”开始,为将来使用保留。

本地回环地址:127.0.0.1 主机名:localhost,一般用来测试网卡。

由于目前接入网络的设备越来越多,网络地址资源已经开始不够用,IPv6地址开始投入使用,IPv6由128位二进制位组成。

端口号:

用于标识进程(应用程序)的逻辑地址,不同进程的标识,应用程序可以自行申请端口使用,系统收到数据包后会根据数据包中的端口号定位发送到指定的端口号中。

有效端口:0~65535,其中0~1024系统使用或保留端口。

防火墙的功能就是将发送到某程序端口的数据屏蔽掉以及将从该程序端口发出的数据也屏蔽掉。

传输协议:

即通讯的规则。
常见协议:UDP、TCP。

UDP
将数据及源和目的封装成数据包中,不需要建立连接。
每个数据报的大小在限制在64k内。
因无连接,是不可靠协议。
不需要建立连接,速度快。
应用案例:QQ聊天、在线视频用的都是UDP传输协议。

TCP
建立连接,形成传输数据的通道。
在连接中进行大数据量传输。
通过三次握手完成连接,是可靠协议。
必须建立连接,效率会稍低。
应用案例:FTP,File Transfer Protocol(文件传输协议)。

import java.net.*;

public class IPDemo
{
    public static void main(String[] args) throws UnknownHostException {

        //获取本地主机IP地址对象
        InetAddress ip = InetAddress.getLocalHost();

        //通过主机名获取主机的IP地址对象
        //ip = InetAddress.getByName("PC-20140621KFIX");

        //通过IP地址获取对象
        //ip = InetAddress.getByName("192.168.1.100");

        //打印对象的地址及名称
        System.out.println(ip.getHostAddress());
        System.out.println(ip.getHostName());

        System.out.println("----------------");

        //通过主机名获取其他主机的IP地址对象。
        ip = InetAddress.getByName("www.baidu.com");

        System.out.println(ip.getHostAddress());
        System.out.println(ip.getHostName());
    }
}

InetAddress类中有一个静态方法:

static InetAddress[] getAllByName(String host),此方法是在给定主机名的情况下,根据系统上配置的名称服务返回其IP地址所组成的数据。这是由于有些主机名对应的IP地址不唯一,如新浪、百度,都是服务器集群。

域名解析:

在浏览器中输入网站的域名,DNS解析域名成IP,然后计算机再通过获取到的IP访问网站的服务器。

域名解析,最先走是本地的hosts(C:\WINDOWS\system32\drivers\etc\hosts)文件,解析失败了,才去访问DNS服务器解析、获取IP地址。

通过hosts文件也可以屏蔽游戏网站内容弹出,例如:在hosts文件中添加,127.0.0.1 www.game18.com

UDP协议-发送端&接收端:

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

UDP传输:
DatagramSocket(用来发送和接收数据报包的套接字)与DatagramPacket(数据报包)。
- 建立发送端,接收端。
- 建立数据包。
- 调用Socket的发送接收方法。
- 关闭Socket。

发送端与接收端是两个独立的运行程序。

/*
UDP发送端 :
*/

import java.net.*;

public class UDPSendDemo
{
        public static void main(String[] args) throws Exception {

                System.out.println("发送端启动......");
                /* * 创建UDP传输的发送端。 * 思路: * 1. 建立udp的socket服务。 * 2. 将要发送的数据封装到数据包中。 * 3. 通过udp的socket服务将数据包发送出去。 * 4. 关闭socket服务。 */

                //1. udpsocket服务。使用DatagramSocket对象。
                //如果发送端端口未指定,就会随机分配未被使用的端口。
                DatagramSocket ds = new DatagramSocket(8888);

                //2. 将要发送的数据封装到数据包中。
                String str = "udp传输演示,哥们来了!";
                byte[] buf = str.getBytes();

                //使用DatagramPacket将数据封装到该对象包中。
                DatagramPacket dp = new DatagramPacket(buf,buf.length,InetAddress.getByName("192.168.1.100"),10000);

                //3. 通过udp的socket服务将数据包发送出去,使用send方法。
                ds.send(dp);

                //4. 关闭资源
                ds.close();
        }
}

/*
UDP接收端:
*/

import java.net.*;

public class UDPReceDemo
{
        public static void main(String[] args) throws Exception {

                System.out.println("接收端启动......");
                /* * 建立UDP接收端的思路。 * 思路: * 1. 建立udp的socket服务,因为是要接收数据,必须要明确一个端口号。 * 2. 创建数据包,用于存储接收到的数据,方便用数据包对象的方法解析这些数据。 * 3. 使用socket服务的receive方法将接收的数据存储到数据包中。 * 4. 通过数据包的方法解析数据包中的数据。 * 5. 关闭资源。 */

                //1. 建立udpsocket服务。
                DatagramSocket ds = new DatagramSocket(10000);

                //2. 创建数据包。
                byte[] buf = new byte[1024];
                DatagramPacket dp = new DatagramPacket(buf,buf.length);

                //3. 使用接收方法将接收到的数据存储到数据包中。
                ds.receive(dp);  //这是阻塞式的方法。

                //4. 通过数据包对象的方法,解析其中的数据,比如:地址,端口,数据内容。
                String ip = dp.getAddress().getHostAddress();

                //获取的端口号是发送端所在的端口号。
                int port = dp.getPort();

                //解析出发送端发过来的数据
                String text = new String(dp.getData(),0,dp.getLength());

                System.out.println(ip + ":" + port + ":" + text);

                //5. 关闭资源
                ds.close();
        }
}

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

/*
聊天程序(单窗口模式-群聊):
*/

UDP发送端:

import java.net.*;
import java.io.*;

public class Send implements Runnable {
    private DatagramSocket ds;

    //通过构造函数接收一个DatagramSocket对象
    public Send(DatagramSocket ds){
            this.ds = ds;
    }

    public void run(){

        try{

            //标准键盘录入
            BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));

            String line = null;

            //循环接收录入的内容
            while((line = bufr.readLine()) != null){

                byte[] buf = line.getBytes();

                //255是广播地址,这样,所有192.168.1.*这个网段的所有人都会收到信息。
                //把数据封装到DatagramPacket包中
                DatagramPacket dp = new DatagramPacket(buf,buf.length,InetAddress.getByName("192.168.1.255"),10001);

                //通过DatagramSocket对象把DatagramPacket包发送出去
                ds.send(dp);

                //如果键盘录入的内容为886,就退出循环
                if("886".equals(line))
                        break;
            }

            //关闭发送端
            ds.close();

        }catch(Exception e){
                e.printStackTrace();
        }
    }
}
UDP接收端:
import java.net.*;

public class Rece implements Runnable {
    private DatagramSocket ds;

    //通过构造函数传入DatagramSocket对象
    public Rece(DatagramSocket ds){
        this.ds = ds;
}

    public void run(){

        try{

            //循环接收数据
            while(true){

                //创建一个最多接收1024字节的DatagramPacket对象
                byte[] buf = new byte[1024];
                DatagramPacket dp = new DatagramPacket(buf,buf.length);

                //接收发送过来的数据
                ds.receive(dp);

                //获取IP、端口和数据
                String ip = dp.getAddress().getHostAddress();
                int port = dp.getPort();
                String text = new String(dp.getData(),0,dp.getLength());

                System.out.println(ip + ":" + port + ":" + text);

                //接收到886就显示对方已退出聊天
                if(text.equals("886")){
                        System.out.println(ip + "...退出聊天室");
                }
            }

        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

启动发送端、接收端线程程序:

import java.io.*;
import java.net.*;

public class ChatDemo {
    public static void main(String[] args) throws IOException {

        //创建不带参数的DatagramSocket对象作为发送端
        DatagramSocket send = new DatagramSocket();

        //带有端口参数的DatagramSocket对象作为接收端
        DatagramSocket rece = new DatagramSocket(10001);

        Send s = new Send(send);
        Rece r = new Rece(rece);

        //开启线程
        new Thread(s).start();
        new Thread(r).start();

    }
}

TCP协议-客户端&服务端:

客户端(Client)首先与服务端(Server)建立连接,形成通道(其实就是IO流),然后,数据就可以在通道之间进行传输,并且单个Server端可以同时与多个Client端建立连接。

通信流程:

  1. Socket和ServerSocket,建立客户端和服务器端.
  2. 建立连接后,通过Socket中的IO流进行数据的传输。
  3. 关闭socket。
  4. 同样,客户端与服务器端是两个独立的应用程序。

TCP客户端

  1. 客户端需要明确服务器的ip地址以及端口,这样才可以去试着建立连接,如果连接失败,会出现异常。
  2. 连接成功,说明客户端与服务端建立了通道,那么通过IO流就可以进行数据的传输,而Socket对象已经提供了输入流和输出流对象,通过getInputStream(),getOutputStream()获取即可。
  3. 与服务端通讯结束后,关闭Socket。

TCP服务端

  1. 服务端需要明确它要处理的数据是从哪个端口进入的。
  2. 当有客户端访问时,通过accept()获取已连接的客户端对象,并通过该对象与客户端通过IO流进行数据传输。
  3. 当该客户端访问结束,关闭该客户端。

/*
TCP客户端:
*/

import java.io.*;
import java.net.*;

public class ClientDemo
{
    public static void main(String[] args) throws UnknownHostException,IOException {

        //1.创建客户端socket服务并明确要发送的地址端口。
        Socket socket = new Socket("192.168.1.100",10002);

        //2.获取socket流中的输出流
        OutputStream out = socket.getOutputStream();

        //3.使用输出流将指定的数据写出去。
        out.write("tcp演示:哥们又来了!".getBytes());

        //4.读取客户端返回的数据,使用Socket读取流。
        InputStream in = socket.getInputStream();

        //解析并打印从服务端接收的数据
        byte[] buf = new byte[1024];
        int len = in.read(buf);
        String text = new String(buf,0,len);
        System.out.println(text);

        //5.断开链接,关闭资源,socket获取的输出流也被关闭,没有必要再写代码关闭。
        socket.close();
    }
}

/*
TCP服务端:
*/

import java.net.*;
import java.io.*;

public class ServerDemo{

    public static void main(String[] args) throws IOException {

        //1. 创建服务端对象并监视端口
        ServerSocket ss = new ServerSocket(10002);

        //2. 获取连接过来的客户端对象。
        Socket s = ss.accept();  //这是阻塞式的方法

        //获取发送端IP
        String ip = s.getInetAddress().getHostAddress();

        //3. 通过socket对象获取输入流
        InputStream in = s.getInputStream();

        //4.获取发送过来的信息
        byte[] buf = new byte[1024];
        int len = in.read(buf);
        String text = new String(buf,0,len);
        System.out.println(ip + ":" + text);

        //5.使用客户端socket对象的输出流给客户端返回数据
        OutputStream out = s.getOutputStream();
        out.write("收到".getBytes());

        //6.关闭发送端对象
        s.close();

        //实际中接收端应该一直开启
        ss.close();
    }
}

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

/*
TCP协议上传文本文件:
*/

TCP服务端:

import java.net.*;
import java.io.*;

public class UploadServer
{
    public static void main(String[] args) throws IOException {

        //1.创建服务端对象,监视端口
        ServerSocket ss = new ServerSocket(10005);

        //2.获取客户端对象
        Socket s = ss.accept();
        System.out.println(s.getInetAddress().getHostAddress() + "......connected");

        //3.使用缓冲区装饰客户端的流
        BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));

        //使用缓冲区装饰FileWriter并关联输出文件
        BufferedWriter bufw = new BufferedWriter(new FileWriter("d:\\demo\\server.txt"));

        //4.循环读取流的数据并写入文件
        String line = null;
        while((line = bufIn.readLine()) != null){
            bufw.write(line);

            //由于readLine方法读取的数据不带换行符,要加上
            bufw.newLine();
        }

        //由于客户端的接收数据方法readLine要读到换行符才认为读一行结束,所以使用带有println的PrintWriter,自动刷新设为true,因为要刷新才会发送数据过去
        PrintWriter out = new PrintWriter(s.getOutputStream(),true);

        //5.给客户端反馈数据
        out.println("上传成功");

        //6.关闭资源
        bufw.close();
        s.close();
        ss.close();
    }
}

TCP客户端:

import java.net.*;
import java.io.*;

public class UploadClient
{
    public static void main(String[] args) throws UnknownHostException,IOException {

        //1.创建对象
        Socket s = new Socket("192.168.1.100",10005);

        //使用缓冲区装饰FileReader并关联要上传文件
        BufferedReader bufr = new BufferedReader(new FileReader("d:\\demo\\client.txt"));

        //2.使用带有prinln方法的PrinWriter装饰输出流
        PrintWriter out = new PrintWriter(s.getOutputStream(),true);

        //3.循环输出数据
        String line = null;
        while((line = bufr.readLine()) != null){
            out.println(line);
        }

        //告诉服务端,客户端写完了。
        s.shutdownOutput();

        //使用缓冲区装饰输入流,因为从服务端发过来的数据可能不止一行
        BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));

        //4.获取服务端发过来的数据
        String str = bufIn.readLine();
        System.out.println(str);

        5.关闭资源
        bufr.close();
        s.close();
    }
}

客户端和服务器端原理:

IE浏览器发送的请求是:

(请求行。请求方式:GET;请求的资源路径:HTTP/协议版本:1.1。)
GET / HTTP/1.1

(请求消息头,属性名:属性值。)
Host: localhost:9090
Connection: keep-alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,/;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.81 Safari/537.36
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8

P.S.

  1. 消息头中属性名及属性值的具体含义,会在JavaWeb中学习到。
  2. HTTP是一个客户端和服务端请求和应答的标准,客户端按照HTTP的标准发送数据到服务端,服务端按照HTTP的标准解析收到的数据。很多软件都内置了此标准。

/*
模拟一个浏览器获取信息:
*/

import java.net.*;
import java.io.*;

public class MyBrowser
{
    public static void main(String[] args) throws IOException {

        Socket s = new Socket("192.168.1.100",8080);

        //模拟浏览器,向tomcat服务端发送符合http协议的请求消息。
        PrintWriter out = new PrintWriter(s.getOutputStream(),true);
        out.println("GET /myweb/1.html HTTP/1.1");
        out.println("Accept: */*");
        out.println("Host: 192.168.1.100:8080");
        out.println("Connection: close");

        //为了防止数据丢失没收到换行符,发多几个
        out.println();
        out.println();

        //通过输入流获取服务器返回的信息并打印出来
        InputStream in = s.getInputStream();

        byte[] buf = new byte[1024];
        int len = in.read(buf);

        String str = new String(buf,0,len);
        System.out.println(str);

        s.close();
    }
}

HTTP服务端发回的应答消息:

(应答行。HTTP的协议版本:1.1;应答状态码:200;应答状态描述信息:OK。)
HTTP/1.1 200 OK
(应答消息属性信息,属性名:属性值。)
Server: Apache-Coyote/1.1
Accept-Ranges: bytes
ETag: W/”211-1433908112666”
Last-Modified: Wed, 10 Jun 2015 03:48:32 GMT
Content-Type: text/html
Content-Length: 211
Date: Wed, 10 Jun 2015 03:52:16 GMT
Connection: close

应答行中属性名及属性值的具体含义会在JavaWeb课程中学习到。

URL&URLConnection:

URI:统一资源标示符。是Web上可用的每种资源 - HTML文档、图像、视频片段、程序。
URL:统一资源定位符,也就是说根据URL能够定位到网络上的某个资源,它是指向互联网“资源”的指针。
每个URL都是URI,但不一定每个URI都是URL。这是因为URI还包括一个子类,即统一资源名称(URN),它命名资源但不指定如何定位资源。

import java.net.*;
import java.io.*;

public class URLDemo
{
    public static void main(String[] args) throws MalformedURLException,IOException {

        String str_url = "http://192.168.1.100:8080/myweb/1.html?name=lisi";

        //通过url地址获取url对象
        URL url = new URL(str_url);

        //通过url对象获取其相应的信息
        System.out.println("getProtocol:" + url.getProtocol());//使用的协议
        System.out.println("getHost:" + url.getHost());//IP地址
        System.out.println("getPort:" + url.getPort());//端口号
        System.out.println("getFile:" + url.getFile());//文件名
        System.out.println("getPath:" + url.getPath());//路径
        System.out.println("getQuery:" + url.getQuery());//查询的信息

        //获取url输入流
        InputStream in = url.openStream();//相当于url.openConnection().getInputStream();

        byte[] buf = new byte[1024];
        int len = in.read(buf);

        String text = new String(buf,0,len);

        System.out.println(text);

        in.close();
    }
}

URLConnection对象会把响应头解析掉,最终获取的只是服务器发送过来的主体数据。

常见网络结构:

  1. C/S client/server
    特点:
    该结构的软件,客户端和服务端都需要编写。
    开发成本较高,维护较为麻烦。
    好处:
    客户端在本地可以分担一部分任务。例如,杀毒软件直接对本机文件进行杀毒。

  2. B/S browser/server
    特点:
    该结构的软件,只开发服务器端,不开发客户端,因为客户端直接由浏览器取代。
    开发成本相对低,维护更为简单。
    缺点:
    所有运算都要在服务端完成。
    TCP协议服务端多线程技术:

/*
多客户端上传图片:
*/

import java.net.*;
import java.io.*;

public class UploadTask implements Runnable {
    private Socket s;

    //通过构造函数传入客户端对象
    public UploadTask(Socket s){
        this.s = s;
    }

    public void run(){

        int count = 0;

        String ip = s.getInetAddress().getHostAddress();
        System.out.println(ip + "......connected");

        try{
            //获取客户端的流对象
            InputStream in = s.getInputStream();

            //资源存放在c:\\pic里
            File dir = new File("c:\\pic");
            if(!dir.exists()){
                dir.mkdirs();
            }

            //创建file对象关联要上传的文件,文件名为ip地址
            File file = new File(dir,ip + ".bmp");

            //如果文件已经存在于服务端。
            while(file.exists()){
                file = new File(dir,ip + "(" + (++count) + ").bmp");
            }

            //读取数据写入文件输出流中
            FileOutputStream fos = new FileOutputStream(file);
            byte[] buf = new byte[1024];
            int len = 0;
            while((len = in.read(buf)) != -1){
                fos.write(buf,0,len);
            }

            //输出反馈
            OutputStream out = s.getOutputStream();
            out.write("上传成功".getBytes());

            fos.close();
            s.close();
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

public class UploadPicServer {
    public static void main(String[] args) throws FileNotFoundException,IOException {

        //创建服务端对象
        ServerSocket ss = new ServerSocket(10006);

        //每连接上一个客户端,就开启一个线程
        while(true){
            Socket s = ss.accept();
            new Thread(new UploadTask(s)).start();
        }
    }
}