2009年5月11日星期一

我是“魔兽”?你是禽兽!

转帖自:http://blog.sina.com.cn/s/blog_594331a40100dj2i.html


铿锵的标题,却有个低调的起笔:

我在小学三年级的时候就有了近视眼的趋势,父母为此急切万分,四处寻求治愈之法。后来听说某医院独家引进了最新的近视眼治疗仪,采用电流刺激穴位的原 理,“大家都说好”。很快我的生活便多了一项内容:坐在一张统一制式的圆凳上,闭上双眼,任由自己的天应穴和太阳穴承受探针的规律性点击,整个屋子充满了 马达“咔哒咔哒”的声音。

为了表示与普通的眼保健操的区别,探针的末梢传导了电流。毕竟我是肉眼凡胎,肯定感受到理所当然的疼痛。电流的程度可以调节,最大档的强度能够”啪”的一 声将我的脑袋震开,一片恍惚之后,好像有千千万万颗锋利的针扎进了我的头骨,别说活动了,思考都成了折磨。二十多年过去了,那种滋味我记忆犹新。

每周三次,每次三十分钟,每个月一个疗程,一个疗程相当于我们家三分之一的“利润”。医生不厌其烦的叮嘱包围了我:千万别近视眼,否则以后什么好工作都没 门,一辈子就废了。周围的宣传海报声嘶力竭的声讨近视眼的罪行,照片上面一只只病态的眼睛直勾勾的瞪着我。无名的恐惧笼罩心头,一个声音在耳边幽幽的低 吟:如果戴上了眼镜,你对不起任何人。

于是我只得咬紧牙关,忍受日渐增强的电流,每次来到医院,胸中都激荡着奔赴刑场的豪迈——准确的说,我是在害怕,害怕自己因为忍不住疼痛功败垂成,沦为戴着眼镜的“罪人”。

一段时间过去了,且不说视力表是否有了变化,天应穴附近脆弱的皮肤倒是出现了显眼的棕色焦痕(理论上属于轻度烧伤)。我的非主流形象引来了同学的纷纷围观,他们充分发挥了创作灵感,“四眼狗”的称号不胫而走。

诚然,童言无忌。然而,童心又是如此脆弱。整个一个学期我都是在郁郁寡欢中度过,身体和精神的双重煎熬让我的三年级失去了应有的光彩。

终于,父母发现苗头不对,再加上医生的托辞已经了无新意,“治疗”停止了。后来他们仍然抱着“假性近视”的美好愿望东奔西走,可是我的眼镜片还是越来越 厚。直到现在,父母还把我的近视眼当作对我最大的失职。其实,一切都是我躲在被窝里面看书的结果,与他们毫无关联。尤为重要的是,他们将我从三年级的阴霾 中拯救出来——而且,还有更多。

所以我感谢他们,尽管这是一份迟到的感谢。当我晃晃悠悠的跨入了长辈的行列,我方才明白:负责的选择是多么的可贵。

起笔结束了,我的文章也完成了大半。随着网瘾治疗中心的丑闻在主流媒体不断曝光,对于电击治疗的闹剧的口诛笔伐必将一发不可收拾。从前我在杂志和博客上没 少“突突突”的开火,“黄沙百战穿金甲,不破楼兰终不还”,热血无比。如今的我却异常的平和,从电击眼睛到电击网瘾,历史的相似,其实并不惊人。

我厌倦了“制度”、“教育”之类的讨论,对比从前,我起码看到了进步。遗憾的是,这么多年过去了,我们对家庭教育的停滞乃至倒退依然视而不见听而不闻。家 庭教育是个“从我做起”的漫长工程,无论父母儿女,都得从学生做起。当一个个家长将自己的骨肉甩给幼儿园、甩给学校、甩给补习班、甩给那些“有你们管孩子 就够了”的种种机构时,你们的撒手,就等于为网瘾治疗中心——将其称为“集中营”也不为过——添了砖、加了瓦。

易中天老师双眉如刀、两眼如炬,洞门中开的大嘴露出唯美的小门牙:悲剧啊。

我不知道将儿女送到网瘾治疗中心的父母对于下列文字有何想法:

杨永信拿起仪器的两个端子,对着少女的太阳穴轻轻地点了一下。

难受吗?他盯着少女的脸问道。

不难受!我没有网瘾!少女说。

那好,再来一下。杨永信又点了一下,少女颤抖了一下,可她咬紧牙关,不说难受。杨永信在两个太阳穴上同时点了一下,少女受不了了,叫起来:我难受,我难受,医生,你用的什么东西,我的脑袋为什么这么难受?

不是仪器的问题,是你有网瘾,有网瘾就难受。

杨永信开始心理引导,告诉我,你叫什么名字?

我不想告诉你,我想离开这个地方,我想去找我未婚夫

杨永信又点了两次,少女终于挺不住了,泪水顺着眼角流下来

杨永信又亮剑了

好吧,医生,我错了。少女终于缴械了,眼泪止不住地流淌着她与杨永信交谈了45分钟,向杨永信保证,留下来治疗,并且会向父母道歉。

——摘自“国内心理卫生诊疗专业机构创建网络成瘾治疗中心第一人”杨永信的著作:《战网魔》

东北人的血统让我第一时间想起了小品《卖拐》:

赵本山:……信不信,你的腿随着我的手往高抬,能抬多高抬多高,往下使劲落,好不好?信不信?腿指定有病,右腿短!来,起来!

(范伟配合做动作)

赵本山:停!麻没?

范 伟:麻了。

高秀敏:哎,他咋麻了呢?

赵本山:你跺,你也麻!

杨永信,我和你打赌:你电,你也麻!你敢不敢站在足以媲美外星科技的仪器的另一端,接受网瘾测试的检验?

也许你的脸皮足够承受,那么假如面对的是自己的骨肉,你忍不忍心按下发射电流的按钮?

“问题少年的背后必定有问题父母”,这么有哲理的话居然出自某网瘾治疗中心之口,啧啧,人间正道是沧桑……

此时此刻,我无比怀念千里之外的女儿,一岁半的她说话已经颇有周杰伦唱歌的神韵,可是万一她以后追星成瘾,我怎么办?我一时没有答案,但是我明白,我要同她一起成长。

作为玩家,我不介意被杨永信称为“魔兽”,让一切怪诞和荒谬在我们这一代戛然而止吧。

作为父亲,我必须称杨永信为禽兽,你可能带走了网瘾,但是,你为孩子们带来了更为可怕的心魔。

最后,作为同行,请允许我向率先大规模曝光的《中国青年报》起立致敬。

一个网戒中心的生态系统

“戒网专家”电击治网瘾惹争议

谁都想在网瘾治疗市场分杯羹

2009年4月26日星期日

在C#中利用Keep-Alive处理Socket网络异常断开的方法

转帖自:http://blog.csdn.net/octverve/archive/2008/02/06/2085225.aspx

  最近我负责一个IM项目的开发,服务端和客户端采用TCP协议连接。服务端采用C#开发,客户端采用Delphi开发。在服务端开发中我碰到了各种各样的网络异常断开现象。在处理这些异常的时候有了一些心得,现在写出来和大家分享一下。

那网络异常断开原因主要有那些呢?归纳起来主要有以下两种:

1、客户端程序异常。

  对于这种情况,我们很好处理,因为客户端程序异常退出会在服务端引发ConnectionReset的Socket异常(就是WinSock2中的10054异常)。只要在服务端处理这个异常就可以了。

2、网络链路异常。

  如:网线拔出、交换机掉电、客户端机器掉电。当出现这些情况的时候服务端不会出现任何异常。这样的话上面的代码就不能处理这种情况了。对于这种情况在MSDN里面是这样处理的,我在这里贴出MSDN的原文:

如果您需要确定连接的当前状态,请进行非阻止、零字节的 Send 调用。如果该调用成功返回或引发 WAEWOULDBLOCK 错误代码 (10035),则该套接字仍然处于连接状态;否则,该套接字不再处于连接状态。

  但是我在实际应用中发现,MSDN说的这种处理方法在很多时候根本无效,无法检测出网络已经异常断开了。那我们该怎么办呢?

  我们知道,TCP有一个连接检测机制,就是如果在指定的时间内(一般为2个小时)没有数据传送,会给对端发送一个Keep-Alive数据报, 使用的序列号是曾经发出的最后一个报文的最后一个字节的序列号,对端如果收到这个数据,回送一个TCP的ACK,确认这个字节已经收到,这样就知道此连接 没有被断开。如果一段时间没有收到对方的响应,会进行重试,重试几次后,向对端发一个reset,然后将连接断掉。

  在Windows中,第一次探测是在最后一次数据发送的两个小时,然后每隔1秒探测一次,一共探测5次,如果5次都没有收到回应的话,就会断开 这个连接。但两个小时对于我们的项目来说显然太长了。我们必须缩短这个时间。那么我们该如何做呢?我要利用Socket类的IOControl()函数。 我们来看看这个函数能干些什么:

使用 IOControlCode 枚举指定控制代码,为 Socket 设置低级操作模式。

命名空间:System.Net.Sockets
程序集:System(在 system.dll 中)

语法

C#
public int IOControl (
IOControlCode ioControlCode,
byte[] optionInValue,
byte[] optionOutValue
)


参数
ioControlCode
一个 IOControlCode 值,它指定要执行的操作的控制代码。

optionInValue
Byte 类型的数组,包含操作要求的输入数据。

optionOutValue
Byte 类型的数组,包含由操作返回的输出数据。

返回值
optionOutValue 参数中的字节数。

如:

socket.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null);

我们要搞清楚的就是inOptionValues的定义,在C++里它是一个结构体。我们来看看这个结构体:

struct tcp_keepalive
{
u_long onoff; //是否启用Keep-Alive
u_long keepalivetime; //多长时间后开始第一次探测(单位:毫秒)
u_long keepaliveinterval; //探测时间间隔(单位:毫秒)
}
;

在C#中,我们直接用一个Byte数组传递给函数:

uint dummy = 0;
byte[] inOptionValues = new byte[Marshal.SizeOf(dummy) * 3];
BitConverter.GetBytes((
uint)1).CopyTo(inOptionValues, 0);//是否启用Keep-Alive
BitConverter.GetBytes((
uint)5000).CopyTo(inOptionValues, Marshal.SizeOf(dummy));//多长时间开始第一次探测
BitConverter.GetBytes((
uint)5000).CopyTo(inOptionValues, Marshal.SizeOf(dummy) * 2);//探测时间间隔

具体实现代码:

public static void AcceptThread()
{
Thread.CurrentThread.IsBackground
= true;
while (true)
{
uint dummy = 0;
byte[] inOptionValues = new byte[Marshal.SizeOf(dummy) * 3];
BitConverter.GetBytes((
uint)1).CopyTo(inOptionValues, 0);
BitConverter.GetBytes((
uint)5000).CopyTo(inOptionValues, Marshal.SizeOf(dummy));
BitConverter.GetBytes((
uint)5000).CopyTo(inOptionValues, Marshal.SizeOf(dummy) * 2);
try
{
Accept(inOptionValues);
}

catch { }
}

}


private static void Accept(byte[] inOptionValues)
{
Socket socket
= Public.s_socketHandler.Accept();
socket.IOControl(IOControlCode.KeepAliveValues, inOptionValues,
null);
UserInfo info
= new UserInfo();
info.socket
= socket;
int id = GetUserId();
info.Index
= id;
Public.s_userList.Add(id, info);
socket.BeginReceive(info.Buffer,
0, info.Buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallBack), info);
}

好了,这样就成功了。