2015年11月29日星期日

Tomcat源码分析(一) - Crazy兔子

本邮件内容由第三方提供,如果您不想继续收到该邮件,可 点此退订
Tomcat源码分析(一) - Crazy兔子  阅读原文»

【摘要】Tomcat 源码分析 阅读全文

不懂CSS的后端难道就不是好程序猿? - 欢醉  阅读原文»

【摘要】由于H5在移动端的发展如日中天,现在大部分公司对高级前端需求也是到处挖墙角,前端薪资也随之水涨船高,那公司没有配备专用的前端怎么办呢? 作为老板眼中的"程序猿" 前端都不会是非常无能的表现,那作为后端人员需要不需要懂前端呢?为了提升价值还是需要懂点的,不然一点问题就找前端,那是不是让别人疯了…... 阅读全文

阅读更多内容

2015年11月28日星期六

PHP中的数据库二、memcache - 枕边书

本邮件内容由第三方提供,如果您不想继续收到该邮件,可 点此退订
PHP中的数据库二、memcache - 枕边书  阅读原文»

【摘要】在一个高并发的web应用中,数据库存取瓶颈一直是个大问题,一旦达到某个极限,数据库很容易崩溃,但是如果我们把常用的数据放到内存中,在需要的时候从内存中取,不光读取速度快,而且节约数据库IO。memcache简介Memcache是一个高性能的分布式的内存对象缓存系统,通过在内存里维护一个统一的巨大的h... 阅读全文

(spring-第11回【IoC基础篇】)BeanWrapper--实例化Bean的第四大利器 - 煎饼果子多放葱  阅读原文»

【摘要】BeanWrapperImpl填充Bean属性;BeanWrapper的三重身份;属性访问器PropertyAccessor;属性描述器PropertyDescriptor;属性编辑器注册表PropertyEditorRegistry; 阅读全文

阅读更多内容

2015年11月27日星期五

“Zhuang.Data”轻型数据库访问框架(二)框架的入口DbAccessor对象 - weibin268

本邮件内容由第三方提供,如果您不想继续收到该邮件,可 点此退订
"Zhuang.Data"轻型数据库访问框架(二)框架的入口DbAccessor对象 - weibin268  阅读原文»

【摘要】目录:"Zhuang.Data"轻型数据库访问框架(一)开篇介绍"Zhuang.Data"轻型数据库访问框架(二)框架的入口DbAccessor对象先来看一段代码DbAccessor dba = DbAccessor.Create();var dt = dba.QueryDataTable("sel... 阅读全文

KnockoutJS Documentation-延迟更新(deferred update) - #WU  阅读原文»

【摘要】复杂的应用通常包含着大量错综复杂的依赖,更新一个监控属性可能会级联触发很多计算监控属性、手动的订阅、UI绑定更新。这些更新可能是代价昂贵的,一些不必要的中间值被推送给视图,或者导致额外的计算监控属性评估,严重影响效率。即使在简单的应用里,更新关联的多个属性或一个属性更新多次(如填充监控数组)也会导致... 阅读全文

阅读更多内容

2015年11月26日星期四

sql server 基础教程[温故而知新三] - 请叫我头头哥

本邮件内容由第三方提供,如果您不想继续收到该邮件,可 点此退订
sql server 基础教程[温故而知新三] - 请叫我头头哥  阅读原文»

【摘要】子曰:"温故而知新,可以为师矣。"孔子说:"温习旧知识从而得知新的理解与体会,凭借这一点就可以成为老师了。" 尤其是咱们搞程序的人,不管是不是全栈工程师,都是集十八般武艺于一身。不过有时候有些知识如果有很久没用了的话,就会忘记,甚至是忘的你一点都想不起来,尤其是一些基础的东西。所以我才打算写个"温故... 阅读全文

使用七牛云存储上传文件学习案例 - 七岁童年  阅读原文»

【摘要】最近学习了使用七牛云储存上传文件的经验过程,和大家分享一下。语言:C#七牛云储存用户注册地址:https://portal.qiniu.com/signup?code=3lciek5byj2oi起初看七牛云储存官方的开发文档,按照上面的说的进行配置的过程中,遇到了2个问题,一个是公司网络的问题(泪奔... 阅读全文

阅读更多内容

2015年11月24日星期二

WCF 自托管、无配置文件实现jsonp(跨域)的访问 - 文楚

本邮件内容由第三方提供,如果您不想继续收到该邮件,可 点此退订
WCF 自托管、无配置文件实现jsonp(跨域)的访问 - 文楚  阅读原文»

【摘要】以下内容基于WCF4.0,本文将对比讨论配置文件方案和无配置文件方案的实现方式。 WCF4.0加入了对RESTFU和标准终结点的支持,这为实现跨域提供了简单的方式。一、有配置文件的情况:首先我们先定义一个服务:[ServiceContract]public class MinitorServer{.... 阅读全文

openstack单元测试用组件一览 - 飞扬青云  阅读原文»

【摘要】1、进入短信界面2、菜单-设置3、修改短信中心号码(Set the SIM's smsc number) 保存【测试结果】:提示保存成功,但是号码没有改变,退出重新进入设置才会看到号码更新【预期结果】:提示保存成功,号码变为修改过的号码相关Activity:通过Logcat中I/ActivityMa... 阅读全文

阅读更多内容

2015年11月22日星期日

Windows10自适应和交互式toast通知[1] - 蘑菇先生

本邮件内容由第三方提供,如果您不想继续收到该邮件,可 点此退订
Windows10自适应和交互式toast通知[1] - 蘑菇先生  阅读原文»

【摘要】阅读目录:概述toast通知的结构视觉区域(Visual)行为(Actions)特定场景下的Toast通知带多内容的通知带行为的通知(例子1)带行为的通知(例子2)带文本输入框和行为的通知(例子1)带文本输入框和行为的通知(例子2)带下拉框输入和行为的通知提醒通知前后台激活的例子和的Schema的S... 阅读全文

如何在 ie6 中使用 "localStorage" - 韩子迟  阅读原文»

【摘要】好吧,我只是个标题党,ie6 下根本无法使用跟 h5 沾边的 "localStorage" 。今天要向大家介绍的是 ie 特有的 userData 的存储方式,并且对它进行封装,使得不支持 localStorage 的浏览器能像使用 localStorage 一样使用 userData。 userD... 阅读全文

阅读更多内容

2015年11月21日星期六

C语言学习 数独游戏 - 乌龙哈里

本邮件内容由第三方提供,如果您不想继续收到该邮件,可 点此退订
C语言学习 数独游戏 - 乌龙哈里  阅读原文»

【摘要】花了1周多时间学习了C语言,开始练手写解数独游戏的程序。 阅读全文

JavaScript高级---组合模式设计 - EthanCoco  阅读原文»

【摘要】一、设计模式javascript里面给我们提供了很多种设计模式:工厂、桥、组合、门面、适配器、装饰者、享元、代理、观察者、命令、责任链在前面我们实现了工厂模式和桥模式工厂模式 :核心:为了生产对象,实现解耦。桥接模式 :(桥接模式是一种既能把两个对象连接在一起,又能避免二者间的强耦合的方法。通过"桥... 阅读全文

阅读更多内容

2015年11月20日星期五

[SCOM]另辟蹊径获取SCOM监控的磁盘空间数据

本邮件内容由第三方提供,如果您不想继续收到该邮件,可 点此退订
[SCOM]另辟蹊径获取SCOM监控的磁盘空间数据  阅读原文»

[SCOM]另辟蹊径获取SCOM监控的磁盘空间数据

核心:SCOM几乎所有的数据都是存放在DB中;
难点:DB表结构、字段关联、多表关联;
方案:从DB中获取;

一般情况下,如果仅仅是为了查看数据,由于管理包自带了这些监视器,只需通过SCOM控制台直接查看相应的仪表板,树状图节点路径如下:"监视�DMicrosoftWindows 服务器�D运行状况监视�D磁盘运行状况"即可在展示台中看到逻辑磁盘容量大小。(当然也自带了操作系统、群集、网络的运行状况等仪表板,本文只针对逻辑磁盘空间进行说明。)如下图

wKioL1ZNZETAEdvTAAEn9Nz3sHk732.png

如果第三方工具要实时从SCOM中获取到数据,并进行处理时会发现第三方工具存在无法直接调用监视器的情况,这时也许会采取另外一个办法:创建规则(而规则是利用WMI进行获取数据的,第三方工具可以直接调用WMI工作)。

不过会存一些问题,比如SCOM是否重复监控:如果磁盘容量发生警报,自带的监视器会告警,另外自定义的规则是否存在同时告警。

SCOM警报规则需要手动关闭,这是警报监视器最大的区别,在实际环境中多数推崇能使用监视器的就尽可能使用监视器。采用监视器一来是可实时反馈对象的正异常情况,二来可减轻运维人员的工作负担。

由于SCOM监控获取到的数据均存放到OperationsManager这个数据库中的,这时可以考虑让第三方工具直接从数据库获取需要的数据。OperationsManager数据表结构不清楚,是否存在某些表和字段存放逻辑磁盘大小的呢?

这时可通过SQL语句进行查询和了解表结构和字段间的关联

获取所有表名

  SELECT Name FROM DatabaseName..SysObjects Where XType='U' ORDER BY Name  

XType='U':表示所有用户表;
XType='S':表示所有系统表;

获取所有字段名

  SELECT Name FROM SysColumns WHERE id=Object_Id('TableName')  

获取主键字段

  SELECT  name FROM SysColumns WHERE id=Object_Id('表名') and colid=(select top 1 keyno from sysindexkeys where id=Object_Id('表名'))  

通过分析发现SCOM的DB中存在以下列表

总结通式:MT_Microsoft$操作系统类型$版本$LogicalDisk

注:微软和开源操作系统表存在差异

  MT_Microsoft$AIX$5$3$LogicalDisk  MT_Microsoft$AIX$6$1$LogicalDisk  MT_Microsoft$AIX$7$LogicalDisk  MT_Microsoft$Linux$RHEL$4$LogicalDisk  MT_Microsoft$Linux$RHEL$5$LogicalDisk  MT_Microsoft$Linux$RHEL$6$LogicalDisk  MT_Microsoft$Linux$SLES$10$LogicalDisk  MT_Microsoft$Linux$SLES$11$LogicalDisk  MT_Microsoft$Linux$SLES$9$LogicalDisk  MT_Microsoft$Linux$Universal$LogicalDisk  MT_Microsoft$Windows$Server$2000$LogicalDisk  MT_Microsoft$Windows$Server$2003$LogicalDisk  MT_Microsoft$Windows$Server$2008$LogicalDisk  MT_Microsoft$Windows$Server$6$2$LogicalDisk  

涉及到表:

  [OperationsManager].[dbo].  [OperationsManager].[dbo].[MT_Microsoft$Windows$Server$2000$LogicalDisk]  

SQL核心语句:

  SELECT    T1.Path      ,T2000.[FileSystem_0D653B7C_3F9D_0EA1_6FF0_3CEF476DBED5] as '  文件系统'     ,T2000.[DeviceID_DF2FF114_783D_E8EC_DC76_0FC98EF70DB4] as '逻辑分区'     ,T2000. as '大小(字节)'    into #TempTB1  FROM [OperationsManager].[dbo].[MT_Microsoft$Windows$Server$2000$LogicalDisk] T2000  ,[OperationsManager].[dbo]. T1    WHERE T1.BaseManagedEntityId = T2000.BaseManagedEntityId  

效果图:

wKioL1ZNZGbQy4ebAAB5-TPqL3o015.png附SQL语句:

  USE OperationsManager  SELECT    T1.Path      ,T2000.[FileSystem_0D653B7C_3F9D_0EA1_6FF0_3CEF476DBED5] as '  文件系统'     ,T2000.[DeviceID_DF2FF114_783D_E8EC_DC76_0FC98EF70DB4] as '逻辑分区'     ,T2000. as '大小(字节)'    into #TempTB1  FROM [OperationsManager].[dbo].[MT_Microsoft$Windows$Server$2000$LogicalDisk] T2000  ,[OperationsManager].[dbo]. T1    WHERE T1.BaseManagedEntityId = T2000.BaseManagedEntityId  insert into #TempTB1  SELECT    T1.Path      ,T2003.[FileSystem_0D653B7C_3F9D_0EA1_6FF0_3CEF476DBED5]      ,T2003.[DeviceID_DF2FF114_783D_E8EC_DC76_0FC98EF70DB4]      ,T2003.  FROM [OperationsManager].[dbo].[MT_Microsoft$Windows$Server$2003$LogicalDisk] T2003    ,[OperationsManager].[dbo]. T1  WHERE T1.BaseManagedEntityId = T2003.BaseManagedEntityId  insert into #TempTB1  SELECT    T1.Path      ,T2008.[FileSystem_0D653B7C_3F9D_0EA1_6FF0_3CEF476DBED5]      ,T2008.[DeviceID_DF2FF114_783D_E8EC_DC76_0FC98EF70DB4]      ,T2008.  FROM [OperationsManager].[dbo].[MT_Microsoft$Windows$Server$2008$LogicalDisk] T2008    ,[OperationsManager].[dbo]. T1  WHERE T1.BaseManagedEntityId = T2008.BaseManagedEntityId  insert into #TempTB1  SELECT    T1.Path      ,T2012.[FileSystem_0D653B7C_3F9D_0EA1_6FF0_3CEF476DBED5]      ,T2012.[DeviceID_DF2FF114_783D_E8EC_DC76_0FC98EF70DB4]      ,T2012.    FROM [OperationsManager].[dbo].[MT_Microsoft$Windows$Server$6$2$LogicalDisk] T2012    ,[OperationsManager].[dbo]. T1    WHERE T1.BaseManagedEntityId = T2012.BaseManagedEntityId  insert into #TempTB1  SELECT    T1.Path      ,TAIX53.[FileSystem_33C79158_2D36_06FA_FAF1_27BB8F983A0C]      ,TAIX53.[DeviceID_76FE57D8_F808_15E1_EDC0_E929FBA69DEF]      ,TAIX53.    FROM [OperationsManager].[dbo].[MT_Microsoft$AIX$5$3$LogicalDisk] TAIX53    ,[OperationsManager].[dbo]. T1    WHERE T1.BaseManagedEntityId = TAIX53.BaseManagedEntityId  insert into #TempTB1  SELECT    T1.Path      ,TAIX61.[FileSystem_33C79158_2D36_06FA_FAF1_27BB8F983A0C]      ,TAIX61.[DeviceID_76FE57D8_F808_15E1_EDC0_E929FBA69DEF]      ,TAIX61.    FROM [OperationsManager].[dbo].[MT_Microsoft$AIX$6$1$LogicalDisk] TAIX61    ,[OperationsManager].[dbo]. T1    WHERE T1.BaseManagedEntityId = TAIX61.BaseManagedEntityId  insert into #TempTB1  SELECT    T1.Path      ,TAIX7.[FileSystem_33C79158_2D36_06FA_FAF1_27BB8F983A0C]      ,TAIX7.[DeviceID_76FE57D8_F808_15E1_EDC0_E929FBA69DEF]      ,TAIX7.    FROM [OperationsManager].[dbo].[MT_Microsoft$AIX$7$LogicalDisk] TAIX7    ,[OperationsManager].[dbo]. T1    WHERE T1.BaseManagedEntityId = TAIX7.BaseManagedEntityId  insert into #TempTB1  SELECT    T1.Path      ,TRHEL4.[FileSystem_33C79158_2D36_06FA_FAF1_27BB8F983A0C]      ,TRHEL4.[DeviceID_76FE57D8_F808_15E1_EDC0_E929FBA69DEF]      ,TRHEL4.    FROM [OperationsManager].[dbo].[MT_Microsoft$Linux$RHEL$4$LogicalDisk] TRHEL4    ,[OperationsManager].[dbo]. T1    WHERE T1.BaseManagedEntityId = TRHEL4.BaseManagedEntityId  insert into #TempTB1  SELECT    T1.Path      ,TRHEL5.[FileSystem_33C79158_2D36_06FA_FAF1_27BB8F983A0C]      ,TRHEL5.[DeviceID_76FE57D8_F808_15E1_EDC0_E929FBA69DEF]      ,TRHEL5.    FROM [OperationsManager].[dbo].[MT_Microsoft$Linux$RHEL$5$LogicalDisk] TRHEL5    ,[OperationsManager].[dbo]. T1    WHERE T1.BaseManagedEntityId = TRHEL5.BaseManagedEntityId  insert into #TempTB1  SELECT    T1.Path      ,TRHEL6.[FileSystem_33C79158_2D36_06FA_FAF1_27BB8F983A0C]      ,TRHEL6.[DeviceID_76FE57D8_F808_15E1_EDC0_E929FBA69DEF]      ,TRHEL6.    FROM [OperationsManager].[dbo].[MT_Microsoft$Linux$RHEL$6$LogicalDisk] TRHEL6    ,[OperationsManager].[dbo]. T1    WHERE T1.BaseManagedEntityId = TRHEL6.BaseManagedEntityId  insert into #TempTB1  SELECT    T1.Path      ,TRHEL6.[FileSystem_33C79158_2D36_06FA_FAF1_27BB8F983A0C]      ,TRHEL6.[DeviceID_76FE57D8_F808_15E1_EDC0_E929FBA69DEF]      ,TRHEL6.    FROM [OperationsManager].[dbo].[MT_Microsoft$Linux$RHEL$6$LogicalDisk] TRHEL6    ,[OperationsManager].[dbo]. T1    WHERE T1.BaseManagedEntityId = TRHEL6.BaseManagedEntityId  insert into #TempTB1  SELECT    T1.Path      ,TSLES9.[FileSystem_33C79158_2D36_06FA_FAF1_27BB8F983A0C]      ,TSLES9.[DeviceID_76FE57D8_F808_15E1_EDC0_E929FBA69DEF]      ,TSLES9.    FROM [OperationsManager].[dbo].[MT_Microsoft$Linux$SLES$9$LogicalDisk] TSLES9    ,[OperationsManager].[dbo]. T1    WHERE T1.BaseManagedEntityId = TSLES9.BaseManagedEntityId  insert into #TempTB1  SELECT    T1.Path      ,TSLES10.[FileSystem_33C79158_2D36_06FA_FAF1_27BB8F983A0C]      ,TSLES10.[DeviceID_76FE57D8_F808_15E1_EDC0_E929FBA69DEF]      ,TSLES10.    FROM [OperationsManager].[dbo].[MT_Microsoft$Linux$SLES$10$LogicalDisk] TSLES10    ,[OperationsManager].[dbo]. T1    WHERE T1.BaseManagedEntityId = TSLES10.BaseManagedEntityId  insert into #TempTB1  SELECT    T1.Path      ,TSLES11.[FileSystem_33C79158_2D36_06FA_F

阅读更多内容

2015年11月19日星期四

Kiwi,BDD行为测试框架--iOS攻城狮进阶必备技能 - iOS122

本邮件内容由第三方提供,如果您不想继续收到该邮件,可 点此退订
Kiwi,BDD行为测试框架--iOS攻城狮进阶必备技能 - iOS122  阅读原文»

【摘要】Kiwi 是一个适用于iOS开发的行为驱动测试框架,旨在提供一个足够简单易用的BDD库. 阅读全文

《JavaScript权威指南》学习笔记 第二天 下好一盘大棋 - roverliang  阅读原文»

【摘要】前段学习js的时候总是零零散散的,以至于很多东西都模棱两可。时间稍微一久,就容易忘记。最主要的原因是这些东西,原来学的时候就不是太懂,以至于和其他知识无法形成记忆链,所以孤零零的知识特别容易忘记。重温犀牛书,加上最近对记忆宫殿的记忆方法有点感兴趣,于是结合起来来做笔记。开始吧,上去先扔一张我画的..... 阅读全文

阅读更多内容

2015年11月18日星期三

web监控:zabbix自动发现+python之pycur模块对网站访问质量监控

本邮件内容由第三方提供,如果您不想继续收到该邮件,可 点此退订
web监控:zabbix自动发现+python之pycur模块对网站访问质量监控  阅读原文»

用户名:sunday208 文章数:72 评论数:15
访问量:174755:2049:1162:5 注册日期:2008-05-08

一、效果图:

wKiom1ZLJ6PhCARDAAUWs8pXNuc408.jpg

wKioL1ZLJ_nDj1jwAATQJ4Tmzok200.jpg

wKiom1ZLJ6uyjgZoAARuTXFk0S0881.jpg

wKiom1ZLJ7PjdgwKAAXhCOMawWo018.jpg

二、需求说明:

最近需要对节点到源站、办公环境访问业务平台网站质量和办公网络线路质量的监控,简单的ping可以检测到一些东西,但是http请求的检查也要进行,于是就研究了下pycurl。

PycURl是一个C语言写的libcurl的python绑定库。libcurl 是一个自由的,并且容易使用的用在客户端的 URL 传输库。它的功能很强大,PycURL 是一个非常快速(参考多并发操作)和丰富完整特性的,但是有点复杂的接口。

三、python脚本:

1、编写httptime.py脚本

命令vi zabbix_agentd.conf.d/httptime.py

#!/usr/bin/python

# coding: UTF-8

import pycurl

import sys

import os

import json

class Test:

def __init__(self):

self.contents = ''

def body_callback(self,buf):

self.contents = self.contents + buf

def web_performance(input_url,mykey):

t = Test()

c = pycurl.Curl()

c.setopt(pycurl.WRITEFUNCTION,t.body_callback)

c.setopt(pycurl.ENCODING, 'gzip')

c.setopt(pycurl.URL,input_url)

c.perform()

if mykey == "NAMELOOKUP_TIME":

#DNS解析时间ms

NAMELOOKUP_TIME = c.getinfo(c.NAMELOOKUP_TIME)

print "%.2f"%(NAMELOOKUP_TIME*1000)

#return mykey

elif mykey == "CONNECT_TIME":

#建立连接时间ms

CONNECT_TIME = c.getinfo(c.CONNECT_TIME)

print "%.2f" %(CONNECT_TIME*1000)

#return mykey

elif mykey == "PRETRANSFER_TIME":

#准备传输时间ms

PRETRANSFER_TIME = c.getinfo(c.PRETRANSFER_TIME)

print "%.2f" %(PRETRANSFER_TIME*1000)

#return mykey

elif mykey == "STARTTRANSFER_TIME":

#传输开始时间ms

STARTTRANSFER_TIME = c.getinfo(c.STARTTRANSFER_TIME)

print "%.2f" %(STARTTRANSFER_TIME*1000)

#return mykey

elif mykey == "TOTAL_TIME":

#传输结束总时间ms

TOTAL_TIME = c.getinfo(c.TOTAL_TIME)

print "%.2f" %(TOTAL_TIME*1000)

#return mykey

elif mykey == "SIZE_DOWNLOAD":

#下载数据包大小bytes/s

SIZE_DOWNLOAD = c.getinfo(c.SIZE_DOWNLOAD)

print "%d" %(SIZE_DOWNLOAD)

#return mykey

elif mykey == "HEADER_SIZE":

#HTTP头部大小bytes

HEADER_SIZE = c.getinfo(c.HEADER_SIZE)

print "%d" %(HEADER_SIZE)

#return mykey

elif mykey == "SPEED_DOWNLOAD":

#平均下载速度bytes/s

SPEED_DOWNLOAD=c.getinfo(c.SPEED_DOWNLOAD)

print "%d" %(SPEED_DOWNLOAD)

#return mykey

def web_discovery():

website = ['www.qq.com','www.sina.com','sunday208.blog.51cto.com'];

devices = []

for devpath in website:

device = os.path.basename(devpath)

devices += [{'{#SITENAME}':device}]

print json.dumps({'data':devices},sort_keys=True,indent=7,separators=(',',':'))

if __name__ == '__main__':

if sys.argv[1] == "web_discovery":

web_discovery()

if sys.argv[1] == "web_performance":

input_url = sys.argv[2]

mykey = sys.argv[3]

web_performance(input_url,mykey)

2、加上执行权限:

命令chmod +xzabbix_agentd.conf.d/httptime.py

3、应用到zabbix客户端配置:

命令tail -1 zabbix_agentd.conf

UserParameter=HTTP_CURL[*],python /opt/soft/zabbix/etc/zabbix_agentd.conf.d/httptime.py $1 $2 $3

4、重启zabbix客户端:

/etc/init.d/zabbix_agentd restart

5、服务端测试:

命令如下:

zabbix_get -s 192.168.2.32 -p 10050 -k "HTTP_CURL[web_discovery]"

zabbix_get -s 192.168.2.32 -p 10050 -k "HTTP_C

服务器排障 之 nginx 499 错误的解决  阅读原文»

服务器排障 之 nginx 499 错误的解决

问题描述:

Nginx 服务器大量报错

  220.181.165.136 - - [18/May/2015:10:31:02 +0800] "POST /v1/jobsHTTP/1.1" 499 0 "" "bdHttpRequest/1.0.0"  115.239.212.7 - - [18/May/2015:10:31:03 +0800] "GET /v1/job/643309e3-dc73-4025-aa69-c9405c1d818fHTTP/1.1" 499 0"http://www.baidu.com/?tn=91638679_hao_pg&s_j=1""Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko"  140.207.202.187 - - [18/May/2015:10:30:58 +0800] "POST/v3/violations HTTP/1.1" 499 0 "-" "-"  42.236.10.71 - - [18/May/2015:10:30:59 +0800] "POST /v3/violationsHTTP/1.1" 499 0 "-" "-"  106.120.173.17 - - [18/May/2015:10:30:58 +0800] "POST/v3/violations HTTP/1.1" 499 0 "-" "Mozilla/5.0 (Windows NT6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.131Safari/537.36"  180.97.35.164 - - [18/May/2015:10:30:52 +0800] "GET/v1/job/f86bdecc-2a61-4a42-bb7b-aa794b77f89b HTTP/1.1" 499 0"http://www.baidu.com/s?word=%E5%8D%81%E5%A0%B0%E5%A4%A9%E6%B0%94&tn=sitehao123&ie=utf-8""Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)"  

问题分析:

出现的原因

google定义:

499 / ClientClosed Request

An Nginx HTTP server extension. This codeis introduced to log the case when the connection is closed by client whileHTTP server is processing its request, making server unable to send the HTTP header back

维基百科定义:

499Client Closed Request (Nginx)

Used in Nginx logs to indicate when the connection has been closed by client while the server is still processing itsrequest, making server unable to send a status code back

Nginx源码:

grep一下nginx源码,定义在ngx_request_t.h :

  /*  * HTTP does notdefine the code for the case when a client closed  * the connectionwhile we are processing its request so we introduce  * own code to logsuch situation when a client has closed the connection  * before we even tryto send the HTTP header to it  */  #define NGX_HTTP_CLIENT_CLOSED_REQUEST 499  

这是nginx定义的一个状态码,用于表示这样的错误:服务器返回http头之前,客户端就提前关闭了http连接

继续grep :

wKioL1ZK_oWDZn9NAAC_HrhuQAs422.png

很有可能是因为服务器端处理的时间过长,客户端"不耐烦"

要解决此问题,就需要在程序上面做些优化了。

再grep下"NGX_HTTP_CLIENT_CLOSED_REQUEST",发现目前这个状态值只在ngx_upstream中赋值

upstream在以下几种情况下会返回499:

  (1)upstream 在收到读写事件处理之前时,会检查连接是否可用:  ngx_http_upstream_check_broken_connection,      if (c->error) { //connecttion错误       ……          if (!u->cacheable) { //upstream的cacheable为false,这个值跟http_cache模块的设置有关。指示内容是否缓存。              ngx_http_upstream_finalize_request(r, u, NGX_HTTP_CLIENT_CLOSED_REQUEST);          }  }  

如上代码,当连接错误时会返回499。

(2)server处理请求未结束,而client提前关闭了连接,此时也会返回499。

(3)在一个upstream出错,执行next_upstream时也会判断连接是否可用,不可用则返回499。

总之,这个错误的比例升高可能表明服务器upstream处理过慢,导致用户提前关闭连接。而正常情况下有一个小比例是正常的。

继续分析:

问题的核心就是要排查为什么服务端处理时间过长

可能问题

1 后台python程序处理请求时间过长

2 mysql慢查询

通过查看监控:

1 cpu和内存的使用,都在正常范围

2 后台程序访问正常

3 MySQL没有慢查询

结果:

经过询问老大后得知,这个nginx为查询违章的api,用户提交查询后, python就去数据库或者交通局的网站查询。这个查询会有消耗一定的时间,所以,用户会主动断开连接

解决问题:

proxy_ignore_client_abort on; #让代理服务端不要主动关闭客户端的连接。

默认 proxy_ignore_client_abort 是关闭的,此时在请求过程中如果客户端端主动关闭请求或者客户端网络断掉,那么 Nginx 会记录 499,同时 request_time 「后端已经处理」的时间,而upstream_response_time "-" (已验证)

如果使用了 proxy_ignore_client_abort on ;

那么客户端主动断掉连接之后,Nginx 会等待后端处理完(或者超时),然后记录「后端的返回信息」到日志。所以,如果后端返回 200就记录 200 ;如果后端放回 5XX ,那么就记录 5XX

如果超时(默认60s,可以用 proxy_read_timeout 设置)Nginx 会主动断开连接,记录 504

注:只在做反向代理的时候加入,作为其他服务器的时候,关闭为好,默认设置是关闭的!

本文出自 "一个奋斗的小运维" 博客,请务必保留此出处http://yucanghai.blog.51cto.com/5260262/1713803

分享至 一键收藏,随时查看,分享好友!

2015年11月17日星期二

15天玩转redis —— 第四篇 哈希对象类型 - 一线码农

本邮件内容由第三方提供,如果您不想继续收到该邮件,可 点此退订
15天玩转redis ―― 第四篇 哈希对象类型 - 一线码农  阅读原文»

【摘要】redis中的hash也是我们使用中的高频数据结构,它的构造基本上和编程语言中的HashTable,Dictionary大同小异,如果大家往后有什么逻辑需要用Dictionary存放的话,可以根据场景优先考虑下redis哦,起码可以装装逼嘛,现在我默认你已经有装逼的冲动了,打开redis手册,看..... 阅读全文

魅族/锤子/苹果 悬停效果的实现 - xerrard  阅读原文»

【摘要】魅族/锤子/苹果 悬停效果的实现一、背景:近日研究当前主流手机的单手操作效果。一类是小米的单手小屏模式:将原本5寸以上的屏幕缩小到3.5/4寸的大小,以方便单手操作另外一类是魅族/锤子/苹果的 悬停效果:屏幕可以下拉到下半部分,这样单手可以方便的操作到屏幕上方区域二、关于DecorView的基本概念... 阅读全文

阅读更多内容

2015年11月15日星期日

Mogilefs分布式文件系统-Keepalived+Nginx双主模型实现图片分布式存储、访问

本邮件内容由第三方提供,如果您不想继续收到该邮件,可 点此退订
Mogilefs分布式文件系统-Keepalived+Nginx双主模型实现图片分布式存储、访问  阅读原文»

用户名:bengbengtu1 文章数:27 评论数:6
访问量:1886:591:326:2 注册日期:2014-10-20

Mogilefs分布式文件系统-Keepalived+Nginx双主模型实现图片分布式存储、访问

一、分布式文件系统:

分布式文件系统(Distributed File System)是指文件系统管理的物理存储资源不一定直接连接在本地节点上,而是通过计算机网络与节点相连。计算机通过文件系统管理、存储数据,单纯通过增加硬盘个数来扩展计算机文件系统的存储容量的方式,在容量大小、容量增长速度、数据备份、数据安全等方面的表现都差强人意。

分布式文件系统可以有效解决数据的存储和管理难题:将固定于某个地点的某个文件系统,扩展到任意多个地点/多个文件系统,众多的节点组成一个文件系统网络。每个节点可以分布在不同的地点,通过网络进行节点间的通信和数据传输。人们在使用分布式文件系统时,无需关心数据是存储在哪个节点上、或者是从哪个节点从获取的,只需要像使用本地文件系统一样管理和存储文件系统中的数据。

二、常见的分布式文件系统:

GFS

(Google File System)

Google公司为了满足本公司需求而开发的基于Linux的专有分布式文件系统。

HDFS

(Hadoop Distributed File System)

Hadoop 实现的一个分布式文件系统

TFS

(Taobao File System)

是一个高可扩展、高可用、高性能、面向互联网服务的分布式文件系统,它构筑在普通的Linux机器 集群上,可为外部提供高可靠和高并发的存储访问,主要针对海量的非结构化数据,TFS为淘宝提供海量小文件存储,通常文件大小不超过1M,满足了淘宝对小文件存储的需求

GlusterFS

(Gluster File System)

主要应用在集群系统中,具有很好的可扩展性,通过网络互联成一个并行的网络文件系统;与Hadoop HDFS不同的是:GlusterFS使用了弹性哈希算法来定位文件存储的位置。 由于使用了弹性哈希算法,GlusterFS不需要专门的Meta-Data Server来保存元数据,因此可以避免因为元数据服务器宕机导致的整个集群不可用。也正是因为不需要元数据服务器,所以GlusterFS在多个挂载点同时进行数据读写的时候,其整体性能很突出。
ceph C++编写的代码,支持Fuse,并且没有单点故障依赖, 于是下载安装, 由于 ceph 使用 btrfs 文件系统, 而btrfs 文件系统需要 Linux 2.6.34 以上的内核才支持。
MooseFS mooseFS是一款网络分布式文件系统。它把数据分散在多台服务器上,但对于用户来讲,看到的只是一个源。
MogileFS perl语言研发的; 性能好;海量图片存储
FastDFS c语言研发

三、MogileFS介绍

第1个部分: 是server端,包括mogilefsd和mogstored两个程序。前者即是mogilefsd的tracker,它将一些全局信息保存在数据库 里,例如站点domain,class,host等,默认监听在7001端口。后者即是存储节点(store node),它其实是个HTTP Daemon,默认侦听在7500端口,接受客户端的文件备份请求。在安装完后,要运行mogadm工具将所有的store node注册到mogilefsd的数据库里,

mogilefsd会对这些节点进行管理和监控。

第2个部分:是utils(工具集),主要是MogileFS的一些管理工具,例如mogadm等。
第3个部分:是客户端API,目前只有Perl API(MogileFS.pm)、PHP,用这个模块可以编写客户端程序,实现文件的备份管理功能,提供MogileFS.pm。



wKiom1ZDe_3gJCp5AAB-Aab00DM683.png

ip地址 主机名 安装包
172.16.16.3 ① master1 keepalived+nginx
172.16.16.4 master2 keepalived+nginx
172.16.16.2 mogilefs1 mogilefs相关软件包
172.16.16.8 mogilefs2 mogilefs相关软件包
172.16.116.233 mysql mariadb-server

① mster1

  ####keepalived相关配置  [root@master1 ~]# yum install -y keepalived  [root@master1 ~]# vim /etc/keepalived/keepalived.conf  ! Configuration File for keepalived  global_defs {     notification_email {          root@localhost     }     notification_email_from keepalived@localhost     smtp_server 127.0.0.1     smtp_connect_timeout 30     router_id LVS_DEVEL  }  vrrp_instance VI_1 {      state MASTER      interface eth0      virtual_router_id 51      priority 100      advert_int 1      authentication {          auth_type PASS          aut

阅读更多内容

[自己动手玩黑科技] 1、小黑科技——如何将普通的家电改造成可以与手机App联动的“智能硬件” - beautifulzzzz

本邮件内容由第三方提供,如果您不想继续收到该邮件,可 点此退订
[自己动手玩黑科技] 1、小黑科技――如何将普通的家电改造成可以与手机App联动的"智能硬件" - beautifulzzzz  阅读原文»

NOW,5 将此黑科技传授予你~

一、普通家电控制电路板分析

普通家电其人机接口一般由按键和指示灯组成(高端的会稍微复杂,这里不考虑)

这样交互过程,其实就是:由当前指示灯信息,按照操作流程按相应按键,来实现相应功能的过程:

  

注:每次按动相应的按键都会导致相应的指示灯自身的状态从0到1或者从1到0变化,这其实是一个最好的反馈

那么,我们将一个完整的过程定义为:按动某个按键(或几个,同时或依次),等待某个指示灯呈现某种状态,得到基于特定电器的固有功能(操作)

例如:你按动电热水壶的烧水按钮,此时烧水指示灯变亮,等到烧水指示灯变暗,表明该烧水过程完成

这样,我们可以把传统电器抽象成一个黑盒,你输入一些按钮控制信号,等待特定指示灯返回信号,再做出另外一些控制信号,依次类推,直到得到某个最终反馈信号,表明你通过这一交互过程完成了对黑盒的交互实现了黑盒给定的功能。

因此想要做个手机控制的家电就要用硬件在人与机之间插一脚~

呵呵:从上图来看电水壶已经不纯了,硬件插入到人壶之间后,还把壶卖给了手机,手机又偷偷地把壶和人卖给了服务器~

二、如何设计硬件电路读取指示灯的工作状态和控制开关按下

综上,硬件部分关键在于读灯和按键!

我本来以为:

  ① 一般的传统家电的指示灯电路是在,3.3V~5V左右的电压下工作的,分为高电平点亮和低电平点亮

  ②一般的传统家电的按键按下会产生一个低电平\高电平传入MCU进行去抖等判断

所以原初认为这样就能搞定问题了:

注:通过在电器外部插入一个MCU用来监听内部按键按下和LED管脚的状态来获取,并且可以伪造按键按下信息和LED状态

而实际上是:

  ① 没有所谓的高低电平,得从数字电路的01中跳出来,高电平是大于某个阈值的电平,低电平是低于某个阈值的电平

  ② 按键存在一些特殊情况,比如多路复用(下面是我遇到的情况:就不能仅仅看为高低电平来处理)

注:图中采用3个按钮复用一个MCU输入,通过串联不同阻值的电阻来使不同按键按下后MCU按键输入引脚产生不同的电压,MCU采用一定方式区分出这些电压就能区分出是哪个按键按下

因此:

想获得指示灯状态可以采用——读取指示灯引脚上的电压值,然后在嵌入式程序里计算,如果高于高电平阈值被认为是高电平,如果低于低电平阈值被认为是低电平。

要想模拟按键按下可以采用——在每个按键上并一路继电器开关,插入监听的MCU通过控制继电器模拟按键按下。

如下,是我针对某一款家用电器的改造方案:

其中:

  该家电一共有5个指示灯(图中左上),左1和左2状态互补,其他三个灯状态独立,每个灯与家电内部的MCU都有相应的独立线路相连。由于,上面分析我们不能简单地以数字信号来获取指示灯的状态,所以这里分别利用外置监听MCU的PA0,PA1,PA2和PA3四个引脚来做AD输入读取左1,左3,左4和左5四个指示灯引脚两端的电压值。

  经反复测量分析得到关于指示灯的部分工作信息(图中左下):在家电开机的时候高电平维持在3.28~3.29V左右,此时指示灯是关的(也就是低电平灯亮);低电平维持在0.6V一下,此时指示灯是开着的。特殊情况:在机器断电情况下,PA0~PA3四路AD输入电压在1.5~1.8V之间。

  因此,我将1V作为低电平的上限,将3.2V作为高电平的下线,1~3.2V处于浮空状态。

  对于模拟按键按下则通过3个继电器,每个继电器并联在一个按键之上。如图中间偏上CONTROL WIRE,其中RED WIRE连接在三个按键的公共输入端,BLACK WIRE、WRITE WIRE和GREEN WIRE分别连到相应按钮的另一端。而这四个线又连接到右上角所示的电路上。该电路是一个包含ULN2003和3个继电器的电路。

  因此,只要将VCC和GND加上电压,外部监听MCU用3个引脚连接到IN5\IN6\IN7,若MCU置相应引脚为高则相当于相应按键按下,置低相当于抬起。人的手动按按键的过程相当于按下+延时+抬起的过程。

三、编写嵌入式代码实现4路AD读取指示灯电压值+3路继电器开关控制

上面说到,用继电器模拟按键按下,其实就是设置相应的管脚为:

  高电平+延时一段时间+低电平(高电平表示开始按下,延时表示按下的一个短暂的时间,低电平表示又抬起来了)

其相应的代码大家也能明白:(解析TxBuffer1[0]里的内容,为'a'则让PA4对应的继电器模拟按键按下,为'b'则让PA5对应的继电器模拟按键按下,依次类推...

1 switch(TxBuffer1[0]){
2 case 'a':
3 GPIO_SetBits(GPIOA, GPIO_Pin_4);//置PA4引脚为高
4 ticks = 900000;//延时
5 while(ticks--);
6 GPIO_ResetBits(GPIOA, GPIO_Pin_4);//置PA4引脚为低
7 break;
8 case 'b':
9 GPIO_SetBits(GPIOA, GPIO_Pin_5);
10 ticks = 900000;
11 while(ticks--);
12Java游戏服务器-Netty自动重连与会话管理 - Metazion  阅读原文»

网游少不了网络通信,不像写C++时自己造轮子,Java服务器使用Netty。Netty做了很多工作,使编写网络程序变得轻松简单。灵活利用这些基础设施,以实现我们的需求。

其中一个需求是自动重连。自动重连有两种应用场景:

  • 开始连接时,对端尚未开启
  • 连接中途断开

在有多个服务器(比如LoginServer和GameServer等)时,这样就不用考虑服务器启动顺序。
有需求就需要有解决方案,其实很简单,Netty已经提供,如下:

ctx.channel().eventLoop().schedule(() -> tryConnect(), reconnectInterval, TimeUnit.SECONDS);

tryConnect是实际执行连接的方法,后面两个参数表示每隔reconnectInterval秒重连一次即执行tryConnect,而对应上述两种应用场景的分别是connect失败和channel inactive时,详见后面代码。

自动重连解决后,还有一个问题是如何管理连接。Netty使用Channel来抽象一个连接,但实际开发时,通常逻辑上会有一个会话(Session)对象用来表示对端,可以在其上添加各种逻辑属性方法等,以及收发网络消息。这样一个Channel就需要对应一个Session,且方便互相索引。

首先考虑如何创建这个Session。

为了方便Netty使用和复用,我抽象了一个TcpServer/TcpClient类分别表示服务器和客户端。理想情况是 TcpServer和TcpClient合并为一个,不同行为由Session来决定。但因为Netty的服务器和客户端分别使用ServerBootstrap和Bootstrap,其分别包含bind和connect,这个想法未能实现。

Session有两种,ListenSession负责监听连接请求,TransmitSession负责传输数据。在实际应用中,有这么一种需求,比如GameServer主动连接LoginServer,这时GameServer即作为client端。在连接成功时,需要GameServer主动发个注册消息给LoginServer,LoginServer籍此得知是哪个服务器组。此时,GameServer可能同时会以Client身份连接另一个服务器比如Gateway而且同样要发消息。那么作为client端主动连接的TransmitSession最好细化,需要包含要连接的主机地址、端口和重连时间等信息,也需要在Active时发送不同消息,而Server端TransmitSession并不需要。所以设计上TransmitSession又分为ClientSession和ServerSession。SeverSession由TcpServer在建立连接时自动创建,而ListenSession和ClientSession则由使用者自行创建并交由TcpServer/TcpClient管理。

接口如下:

public abstract class ListenSession {
private boolean working = false;
private int localPort = 0;
private int relistenInterval = 10;
...
public abstract ServerSession createServerSession();
}

public abstract class TransmitSession {
protected Channel channel = null;
protected boolean working = false;
...
public abstract void onActive() throws Exception;
public abstract void onInactive() throws Exception;
public abstract void onException() throws Exception;
public abstract void onReceive(Object data) throws Exception;
public abstract void send(Object data);
}

public abstract class ClientSession extends TransmitSession {
private String remoteHost = "";
private int remotePort = 0;
private int reconnectInterval = 10;
...
}

其次考虑如何管理Channel和Session的对应关系。除了使用一个类似HashMap\的容器来管理外,一个更自然的想法是直接把Session记录在Channel上就好了,这样就省去了每次查找的开销。Netty已经提供了,即Channel的AttrMap。这里或许有疑问的是,client端connect成功的Channel和Active/Inactive时的Channel是同一个,所以可以方便放置/取出数据。而server端bind成功的Channel放置的是ListenSession,Active/Inactive时的Channel却是一个新的,并非bind的Channel,怎么取出之前放入的ListenSession来呢?Netty也想到了,所以提供了Channel.parent()方法,每一个Active时的Channel是由bind时的Channel创建的,后者就是前者的parent。

综上,TcpServer示例如下:

public class TcpServer {
private final AttributeKey<ListenSession> LISTENSESSIONKEY = AttributeKey.valueOf("LISTENSESSIONKEY");
private final AttributeKey<ServerSession> SERVERSESSIONKEY = AttributeKey.valueOf("SERVERSESSIONKEY");

private final ServerBootstrap bootstrap = new ServerBootstrap();
private EventLoopGroup bossGroup = null;
private EventLoopGroup workerGroup = null;

private ArrayList<ListenSession> listenSessions = new ArrayList<ListenSession>();
...
private void start() {
bossGroup = new NioEventLoopGroup(1);
workerGroup = new NioEventLoopGroup(4);
bootstrap.group(bossGroup, workerGroup);
bootstrap.channel(NioServerSocketChannel.class);
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("encode", new ObjectEncoder());
pipeline.addLast("decode", new ObjectDecoder(ClassResolvers.cacheDisabled(null)));
pipeline.addLast(workerGroup, new ChannelInboundHandlerAdapter() {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ListenSession listenSession = ctx.channel().parent().attr(LISTENSESSIONKEY).get();
ServerSession serverSession = listenSession.createServerSession();
ctx.channel().attr(SERVERSESSIONKEY).set(serverSession);
serverSession.setChannel(ctx.channel());
serverSession.onActive();
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
ServerSession serverSession = ctx.channel().attr(SERVERSESSIONKEY).get();
serverSession.onInactive();
}
...
}
...
private void tryListen(ListenSession listenSession) {
if (!listenSession.isWorking()) {
return;
}

final int port = listenSession.getLocalPort();
final int interval = listenSession.getRelistenInterval();

ChannelFuture f = bootstrap.bind(port);
f.addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture f) throws Exception {
if (f.isSuccess()) {
f.channel().attr(LISTENSESSIONKEY).set(listenSession);
} else {
f.channel().eventLoop().schedule(() -> tryListen(listenSession), interval, TimeUnit.SECONDS);
}
}
});
}
}

如果监听失败则隔interval秒重试,新连接建立时创建ServerSession关联该Channel。
TcpClient的实现大同小异,不同点在于需要在Channel Inactive时执行重连:

public class TcpClient {
private final AttributeKey<ClientSession> SESSIONKEY = AttributeKey.valueOf("SESSIONKEY");

private final Bootstrap bootstrap = new Bootstrap();
private EventLoopGroup workerGroup = null;

private ArrayList<ClientSession> clientSessions = new ArrayList<ClientSession>();
...
private void start() {
workerGroup = new NioEventLoopGroup();
bootstrap.group(workerGroup);
bootstrap.channel(NioSocketChannel.class);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("encode", new ObjectEncoder());
pipeline.addLast("decode", new ObjectDecoder(ClassResolvers.cacheDisabled(null)));
pipeline.addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ClientSession clientSession = ctx.channel().attr(SESSIONKEY).get();
clientSession.setChannel(ctx.channel());
clientSession.onActive();
}

@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
ClientSession clientSession = ctx.channel().attr(SESSIONKEY).get();
clientSession.onInactive();

final int interval = clientSession.getReconnectInterval();
ctx.channel().eventLoop().schedule(() -> tryConnect(clientSession), interval, TimeUnit.SECONDS);
}
...
}
...
private void tryConnect(ClientSession clientSession) {
if (!clientSession.isWorking()) {
return;
}

final String host = clientSession.getRemoteHost();
final int port = clientSession.getRemotePort();
final int interval = clientSession.getReconnectInterval();

ChannelFuture future = bootstrap.connect(new InetSocketAddress(host, port));
future.addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture f) throws Exception {
if (f.isSuccess()) {
f.channel().attr(SESSIONKEY).set(clientSession);
} else {
f.channel().eventLoop().schedule(() -> tryConnect(clientSession), interval, TimeUnit.SECONDS);
}
}
});
}
}

如果需要监听多个端口或连接多个目的主机,只需要创建多个ClientSession/ListenSession即可。如:

private TcpServer tcpServer = new TcpServer();

private LSServer lsServer = new LSServer();
private LSClient lsClient = new LSClient();

lsServer.setLocalPort(30001);
lsServer.setRelistenInterval(10);
tcpServer.attach(lsServer);

lsClient.setLocalPort(40001);
lsClient.setRelistenInterval(10);
tcpServer.attach(lsClient);

另外值得一提的是网上很多例子,都会在bind端口后,调用如下代码:

f.channel().closeFuture().sync();

这会阻塞当前线程,其实就是在当前线程做main loop。而实际游戏服务器中,通常main线程做逻辑线程,逻辑线程需要自己tick,也就是自定义main loop,我们在其中执行一些每帧更新的逻辑。所以并不需要上面这种方式。

公共库仓库:JMetazion

服务器示例仓库:JGameDemo

新建QQ交流群:330459037


本文链接:Java游戏服务器-Netty自动重连与会话管理,转载请注明。

阅读更多内容