热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

SuperSocket入门(五)常用协议实现模版及FixedSizeReceiveFilter示例_0

Socket里面的协议解析是Socket通讯程序设计中最复杂的地方,如果你的应用层协议设计或实现不佳,Socket通讯中常见的粘包,分包就
Socket里面的协议解析是Socket通讯程序设计中最复杂的地方,如果你的应用层协议设计或实现不佳,Socket通讯中常见的粘包,分包就难以避免。SuperSocket内置了命令行格式的协议CommandLineProtocol,如果你使用了其它格式的协议,就必须自行实现自定义协议CustomProtocol。看了一篇文档之后, 你可能会觉得用 SuperSocket 来实现你的自定义协议并不简单。 为了让这件事变得更容易一些, SuperSocket 提供了一些通用的协议解析工具, 你可以用他们简单而且快速的实现你自己的通信协议:

  • TerminatorReceiveFilter (SuperSocket.SocketBase.Protocol.TerminatorReceiveFilter, SuperSocket.SocketBase) ---结束符协议
  • CountSpliterReceiveFilter (SuperSocket.Facility.Protocol.CountSpliterReceiveFilter, SuperSocket.Facility)---固定数量分隔符协议
  • FixedSizeReceiveFilter (SuperSocket.Facility.Protocol.FixedSizeReceiveFilter, SuperSocket.Facility)---固定请求大小协议
  • BeginEndMarkReceiveFilter (SuperSocket.Facility.Protocol.BeginEndMarkReceiveFilter, SuperSocket.Facility)---带起止符协议
  • FixedHeaderReceiveFilter (SuperSocket.Facility.Protocol.FixedHeaderReceiveFilter, SuperSocket.Facility)---头部格式固定并包含内容长度协议

     1、TerminatorReceiveFilter结束符协议

 结束符协议和命令行协议类似,一些协议用结束符来确定一个请求.例如, 一个协议使用两个字符 "##" 作为结束符, 于是你可以使用类 "TerminatorReceiveFilterFactory":

           结束符协议TerminatorProtocolServer :

public class TerminatorProtocolServer : AppServer
{
public TerminatorProtocolServer()
: base(new TerminatorReceiveFilterFactory("##"))
{
}
}

基于TerminatorReceiveFilter实现你的接收过滤器(ReceiveFilter):

public class YourReceiveFilter : TerminatorReceiveFilter
{
//More code
}

实现你的接收过滤器工厂(ReceiveFilterFactory)用于创建接受过滤器实例:

public class YourReceiveFilterFactory : IReceiveFilterFactory
{
//More code
}

     2、CountSpliterReceiveFilter 固定数量分隔符协议

有些协议定义了像这样格式的请求 "#part1#part2#part3#part4#part5#part6#part7#". 每个请求有7个由 '#' 分隔的部分. 这种协议的实现非常简单:

///


/// 请求格式:#part1#part2#part3#part4#part5#part6#part7#
///

public class CountSpliterAppServer : AppServer
{
public CountSpliterAppServer()
: base(new CountSpliterReceiveFilterFactory((byte)'#', 8)) //8个分隔符,7个参数。除使用默认的过滤工厂,还可以参照上一个实例定制协议
{
}
}

     3、FixedSizeReceiveFilter 固定请求大小协议

在这种协议之中, 所有请求的大小都是相同的。如果你的每个请求都是有8个字符组成的字符串,如"HUANG LI", 你应该做的事就是想如下代码这样实现一个接收过滤器(ReceiveFilter):

class MyReceiveFilter : FixedSizeReceiveFilter
{
public MyReceiveFilter()
: base(8) //传入固定的请求大小
{
}
protected override StringRequestInfo ProcessMatchedRequest(byte[] buffer, int offset, int length, bool toBeCopied)
{
//TODO: 通过解析到的数据来构造请求实例,并返回
}
}

然后在你的 AppServer 类中使用这个接受过滤器 (ReceiveFilter):

public class MyAppServer : AppServer
{
public MyAppServer()
: base(new DefaultReceiveFilterFactory()) //使用默认的接受过滤器工厂 (DefaultReceiveFilterFactory)
{
}
}

     4、BeginEndMarkReceiveFilter 带起止符协议

在这类协议的每个请求之中 都有固定的开始和结束标记。例如, 我有个协议,它的所有消息都遵循这种格式 "&xxxxxxxxxxxxxx#"。因此,在这种情况下, "&" 是开始标记, "#" 是结束标记,于是你的接受过滤器可以定义成这样:

class MyReceiveFilter : BeginEndMarkReceiveFilter
{
//开始和结束标记也可以是两个或两个以上的字节
private readonly static byte[] BeginMark = new byte[] { (byte)'&' };
private readonly static byte[] EndMark = new byte[] { (byte)'#' };
public MyReceiveFilter()
: base(BeginMark, EndMark) //传入开始标记和结束标记
{
}
protected override StringRequestInfo ProcessMatchedRequest(byte[] readBuffer, int offset, int length)
{
//TODO: 通过解析到的数据来构造请求实例,并返回
}
}

然后在你的 AppServer 类中使用这个接受过滤器 (ReceiveFilter):

public class MyAppServer : AppServer
{
public MyAppServer()
: base(new DefaultReceiveFilterFactory()) //使用默认的接受过滤器工厂 (DefaultReceiveFilterFactory)
{
}
}

     5、FixedHeaderReceiveFilter 头部格式固定并包含内容长度协议

这种协议将一个请求定义为两大部分, 第一部分定义了包含第二部分长度等等基础信息. 我们通常称第一部分为头部.

例如, 我们有一个这样的协议: 头部包含 6 个字节, 前 4 个字节用于存储请求的名字, 后两个字节用于代表请求体的长度:

/// +-------+---+-------------------------------+
/// |request| l | |
/// | name | e | request body |
/// | (4) | n | |
/// | |(2)| |
/// +-------+---+-------------------------------+

使用 SuperSocket, 你可以非常方便的实现这种协议:

class MyReceiveFilter : FixedHeaderReceiveFilter
{
public MyReceiveFilter()
: base(6)
{
}
protected override int GetBodyLengthFromHeader(byte[] header, int offset, int length)
{
return (int)header[offset + 4] * 256 + (int)header[offset + 5];
}
protected override BinaryRequestInfo ResolveRequestInfo(ArraySegment header, byte[] bodyBuffer, int offset, int length)
{
return new BinaryRequestInfo(Encoding.UTF8.GetString(header.Array, header.Offset, 4), bodyBuffer.CloneRange(offset, length));
}
}

你需要基于类FixedHeaderReceiveFilter实现你自己的接收过滤器.


    • 传入父类构造函数的 6 表示头部的长度;
    • 方法"GetBodyLengthFromHeader(...)" 应该根据接收到的头部返回请求体的长度;
    • 方法 ResolveRequestInfo(....)" 应该根据你接收到的请求头部和请求体返回你的请求类型的实例.

    实际使用场景:

     到这里五种协议的模板你都已经了解了一遍,并且知道了相关的格式处理。接下来看一个网络示例:

通讯协议格式:

      在看到上图协议是在纠结客户端发送16进制,服务器怎么接收,16进制的报文如下:


26 01 00 19 4E 4A 30 31 31 01 44 41 31 31 32 00 07 00 00 00 00 00 00 34 23


     16进制也好,10进制也好,其他的进制也好,最终都是转换成byte[],其实在处理数据时,发送过去的数据都是可以转换成为byte[]的,所以服务的只要解析byte[]数组就行了。按照协议来解析就能得到想要的数据。下面使用FixedSizeReceiveFilter的例子,代码如下:

根据上面的通讯协议,开始来实现解析:

第一步、定义一个和协议合适的数据结构


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
/****************************************************************
* 作者:黄昏前黎明后
* CLR版本:4.0.30319.42000
* 创建时间:2017-01-23 21:12:30
* 2017
* 描述说明:协议数据包
*
* 修改历史:
*
*
****************************************************************
*/
namespace SuperSocketDemo
{
public class HLData
{
///


/// 开始符号
///

public char Head { get; set; }
///
/// 协议包数据
///

public byte Ping { get; set; }
///
/// 数据长度
///

public ushort Lenght { get; set; }
///
/// 终端ID
///

public uint FID { get; set; }
///
/// 目标类型
///

public byte Type { get; set; }
///
/// 转发终端ID
///

public uint SID { get; set; }
///
/// 发送计数
///

public ushort SendCount { get; set; }
///
/// 保留字段
///

public byte[] Retain { get; set; }
///
/// 异或校验
///

public byte Check { get; set; }
///
/// 结束符号
///

public char End { get; set; }
public override string ToString()
{
return string.Format("开始符号:{0},包数据:{1},数据长度:{2},终端ID:{3},目标类型:{4},转发终端ID:{5},发送包计数:{6},保留字段:{7},异或校验:{8},结束符号:{9}",
Head, Ping, Lenght, FID, Type, SID, SendCount, Retain, Check, End);
}
}
}
HLData

      第二步、建立一个RequestInfo来给server数据接收


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SuperSocket.SocketBase.Protocol;
/****************************************************************
* 作者:黄昏前黎明后
* CLR版本:4.0.30319.42000
* 创建时间:2017-01-22 21:03:31
* 2017
* 描述说明:数据请求
*
* 修改历史:
*
*
****************************************************************
*/
namespace SuperSocketDemo
{
public class HLProtocolRequestInfo : RequestInfo
{
public HLProtocolRequestInfo(HLData hlData)
{
//如果需要使用命令行协议的话,那么命令类名称HLData相同
Initialize("HLData", hlData);
}
}
}

HLProtocolRequestInfo 类

     第三步、FixedSize协议解析


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SuperSocket.SocketBase.Protocol;
using SuperSocket.Facility.Protocol;
using SuperSocket.Common;
/****************************************************************
* 作者:黄昏前黎明后
* CLR版本:4.0.30319.42000
* 创建时间:2017-01-22 21:06:01
* 2017
* 描述说明:协议解析类,固定请求大小的协议
*
* 修改历史:
*
*
****************************************************************
*/
namespace SuperSocketDemo
{
///


/// 固定请求大小的协议,(帧格式为HLProtocolRequestInfo)
///

public class HLProtocolReceiveFilter : FixedSizeReceiveFilter
{
public HLProtocolReceiveFilter() : base(25)//总的字节长度 1+1+2+5+1+5+2+6+1+1 = 25
{
}
protected override HLProtocolRequestInfo ProcessMatchedRequest(byte[] buffer, int offset, int length, bool toBeCopied)
{
var HLData = new HLData();
HLData.Head
= (char)buffer[offset];//开始标识的解析,1个字节
HLData.Ping = buffer[offset + 1];//数据,从第2位起,只有1个字节
HLData.Lenght = BitConverter.ToUInt16(buffer, offset + 2);//数据长度,从第3位开始,2个字节
HLData.FID = BitConverter.ToUInt32(buffer, offset + 4);//本终端ID,从第5位开始,5个字节
HLData.Type = buffer[offset + 9];//目标类型,从第10位开始,1个字节
HLData.SID = BitConverter.ToUInt32(buffer, offset + 10);//转发终端ID,从第11位开始,5个字节
HLData.SendCount = BitConverter.ToUInt16(buffer, offset + 15);//发送包计数,从第16位开始,2个字节
HLData.Retain = buffer.CloneRange(offset + 17, 6);//保留字段,从18位开始,6个字节
HLData.Check = buffer[offset + 23];//异或校验,从24位开始,1个字节
HLData.End = (char)buffer[offset + 24];//结束符号,从第25位开始,一个字节
return new HLProtocolRequestInfo(HLData);
}
}
}
HLProtocolReceiveFilter类

     第四步、建立协议工厂HLReceiveFilterFactory


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Protocol;
using System.Net;
/****************************************************************
* 作者:黄昏前黎明后
* CLR版本:4.0.30319.42000
* 创建时间:2017-01-23 :22:01:25
* 2017
* 描述说明:协议工厂
*
* 修改历史:
*
*
****************************************************************
*/
namespace SuperSocketDemo
{
public class HLReceiveFilterFactory: IReceiveFilterFactory
{
public IReceiveFilter CreateFilter(IAppServer appServer, IAppSession appSession, IPEndPoint remoteEndPoint)
{
return new HLBeginEndMarkReceiveFilter();
}
}
}

HLReceiveFilterFactory类

     第五步、自定义HLProtocolSession继承AppSession


using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Protocol;
using System;
/****************************************************************
* 作者:黄昏前黎明后
* CLR版本:4.0.30319.42000
* 创建时间:2017-01-22 21:15:11
* 2017
* 描述说明:自定义HLProtocolSession
*
* 修改历史:
*
*
****************************************************************
*/
namespace SuperSocketDemo
{
public class HLProtocolSession : AppSession
{
protected override void HandleException(Exception e)
{
}
}
}

HLProtocolSession类

     第六步、自定义HLProtocolServer继承AppServer


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Protocol;
/****************************************************************
* 作者:黄昏前黎明后
* CLR版本:4.0.30319.42000
* 创建时间:2017-01-22 21:16:57
* 2017
* 描述说明:自定义server
*
* 修改历史:
*
*
****************************************************************
*/
namespace SuperSocketDemo
{
public class HLProtocolServer : AppServer
{
///


/// 使用自定义协议工厂
///

public HLProtocolServer()
:
base(new HLReceiveFilterFactory())
{
}
}
}
HLProtocolServer类   

     第七步、加上起止符协议HLBeginEndMarkReceiveFilter


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SuperSocket.Common;
using SuperSocket.Facility.Protocol;
/****************************************************************
* 作者:黄昏前黎明后
* CLR版本:4.0.30319.42000
* 创建时间:2017-01-23 22:07:03
* 2017
* 描述说明:带起止符的协议, "&" 是开始标记, "#" 是结束标记,开始结束标记由自己定义
*
* 修改历史:
*
*
****************************************************************
*/
namespace SuperSocketDemo
{
public class HLBeginEndMarkReceiveFilter : BeginEndMarkReceiveFilter
{
private readonly static char strBegin = '&';
private readonly static char strEnd = '#';
//开始和结束标记也可以是两个或两个以上的字节
private readonly static byte[] BeginMark = new byte[] { (byte)strBegin };
private readonly static byte[] EndMark = new byte[] { (byte)strEnd };
public HLBeginEndMarkReceiveFilter() : base(BeginMark, EndMark)
{
}
///


/// 这里解析的到的数据是会把头和尾部都给去掉的
///

///
///
///
///
protected override HLProtocolRequestInfo ProcessMatchedRequest(byte[] readBuffer, int offset, int length)
{
var HLData = new HLData();
HLData.Head
= strBegin;//自己定义开始符号
HLData.Ping = readBuffer[offset];//数据,从第1位起,只有1个字节
HLData.Lenght = BitConverter.ToUInt16(readBuffer, offset + 1);//数据长度,从第2位开始,2个字节
HLData.FID = BitConverter.ToUInt32(readBuffer, offset + 3);//本终端ID,从第4位开始,5个字节
HLData.Type = readBuffer[offset + 8];//目标类型,从第9位开始,1个字节
HLData.SID = BitConverter.ToUInt32(readBuffer, offset + 9);//转发终端ID,从第10位开始,5个字节
HLData.SendCount = BitConverter.ToUInt16(readBuffer, offset + 14);//发送包计数,从第15位开始,2个字节
HLData.Retain = readBuffer.CloneRange(offset + 16, 6);//保留字段,从17位开始,6个字节
HLData.Check = readBuffer[offset + 22];//异或校验,从23位开始,1个字节
HLData.End = strEnd;//结束符号,自己定义
return new HLProtocolRequestInfo(HLData);
}
}
}
HLBeginEndMarkReceiveFilter类

     第八步、服务启动和停止


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Protocol;
using SuperSocket.SocketEngine;
/****************************************************************
* 作者:黄昏前黎明后
* CLR版本:4.0.30319.42000
* 创建时间:2017-01-19 00:02:17
* 2017
* 描述说明:服务启动和停止入口
*
* 修改历史: 2017 -01-19 调整自定义mysession和myserver
* 2017 -01-23 通讯协议解析,直接使用入口注册事件
*
****************************************************************
*/
namespace SuperSocketDemo
{
class Program
{
///


/// SuperSocket服务启动或停止
///

///
static void Main(string[] args)
{
Console.WriteLine(
"请按任何键进行启动SuperSocket服务!");
Console.ReadKey();
Console.WriteLine();
var HLProtocolServer = new HLProtocolServer();
// 设置端口号
int port = 2017;
//启动应用服务端口
if (!HLProtocolServer.Setup(port)) //启动时监听端口2017
{
Console.WriteLine(
"服务端口启动失败!");
Console.ReadKey();
return;
}
Console.WriteLine();
//注册连接事件
HLProtocolServer.NewSessionConnected += HLProtocolServer_NewSessionConnected;
//注册请求事件
HLProtocolServer.NewRequestReceived += HLProtocolServer_NewRequestReceived;
//注册Session关闭事件
HLProtocolServer.SessionClosed += HLProtocolServer_SessionClosed;
//尝试启动应用服务
if (!HLProtocolServer.Start())
{
Console.WriteLine(
"服务启动失败!");
Console.ReadKey();
return;
}
Console.WriteLine(
"服务器状态:" + HLProtocolServer.State.ToString());
Console.WriteLine(
"服务启动成功,请按'E'停止服务!");
while (Console.ReadKey().KeyChar != 'E')
{
Console.WriteLine();
continue;
}
//停止服务
HLProtocolServer.Stop();
Console.WriteLine(
"服务已停止!");
Console.ReadKey();
}
static void HLProtocolServer_SessionClosed(HLProtocolSession session, SuperSocket.SocketBase.CloseReason value)
{
Console.WriteLine(session.RemoteEndPoint.ToString()
+ "连接断开. 断开原因:" + value);
}
static void HLProtocolServer_NewSessionConnected(HLProtocolSession session)
{
Console.WriteLine(session.RemoteEndPoint.ToString()
+ " 已连接.");
}
///
/// 协议并没有什么太多复杂逻辑,不需要用到命令模式,直接用这种方式就可以了
///

///
///
private static void HLProtocolServer_NewRequestReceived(HLProtocolSession session, HLProtocolRequestInfo requestInfo)
{
Console.WriteLine();
Console.WriteLine(
"数据来源: " + session.RemoteEndPoint.ToString());
Console.WriteLine(
"接收数据内容:"+requestInfo.Body);
}
}
}
Program类

     通讯协议需要使用小工具进行调试,本人使用的是TCP/UDP端口调试工具SocketTool V2.大家可以直接进行下载。使用HEX模式进行发送16进制报文,服务器输出结果:

 

本文参考官方文档 内置的常用协议实现模版


推荐阅读
  • 深入理解Kafka服务端请求队列中请求的处理
    本文深入分析了Kafka服务端请求队列中请求的处理过程,详细介绍了请求的封装和放入请求队列的过程,以及处理请求的线程池的创建和容量设置。通过场景分析、图示说明和源码分析,帮助读者更好地理解Kafka服务端的工作原理。 ... [详细]
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • 本文介绍了一个在线急等问题解决方法,即如何统计数据库中某个字段下的所有数据,并将结果显示在文本框里。作者提到了自己是一个菜鸟,希望能够得到帮助。作者使用的是ACCESS数据库,并且给出了一个例子,希望得到的结果是560。作者还提到自己已经尝试了使用"select sum(字段2) from 表名"的语句,得到的结果是650,但不知道如何得到560。希望能够得到解决方案。 ... [详细]
  • Go Cobra命令行工具入门教程
    本文介绍了Go语言实现的命令行工具Cobra的基本概念、安装方法和入门实践。Cobra被广泛应用于各种项目中,如Kubernetes、Hugo和Github CLI等。通过使用Cobra,我们可以快速创建命令行工具,适用于写测试脚本和各种服务的Admin CLI。文章还通过一个简单的demo演示了Cobra的使用方法。 ... [详细]
  • 第四章高阶函数(参数传递、高阶函数、lambda表达式)(python进阶)的讲解和应用
    本文主要讲解了第四章高阶函数(参数传递、高阶函数、lambda表达式)的相关知识,包括函数参数传递机制和赋值机制、引用传递的概念和应用、默认参数的定义和使用等内容。同时介绍了高阶函数和lambda表达式的概念,并给出了一些实例代码进行演示。对于想要进一步提升python编程能力的读者来说,本文将是一个不错的学习资料。 ... [详细]
  • Python爬虫中使用正则表达式的方法和注意事项
    本文介绍了在Python爬虫中使用正则表达式的方法和注意事项。首先解释了爬虫的四个主要步骤,并强调了正则表达式在数据处理中的重要性。然后详细介绍了正则表达式的概念和用法,包括检索、替换和过滤文本的功能。同时提到了re模块是Python内置的用于处理正则表达式的模块,并给出了使用正则表达式时需要注意的特殊字符转义和原始字符串的用法。通过本文的学习,读者可以掌握在Python爬虫中使用正则表达式的技巧和方法。 ... [详细]
  • 本文介绍了一种轻巧方便的工具——集算器,通过使用集算器可以将文本日志变成结构化数据,然后可以使用SQL式查询。集算器利用集算语言的优点,将日志内容结构化为数据表结构,SPL支持直接对结构化的文件进行SQL查询,不再需要安装配置第三方数据库软件。本文还详细介绍了具体的实施过程。 ... [详细]
  • Android自定义控件绘图篇之Paint函数大汇总
    本文介绍了Android自定义控件绘图篇中的Paint函数大汇总,包括重置画笔、设置颜色、设置透明度、设置样式、设置宽度、设置抗锯齿等功能。通过学习这些函数,可以更好地掌握Paint的用法。 ... [详细]
  • 本文详细介绍了Python中正则表达式和re模块的使用方法。首先解释了转义符的作用,以及如何在字符串中包含特殊字符。然后介绍了re模块的功能和常用方法。通过学习本文,读者可以掌握正则表达式的基本概念和使用技巧,进一步提高Python编程能力。 ... [详细]
  • 本文整理了315道Python基础题目及答案,帮助读者检验学习成果。文章介绍了学习Python的途径、Python与其他编程语言的对比、解释型和编译型编程语言的简述、Python解释器的种类和特点、位和字节的关系、以及至少5个PEP8规范。对于想要检验自己学习成果的读者,这些题目将是一个不错的选择。请注意,答案在视频中,本文不提供答案。 ... [详细]
  • Ihaveaworkfolderdirectory.我有一个工作文件夹目录。holderDir.glob(*)>holder[ProjectOne, ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 知识图谱——机器大脑中的知识库
    本文介绍了知识图谱在机器大脑中的应用,以及搜索引擎在知识图谱方面的发展。以谷歌知识图谱为例,说明了知识图谱的智能化特点。通过搜索引擎用户可以获取更加智能化的答案,如搜索关键词"Marie Curie",会得到居里夫人的详细信息以及与之相关的历史人物。知识图谱的出现引起了搜索引擎行业的变革,不仅美国的微软必应,中国的百度、搜狗等搜索引擎公司也纷纷推出了自己的知识图谱。 ... [详细]
  • 本文介绍了一种划分和计数油田地块的方法。根据给定的条件,通过遍历和DFS算法,将符合条件的地块标记为不符合条件的地块,并进行计数。同时,还介绍了如何判断点是否在给定范围内的方法。 ... [详细]
  • 本文介绍了Oracle数据库中tnsnames.ora文件的作用和配置方法。tnsnames.ora文件在数据库启动过程中会被读取,用于解析LOCAL_LISTENER,并且与侦听无关。文章还提供了配置LOCAL_LISTENER和1522端口的示例,并展示了listener.ora文件的内容。 ... [详细]
author-avatar
N__Z少爷_763
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有