分类目录归档:Programming

C# Socket如何判断连接已断开

相信很多Unity3D的程序员都会遇到网络编程的情况,虽然目前可能大部分的手游都会优先选择使用非长连接技术来实现,也就是我们常说的直接使用Http来完成跟服务器通信的部分,但是还是难免会有遇到需要使用实时网络连接的时候。那么这时候,我们通常还是需要回到网络编程的根源,那就是直接使用Socket进行编程,.NET Framework中的Socket已经封装得非常的完备了,真的很好使。

但是在使用的过程中难免还是会碰到一丢丢问题的,对吧。今天我要记录的就是在处理连接已断开的这种情况。

在手游的使用场景中,以下情况可能会出现客户端与服务器出现无法连接的情况:

  1. 客户端无法连接到网络,例如:客户端设备当前接入的Wi-Fi热点无法连接到互联网,客户端设备进入无线网络信号很差的地方(例如某些大厦的电梯,某些地铁线路等等),这个时候其实客户端和服务器都没有主动关闭过这个TCP连接,但是网络已经无法正常连接了;
  2. 服务器主动关闭了这条TCP连接,服务器为什么要主动关闭掉我的这条TCP连接呢?设想一下,客户端跟服务器建立TCP连接之后,客户端在10分钟内都没有向服务器发送过任何数据,那么服务器还有必要保留这条连接吗?如果当前在线的玩家较多,服务器可用的连接数已经达到一个容限值,那么是否需要及时将某些已经不活跃(例如10分钟以上没有发送任何消息)的连接主动关闭,回收这些连接资源,以备其他新登录游戏的玩家设备连接使用呢?所以,当服务器主动关闭了TCP连接的时候,10分钟我上完厕所回来了,我重新拿起我的手机,想继续玩我的游戏,那么就需要处理客户端如何重新连接到服务器的问题了;
  3. 服务器网络出现问题,例如服务器所在机房链路出现问题,通过某些网络就是无法访问服务器,例如服务器网线不小心被挖断了或者网线被网管拔了,服务器电源烧了,主板坏了,等等诸如此类的物理设备出现故障的情况下,客户端是无法连接到服务器的,服务器也不可能通过TCP协议给客户端发送什么FIN,ACK,SYN等等状态码啦,这个时候客户端也需要有正常的逻辑来处理这个情况。

其实说了这么多就是想表达一下,我们的客户端可能在任何情况下与服务器失去连接,而这个时候客户端需要合理地来处理这种连接已断开的情况。

我们可以查看一下.NET Framework的文档,看看Socket中的一些相关的属性和方法:

Socket.Connected Property

Gets a value that indicates whether a Socket is connected to a remote host as of the last Send or Receive operation.

The Connected property gets the connection state of the Socket as of the last I/O operation. When it returns false, the Socket was either never connected, or is no longer connected.

The value of the Connected property reflects the state of the connection as of the most recent operation. If you need to determine the current state of the connection, make a nonblocking, zero-byte Send call. If the call returns successfully or throws a WAEWOULDBLOCK error code (10035), then the socket is still connected; otherwise, the socket is no longer connected.

If you call Connect on a User Datagram Protocol (UDP) socket, the Connected property always returns true; however, this action does not change the inherent connectionless nature of UDP.

这个是Connected属性的相关描述,大体的意思就是这个Connected属性会根据当前这个Socket最后一次的I/O操作来进行设定,也就是说这个属性为True还是为False会在Socket最后一次I/O操作完毕之后进行设定,应该也是根据错误返回码SocketError来进行设定的。如果我们使用的是阻塞的Socket的话,那么这个状态会在最后一次I/O操作出错后将这个属性设置为False,如果使用的是非阻塞的Socket就需要我们自己通过发送一个零字节的数据包,然后根据错误返回码来判断Socket连接是否断开。


 

Socket.Receive Method (Byte[], Int32, Int32, SocketFlags, SocketError)

Receives data from a bound Socket into a receive buffer, using the specified SocketFlags.

在我们的项目中我们使用了这个方法来读取Socket中的数据 ,这个方法中的最后一个参数是一个out参数,也就是如果我们在读取数据的时候出现了一些异常,错误码会通过这个参数返回。


Socket.Send Method (Byte[], Int32, Int32, SocketFlags, SocketError)

Sends the specified number of bytes of data to a connected Socket, starting at the specified offset, and using the specified SocketFlags

我们使用这个Send方法来进行数据的发送,同样也是通过最后一个out参数SocketError来进行错误码的返回。

这个SocketError类型中呢有很多很多预定义的错误码,这个需要我们仔细查阅看看有哪些是我们需要使用到的,这个因项目类型和需求不同,这里不一一阐述了。

那么在我们实际的开发过程中呢,我们肯定可以通过判断SocketError错误码来进行一些异常情况的处理,不过有些时候我们其实并不想在出现异常了之后才处理,例如我有一个发送消息的队列,有一条消息发送出错了,我还得把这条消息再放回队列,可是如果直接入队到消息队列的队尾可能会有消息先后顺序的问题等等。所以有些时候我们就需要在异常出现之前就想好解决方法,也就是在我们读取或者发送数据之前就先检查一下连接是否正常消息是否能够成功发送出去,那么这又要肿么搞呢?

先看看Poll方法的描述吧:

Socket.Poll Method

Determines the status of the Socket.

Remarks

The Poll method will check the state of the Socket. Specify SelectMode.SelectRead for the selectMode parameter to determine if the Socket is readable. Specify SelectMode.SelectWrite to determine if the Socket is writable. Use SelectMode.SelectError to detect an error condition. Poll will block execution until the specified time period, measured in microseconds, elapses. Set the microSeconds parameter to a negative integer if you would like to wait indefinitely for a response. If you want to check the status of multiple sockets, you might prefer to use the Select method.

 看上去这个方法貌似就是我们想要的呢,对吧?如果你这么想那就太Naive了,在实际使用的过程中,我就犯了一个不仔细看文档,导致栽了一个大跟头的错误。我写了一段代码用来判断当前Socket是否已经跟服务器断开连接。

Socket socket;
// 省去初始化和连接服务器代码若干
if (!socket.Poll(1000, SelectMode.SelectRead)) {
// 做错误提示等相关的操作
}

事实证明这样是瞎搞,因为.NET Framework的文档关于SelectMode.SelectRead这样描述的。

true if Listen has been called and a connection is pending;
-or-
true if data is available for reading;
-or-
true if the connection has been closed, reset, or terminated;
otherwise, returns false.

你说这不是坑爹是啥?这个意思就是说,如果我们是一个服务器端的Socket,调用了Listen方法,并且有一个外部连接在等待我们处理的时候,这个Poll方法会返回true,如果当前Socket中有数据可以读取时,这个Poll方法也会返回true,或者当这个Socket连接被关闭、重置、强制终止了也会返回true,否则返回false。

实际测试的时候,发现这个值只在Socket缓冲区中有数据可以读取和连接被服务器关闭了之后才会返回true,好吧,我承认自己是猪吧。那么回到我们的初衷上来,我们还是希望能在读取数据之前就能知道这个连接是否已经被关闭了(是否与服务器断开连接了)。那么可以通过下面这段代码来进行判断。

// 省去初始化和连接服务器代码若干
if (socket.Available == 0 && socket.Poll(1000, SelectMode.SelectRead)) {
// 因为根据文档的描述,如果连接正常的话,这两个条件不可能同时成立
// 只有在连接已断开的时候,才可能会出现这两个条件同时成立的情况
}

写到这里的时候,突然之间很想看看Available这个属性又是怎么一回事,好吧,我们再来看看.NET Framework的文档吧。

Socket.Available Property

Gets the amount of data that has been received from the network and is available to be read.

Remarks

If you are using a non-blocking Socket, Available is a good way to determine whether data is queued for reading, before calling Receive. The available data is the total amount of data queued in the network buffer for reading. If no data is queued in the network buffer, Available returns 0.

If the remote host shuts down or closes the connection, Available can throw a SocketException. If you receive a SocketException, use the SocketException.ErrorCode property to obtain the specific error code. After you have obtained this code, refer to the Windows Sockets version 2 API error code documentation in the MSDN library for a detailed description of the error.

虽然文档中提到如果我们使用的是非阻塞的Socket的话,那么Available属性将会是一个用于检测Socket缓冲区中是否有数据可以Receive的好选择,这样我们如果使用阻塞的Socket难道就不能用了吗?当然也不是,肯定还是可以用的,只是说非阻塞的Socket如果一直循环调用Receive很有可能会做无用功,因为如果当前Socket缓冲区中没有数据可以读取的话,Receive方法会很快速的结束调用并且抛出一个SocketException,而如果每次Receive之前检查一下变量的成本相对于调用一次Receive方法可能开销会更小一些吧。而对于阻塞的Socket来说,每次Receive调用都是阻塞直到有数据返回,或者超时后抛出一个超时异常。所以我们阻塞的Socket也是可以用Available这个属性来进行提前判断的。但是实际测试中,Unity3D中使用的Mono中的Socket实现并非和.NET Framework的文档描述一致。关于如果Remote Host关闭连接之后,再调用这个Available属性会抛出异常的行为就没有。

 关于SelectMode中另外两个SelectWrite和SelectError类型,显然我们也不能放过啊。一一测试之后发现,在服务器主动断开连接之后,通过Socket.Poll(1000, SelectMode.SelectWrite)返回一直都是true。即便我们通过Send方法发出去的字节数为0,返回的SocketError中的错误码为Shutdown,依然不为所动啊,所以对于这个SelectMode.SelectWrite我已经凌乱了,希望哪天可以找到合理的解释。

但是Socket.Poll(1000, SelectMode.SelectError)在服务器断开连接之后会在第一时间内从false变成true,貌似这个还是蛮靠谱的,但是看了一下文档之后我又有点凌乱了。

true if processing a Connect that does not block, and the connection has failed;
-or-
true if OutOfBandInline is not set and out-of-band data is available;
otherwise, returns false.

这个意思是说,如果正在建立一个非阻塞的连接,然后这个连接失败了会返回true,或者没有设置OutOfBandInline但是又收到了out-of-band数据的时候会返回true,否则就返回false。

综合上面说的那一大堆东西,最终得出的结论就是Connected字段可以用,但是有滞后性,如果想通过这个字段来检测连接是否已经断开的话,就要明白这个字段是在我们通过Receive或者Send方法成功进行一次I/O操作之后,系统根据最后一次I/O操作的错误返回码来设定Connected字段的值的。

而如果想在进行I/O操作之前就检测到连接出现断开的情况的话,是可以通过Poll来达成目的的,当然这个会比较纠结。首先Poll配合SelectMode.SelectRead和SelectMode.SelectWrite都不能非常直接的反馈连接是否被断开 。但是貌似这个SelectMode.SelectError还是蛮靠谱的,所以呢,从目前测试得到的结果中,可以使用这个来提前判断连接是否已经断开。

不过在实际的应用中,我个人倒是更倾向于在使用SelectMode.SelectError做判断的同时,一定还要对在Receive和Send方法中返回的SocketError进行检查,这个返回码是直接从Native层返回的Socket错误码,非常可靠也很能说明问题。例如”HostDown”、”ShutDown“、”NetworkDown“等等状态码还是很能直观地反应当前Socket的状态的,所以还是需要好好地看看.NET Framework中关于SocketError的描述文档。

Unity3D Mecanim动画系统骨骼动画问题解决方法

这几天开始做游戏中跟动画相关的部分了,此次新项目我们决定一次从新开始,就是能用新的东西就都用新的东西,没有必要总是把自己局限在之前的认知里头,所以此次我们大胆而又现实的采用了Unity 4.x版本新增的Mecanim动画系统,并且我们果断地又选择了使用Humanoid动画类型。好吧,一切从新开始。

一切从新开始就意味着会有很多新的坑需要自己一个个去填,我们一直都拥有一颗坚强受虐的心不是吗。在我们的动画设计师将骨骼动画调好了之后,给了我一组FBX文件,导入Unity之后,预览一下这个动画吧。

animation-1

animation-2

 

 

 

 

 

 

我们可以很明显得看出这两个动画中人物角色的右手和武器之间的位置关系是不对的,跟动画设计师在3DS Max中制作和预览的效果也不对,好吧,碰到第一个坑,上吧。

通常我们先做的第一件事情就是先查查文档,看看Unity官方是否有一些最佳实践的建议,可是我翻遍了Unity Manual中关于Mecanim Animation System的所有文档没有看到相关的内容,而游戏引擎通常对于开发者来说就是个黑盒,出了问题除了查查看看别人是否碰到同样的问题也就是直接找Support这条路了,或者就是自己各种尝试呗。好吧,那就先Google一下吧,找啊找,找啊找,找了将近两天看了很多跟Mecanim动画相关的问题,但是就是没有找到这个这么基础的问题相关的帖子啥的,简直没有天理啊。

既然Humanoid不行,那我们试试Generic动画吧,这下就都OK了,那么显然动画资源自身肯定木有问题了,这下就确定肯定是Unity按照Humanoid动画导入FBX资源的时候有某些设置我们没有按照要求来做,所以导致了这个武器完全不跟着右手骨骼运动的情况。自己纠结了又一天,又是各种查啊各种试,完全木有进展。最后,只能求助于其他有经验的人了,通过我们的动画设计师,找到了一个他一个做技术美术的朋友,该大牛对Unity非常熟悉,专门解决项目中碰到的技术问题,哪里有问题哪里就有他。直接远程协助一下,看着大神一点点地调整,先是检查了一下Avatar设置中骨骼对应的关系是否正确,确认无误之后,再次运行看看效果依然如此,大牛稍微思考了一下,马上打开了Unity导入动画的页面,找到了Animations标签页,然后展开Mask设置中的Transform节点,将所有未勾选的人物角色骨骼节点都勾选上,然后Apply一下,再次运行游戏,搞定。

animation-3

animation-4

 

 

 

 

 

 

再回顾一下完整的调整过程吧,首先我们要确定我们出问题的骨骼具体是哪根,因为目前看到的现象是右手拿的刀和挂在腰上的刀鞘出现了问题,打开Avatar设置页面,查看一下,确定是Bip001 Prop1和Bip001 Prop2这两根骨骼出了问题。

Bip001 Prop1

 

确定了是这根骨骼出现问题了,然后进入到出现问题的动画文件[email protected]的导入设置页面,打开Animations标签页 =》展开Mask节点 =》 展开Transform子节点 =》 找到左右手对应的刀和刀鞘的骨骼,勾选上,然后点击Apply按钮就好了:

import setting

QQ20140816-6@2x

QQ20140816-7@2x

 

 

 

 

 

 

 

 

 

 

虽然大神帮我们解决了这个困扰了我将近4天的问题,那么究竟为啥捏?其实在被困扰的4天里头,其实也发现了一些蛛丝马迹的,就是每次我们导入动画[email protected]的时候,Unity总会提示以下的警告:

MuscleClip ‘jin_wei_jun@run’ conversion warning: Bone position is different in avatar and animation
‘Bip001 L Thigh’ : position error = 31.473890 mm
‘Bip001 R Thigh’ : position error = 8.405679 mm
‘Bip001 L Clavicle’ : position error = 37.673038 mm
‘Bip001 R Clavicle’ : position error = 76.461647 mm

UnityEditor.DockArea:OnGUI()

和这样的一个警告:

MuscleClip ‘jin_wei_jun@run’ conversion warning: ‘Bip001/Bip001 Pelvis’ is between humanoid transforms and has rotation animation. This might lower retargeting quality.
MuscleClip ‘jin_wei_jun@run’conversion warning: ‘Bip001/Bip001 Pelvis/Bip001 Spine’ has translation animation. It is not supported.
MuscleClip ‘jin_wei_jun@run’ conversion warning: ‘Bip001/Bip001 Pelvis/Bip001 Spine’ is between humanoid transforms and has rotation animation. This might lower retargeting quality.
MuscleClip ‘jin_wei_jun@run’conversion warning: ‘Bip001/Bip001 Pelvis/Bip001 Spine/Bip001 L Thigh’ has translation animation. It is not supported.
MuscleClip ‘jin_wei_jun@run’conversion warning: ‘Bip001/Bip001 Pelvis/Bip001 Spine/Bip001 R Thigh’ has translation animation. It is not supported.
MuscleClip ‘jin_wei_jun@run’conversion warning: ‘Bip001/Bip001 Pelvis/Bip001 Spine/Bip001 Spine1/Bip001 Neck/Bip001 L Clavicle’ has translation animation. It is not supported.
MuscleClip ‘jin_wei_jun@run’conversion warning: ‘Bip001/Bip001 Pelvis/Bip001 Spine/Bip001 Spine1/Bip001 Neck/Bip001 R Clavicle’ has translation animation. It is not supported.

UnityEditor.DockArea:OnGUI()

这两个警告大体的意思是啥呢,刚开始自己没有太注意,后来总是找不到原因就想着会不会还就真是这俩警告给弄的呢,自己仔细看了一下。第一个警告的意思,应该说的是动画中有几个骨骼的位置跟这个动画实际使用的Avatar中骨骼的位置不相符,这个原因我大概能理解的原因是这样的。我们在制作的过程中会将模型文件和动画文件分开,我在导入模型文件的时候会创建一个属于这个模型的Avatar,其他的动画都会直接引用这个Avatar文件而不会每个动画都创建自己的Avatar,而不同的动画中人物可以会有一些不同的动作,这就会让动画中角色的一些骨骼位置和模型(模型默认姿态就是站姿)站立姿态时的骨骼的位置会出现一些不匹配的情况,所以这个可以理解了。那么第二个警告呢,这个警告都是说某根骨骼有位移动画和旋转动画,而这些动画会降低Retargeting的质量,同时Humanoid动画不支持在这些骨骼上使用位移动画。虽然这个警告中提到的带有位移和旋转动画的骨骼不少,但是涉及的就是角色躯干、胸部、臀部、左右手、左右脚这几个非常关键的骨骼,并没有提到影响到刀和刀鞘的骨骼Bip001 Prop1和Bip001 Prop2啊。

综合大神提供的解决方案,应该可以初步得出结论,导入FBX动画的时候,选择Humanoid动画类型进行导入的时候,Unity会自行进行计算和判断,然后根据Retargeting最佳原则,设置Animations选项卡中Mask节点下Transform子节点中的骨骼是否需要在Humanoid动画中应用位移动画,而这个时候Bip001 Prop1和Bip001 Prop2这俩骨骼就被忽略了,所以我们需要手动的去勾选,如果有必要的话,那么就把所有的未勾选的骨骼都勾选上,然后再Apply一下吧,如果出现Apply一次不生效的话,重新重复设置遍,然后再Apply一下吧(我自己碰到了一次把所有的都勾选,然后Apply之后无效,重新展开之后发现只有部分被勾选上了,重新再全勾选Apply一次才好的情况)。

刚才我们提到了使用Generic动画的时候就不会出现这个问题对吧,那么为了印证这个结论是否正确,我们可以检查一下Generic动画导入设置页面中Animations标签页中Mask节点下Transform子节点中的骨骼节点是神马情况就好了。如下图,所有的节点默认都是勾选上的。

QQ20140816-10@2x

鉴于此,我们可以得出结论就是,在我们使用Humanoid动画类型导入FBX文件时,Unity会以最佳匹配Retargeting规则的方式自行计算,看看那些骨骼是需要勾选Transform动画选项的,而默认使用Generic动画就会将所有的骨骼节点Transform动画选项都勾选上,所以效果是正确的。OK,所以问题到这里就彻底明了了。

如何学习编程语言

标题比较大,我自己能写的东西比较少,毕竟自己从学习编程到现在总共也就6~7年的时间,写代码的时间也就4年时间,谈不上有什么非常好的经验,不过这些年来也有一些想法和感触,自己记录一下。

大学期间,学校安排的计算机相关课程中有《计算机基础》《C编程语言》《C++编程语言》《数据结构与算法》《数据库原理与技术》《空间数据库》以及一些计算机编程周边的课程,例如《复变函数》《数值分析》《空间分析》《运筹学》等等。回过头来看看,这些所有课程我的考试成绩貌似都不错,但实际上神马都木有深入学习过,只是为了应付考试,尼玛杯具啊。

在某一次《数据结构与算法》的课上,我们可爱又严厉的王盼成老师(昵称盼盼)在课间跟我们闲聊,语重心长地跟我们讲“做地信的最好能会写程序,这样未来空间会更大一些”,我当时也不知道哪根筋搭错了,然后在下面搭了一句“老师,每个人有每个人的活法,也不是所有人都希望自己成为一个程序猿”,老师听到我说完这句话之后回了一句“我相信每个人有每个人的死法”。在老师说完这句话之后,几秒中之内整个教室都安静了一会儿,然后又该开始上课了,之后的整堂课我都在想这个事情。然后我就开始学习编程了,之前我们学过的《C编程语言》和《C++编程语言》我已经完全交回给老师了,而宿舍中其他的同学在C/C++方面已经有了一些成绩了,出于当时的虚荣也罢不服输也好,我选择了跟宿舍其他同学不同的一条路来学习编程。

我开始学习Java编程语言,没有同学和老师可以请教,但是我有整个图书馆和网络做老师。从那个时候开始,每天都泡在图书馆的阅览室中读各种Java相关的编程书籍,读得很粗糙,但是确实读了不少,每天回到宿舍就开始看一些视频,上JavaEye(先已更名位ITEye)去看各种博客和帖子。那时候看得多,写得少,记得当时毕业找工作的简历上,我写的编程经验中有一句话是“有丰富的Demo经验”。在那个阶段,我接触了Java编程的各个方面,但是每个方面接触得都比较浅显,刚刚踏入编程世界的我,觉得Java编程语言中有太多太多的精彩了。这个阶段,应该算作我开始在编程上入门了。

加入超图之后,一直在做JNI相关的事情,这应该是Java编程世界中的一个非常冷门的方向,当时只有某些做中间件封装的企业会使用该技术来重用之前已有的C/C++类库。我在超图待了一年的时间,写了一些JNI的代码,看了很多JNI封装C/C++ SDK的代码,对JNI也有了一个更全面的认识,但是对于Java编程语言本身并没有更深入的理解。

加入喜讯之后,开始做Android方面的开发,这在当时可真是个热门的方向,在移动互联网被鼓吹到风口浪尖时,Android程序猿也算是非常抢手的资源啊。Android的开发跟普通的J2SE开发没有任何不同的地方,在对Android Framework有了一个完整的认知之后,所有的代码无非也就是逻辑和UI的实现,客户端程序大抵如此。但是在喜讯我已经不再只是一个纯粹写程序实现的了,我需要自己来设计整个程序的结构,如何保证程序能快速响应未来的设计和需求变得更为重要。在喜讯天天、画说和MARK这三个项目中,我开始尝试着做不同的设计尝试,并且尝试着形成自己的编码风格,并在团队内推广。

如今开始做游戏,主要是在Unity3D环境下完成,Unity3D下几乎所有的逻辑和功能代码都是通过C#来完成(当然你也可以通过JavaScript和Boo来实现),在学习C#上障碍相对较小,毕竟大部分的工作并不是跟语言的细节纠结而是完成具体的逻辑和功能,天下万般程序逻辑皆相通,所以也还算较为顺利的开始了Unity3D游戏开发。术业有专攻,每个人擅长的模块都不太一样,在游戏项目中,我主要负责的是网络通信模块,包括HTTP通信和TCP通信模块代码的实现。非常感谢已经有众多程序猿在这些方向上作出了很多的努力并且将其劳动成果分享出来。在这些可爱的人无私分享的前提下,我们可以减少很多的代码工作量,只需要阅读这些优美的代码,理解他们思考和实现的逻辑,在此基础上加入自己的思考,融合自己的逻辑实现即可完成我们需要的功能模块。

从自己开始真正地学习编程到现在,应该有五年有余,在这段时间中我不能说自己编程能力提高得有多么的快多么的高,我只能说通过自己偶尔的横向和纵向跟他人做一个简单的对比,发现自己的不足和找到自己的优势,整体来讲,我肯定不算低效和低能的程序猿,起码能算是一个合格的程序猿,那么我是如何学习的呢?

阅读,在刚刚开始接触编程的时候,阅读了大量的书籍,关于Java编程语言的,关于Java社区流行的各种框架的书籍,网络上很多程序猿们分享的博客文章和编程教程。初期大量的阅读,让我的视野在很早就打开了,不会只局限于某个细小的方向,并且通篇完整地阅读过一本关于Java编程语言的书籍对于我的影响非常大,我当时读的是《Thinking in Java》,通过这本书接触Java也算是一种幸运。通过阅读《Thinking in Java》,我了解了Java语言基础的使用方法,养成了还算不错的编码风格,同时接触到了Java API设计的原则和方法,明白了面向对象编程的概念。之后继续阅读了很多Java社区流行框架的书籍,例如Struts, Spring, Hibernate, Lucene, Tomcat, Heritrix 等等,虽然我对这些我曾经都尝试过编写不少的Demo的框架如今了解很少,也无法使用他们来完成我想做的事情。但是这些阅读让我见识到了编程的美好,让我看到了世界上众多优秀的程序猿他们是如何通过自己的努力改变了众多程序猿的工作方式。与其说我在学习这些框架,不如说我在认识这些框架在Java社区中的生存方式,正是因为这种开放的社区驱动让我对Java编程热情猛增,直至最后毕业选择了做一个程序猿而不是在我上大学之前就一直向往的管理培训生。

社区,阅读Java编程相关的书籍,总会不经意的发现Java社区中众多开源的框架和这些开源框架的开发者都在网络上积极地做一些事情,出于简单的崇拜也好,和更深层次地学习也罢。在学习Java语言前期,我开始尝试泡在各种不同的Java社区中,虽然很多时候我完全不懂别人在聊什么在讨论什么,但是看到别人在讨论某些问题,能让我先有一个认知,然后做很多的扩展阅读,然后时间长了就会发现自己的认识逐渐在提高,慢慢地能看懂别人讨论的问题了,然后偶尔还能发个言,帮助甚大,提升明显。Java社区中,我在JavaEye(现已更名为ITEye),在这个社区回过帖,潜过水,开过博客,写过文章,发过简历。还因为在JavaEye发布简历,途牛网的陈福炜给过我一个电话面试的机会,但是被我很傻逼的给浪费和错过了。社区能让你了解到很多方向的东西,能看到其他的人在关注什么在做什么,以及很多事情别人已有的解决方案,还能认识很多可爱的人。

搜索引擎,程序猿可以不会写Hello World,但是一定要能熟练使用Google,熟练使用Google并不难,但是也不是很简单。简单的输入一段自己的需求文字,奢求Google给你找到一个满意的答案,这个可能性比较小。Good Question 才能有 Good Answer。学会抽象出来自己碰到的问题和需求的关键字,尽量去除修饰词语,然后交给Google就好了,通常Google都能找到你想要的答案。很多时候我们搜索的问题在中文世界中是没有相关内容的,这个时候你就要能将你自己的问题和需求翻译成英文,然后再交给Google了,然后自己通过阅读英文内容来找到答案了,程序猿英文是一定要好的,不论你写什么程序,否则都是扯淡。

文档,不论我们是学习一门新的编程语言还是尝试使用一个新的框架,官方的文档总是最重要的工具和参考。学习Java的时候就需要熟练掌握如何从JDK Help Documention中找到自己需要的东西,学习Android就应该能从Android Developers官方站点上找到想要的知识点内容。这些文档中,不但明确地告诉我们如何去使用API,更会让我们对整体语言和框架形成一个完整的认知,在后续的学习和编码过程中,我们会形成一个非常好的习惯,碰到不确定的问题直接找官方文档,看个究竟之后就能继续了。另外,熟悉API文档的同时也会让我们对整个API的结构有一个非常全面的认知,并且会形成使用API的习惯,当你需要使用一个之前未曾使用过的方法,我们会习惯性的认为某个类有某个方法,直接一试或者直接一查,往往屡试不爽,这能很大程度上节约时间提高效率。关键是熟练掌握API内容能带来的好处在前期做设计和技术预言的时候将会更加显著,很多时候我们在设计一个新功能,该功能需要某些接口来帮助我们完成一些事情,如果这时我们清楚地知道某些API是可用的,某些API是框架或者语言不提供,需要我们自行实现的,马上能辅助我们作出一个合适的判断,包括我们在做整体系统架构和技术选型时,这些都是非常有帮助的。

开源代码,站在巨人的肩膀上,你能看到更多更美的风景。开源的代码,通常都不会难看,通常都是非常优雅的实现,而且有着某个团队或者某人在持续改进的,阅读这些优雅的代码,能让我们学习别人的编码方式,看看别人的思考角度和实现风格。开源代码已经改变了整个世界,Linux系统和基于Linux的Android系统已经是每时每刻在互联网和移动互联网上扮演着台前幕后的重要角色,我们每天都在用的各种应用和网络服务,几乎都不可能离开开源软件和代码的支持。阅读Java API实现的代码,阅读Android Framework和Android Packages的源码,真的学习到了很多的东西,包括形成良好的编程风格。

关注变化,编程技术每天都在进步,每一年都会有一些新鲜的技术和框架出来,保持对新鲜技术的关注和好奇,尝试着去使用他们,如果能接触到源码那就更好,尝试去阅读和改进。不一定要追随所有新的技术潮流,不是说每个新的技术都需要放到项目中来,也不是每个新的技术或框架都能解决你现有的问题。关注新的技术,其实就是关注它究竟解决了什么问题,这个问题是否就是你目前碰到的,如果是的话,那么就可以很好地解决你的问题。关注新的技术,也是关注它究竟是如何解决了这个问题,和我们自己的解决方法相对比一下,两个解决方法的优势和劣势分别在哪儿,尝试着将别人的优点借鉴一下,将自己的解决方案完善了,如果能将我们自己的优势反馈到社区中帮助到更多的人,显然是一件美妙的事情。

分享,与别人分享你的技术成果,总是能让我们收到更大的收获。开源社区就是分享精神和开放精神的最佳形式,每个人都可以将自己好的东西分享出来,不断地来改进已有的东西,然后每个能接触到的人都可以再从中获取到更好的东西。分享能将个人的能量放大,如果社区或团体足够大,那么分享出来的能量将被放大的倍数也会更大,分享的人收到的反馈也会更大。程序猿都是好面子滴,谁尼玛不希望那个被称为牛人的人是自己呢?尝试着跟身边的人分享自己的技术成果吧,尝试着跟更多的人分享自己的技术成果吧,GitHub无疑是一个非常合适的去处,大胆地分享自己的代码吧,不要怕被人嘲笑,每个人提交的patch都是在给我们自己能力打补丁呢,拥抱那些人吧,那些人在你成为牛人的道路上扮演的可是推进式发动机的角色啊。

关注那些先进的生产力和代表先进生产力的人,例如Markdown代表着文档的先进生产力,使用Git管理代码的人代表了先进生产力,使用终端和脚本代替重复性鼠标操作的人代表了先进生产力,那些通过技术改变人们生活的人,他们代表了先进生产力。尝试着去关注这些事物和这些人,看看他们是如何做到的,他们做到这个程度付出了多少,以及他们是如何努力的。争取哪天我们也成为先进生产力的代表,那样岂非很爽,先进生产力(尼玛高中政治就学到了这个词)永远都不会被淘汰的。

关于strlen的小迷惑

首先,说明一下,我不是一个C程序猿,也不是一个C++程序猿,我从入门开始真正学习的编程语言以及正经用过的变成语言,也就是Java和C#,虽然我对其他的语言都非常感兴趣,同时也写过一些Python的脚本和C的代码(主要集中在JNI)。

由于这些年来的编程习惯,我认为字符串都应该会有自动获取其长度的方法,在前几天的工作中就碰到需要使用这个方法的场景了。由于我对strlen()使用的不慎,导致程序输出时而正确时而错误,最终定位到是因为使用strlen()方法获取字符串长度在多次执行后的输出不等,导致程序出现了不可控的情况。

那么是否是因为strlen()方法的问题呢,我自己首先十分肯定绝对不会是这个方法的问题,这些方法在时间和实践中都尼玛得到过太多的验证,且看Linux中的实现:
[c light=”true”]
#ifndef __HAVE_ARCH_STRLEN
/**
* strlen – Find the length of a string
* @s: The string to be sized
*/
size_t strlen(const char * s)
{
const char *sc;

for (sc = s; *sc != '\0'; ++sc)
/* nothing */;
return sc – s;
}
#endif
[/c]
这么简洁的实现,多么漂亮啊。肿么可能会出错捏?好吧,实际的应用场景是我需要将一个从C#运行时托管层将一个byte数组传入Native层,在Native层面做一些操作的时候呢,我直接使用了strlen()函数来确认当前byte数组的长度,尼玛脑残啊。托管层byte数组虽说可以认为是Native层的unsigned char数组,但是我并木有意识到C层的字符串都是需要’\0’来作为终止符滴,显然从托管层传入的数组中不会自行添加这个到数组中。所以才会出现偶尔内存中该数组后面确实没有任何值,那么strlen()函数在刚好读取到数组后的值为空时,那么其长度刚好正确,在某些时候该数组后连续的内存地址中并不为空,那么strlen()就会返回一个错误的长度,而我的逻辑代码又依赖于该长度,所以你只到的,Just Fucked Up!

所以在托管层申请的内存,传入Native层面时,一定要注意两个平台下对某些概念处理的不一致,否则到时候又得吃自己的狗屎了。WTF。

Ubuntu下让ADB识别所有设备的两个方法

方法一,完全参考谷歌的文档,内容如下
在/etc/udev/rules.d目录下新建一个51-android.rules文件,将下面所有内容拷贝到该文件中:

SUBSYSTEM=="usb", ATTR{idVender}=="0502", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="0b05", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="413c", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="0489", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="04c5", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="091e", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="18d1", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="109b", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="0bb4", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="12d1", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="24e3", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="2116", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="0482", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="17ef", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="1004", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="22b8", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="0409", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="2080", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="0955", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="2257", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="10a9", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="1d4d", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="0471", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="04da", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="05c6", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="1f53", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="04e8", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="04dd", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="054c", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="0fce", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="2340", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="0930", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="19d2", MODE="0666", GROUP="plugdev"

方法二,有点Hack的味道,参考“孔雀的小屋”滴,就是将下面这行内容拷贝至/etc/udev/rules.d/51-android.rules文件中,
通杀所有Android设备,上面的那些都可作为浮云

SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", MODE="0666", GROUP="plugdev"