OpenGLES入门笔记一
学习地址:http://www.raywenderlich.com/3664/opengl-tutorial-for-ios-opengl-es-2-0
中文翻译:http://www.cnblogs.com/zilongshanren/archive/2011/08/08/2131019.html
文章地址:http://www.tuicool.com/articles/VZVJra
关键词:vertex、fragment、shaders、buffer
Xcode中OpenGL ES框架详情:
OpenGL ES1.0被OpenGL ES2.0的‘片元着色器’取代。开发人员在使用OpenGL ES2.0API进行开发时候,可以通过编写顶点以及片元着色器程序,来完成一些顶点变换和纹理颜色计算工作,实现更加灵活、精细化的计算与渲染。
一、着色(Shader)语言(2.0)
着色语言是一种类C的编程语言,但不支持double、byte、short、long(3.0有所不同),并且取消了union、enum、unsigned和位运算。
着色器内建多种原生数据类型和构建数据类型:matrix、vec2、vec3(向量型)、float、bool、int等。按照数据类型分为标量、向量、矩阵、采样器、结构体和数组等。
Shader支持下面数据类型:
- float bool int 基本数据类型
- vec2 包含了2个浮点数的向量
- vec3 包含了3个浮点数的向量
- vec4 包含了4个浮点数的向量
- ivec2 包含了2个整数的向量
- ivec3 包含了3个整数的向量
- ivec4 包含了4个整数的向量
- bvec2 包含了2个布尔数的向量
- bvec3 包含了3个布尔数的向量
- bvec4 包含了4个布尔数的向量
- mat2 *2维矩阵
- mat3 *3维矩阵
- mat4 *4维矩阵
- sampler1D 1D纹理采样器
- sampler2D 2D纹理采样器
- sampler3D 3D纹理采样器
1.顶点着色器
1.1顶点着色器实例代码
- uniform mat4 uMVPMatrix; // 应用程序传入顶点着色器的总变换矩阵
- attribute vec4 aPosition; // 应用程序传入顶点着色器的顶点位置
- attribute vec2 aTextureCoord; // 应用程序传入顶点着色器的顶点纹理坐标
- attribute vec4 aColor // 应用程序传入顶点着色器的顶点颜色变量
- varying vec4 vColor // 用于传递给片元着色器的顶点颜色数据
- varying vec2 vTextureCoord; // 用于传递给片元着色器的顶点纹理数据
- void main()
- {
- gl_Position = uMVPMatrix * aPosition; // 根据总变换矩阵计算此次绘制此顶点位置
- vColor = aColor; // 将顶点颜色数据传入片元着色器
- vTextureCoord = aTextureCoord; // 将接收的纹理坐标传递给片元着色器
- }
顶点着色器实例代码
1.2顶点着色器介绍
顶点着色器是一个可编辑的处理单元,执行定点变换、纹理坐标变换、光照、材质等顶点的相关操作。
顶点着色器主要传入相应的Attribute变量、Uniforms变量、采样器和临时变量,经过顶点着色器生成Varying变量。
(1)attribute变量(属性变量)只能用于顶点着色器中,不能用于片元着色器。一般用该变量来表示一些顶点数据,如:顶点坐标、纹理坐标、颜色等。
(2)Uniform变量(一致变量)用来将数据值从应用程序有序传递到片元着色或者顶点着色器。该变量有点类似于C语言中的常量(const),及该变量的值不能被Shader程序修改。一般用该变量表示变换矩阵,光照参数和纹理采样器等。
(3)varying变量(易变变量)是从顶点着色器传递到片元着色器的数据变量。顶点着色器可以使用易变变量来传递需要插值的颜色、法向量、纹理坐标等人一直。在顶点与片元shader程序间传递数据是很容易的,一般在顶点shader中修改varying变量值,然后片元shader使用该值,当然,变量在顶点以及片元这两段shader程序中的声明是一致的。例如:vColor变量。
(3)gl_Position为内建变量,表示变换后点的空间位置,顶点着色器从应用程序中获取原始的顶点位置数据,这些原始的顶点数据在顶点着色器中移动、平移、旋转和缩放等数学变化后,生成新的顶点位置。新的顶点位置通过在顶点着色器中写入gl_Position传递到渲染管线的后继阶段继续处理。
2.片元着色器
2.1 片元着色器示例代码
- precision mediump float; // 设置工作精度
- varying vec4 vColor; // 接收从顶点着色器过来的顶点颜色数据
- varying vec2 vTextureCoord; // 接收从顶点着色器过来的纹理坐标
- uniform sampler2D sTexture; // 纹理采样器,代表一幅纹理
- void main()
- {
- gl_FragColor = texture2D(sTexture, vTextureCoord) * vColor;// 进行纹理采样
- }
片元着色器示例代码
片元着色器的主要功能为根据接受的记录片元纹理坐标的易变变量中的纹理坐标,调用texture2D内建函数从采样器重进行纹理采样,得到此片元的颜色值。最后,将采样到的颜色值传给gl_FragColor内建变量,完成片元的着色。
2.2 片元着色器介绍
片元着色器是一个处理片元值以及相关联数据的可编程单元,片元着色器可执行纹理的访问,颜色的汇总、雾化等操作,每片元执行一次,片元着色器替代了纹理、颜色求和、雾以及Alpha测试,这一部分是需要开发者自己开发的。
(1)varying指的是从顶点着色器传递到片元着色器的数据变量。
(2)gl_FragColor为内置变量,用来保存片元着色器计算完成的片元颜色值,此颜色值将送入渲染管线的后继阶段进行处理。
附:最新OpenGLES的宏
- /*
- Copyright: (c) 2010-2012 Apple Inc. All rights reserved.
- */
- #ifndef __gltypes_h_
- #define __gltypes_h_
- #include <stdint.h>
- typedef uint32_t GLbitfield;
- typedef uint8_t GLboolean;
- typedef int8_t GLbyte;
- typedef float GLclampf;
- typedef uint32_t GLenum;
- typedef float GLfloat;
- typedef int32_t GLint;
- typedef int16_t GLshort;
- typedef int32_t GLsizei;
- typedef uint8_t GLubyte;
- typedef uint32_t GLuint;
- typedef uint16_t GLushort;
- typedef void GLvoid;
- #if !defined(GL_ES_VERSION_2_0)
- typedef char GLchar;
- #endif
- typedef int32_t GLclampx;
- typedef int32_t GLfixed;
- #if !defined(GL_ES_VERSION_3_0)
- typedef uint16_t GLhalf;
- #endif
- #if !defined(GL_APPLE_sync) && !defined(GL_ES_VERSION_3_0)
- typedef int64_t GLint64;
- typedef struct __GLsync *GLsync;
- typedef uint64_t GLuint64;
- #endif
- typedef intptr_t GLintptr;
- typedef intptr_t GLsizeiptr;
- #endif
数据类型别名
二、加载着色器示例代码
- private int loadShader( int shaderType, String source)
- {
- // 创建一个新shader
- int shader = GLES20.glCreateShader(shaderType);
- // 若创建成功则加载shader
- if (shader != )
- {
- // 加载shader源代码
- GLES20.glShaderSource(shader, source);
- // 编译shader
- GLES20.glCompileShader(shader);
- // 存放编译成功shader数量的数组
- int[] compiled = new int[];
- // 获取Shader的编译情况
- GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, );
- if (compiled[] == )
- {
- //若编译失败则显示错误日志并删除此shader
- Log.e(TAG, "Could not compile shader " + shaderType + ":");
- Log.e(TAG, GLES20.glGetShaderInfoLog(shader));
- GLES20.glDeleteShader(shader);
- shader = ;
- }
- }
- return shader;
- }
加载着色器示例代码
上述示例代码中主要用到了三个方法:
- GLES20.glCreateShader(shaderType);
- GLES20.glShaderSource(shader, source);
- GLES20.glCompileShader(shader);
(1)GLES2.0.glCreateShader(int type),创建一个容纳Shader的容器,成为Shader容器。
- 函数原型:
- int glCreateShader (int type)
GL_API GLuint GL_APIENTRY glCreateShader (GLenum type)// 最新版的函数原型
- 方法参数:
- GLES20.GL_VERTEX_SHADER (顶点shader)
- GLES20.GL_FRAGMENT_SHADER (片元shader)
- 参数查看OpenGLES/ES2/gl.h
如果调用成功的话,函数将返回一个整形的正整数作为Shader容器的id。如果对C++熟悉的话,该函数的返回值理解为指针或者句柄更合适。
(2)GLES20.glShaderSource(shader, Source),添加Shader的源代码。源代码应该以字符串数组的形式表示。当然,也可以只用一个字符串来包含所有的源代码。
- 函数原型:
- void glShaderSource (int shader, String string)
GL_API void GL_APIENTRY glShaderSource (GLuint shader, GLsizei count, const GLchar* const *string, const GLint* length) //最新版函数原型
- 参数含义:
- shader是代表shader容器的id(由glCreateShader返回的整形数);
- string是包含源程序的字符串数组。
(3)GLES2.0.glCompileShader(shader),对Shader容器中的源码进行编译。
- 函数原型:
- void glCompileShader (int shader)
- 参数含义:
- shader是代表shader容器的id。
(4)调试
调试一个Shader是非常困难的。Shader的世界里没有printf,无法再控制台中打印调试信息,更没有断点,甚至很多编辑器对Shader程序关键字、变量高亮显示都不支持。但是可以通过一些OpenGL提供的函数来获取编译和链接过程中的信息。
在编译阶段使用glGetShaderiv获取编译情况,在连接阶段使用glGetProgramiv获取连接情况。当错误产生的时候,还可以从infoLog中获取更多的信息。infoLog中存储了关于上一操作执行的相关信息,比如编译阶段的警告和错误,以及连接阶段产生的问题。不幸的是对于错误信息没有统一的标准,所以不同的硬件或驱动程序将提供不同的错误信息。
(4.1)编译阶段使用glGetShaderiv获取变异情况
- 函数原型:
- void glGetShaderiv (int shader, int pname, int[] params, int offset)
- 参数含义:
- shader是一个shader的id;
- pname使用GL_COMPILE_STATUS;
- params是返回值,如果一切正常返回GL_TRUE代,否则返回GL_FALSE。
(4.2)编译阶段使用glGetShaderInfoLog获取编译错误
- 函数原型:
- String glGetShaderInfoLog (int shader)
- 参数含义:
- shader是一个顶点shader或者片元shader的id。
(4.3)在连接阶段使用glGetProgramiv获取连接情况
- 函数原型:
- void glGetProgramiv (int program, int pname, int[] params, int offset)
- 参数含义:
- program是一个着色器程序的id;
- pname是GL_LINK_STATUS;
- param是返回值,如果一切正常返回GL_TRUE代,否则返回GL_FALSE。
(4.4)在连接阶段使用glGetProgramInfoLog获取连接错误
- 函数原型:
- String glGetProgramInfoLog (int program)
- 参数含义:
- program是一个着色器程序的id。
(4.5)清理Shader的glDeleteShader方法
当不再需要某一个Shader或者某一个程序的时候,需要对其进行清理,已释放资源。前面,提到过如何向一个程序中添加一个Shade。这里可以调用下面的函数来将一个Shader从一个程序中清除掉。
- 函数原型:
- void glDeleteShader (int shader);
- 参数含义:
- shader是要被排除的顶点shader或者片元shader的id。
如果,一个Shader被删除之前没有响应的程序中排除,那么这个Shader不会被实际删除,而只是被标记为被删除;当Shader被从程序中排除的时候,才会被真正地删除。
三、创建着色器程序示例代码
- private int createProgram(String vertexSource, String fragmentSource)
- {
- // 加载顶点着色器
- int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
- if (vertexShader == )
- {
- return ;
- }
- // 加载片元着色器
- int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
- if (pixelShader == )
- {
- return ;
- }
- // 创建着色器程序
- int program = GLES20.glCreateProgram();
- // 若程序创建成功则向程序中加入顶点着色器与片元着色器
- if (program != )
- {
- // 向程序中加入顶点着色器
- GLES20.glAttachShader(program, vertexShader);
- // 向程序中加入片元着色器
- GLES20.glAttachShader(program, pixelShader);
- // 链接程序
- GLES20.glLinkProgram(program);
- // 存放链接成功program数量的数组
- int[] linkStatus = new int[];
- // 获取program的链接情况
- GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, );
- // 若链接失败则报错并删除程序
- if (linkStatus[] != GLES20.GL_TRUE)
- {
- Log.e(TAG, "Could not link program: ");
- Log.e(TAG, GLES20.glGetProgramInfoLog(program));
- GLES20.glDeleteProgram(program);
- program = ;
- }
- }
- // 释放shader资源
- GLES20.glDeleteShader(vertexShader );
- GLES20.glDeleteShader(pixelShader);
- return program;
- }
创建着色器示例代码
(1)glCreateProgram,在连接Shader之前,需要创建一个容纳程序的容器,我们称为着色程序容器。可以通过glCreateProgram函数来创建一个程序容器。
- 函数原型:
- int glCreateProgram ()
- 如果函数调用成功将返回一个正整数作为该着色器程序的id。
(2)glAttachShader,接下来,我们要将Shader容器添加到程序中。这是的Shader容器不一定需要被编译,他们甚至不需要包含任何的代码。我们要做的只是将Shader容器添加到程序中。使用glAttachShader函数来为程序添加Shader容器。
- 函数原型:
- void glAttachShader (int program, int shader)
- 参数含义:
- program是着色器程序容器的id;
- shader是要添加的顶点或者片元shader容器的id。
如果你同时拥有了定点Shader和片元Shader,需要分别将他们各自的两个Shader容器添加到程序容器中。
(3)glLinkProgram,链接程序。
- 函数原型:
- void glLinkProgram (int program)
- 参数含义:
- program是着色器程序容器的id。
在连接操作执行以后,可以任意修改Shader的源代码,对Shader重新编译不会影响整个程序,除非重新链接程序。
(4)glUseProgram,加载并使用连接好的程序。
- 函数原型:
- void glLinkProgram (int program)
- 参数含义:
- program是着色器程序容器的id。
如果将program设置为0,表示使用固定功能管线。如果程序已经在使用的时候,对程序进行重新编译,编译后的应用程序会自动替换以前的那个被调用,这个时候你不需要再次调用这个函数。
四、向着色器程序中传递数据
(1)获取着色器程序内成员变量的id,也可以理解成为句柄、指针。
glGetAttribLocation方法:获取着色器程序中,指定为attribute类型变量的id
glGetUniformLocation方法:获取着色器称重,指定为uniform类型变量id
如:获取指向着色器中vertexPosition的index
- GLint vertexHandle = glGetAttribLocation(shaderProgramID, "vertexPosition");
获取指向着色器中modelViewProjectionMatrix的index
- Glint mvpMatrixHandle = glGetUniformLocation(shaderProgramID, "modelViewProjectionMatrix");
(2)传递数据
使用(1)获取的指向着色相应数据成员的各个id,就能将我们自己定义的顶点数据、颜色数据等等各种数据传递着色器当中了。
- // 使用shader程序
- GLES20.glUseProgram(mProgram);
- // 将最终变换矩阵传入shader程序
- GLES20.glUniformMatrix4fv(muMVPMatrixHandle, , false, MatrixState.getFinalMatrix(), );
- // 设置缓冲区起始位置
- mRectBuffer.position();
- // 顶点位置数据传入着色器
- GLES20.glVertexAttribPointer(maPositionHandle, , GLES20.GL_FLOAT, false, , mRectBuffer);
- // 顶点颜色数据传入着色器中
- GLES20.glVertexAttribPointer(maColorHandle, , GLES20.GL_FLOAT, false, *, mColorBuffer);
- // 顶点坐标传递到顶点着色器
- GLES20.glVertexAttribPointer(maTextureHandle, , GLES20.GL_FLOAT, false, , mRectBuffer);
- // 允许使用顶点坐标数组
- GLES20.glEnableVertexAttribArray(maPositionHandle);
- // 允许使用顶点颜色数组
- GLES20.glDisableVertexAttribArray(maColorHandle);
- // 允许使用定点纹理数组
- GLES20.glEnableVertexAttribArray(maTextureHandle);
- // 绑定纹理
- GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
- GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture);
- // 图形绘制
- GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, , );
(2.1)glVertexAttribPointer,定义顶点属性数组
- glVertexAttribPointer(vertexHandle, , GL_FLOAT, GL_FALSE, , (const GLvoid*)teapotVertices);
- glVertexAttribPointer(normalHandle, , GL_FLOAT, GL_FALSE, , (const GLvoid*)teapotNormals);
- glVertexAttribPointer(textureCoordHandle, , GL_FLOAT, GL_FALSE, , (const GLvoid*)teapotTexCoords);
- GL_API void GL_APIENTRY glVertexAttribPointer (GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* ptr)
- 参数含义:
index 指定要修改的顶点着色器中顶点变量的id
size 指定每个顶点属性的组件数量,必须为1、2、3或4。如position是由3个(x,y,z)组成,而颜色是4个(r,g,b,a)
type 指定数组中每个组件的数据类型。可用的符号常量有资料1,初始化是GL_FlOAT
normalized 指定当被访问时,固定点数据值是否应该被归一化(GL_TRUE)或者直接转换为固定点值(GL_FALSE)
stride 指定连续顶点属性之间的偏移量。如果为0,那么顶点属性会被理解成为:他们是紧密排列在一起的。初始化为0.如果normalized被设置为GL_TRUE,意味着整数型的值会被映射至区间[-1,1](有符号整形),或者[0,1](无符号整数),反之,这些值会被直接转换为浮点值而不进行归一化处理
ptr 顶点缓冲数据
- /* DataType */
- #define GL_BYTE 0x1400
- #define GL_UNSIGNED_BYTE 0x1401
- #define GL_SHORT 0x1402
- #define GL_UNSIGNED_SHORT 0x1403
- #define GL_FLOAT 0x1406
- #define GL_FIXED 0x140C
资料1
- /* Boolean */
- #define GL_FALSE 0
- #define GL_TRUE 1
资料2
(2.2)glActiveTexture,选择活动纹理单元。
- GL_API void GL_APIENTRY glActiveTexture (GLenum texture);
- 函数含义:
texture 指定哪一个纹理单元被设置为活动状态。texture必须是GL_TEXTURE之一,其中0<=i<GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS,初始值为GL_TEXTURE0
- /* TextureUnit */
- #define GL_TEXTURE0 0x84C0
- #define GL_TEXTURE1 0x84C1
- #define GL_TEXTURE2 0x84C2
- #define GL_TEXTURE3 0x84C3
- #define GL_TEXTURE4 0x84C4
- #define GL_TEXTURE5 0x84C5
- #define GL_TEXTURE6 0x84C6
- #define GL_TEXTURE7 0x84C7
- #define GL_TEXTURE8 0x84C8
- #define GL_TEXTURE9 0x84C9
- #define GL_TEXTURE10 0x84CA
- #define GL_TEXTURE11 0x84CB
- #define GL_TEXTURE12 0x84CC
- #define GL_TEXTURE13 0x84CD
- #define GL_TEXTURE14 0x84CE
- #define GL_TEXTURE15 0x84CF
- #define GL_TEXTURE16 0x84D0
- #define GL_TEXTURE17 0x84D1
- #define GL_TEXTURE18 0x84D2
- #define GL_TEXTURE19 0x84D3
- #define GL_TEXTURE20 0x84D4
- #define GL_TEXTURE21 0x84D5
- #define GL_TEXTURE22 0x84D6
- #define GL_TEXTURE23 0x84D7
- #define GL_TEXTURE24 0x84D8
- #define GL_TEXTURE25 0x84D9
- #define GL_TEXTURE26 0x84DA
- #define GL_TEXTURE27 0x84DB
- #define GL_TEXTURE28 0x84DC
- #define GL_TEXTURE29 0x84DD
- #define GL_TEXTURE30 0x84DE
- #define GL_TEXTURE31 0x84DF
- #define GL_ACTIVE_TEXTURE 0x84E0
- #define GL_CLIENT_ACTIVE_TEXTURE 0x84E1
资料3
glActiveTexture()确定了后续的纹理状态改变影响哪个纹理,纹理单元的数量是一句该纹理单元所被支持的具体实现。
(2.3)启用或禁用顶点属性数组。调用glEnableVertexAttribArray和glDisableVertexAttribArray传入参数index。如果启用,那么当glDrawArrays或glDrawElements被调用时候,顶点属性数组会被使用。
OpenGLES入门笔记一的更多相关文章
- OpenGLES入门笔记四
原文参考地址:http://www.cnblogs.com/zilongshanren/archive/2011/08/08/2131019.html 一.编译Vertex Shaders和Fragm ...
- OpenGLES入门笔记三
在入门笔记一中比较详细的介绍了顶点着色器和片面着色器. 在入门笔记二中讲解了简单的创建OpenGL场景流程的实现,但是如果在场景中渲染任何一种几何图形,还是需要入门笔记一中的知识:Vertex Sha ...
- OpenGLES入门笔记二
#import <UIKit/UIKit.h> #import <QuartzCore/QuartzCore.h> #import <OpenGLES/ES2/gl.h& ...
- 每天成长一点---WEB前端学习入门笔记
WEB前端学习入门笔记 从今天开始,本人就要学习WEB前端了. 经过老师的建议,说到他每天都会记录下来新的知识点,每天都是在围绕着这些问题来度过,很有必要每天抽出半个小时来写一个知识总结,及时对一天工 ...
- ES6入门笔记
ES6入门笔记 02 Let&Const.md 增加了块级作用域. 常量 避免了变量提升 03 变量的解构赋值.md var [a, b, c] = [1, 2, 3]; var [[a,d] ...
- [Java入门笔记] 面向对象编程基础(二):方法详解
什么是方法? 简介 在上一篇的blog中,我们知道了方法是类中的一个组成部分,是类或对象的行为特征的抽象. 无论是从语法和功能上来看,方法都有点类似与函数.但是,方法与传统的函数还是有着不同之处: 在 ...
- React.js入门笔记
# React.js入门笔记 核心提示 这是本人学习react.js的第一篇入门笔记,估计也会是该系列涵盖内容最多的笔记,主要内容来自英文官方文档的快速上手部分和阮一峰博客教程.当然,还有我自己尝试的 ...
- redis入门笔记(2)
redis入门笔记(2) 上篇文章介绍了redis的基本情况和支持的数据类型,本篇文章将介绍redis持久化.主从复制.简单的事务支持及发布订阅功能. 持久化 •redis是一个支持持久化的内存数据库 ...
- redis入门笔记(1)
redis入门笔记(1) 1. Redis 简介 •Redis是一款开源的.高性能的键-值存储(key-value store).它常被称作是一款数据结构服务器(data structure serv ...
随机推荐
- BGP路由协议详解(完整篇)
原文链接:http://xuanbo.blog.51cto.com/499334/465596/ 2010-12-27 12:02:45 上个月我写一篇关于BGP协议的博文,曾许诺过要完善这个文档,但 ...
- java list随机打乱
java list随机打乱package arrlist; import java.util.ArrayList; import java.util.Collections; import java. ...
- AngularJS引入Echarts的Demo
最近要用到图表展示,想了想,还是首选Echarts,HighCharts和D3.js备用吧, 而项目中也用到了AngularJS,所以需要把Echarts引入到AngularJs中一起使用, 试了试, ...
- CSS 栅格布局
bootstrap3.0教程之栅格系统原理(布局) http://www.jb51.net/css/152846.html [div+css]栅格化布局样式备用坑 http://www.0773lin ...
- python环境搭建-Linux系统下python2.6.6升级python3.5.2步骤
[root@template ~]# python -v # /usr/lib64/python2.6/encodings/utf_8.pyc matches /usr/lib64/python2.6 ...
- android AccessibltyService 辅助服务
1.使用Accessibility可以模拟手机点击,获取屏幕文字,通知消息等. 2.使用该类需新建一个AccessibilityService的子类,并在AndroidManifest.xml文件中注 ...
- 转载--web前端35个jQuery小技巧!
1. 禁止右键点击$(document).ready(function(){ $(document).bind("contextmenu",function(e){ ...
- python之旅2
python基础 1整数 查看整数类型的方法 >>> a = 1 >>> dir(a) ['__abs__', '__add__', '__and__', '__c ...
- SSM框架搭建(转发)
SSM框架,顾名思义,就是Spring+SpringMVC+mybatis. 通过Spring来将各层进行整合, 通过spring来管理持久层(mybatis), 通过spring来管理handler ...
- ARP协议工作流程
地址解析协议,即ARP(Address Resolution Protocol),是根据IP地址获取物理地址的一个TCP/IP协议.主机发送信息时将包含目标IP地址的ARP请求广播到网络上的所有主机, ...