19 网络编程

209 阅读7分钟

1 网络模型及相关协议

目前计算机网络模型有两种:OSI七层模型和TCP/IP五层模型,模型示意图见下图

01 网络模型.png

计算机网络发送和接收数据的过程是:

  • 发送数据:对发送的数据内容按照上述七层模型进行层层加包封装后发送出去
  • 接收数据:对接受的数据按照上述七层模型进行层层拆包后显示出数据内容

计算机网络协议:计算机在网络中通信所遵守的的约定或规则就是计算机网络协议,计算机网络协议可以对速率、传输结构、差错控制等制定统一标准。计算机网络协议中最常用的协议是TCP和UDP协议,下面将详细介绍这两种传输协议。

TCP协议:TCP传输控制协议,是一种面向连接的传输协议,支持全双工的字节流通信方式,可以进行大量数据的传输,在传输过程中可以保证数据传输的可靠性和有序性,缺点是传输效率较低。

UDP协议:UDP用户数据报协议,是一种面向非连接的传输协议,支持全双工的数据报通信方式,每个数据报大小限制在64KB以内,不能保证数据传输的可靠性和有序性,但是传输效率较高。

IP地址:是互联网中主机的唯一标识,由32位二进制组成的整数,叫做IPv4。需要注意的是127.0.0.1是回环地址,也就是本机地址。

端口号:IP地址可以定位到某一台主机,而端口号是定位到该设备上的某个进程,表示范围是:0~65535,其中0~1024之间的端口号被系统占用。被我们熟知的端口号有,FTP 21、Tomcat 8080 等。

2 TCP协议编程模型

在介绍TCP协议编程模型之前,先介绍C/S模式,所谓C/S模型是指客户向服务器发出服务请求,服务器接受请求后提供服务。

TCP协议编程模型也是典型的C/S模式,分为客户端和服务端,详细介绍如下:

  • 服务器:(1)创建ServerSocket类型的对象并提供端口号

    ​ (2) 等待客户端的连接请求,调用accept()方法

    ​ (3) 使用输入输出流通信

    ​ (4) 关闭Socket

  • 客户端:(1)创建Socket类型的对象并提供服务器地址和端口号

    ​ (2) 使用输入输出流通信

    ​ (3) 关闭Socket

上面提到的ServerSocket类和Socket类都是实现TCP编程模型所需要使用的,下面介绍这两个类的常用方法:

ServerSocket类主要描述服务器套接字信息,常用方法如下:

常用方法功能介绍
ServerScoket(int port)根据参数端口构造对象
Socket accept()监听并接受到此套接字的连接请求
void close()关闭套接字

Scoket类主要描述客户端套接字信息,常用方法如下:

常用方法功能介绍
Socket(String host,int port)根据主机名和端口号构造对象
InputStream getInputStream()获取当前套接字的输入流
OutputStream getOutputStream()获取当前套接字的输出流
void close()关闭套接字

TCP编程模型实现代码:

服务器端

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class ServiceString {
    public static void main(String[] args) {
        ServerSocket ser= null;
        Socket st=null;
        try {
            ser = new ServerSocket(8888);
            while(true) {
                System.out.println("等待客户端的连接请求");
                st = ser.accept();
                System.out.println("客户端" + st.getInetAddress() + "连接成功");
                new ServerThread(st).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(null!=ser){
                try {
                    ser.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }

    }
}

客户端

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;

public class ClientString {
    public static void main(String[] args) {
        Socket st=null;
        PrintStream p=null;
        Scanner sc=null;
        BufferedReader br=null;
        try {
            st=new Socket("127.0.0.1",8888);
            System.out.println("服务器连接成功");
            sc=new Scanner(System.in);
            p=new PrintStream(st.getOutputStream());
            br=new BufferedReader(new InputStreamReader(st.getInputStream()));
            while (true){
                System.out.println("请输入你要发送的内容:");
                String s=sc.next();
                p.println(s);
                System.out.println("客户端内容发送完毕");
                if("bye".equalsIgnoreCase(s)){
                    System.out.println("聊天结束");
                    break;
                }
                String str=br.readLine();
                System.out.println("服务器返回的消息是:"+str);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(null!=br){
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(null!=sc){
                sc.close();
            }
            if(null!=p){
                p.close();
            }
            if(null!=st){
                try {
                    st.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

多线程

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.InetAddress;
import java.net.Socket;

public class ServerThread extends Thread{
    private Socket st;

    public ServerThread(Socket st) {
        this.st = st;
    }

    @Override
    public void run() {
        BufferedReader br=null;
        PrintStream ps=null;
        try {
            br=new BufferedReader(new InputStreamReader(st.getInputStream()));
            ps=new PrintStream(st.getOutputStream());
            while (true){
                String str=br.readLine();
                InetAddress inetAddress=st.getInetAddress();
                System.out.println("客户端"+inetAddress+"发来的消息是:"+str);
                if("bye".equals(str)){
                    System.out.println("客户端"+inetAddress+"已下线");
                    break;
                }
                ps.println("I received");
                System.out.println("服务器发送数据成功");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(null!=ps){
                ps.close();
            }
            if(null!=br){
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(null!=st){
                try {
                    st.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

由上面的实现代码,可以注意到以下几点:

  • 客户端Socket与服务器端Socket对应,都包含输入流和输出流
  • 客户端的socket.getInputStream()连接服务器端的socket.getOutputStream()
  • 客户端的socket.getOutputStream()连接服务器端的socket.getInputStream()

3 UDP协议编程模型

关于UDP编程模型分为接收方和发送方,具体实现步骤如下:

接收方:

  • 创建DatagramSocket类型的对象并提供端口号
  • 创建DatagramPacket类型的对象并提供缓冲区
  • 通过Socket接受数据内容存放到Packet中,调用send方法
  • 关闭Socket

发送方:

  • 创建DatagramSocket类型的对象
  • 创建DatagramPacket类型的对象并提供接收方地址
  • 通过Socket将Packet中的数据内容发送出去,调用receive方法

上面提到的DatagramSocket类和DatagramPacket类都是实现UDP编程模型所需要使用的,下面介绍这两个类的常用方法:

DatagramSocket类主要用于描述发送和接收数据报的套接字,其常用方法如下:

常用方法功能介绍
DatagramSocket()无参方式构造对象
DatagramSocket(int port)根据参数端口构造对象
void receive(DatagramPocket p)将接收到的数据存放到参数指定位置
void send(DatagramPocket p)将参数内容发送出去
void close()关闭套接字

DatagramPacket类主要用来描述数据报,数据报用来实现无连接包裹投递服务,其常用方法如下:

常用方法功能介绍
DatagramPocket(byte[] b,int len)根据参数指定的数组来构造对象,用于接受长度为len的数据报
DatagramPocket(byte[] b,int len,InetAdress add,int port)根据参数指定数组构造对象,将数据报发送到指定地址和端口
InetAdress getAdress()获取发送方或接受方的地址
int getPort()获取发送方或接受方的端口号
int getLength()获取发送数据或接收数据的长度

InetAdress类主要描述互联网通信地址信息,常用方法如下:

常用方法功能介绍
static InetAdress getLocalHost()获取本主机的地址
static InetAdress getByname(String host)根据参数指定的主机名获取地址

UDP编程模型实现代码:

发送方

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

public class SendTest {
    public static void main(String[] args) {
        DatagramSocket ds=null;
        try {
            ds=new DatagramSocket();
            byte[] by="hello java".getBytes();
            DatagramPacket dp=new DatagramPacket(by,by.length, InetAddress.getLocalHost(),8888);
            ds.send(dp);
            System.out.println("数据发送成功");
            byte[] brr=new byte[20];
            DatagramPacket dp1=new DatagramPacket(brr,brr.length);
            ds.receive(dp1);
            System.out.println("接收到的数据是"+new String(brr,0,dp1.getLength()));
        } catch (SocketException | UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(null!=ds){
                ds.close();
            }
        }

    }
}

接收方

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class ReceiveTest {
    public static void main(String[] args) {
        DatagramSocket ds=null;
        try {
            ds=new DatagramSocket(8888);
            byte[] brr=new byte[20];
            DatagramPacket dp=new DatagramPacket(brr, brr.length);
            System.out.println("等待数据的到来");
            ds.receive(dp);
            System.out.println("接收到的数据是"+new String(brr,0,dp.getLength()));
            byte[] br="I received".getBytes();
            DatagramPacket dp1=new DatagramPacket(br,br.length,dp.getAddress(),dp.getPort());
            ds.send(dp1);
            System.out.println("回发数据成功");

        } catch (SocketException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(null!=ds){
                ds.close();
            }

        }

    }
}

4 URL类

URL类主要表示统一的资源定位器,也就是指向万维网上“资源”的指针,这个资源可以是文件或目录。通过URL可以访问万维网上的资源,URL基本结构是:<传输协议>://<主机名>:<端口号></资源地址>.

URL类常用方法:

常用方法功能介绍
URL(String s)根据参数指定的字符串信息构造对象
String getProtocol()获取协议名称
String getHost()获取主机名称
int getPort()获取端口号
String getPath()获取路径信息
String getFile()获取文件名
URLConnection openConnection()获取URLConnection类的实例

URLConnection类是个抽象类,表示应用程序和URL之间的通信链接的所有类的超类,其实现类有支持HTTP功能的HTTPURLConnection类,HTTPURLConnection类的常用方法有:

常用方法功能介绍
InputStream getInputStream()获取输入流
void disconnect()断开连接

实例代码:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

public class URLTest {
    public static void main(String[] args) {
        URL url= null;
        try {
            url = new URL("https://www.lagou.com");
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        System.out.println("获取到的协议是"+url.getProtocol());
        System.out.println("获取到的主机名是"+url.getHost());
        System.out.println("获取到的主机号是"+url.getHost());
        try {
            HttpURLConnection htt=(HttpURLConnection) url.openConnection();
            InputStream inputStream=htt.getInputStream();
            BufferedReader br=new BufferedReader(new InputStreamReader(inputStream));
            String str=null;
            while((str=br.readLine())!=null){
                System.out.println(str);
            }
            htt.disconnect();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}