欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

网络编程

程序员文章站 2022-05-10 23:32:25
...

网络模型概述

网络模型:
1. OSI(Open System Interconnection开放系统互联)参考模型
2. TCP/IP参考模型

OSI七层模型简述:
1.物理层
主要定义物理设备标准,如网线的接口类型。主要作用是传输比特流。
2. 数据链路层
将物理层接收的数据进行MAC地址的封装和解封装。设备是交换机。
3. 网络层
将从下层接收到的数据进行IP地址的封装和解封装。工作在这层的设备是路由器。
4. 传输层
定义一些传输数据的协议和端口号,如TCP,UDP。主要是将下层接收到的数据进行分段和传输,到达目的地址后再进行重组。
5. 会话层
通过传输层建立数据传输的通路。功能主要是在系统之间发起会话或者接受会话请求。
6. 表示层
对接收的数据进行解释、加密与解密,压缩与解压缩等。
7. 应用层
终端的应用,如FTP,WEB,QQ。

OSI分层模型和TCP/IP分层模型的对应关系
网络编程

网络通讯要素:
1. IP地址

ip地址用于唯一标示网络中的一个通信实体,这个通信实体可以是一台主机,可以是一台打印机,或者是路由器的某一个端口。而在基于IP协议网络中传输的数据包,必须使用IP      地址来进行标示。ip地址就像写一封信,必须指定收件人的地址一样。每个被传输的数据包中都包括了一个源IP和目标IP。

IP地址是网络中设备的标识。
本地回环地址:127.0.0.1
主机名:localhost

java中提供了一个封装IP地址的对象:InetAddress
InetAddress类主要是用来得到所指定的网络地址,InetAddress类没有直接显式的构造函数。要生成一个InetAddress对象,必须运用一个可用的工厂方法。工厂方法(factory method)仅是一个类中的静态方法返回一个该类实例的约定。这是在一个带有各种参数列表的重载构造函数中完成的,当持有惟一方法名时可使结果更清晰。

域名解析:
IP地址是网络上标识站点的数字地址,为了方便记忆,采用域名来代替IP地址标识站点地址。域名解析就是域名到IP地址的转换过程。域名的解析工作由DNS服务器完成。

public class IPDemo {

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


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

        System.out.println(ip.getHostName()+" : "+ip.getHostAddress());


        //获取其他主机的ip地址对象
        ip=InetAddress.getByName("www.baidu.com");//InetAddress.getByName("localhost");   InetAddress.getByName("192.168.1.100");

        System.out.println(ip.getHostName()+" : "+ip.getHostAddress());
    }

}

2.端口号

ip地址唯一标示了通信实体,但是一个通信实体可以有多个通信程序同时提供网络服务。这个时候就要通过端口来区分开具体的通信程序。一个通信实体上不能有两个通信程序使用同一个端口号。

端口号用于标识进程的逻辑地址。有效端口0~65535,其中0~1024是系统使用或者保留端口。
3. 传输协议
传输协议即通讯的规则。常见的协议:TCP、UDP。

UDP
将数据以及源和目的封装成数据包,不需要建立连接。每个数据报的大小限制在64k内。因为无连接,所以是不可靠协议,但是速度快。
TCP
建立连接,形成传输数据的通道,在连接中进行大数据量的传输。通过三次握手完成连接,是可靠协议。由于必须建立连接,所以效率会稍低。

Socket

Socket又称套接字,是连接运行在网络上两个程序间的双向通讯的端点。Socket就是为网络服务提供的一种机制。通信的两端都有Socket,网络通信其实就是Socket间的通信,数据在两个Socket间通过IO传输。

UDP传输

UDP协议的主要作用就是完成网络数据流和数据报之间的转换—–在信息的发送端,UDP协议将网络数据流封装到数据报,然后将数据报发送出去;在信息的接收端,UDP协议将数据报转换成实际数据报内容。
UDP传输有两个重要的类:DatagramSocketDatagramPacket。前者是用来发送和接收数据包的套接字,后者表示数据包,每条报文仅根据该包中的包含的信息从一台机器路由到另一台机器。
UDP传输的过程:
1. 建立发送端,接收端
2. 建立数据包
3. 调用Socket的发送接收方法
4. 关闭Socket。
其中,发送端与接收端是两个独立的运行程序。

DatagramPacket:创建的时候分为接收和发送两种
接收: DatagramPacket(byte[] buf, int length):用来接收长度为 length 的数据包。
发送:DatagramPacket(byte[] buf, int length, InetAddress address, int port):用来将长度为 length 的包发送到指定主机上的指定端口号。

创建UDP传输的发送端:

public class UDPSendDemo {

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

        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传输演示:it's cold";

        // 使用DatagramPacket将数据封装到该对象包中
        byte[] buf=str.getBytes();

        DatagramPacket dp=new DatagramPacket(buf, buf.length,InetAddress.getByName("127.0.0.1"),9001);


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

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

}

创建UDP传输的接收端:

public class UDPReceDemo {

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

        System.out.println("接收端启动...");

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

        //1.建立udp socket服务。
        DatagramSocket ds=new DatagramSocket(9001);//9001是接收端的端口


//      while(true){

        //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聊天室程序:
发送端:


public class UDPSendDemo2 {

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

        System.out.println("发送端启动...");

        //1. udpsocket服务。使用DatagramSocket对象
        DatagramSocket ds=new DatagramSocket(8888);

        //2. 将要发送的数据封装到数据包中
//      String str="udp传输演示:it's cold";
        BufferedReader bufr=new BufferedReader(new InputStreamReader(System.in));
        String line=null;

        while((line=bufr.readLine())!=null){
            // 使用DatagramPacket将数据封装到该对象包中
            byte[] buf=line.getBytes();
            DatagramPacket dp=new DatagramPacket(buf, buf.length,InetAddress.getByName("127.0.0.1"),9001);

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

            if("over".equals(line))
                break;
        }
        //关闭资源
        ds.close();
    }
}

接收端:

public class UDPReceDemo2 {

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

        System.out.println("接收端启动...");

        //1.建立udp socket服务。
        DatagramSocket ds=new DatagramSocket(9001);//9001是接收端的端口

        while(true){

        //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();
    }
}

上面的聊天室程序是两个窗口的,一个窗口显示发送端,一个窗口显示接收端。可以用一个窗口来显示,使用多线程技术。
发送数据任务:

public class Send implements Runnable {

    private DatagramSocket ds;

    public Send(DatagramSocket ds){
        this.ds=ds;
    }

    @Override
    public void run() {

        try {
            BufferedReader bufr=new BufferedReader(new InputStreamReader(System.in));
            String line=null;

            while((line=bufr.readLine())!=null){


                // 使用DatagramPacket将数据封装到该对象包中
                byte[] buf=line.getBytes();
                DatagramPacket dp=new DatagramPacket(buf, buf.length,InetAddress.getByName("127.0.0.1"),10001);//192.168.1.255这个地址是广播地址,其中192.168.1是网络位,最后一个字节由0-255是ip地址位,其中0不能用,0代表网络位,255代表广播位

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

                if("over".equals(line))
                    break;

            }

            ds.close();
        }catch (Exception e) {
        }
    }

}

接收数据任务:

public class Rece implements Runnable {

    private DatagramSocket ds;

    public Rece(DatagramSocket ds){
        this.ds=ds;
    }

    @Override
    public void run() {

        try {
            while(true){

                    //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);
                    if(text.equals("over")){
                        System.out.println(ip+"  退出聊天室");
                    }
                }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

使用多线程技术的聊天程序:

public class ChatDemo {

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

        DatagramSocket send=new 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传输

TCP传输中涉及两个重要的类:SocketServerSocket
ServerSocket类用于表示服务器套接字,其主要功能是等待来自网络的“请求”,它可以通过指定的端口来等待连接的套接字。
Socket类用于表示客户端套接字。
TCP传输过程:
1. 建立客户端和服务器端
2. 建立连接后,通过Ssocket中的IO流进行数据的传输
3. 关闭socket
同样,客户端与服务器端是两个独立的应用程序。

建立客户端:

public class ClientDemo {

    public static void main(String[] args) throws UnknownHostException, IOException {
        /*
        客户端发数据到服务端


        tcp传输,客户端建立的过程
        1. 创建tcp客户端socket服务。使用的是Socket对象。
        建议该对象一创建就明确目的地(要连接的主机)
        2. 如果连接建立成功,说明数据传输通道已建立。
        (这个通道就是socket流:是底层建立好的,说明这里既有输入又有输出。想要输入或者输出流对象,可以找Socket来获取。)
        (可以通过getOutputStream()和getInputStream()来获取
        3. 使用输出流,将数据写出。
        4. 关闭资源。

        */
        //创建客户端socket服务
        Socket socket=new Socket("127.0.0.1",10002);

        //获取socket流中的输出流

        OutputStream out=socket.getOutputStream();

        //使用输出流将指定的数据写出去
        out.write("tcp演示:啊啊啊啊冷".getBytes());

        //关闭资源
        socket.close();

    }

}

建立服务器端:

public class ServerDemo {

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

        /*
        服务端接收客户端发来的数据,并打印在控制台上。


        建立tcp服务端的思路:
        1. 创建服务端socket服务,通过ServerSocket对象。
        2. 服务端必须对外提供一个端口,否则客户端无法连接。
        3. 获取连接过来的客户端对象。
        4. 通过客户端对象获取socket流读取客户端发来的数据。
        并打印在控制台上。
        5. 关闭资源。关闭客户端,关闭服务端。
        */

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

        //2. 获取连接过来的客户端对象
        Socket s=ss.accept();

        String ip=s.getInetAddress().getHostAddress();

        //3. 通过socket对象获取输入流,要读取客户端发来的对象
        InputStream in=s.getInputStream();

        byte[] buf=new byte[1024];

        int len=in.read(buf);
        String text=new String(buf,0,len);
        System.out.println(ip+":"+text);

        s.close();
        ss.close();
    }

}

服务器端收到数据后给客户端反馈的程序:
客户端:

public class ClientDemo2 {

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

        Socket socket=new Socket("127.0.0.1",10002);

        OutputStream out=socket.getOutputStream();

        out.write("tcp演示:啊啊啊啊冷".getBytes());

        //读取服务端返回的数据,要使用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);

        //关闭资源
        socket.close();

    }

}

服务器端:

public class ServerDemo2 {

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

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

        //2. 获取连接过来的客户端对象
        Socket s=ss.accept();

        String ip=s.getInetAddress().getHostAddress();

        //3. 通过socket对象获取输入流,要读取客户端发来的对象
        InputStream in=s.getInputStream();

        byte[] buf=new byte[1024];

        int len=in.read(buf);
        String text=new String(buf,0,len);
        System.out.println(ip+":"+text);

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

        s.close();
        ss.close();
    }

}

TCP传输练习

需求:客户端输入字母数据,发送给服务端,服务端收到后显示在控制台,并将该数据转成大写返回给客户端,直到客户端输入over,转换结束。
即创建一个英文大写转换服务器。
分析:有客户端和服务器端,使用TCP传输。

客户端:

public class TransClient {

    public static void main(String[] args) throws UnknownHostException, IOException {
        /*
        思路:
        客户端:
        1. 需要现有socket端点
        2. 客户端的数据源:键盘
        3. 客户端的目的:socket
        4. 接收服务端的数据,源:socket
        5. 将数据显示再打印出来,目的:控制台
        6. 在这些流中操作的数据,都是文本数据

        转换客户端:
        1. 创建socket客户端对象
        2. 获取键盘录入
        3.将录入的信息发送给socket输出流
        */

        //1. 创建socket客户端对象
        Socket s=new Socket("127.0.0.1",10004);

        //2. 获取键盘录入
        BufferedReader bufr=new BufferedReader(new InputStreamReader(System.in));

        //3. socket输出流
//      new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
        PrintWriter out=new PrintWriter(s.getOutputStream(),true);//自动刷新,将PrintWriter流里的数据刷到socket输入流里

        //4.socket输入流,读取服务端返回的大写内容
        BufferedReader bufIn=new BufferedReader(new InputStreamReader(s.getInputStream()));


        String line=null;
        while((line=bufr.readLine())!=null){

            if("over".equals(line))
                break;

            out.println(line);//println方法里有换行标记,可以作readLine的结束标记

            //读取服务端发回的一行大写内容
            String upperStr=bufIn.readLine();
            System.out.println(upperStr);

        }
        //socket流一关闭,就是在socket流里面植入了结束标记,而此时服务器端的readLine方法还在等着读数据,
        //此时读到了socket流里面结束标记,于是服务器端也会随之关闭
        s.close();
    }

}

服务器端

public class TransServer {

    public static void main(String[] args) throws IOException {
        /*
        转换服务器

        分析
        1. serversocket服务
        2. 获取socket服务
        3. 源:socket,读取客户端发过来需要转换的数据
        4. 目的:显示在控制台
        5. 将数据转成大写发给客户端
        */


        //1. serversocket服务
        ServerSocket ss=new ServerSocket(10004);

        //2. 获取socket对象
        Socket s=ss.accept();

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

        //3.获取socket读取流 ,并装饰    
        BufferedReader bufIn=new BufferedReader(new InputStreamReader(s.getInputStream()));

        //4. 获取socket的输出流,并装饰
        PrintWriter out=new PrintWriter(s.getOutputStream(),true);//自动刷新

        String line=null;
        while((line=bufIn.readLine())!=null){//read和readLine方法都是阻塞式方法
            System.out.println(line);//println方法里有换行标记,可以作readLine的结束标记
            out.println(line.toUpperCase());
        }

        s.close();
        ss.close();
    }

}

TCP在传输过程中,可能会出现客户端和服务器端都在等待的情况,这很有可能是数据没有发出去,原因在于程序中有阻塞式方法,一,没有刷新到socket流里去,二,缺少结束标记(在这里是换行标记)。

上传文本文件

如果上述英文大写转换服务器的程序里读客户端取的不是键盘录入的数据,而是文本文件,而服务端不是将数据作英文大小写转换再打印在控制台上,而是存储到一个文件里,则上述程序的功能可以看做是上传操作(文本文件的上传)。
客户端:

public class UploadClient {

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

        //1. 创建socket客户端对象
        Socket s=new Socket("127.0.0.1",10005);

        //2. 读取文件
        BufferedReader bufr=new BufferedReader(new FileReader("f:\\client.txt"));

        //3.将文件流发送给socket输出流
        PrintWriter out=new PrintWriter(s.getOutputStream(),true);

        String line=null;
        while((line=bufr.readLine())!=null){//这里的readLine方法肯定能结束,因为它读取的是文件
            out.println(line);//是println方法而不是write方法!!!
        }
        //告诉服务端,客户端写完了
//      out.println("over");//加入结束标记,但是要是文本里出现单行的over,会导致错误,这时候可以用上传的时间戳作为标记
        s.shutdownOutput();

        //4. 读取socket流,告知上传成功
        BufferedReader bufIn=new BufferedReader(new InputStreamReader(s.getInputStream()));
        //这里的readLine方法在等待服务端传输数据,而服务端的readLine方法也在等待客户端传输数据,
        //这样就有可能导致两边都在等待,需要加入一个结束标记
        String str=bufIn.readLine();
        System.out.println(str);

        bufr.close();
        s.close();
    }

}

服务器端

public class UploadServer {

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

        //1. 创建serversocket服务
        ServerSocket ss=new ServerSocket(10005);

        //2. 获取socket服务
        Socket s=ss.accept();
        System.out.println(s.getInetAddress().getHostAddress()+"  connected");

        //3. 读取客户端发过来的数据(源)
        BufferedReader bufIn=new BufferedReader(new InputStreamReader(s.getInputStream()));

        //4. 写出到服务端的文件中(目的)
        BufferedWriter bufw=new BufferedWriter(new FileWriter("f:\\server.txt"));

        String line=null;
        //如果不给结束标记的话,它会读入缓冲区,当缓冲区满了,它会自动刷新到写出文件中,
        //缓冲区的默认大小是8k,然后继续读入缓冲区,当最后缓冲区读不满的时候,会一直等待
        while((line=bufIn.readLine())!=null){
//          if("over".equals(line))//加入了结束标记
//              break;
            bufw.write(line);
            bufw.newLine();//换行
            bufw.flush();
        }

        //5. 告知客户端上传成功
        PrintWriter out=new PrintWriter(s.getOutputStream(),true);
        out.println("上传成功");

        bufw.close();
        s.close();
        ss.close();
    }

}