java网络编程


一、TCP/IP协议分层模型

TCP/IP协议的分层有2个模型,分别是TCP/IP参考模型与OSI参考模型。

在TCP/IP参考模型中,是分为数据链路层、互联网IP层、传输层、应用层共4层

在OSI参考模型中分为物理层、数据链路层、网络层、传输层、会话层、表示层、应用层共7层。

TCP协议是目前使用最为广泛的协议,OSI是一个理想模型(硬件跟不上)

OSI协议模型

OSI就是一个开放的通信系统互联参考模型,也是一个定义的很好的协议规范。OSI模型有7层结构,每层都可以有几个子层。OSI的7层从下到上分别是7-应用层、6-表示层、5-会话层、4-传输层、3-网络层、2-数据链路层、1-物理层

  • 物理层:是参考模型的最低层。该层是网络通信的数据传输介质,由连接不同结点的电缆与设备共同构成。主要跟功能是:利用传输介质为数据链路层提供物理连接,负责处理数据传输并监控数据出错率,以便数据流的透明传输。典型设备时集线器HUB

  • 数据链路层:四参考模型的第二层。主要功能是:在物理层提供的服务基础上,在通信的实体间建立数据链路连接,传输以“帧”为单位的数据包,并采用差错控制与流量控制方法,使有差错的物理线路变成无差错的数据链路。典型设备是交换机SWITCH

  • 网络层:是参考模型的第三层。主要功能是:为数据在节点之间传输创建逻辑链路,通过路由选择算法为分组通过通信子网选择最适当的路径,以及实现拥塞控制、网络互连等功能。

  • 传输层:是参考模型的第四层。主要功能是:向用户提供可靠地端到端服务,处理数据包错误、数据包次序,以及其他一些关键传输问题。传输层向高层屏蔽了下层数据通信的细节。因此,它是计算机通信体系结构中关键的一层。

  • 会话层:是参考模型的第五层。主要功能是:负责维扩两个结点之间的传输连接,以便确保点到点传输不中断,以及管理数据交换等功能。

  • 表示层:是参考模型的第六层。主要功能是:用于处理在两个通信系统中交换信息的表示方法,主要包括数据格式变换、数据加密与解密、数据压缩与恢复等功能。

  • 应用层:是参考模型的最高层。主要功能是:为应用软件提供了很多服务,比如文件服务器、数据库服务、电子邮件与其他网络软件服务。

TCP协议

三次握手

TCP是面向连接的协议,因此每个TCP连接都有3个阶段:连接建立、数据传送和连接释放。连接建立经历三个步骤,通常称为三次握手。可以参考网络状态切换图

  • 第一次握手(客户端发送请求)
    客户机发送连接请求报文段到服务器,并进入SYN_SENT状态,等待服务器确认。发送连接请求报文段内容:SYN=1,seq=x;SYN=1意思是一个TCP的SYN标志位置为1的包,指明客户端打算连接的服务器的端口;seq=x表示客户端初始序号x,保存在包头的序列号Sequence Number字段里。

  • 第二次握手(服务端回传确认)
    服务器收到客户端连接请求报文,如果同意建立连接,向客户机发回确认报文段ACK应答,并为该TCP连接分配TCP缓存和变量。服务器发回确认报文段内容:SYN=1,ACK=1,seq=y,ack=x+1;SYN标志位和ACK标志位均为1,同时将确认序号(Acknowledgement Number)设置为客户的ISN加1,即x+1;seq=y为服务端初始序号y。

  • 第三次握手(客户端回传确认)
    客户机收到服务器的确认报文段后,向服务器给出确认报文段ACK,并且也要给该连接分配缓存和变量。此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。客户端发回确认报文段内容:ACK=1,seq=x+1,ack=y+1;ACK=1为确认报文段;seq=x+1为客户端序号加1;ack=y+1,为服务器发来的ACK的初始序号字段+1。
    注意:握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。

计算机网络是通过传输介质、通信设施和网络通信协议,把分散在不同地点的计算机设备互连起来的,实现资源共享和数据传输的系统。网络编程就是编写程序使互联网的两个(或多个)设备(如计算机)之间进行数据传输。Java语言对网络编程提供了良好的支持。通过其提供的接口我们可以很方便地进行网络编程。

IP地址
IP协议有2个版本,分别是IPv4和IPv6,目前IPv4的地址已经耗尽

IPv4采用的是4位点分十进制的计法,例如192.168.4.36,每个位的取值范围为0-255。

由于4位点分十进制的计法很难记忆,所以因为域名 www.baidu.com对应的IP地址为220.181.38.149

Internet依靠DNS实现了机器名和IP地址之间的对应关系,DNS负责完成域名的解析

InetAddress是Java对IP地址的封装。其下有两个子类Inet4Address和Inet6Address。这个类的实例经常和UDP DatagramSockets和Socket,ServerSocket类一起使用
InetAddress 没有公开的构造方法,因此你必须通过一系列静态方法中的某一个来获取它的实例。

不仅可以通过主机名获取ia对象,也可以通过IP地址构建ia对象

还可以通过InetAddress中的方法获取本地机的相关信息

特殊方法isReachable用于测试是否可以到达该地址,防火墙和服务器配置可能阻塞请求,使得在访问时处于不可达状态

二、URL访问网上资源

URL对象代表统一资源定位器(通俗就是网址),是指向互联网资源的指针,资源可以是简单的文件或目录,也可以是对复杂对象的引用,例如对数据库或搜索引擎的查询。用协议名、主机、端口和资源组成,即满足格式

最重要的方法:
url.openConnection():URLConnection 可以获取输入、输出流
url.openStream():InputStream 直接获取输入流

URL与URLConnection比较

URL和URLConnection的区别在于前者代表一个资源的位置,后者代表一种连接
Java语言提供了两种方法读取数据,一种是通过URL对象直接得到相关的网络信息,另一种是先得到一个URLConnection实例,再得到InputStream或OutputStream对象,然后读取数据
前者是一种简单、直接的方法,但缺乏灵活性,并且只能读取只读信息,后者提供了更加灵活有效的方法来读取网络资源

TCP

TCP传输控制协议,属于传输层协议,提供一个可靠的点到点的虚连接。
在实际应用中TCP网络程序提供可靠的数据通信,而UDP网络程序则不保证数据的可靠性,但是协议简单、传输速度快(比如用在音视频数据传输,它们不需要很高的可靠性,偶尔丢帧是可以忍受的)
TCP是Tranfer Control Protocol的 简称,是一种面向连接的端对端的保证可靠传输的协议。
通过TCP协议传输,得到的是一个顺序的无差错的数据流。

在这里插入图片描述

Socket是什么呢?
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

发送方和接收方的成对的两个socket之间必须建立连接,以便在TCP协议的基础上进行通信,当一个socket(通常都是server socket)等待建立连接时,另一个socket可以要求进行连接,一旦这两个socket连接起来,它们就可以进行双向数据传输,双方都可以进行发送 或接收操作。

ServerSocket类

Java.net包中的ServerSocket类用于表示服务器套接字,其主要功能是监听客户端的请求,然后将客户端的请求连接存入队列中,默认请求队列大小是50。

构造方法主要有以下几种形式:
ServerSocket(int port):创建绑定到特定端口的服务器套接字。Port的取值范围为0-65535之间,0表示使用任意未占用端口,建议使用的端口号大于1024。如果端口已经被占用则会

BindException
ServerSocket(int port,int backlog,InetAdress bindAddress):使用指定的端口、监听backlog和要绑定到本地IP地址创建服务器,适用于计算机有多个网卡、多个IP的情景

使用ServerSocket检测当前机已经打开的端口号
在这里插入图片描述

客户端Socket的构造器
Socket(String host, int prot); 在客户端构建Socket对象,如果构建成功则获取对象,否则ConnectException。参数1为链接主机的名称,也可以使用InetAddress表示IP地址;参数2为链接服务器的监听端口号,要求服务器已经打开的链接端口

需求:客户端发起一个请求【hello server】,服务器响应一个信息内容为【server:服务器系统当前时】
1、在TCP编程中Server和client地位不对等,首先需要在服务器端开启监听端口,然后阻塞主线程,等待客户端的连接请求

2、客户端和服务器分别通过各自的Socket对象获取对应的输入输出流(字节流)。服务器的输出流对应客户端的输入流,服务器的输入流对应客户端的输出流,所以在具体的操作中只是编程操作流,而无需关心数据的传输细节

3、关闭相关的流和套接字对象,规则是正向打开,逆向关闭

实现一个QQ聊天室

定义一个类用于封装用户的聊天信息,序列化接口
ip地址 时间 内容
定义一个集合,其中最多存放10条数据

public class User implements Serializable {
    private String str;
    private Date da;
    private Object obj;

    public User(String str, Date da, Object obj) {
        this.str = str;
        this.da = da;
        this.obj = obj;
    }

    public User() {
    }

    public String getStr() {
        return str;
    }

    public void setStr(String str) {
        this.str = str;
    }

    public Date getDa() {
        return da;
    }

    public void setDa(Date da) {
        this.da = da;
    }

    public Object getObj() {
        return obj;
    }

    public void setObj(Object obj) {
        this.obj = obj;
    }

    @Override
    public String toString() {
        return "用户IP地址:" + str + ", 发送内容:" + obj + ", 发送时间:" + da ;
    }
}

public class Server extends Obj {

    //一个集合只能存10条消息(包含:ip地址   时间   内容),有新用户进来,将该集合输出
    static volatile List<User> li = new Vector<>(10);
    //一个集合存放所有用户,只要有新消息通知所有用户
    static volatile Set<Socket> se = new HashSet<>();

    public static void main(String[] args) throws Exception {
        ServerSocket so = null;
        try {
            so = new ServerSocket(8000);
            //不断的接收用户请求,并做出响应
            while (true) {
                Socket socket = so.accept();
                if (so != null) {
                    method(socket);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            end(so);
        }

    }

    public static void method(Socket socket) throws Exception {
        new Thread(() -> {
            try (
                    BufferedReader bu = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                    PrintWriter pr = new PrintWriter(socket.getOutputStream());
            ) {
                System.out.println(Thread.currentThread().getName());
                //新进入聊天室,给当前用户发送历史记录
                start(pr);
                //判断该用户是否为新用户
                exist(socket);
                //开始聊天
                User user = null;
                while (true) {
                    String str = bu.readLine();
                    if ("bye".equals(str)) {
                        //记录消息记录
                        addList(user, str, socket);
                        send();
                        break;
                    }
                    if ("hello".equals(str)) {
                        bb=true;
                        addList(user, str, socket);
                        send();
                        System.out.println(str);
                        pr.println("你好");
                    } else {
                        bb=true;
                        addList(user, str, socket);
                        send();
                        System.out.println(str);
                        pr.println("你想说什么");
                    }
                    pr.flush();
                    bb=false;
                }

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

    }

    public static void start(PrintWriter pr) {
        bb=true;
        pr.println("欢迎进入XXX聊天室");
        pr.flush();
        System.out.println(li.size());
        if (li.size() > 0) {
            pr.println("历史消息为:");
            pr.flush();
            for (User aa : li) {
                pr.println(aa.toString());
                pr.flush();
            }
        } else {
            pr.println("当前没有历史消息");
            pr.flush();
        }
        bb=false;
    }

    public static void exist(Socket socket) {
        if (!se.contains(socket)) {
            se.add(socket);
        }
    }

    //向记录消息的集合中添加数据
    public static void addList(User user, String str, Socket socket) {
        user = new User(socket.getInetAddress().getHostAddress(), new Date(), str);
        if (li.size() <= 10) {
            li.add(user);
        } else {
            li.remove(0);
            li.add(user);
        }
    }

    //向所有用户发送消息
    public static void send() throws Exception {
        for (Socket ss : se) {
            PrintWriter pr = new PrintWriter(ss.getOutputStream());
            pr.println(li.get(li.size()-1));
            pr.flush();
        }
    }


    public static void end(ServerSocket so) {
        try {
            if (so != null) {
                so.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

public class Socket01 extends Obj {

    public static void main(String[] args) throws Exception {
        Socket so = new Socket("localhost", 8000);
        //键盘录入
        BufferedReader bu1 = new BufferedReader(new InputStreamReader(System.in));
        //客户端的输入
        BufferedReader bu = new BufferedReader(new InputStreamReader(so.getInputStream()));
        //向客户端的输出
        PrintWriter pr = new PrintWriter(new OutputStreamWriter(so.getOutputStream()));
        jieshou(bu);
        while (true) {
            Thread.sleep(100);
            String str = bu1.readLine();
            pr.println(str);
            pr.flush();
            if ("bye".equals(str)) {
                break;

            }

        }
        bu.close();
        bu1.close();
        pr.close();
    }

    public static void jieshou(BufferedReader bu) {
        Thread th = new Thread(() -> {
            while (true) {

                    try {
                        Thread.sleep(100);
                        String str1 = bu.readLine();
                        if (str1 != null) {
                            System.out.println(str1);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

            }
        });
        th.setDaemon(true);
        th.start();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值