------------------

前言

------------------ 
发了第一篇文章后,有不少同志留言,看来socket编程仍然是软件系统里面一个比较难的部分。

第一篇文章主要介绍了传输协议的设计,这个是整个socket框架最底层基础的部分,接下来整个socket服务器大楼都将在这个协议设计基础上不断搭建出来。

这篇文章我主要接上文提出的服务器各个性能参数给出解决思路。

-------------------

socket服务端接收模块设计 

-------------------

当服务器Accept一个新的socket之后,会对这个socket进行封装,成为一个connection(当然是自定义了) 。之后的处理都会交给这个connection负责。

由于socket发送的数据存在分包黏包问题,connection接收模块注定了要使用接收队列。当然这个所谓的接受队列并没有大家想象的这么深奥,大致的代码结构如下:

public class SocketReceiveQueue
{
    private Queue<ISocketReceivePackage> queue = null;
    private MemoryStream receiveBuffer = null;
}

即一个queue的接收队列+一个stream。处理逻辑:

a. 接收到数据,压入receiveBuffer

b. 从receiveBuffer读取数据、获取协议包ISocketReceivePackage,这里可能会有多个,也可能一个也没有。

c. 当接收完毕后,协议包再从queue出队列,交给注册的协议处理handler处理。

到目前为止,整块接收逻辑并没有涉及具体的业务、也绝对不应该涉及具体的业务。唯一要额外注意的就是接收包的长度问题,即协议包声明的length是否过大。

这里要注意,由于整个接收模块没有涉及到具体的业务逻辑,也就不应该在这里对任何的输入进行检测(非法攻击、频率等),代码上就是以最快速度解析完协议包,然后丢给上层handler分析。

-------------------

客户端请求性能分析

-------------------

当协议包来到了与业务相关的Handler之后,我们开始进行性能检测。首先是请求频率,使用如下公式:

requestInterval = (requestInterval * requestTimes + interval) / (requestTimes + 1)

计算得到的requestInterval就是客户端的请求频率。 数学上也很简单,就是一个类似f(x) = af(x)+b的迭代算法。这个算法的特定当然就是性能高,我只需要记录用户当前请求时间、请求累计次数之后,就能完全监控了用户的请求性能。

此外还需要记录顾客的错误次数。从设计理论上来说,客户传输过来的数据不应该有错,除非代码出错。当然,如果在值类型之间转换出现的问题算是错误的话(用户正常输入了错误的数值),这个不算入错误。这个错误值是需要记录在数据库里面;一旦发现错误值过大,则直接封这个IP了。

还需要说明的是,在服务器一定有一个触发器,每x秒会遍历一次所有的connection,一旦发现有长时间无请求的空连接,要主动踢出。

-------------------

socket服务端发送模块设计

-------------------

当服务器处理完数据后,需要将处理结果回复给客户端。如果使用简单的设计思路,即直接压入socket发送,性能是非常的低的;因此socket的发送必定需要使用发送队列。使用发送队列的优势在于:

a. 当服务器内部需要发送的数据激增的时候,通过压入发送队列,能够减轻IO的处理压力。很多时候我们会发现整个服务器的性能瓶颈就在IO的处理上(收发) ,而不是服务器的数据库操作等。因此设计上就要以减轻IO处理为目标。

b. 使用发送队列,能够把多个回复数据包合并一次发送,极大减轻了IO的压力。

发送队列的结构也就是一个Queue,大致的设计如下:

代码

public class SendMessageQueueController
{
   Queue<SocketConnection> queue;
} public class SocketConnection
{
    private SocketReceiveQueue receiveQueue;//接收队列
    private SocketSendQueue sendQueue;//发送队列
} public class SocketSendQueue 
{
    private Queue<ISocketSendPackage> queue;//发送协议集合
}

代码逻辑如下:

a. 把需要发送的协议包压入当前SocketSendQueue.

b. 判断SocketConnection是否已经存在在SendMessageQueueController,如果不存在,则入列;如果存在,则返回。

c. SendMessageQueueController每隔x毫秒检查一次发送队列,如果发现有数据,则进入while循环,直到所有SocketConnection出列并发送所有的数据。之后再进入等待。

d. 所谓包合并发送,就是把多个协议包一次写入发送的Stream里面,然后让socket发送。

这块的设计问题主要集中在线程冲突,需要在关键地方加几把锁,否则就容易出现线程冲突了。

------------------

后记

------------------

本文介绍的方法并不是最好的方法,我也相信业界有更加成熟的思路。不过我文中列举的一些设计思路至少我用起来还是能够满足现有需求的。

如果各位同志有更好的思路,希望多多留言指教。

在下一篇文章中将结合具体的传输协议开始设计服务端的通用逻辑模块,例如重发、数据缓存、登录登出等。

经过一年时间的沉淀 再次回首 TCP Socket服务器编程 (二)的更多相关文章

  1. 经过一年时间的沉淀 再次回首 TCP Socket服务器编程--转

    ------------------ 前言 ------------------ 开发了这么多年,发现最困难的程序开发就是通讯系统. 其他大部分系统,例如CRM/CMS/权限框架/MIS之类的,无论怎 ...

  2. 再次回首 TCP Socket服务器编程

    转载:http://www.cnblogs.com/zc22/archive/2010/06/27/1766007.html ------------------ 前言 --------------- ...

  3. C# 的tcp Socket设置自定义超时时间

    简单的c# TCP通讯(TcpListener) C# 的TCP Socket (同步方式) C# 的TCP Socket (异步方式) C# 的tcp Socket设置自定义超时时间 C# TCP ...

  4. 计算机网络再次整理————tcp周边[八]

    前言 tcp的包的格式可以看我以前的计算机网络整理,下面这些周边只是为了开发时候我们能用到一些理论知识. 正文 首先要介绍的就是域名,为啥有域名这东西呢?单纯站在网络的角度上讲这属于应用层的东西了. ...

  5. [译]再次对比TCP与UDP

    免责声明:和往常一样,此文章的观点都属于‘No Bugs’Hare(译注:一个网站) ,也许不一定和翻译者或者Overload编辑的意见一致.同时,翻译者从Lapine翻译到英语也具有一定的难度.除此 ...

  6. 再回首,Java温故知新(二):Java基本数据类型

    Java作为一种强类型语言,意味着每一个变量都会有特定的类型,Java共有8种基本类型,其中有4种整型(byte.short.int.long).两种浮点型(float.double).1种字符型(c ...

  7. 计算机网络再次整理————tcp例子[五]

    前言 本文介绍一些tcp的例子,然后不断完善一下. 正文 服务端: // See https://aka.ms/new-console-template for more information us ...

  8. 再回首数据结构—AVL树(二)

    前面主要介绍了AVL的基本概念与结构,下面开始详细介绍AVL的实现细节: AVL树实现的关键点 AVL树与二叉搜索树结构类似,但又有些细微的区别,从上面AVL树的介绍我们知道它需要维护其左右节点平衡, ...

  9. 计算机网络再次整理————tcp[二]

    前言 本文不会去介绍tcp的具体协议,因为这个tcp 应该不能说是单纯的连接和传输数据这么简单,里面还有很多机制. 正文 首先介绍一下什么是协议族(protocal Family),举个例子PF_IN ...

随机推荐

  1. Jmeter 03 Jmeter脚本开发

    JMeter 工作区介绍 JMeter Http 协议录制 JMeter 脚本调测 JMeter 关联 JMeter 参数化 JMeter 检查点 JMeter 事务 JMeter 集合点 JMete ...

  2. poj2367

    Genealogical tree Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 4420   Accepted: 2933 ...

  3. EasyPlayer RTSP播放器OCX RegSvr32注册报错,DllRegisterServer调用失败,错误代码为0x80040200 解决方法

    问题描述 模块"EasyPlayer-RTSPWebActiveX.ocx" 已加载,但对DLLRegisterServer调用失败,错误代码为0x80040200. 解决方法 是 ...

  4. 【python】-- web开发之JavaScript

    JavaScript JavaScript是一门编程语言,浏览器内置了JavaScript语言的解释器,所以在浏览器上按照JavaScript语言的规则编写相应代码之,浏览器可以解释并做出相应的处理. ...

  5. 洛谷2704 [NOI2001]炮兵阵地

    题目戳这里 Solution 状压DP很好的入门题,用熟练位运算貌似也没那么难. 首先分析一下题目: 看见n=100,m=10,立马就想到了状压,看起来也像DP,所以我们还是采用行号为阶段的状压DP. ...

  6. JVM指令重排

    指令重排的基本原则: a.程序顺序原则:一个线程内保证语义的串行性 b.volatile规则:volatile变量的写,先发生于读 c.锁规则:解锁(unlock)必然发生在随后的加锁(lock)前 ...

  7. FlashFXP上传下载

    正常情况我们的生产环境我们本地是不能直接访问的,因为网段不通,且生产环境不允许我们随便访问,但是对于我们自运维的集群我们有时候需要上去做一些操作,通过堡垒机跳转到生产机器上即可,但是我们不能通过xsh ...

  8. GIT笔记:将项目发布到GITHUB

    GIT笔记:将项目发布到GITHUB 本机配置 1.在项目目录初始化GIT $ git init 2.用命令git add告诉Git,把文件添加到仓库 $ git add . // 这里是所有文件,用 ...

  9. 使用nginx进行负载均衡

    nginx主要用于1:请求分流 2:负载均衡.用在大型系统(集群)上,在单机上体现不出优势.  本实例在windows环境下进行.  一.安装nginx  1.下载nginx1.8.0版 2.解压至1 ...

  10. Elatsicsearch分片和副本相关知识

    1.分片和副本 1.1什么是分片 简单来讲就是咱们在ES中所有数据的文件块,也是数据的最小单元块,整个ES集群的核心就是对所有分片的分布.索引.负载.路由等达到惊人的速度. 分片是把索引数据切分成多个 ...