Java高并发网络编程

Posted AI数据

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java高并发网络编程相关的知识,希望对你有一定的参考价值。

一、OSI网络七层模型

因特网是一个极为复杂的网络,分层有助于我们对网络的理解 。分层也是一种标准,为了使不同厂商的计算机能够互相通信,以便在更大范围内建立计算机网络,有必要建立一个国际范围的网络体系结构标准。

 

 

 

 

 

 

 

ISO组织制定了OSI网络七层模型

应用层
表示层
会话层
传输层
网络层
链路层
物理层

而因特网只用到了五层

应用层
传输层
网络层
链路层
物理层

低三层:

屏蔽底层网络的复杂性

物理层:使原始的数据比特流能在物理介质上传输。

数据链路层:通过校验、确认和反馈重发等手段,形成稳定的数据链路。(01010101)

网络层:进行路由选择和流量控制。(IP协议)

 

传输层:提供可靠的端口到端口的数据传输服务(TCP/UDP协议)。

 

高三层:

会话层:负责建立、管理和终止进程之间的会话和数据交换。

表示层:负责数据格式转换、数据加密与解密、压缩与解压缩等。

应用层:为用户的应用进程提供网络服务。

网络通信协议

 

 

二、传输层控制协议TCP

传输层控制协议(TCP)是Internet一个重要的传输层协议。TCP提供面向连接、可靠、有序、字节流传输服务。应用程序在使用TCP之前,必须先建立TCP连接。

 

 

 

1.TCP握手机制

检测网络是否通畅

 

 

 

 

 

三、用户数据报协议UDP

用户数据报协议UDP是Internet传输层协议。提供无连接、不可靠、数据尽力传输服务。

 

 

 

 

TCP和UDP比较

 

 

四、Socket

1.Scoket概述

IP地址:InetAddress

  • 唯一的标识Internet上的计算机
  • 本地回环地址(hostAddress):127.0.0.1 主机名(hostName):localhost
  • 不易记忆

端口号标识正在计算机上运行的进程(程序)

  • 不同的进程有不同的端口号
  • 被规定为一个16位的整数0~65535。其中,0~1023被预先定义的服务通信占用(如mysql占用端口3306,http占用端口80等)。除非我们需要访问这些特殊服务,否则,应该使用1024~65535这些端口中的某一个进行通信,以免发生端口冲突。

 

端口号与IP地址的组合得出一个网络套接字

我们知道两个进程如果需要进行通讯最基本的一个前提能能够唯一的标示一个进程,在本地进程通讯中我们可以使用PID来唯一标示一个进程,但PID只在本地唯一,网络中的两个进程PID冲突几率很大,这时候我们需要另辟它径了,我们知道IP层的ip地址可以唯一标示主机,而TCP层协议和端口号可以唯一标示主机的一个进程,这样我们可以利用ip地址+协议+端口号唯一标示网络中的一个进程。

能够唯一标示网络中的进程后,它们就可以利用socket进行通信了,什么是socket呢?我们经常把socket翻译为套接字,socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。

 

 

网络应用程序由成对的进程组成,这些进程通过网络相互发送报文。在一对进程之间的通信会话场景中,发起通信的进程被标识为客户,在会话开始时等待联系的进程是服务器。进程通过一个称为套接字的软件接口向网络发送报文和从网络接收报文。套接字是同一台主机内应用层与传输层之间的接口,由于该套接字是建立网络应用程序的可编程接口,因此套接字也称为应用程序和网路之间的应用程序编程接口(API)。应用程序开发者可以控制套接字在应用层端的一切,但是对该套接字的运输层端几乎没有控制权,因此网络编程实际上就是Socket编程

 

Internet应用最广的网络应用编程接口,实现与3种底层协议接口:

  • 数据报类型套接字SOCK_DGRAM(面向UDP接口)
  • 流式套接字SOCK_STREAM(面向TCP接口)
  • 原始套接字SOCK_RAW(面向网络层协议接口IP、ICMP等)

 

主要socket API及其调用过程

 

 

 

socket是"打开—读/写—关闭"模式的实现,以使用TCP协议通讯的socket为例,其交互流程大概如下:

 

 

2.Java中的Socket编程

客户端

public class SocketClient {

    public void client() {
        Socket sc = null;
        OutputStream os = null;
        InputStream is = null;
        try {
            sc = new Socket(InetAddress.getByName("127.0.0.1"), 9092);
            os = sc.getOutputStream();
            os.write("我是客户端,我向你发送消息了".getBytes());
            sc.shutdownOutput(); // 显式的告诉服务端发送完毕

            is = sc.getInputStream();
            byte[] b = new byte[30];
            int len;
            while ((len = is.read(b)) != -1) {
                String str = new String(b, 0, len);
                System.out.println(str);
            }

        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        } finally {

            if (os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            if (sc != null) {
                try {
                    sc.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        SocketClient client = new SocketClient();
        client.client();
    }
}

当Client程序需要从Server端获取信息及其他服务时,应创建一个Socket对象

构造函数

 

new Socket(InetAddress.getByName("127.0.0.1"), 9092)会打开一个套接字

一旦套接字被打开,java.net.Socket类中的getInputStream方法就会返回一个InputStream对象,该对象可以像其他任何流一样使用,接收另一端传来的数据

getOutPutStream则是输出流,可以传给另一端

 

套接字离不开IP地址, Java中有IP相关的类

InetAddress类没有提供公共的构造方法,而是提供了两个静态方法来获取InetAddress实例

 

 InetAddress有如下常用方法:

 

服务器端

public class SocketServer {

    public void server() {
        ServerSocket ss = null;
        Socket s = null;
        InputStream is = null;
        OutputStream os = null;
        try {
            ss = new ServerSocket(9092);
            s = ss.accept();
            is = s.getInputStream();
            /**
             * 1、 read()方法,这个方法从输入流中读取数据的下一个字节。返回 0 到 255 范围内的 int 字节值。如果因为已经到达流末尾而没有可用的字节,则返回值 -1。 2、read(byte[] b,int
             * off,int len)方法,将输入流中最多 len 个数据字节读入 byte 数组。尝试读取len 个字节,但读取的字节也可能小于该值。以整数形式返回实际读取的字节数。 3、read(byte[]
             * b)方法,从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中。以整数形式返回实际读取的字节数。
             */
            byte[] b = new byte[30];
            int len;
            while ((len = is.read(b)) != -1) {
                /**
                 * new String(bytes, offset, length) bytes为要解译的字符串; offset为要解译的第一个索引,比如从0开始就是从字符串bytes的第一个字符开始;
                 * length为要解译的字符串bytes的长度
                 */
                String str = new String(b, 0, len);
                System.out.println(str);
            }
            os = s.getOutputStream();
            os.write("我是服务端,收到了你的信息".getBytes());

        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            if (os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            if (s != null) {
                try {
                    s.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            if (ss != null) {
                try {
                    ss.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }

        }
    }

    public static void main(String[] args) {
        SocketServer server = new SocketServer();
        server.server();
    }
}

服务器需随时待命,因为不知道客户端什么时候会发来请求,此时,我们需要使用ServerSocket,对应的是java.net.ServerSocket类

构造函数

 

 new ServerSocket(9092)建立一个监听9092接口的服务器

ss.accept()告诉程序不停的等待,直到客户端连接到这个接口。一旦有人通过网络发送了正确的连接请求,并以此连接到了端口上,该方法就返回一个表示连接已经建立的Socket对象。

可以使用这个对象来得到输入流和输出流

 

以上是关于Java高并发网络编程的主要内容,如果未能解决你的问题,请参考以下文章

java高并发编程

Java并发编程与高并发解决方案 视频教程

Java并发编程与高并发解决方案--安全发布对象

《java并发编程实战》

长文慎入-探索Java并发编程与高并发解决方案

java nio并发访问问题,我现在利用nio框架制服务器的并发访问,SelectionKey多线程