svg适合做游戏背景音乐开发吗

收藏,1.2k 浏览
问题对人有帮助,内容完整,我也想知道答案
问题没有实际价值,缺少关键内容,没有改进余地
产生这个问题前,我先查询的是svg和canvas的区别,有一个根本的却别是:
svg可以当作xml,可以放大缩小,像html一样操作
而canvas则是一个实实在在画布,像swf那样,编辑起来不是很方便,但是效率高
从这里可以看出两者各有优劣,再后来我又看了CSS3,我发现CSS3结合了两者的长处:
可以结合DOM,和svg一样容易操作
渲染效率高,可以像canvas在dom上画出任意形状
那么我有这么几个问题:
有了css3之后,我们还需要svg还有canvas吗?
他们各自的有缺点是什么
什么场合适合用CSS3,什么场合适合用svg,什么场合适合canvas
目前这三者有什么知名的框架来便于操作、使用吗?
他们三者目前浏览器兼容情况如何,希望能提供参考页面或者图表说明
答案对人有帮助,有参考价值
答案没帮助,是错误的答案,答非所问
4月8日 回答
CSS3在DOM节点多的情况下效率严重下降。
CSS3的旋转比canvas(2d)要好。
CSS3的文字渲染比canvas好。
canvas可以在更底层操作。
canvas支持webgl,可秒杀一切。
canvas(2d)兼容性还不错。
webgl兼容性坑爹,移动平台支持不好。
网页效果请用CSS3;应用、游戏、图表用canvas;如果你觉得展示数据的时候css3不够自由,canvas太麻烦可以用svg。
不是很熟悉
还有一些用来制作css3动画的工具,不过没见到让人满意的……
用的很少,这个勉强算一个?
答案对人有帮助,有参考价值
答案没帮助,是错误的答案,答非所问
4月8日 回答
我首先想到的是svg的兼容性比css3好,svg支持IE8+,css3和html5的部分属性在ie8下不支持
其次,说css3像svg一样可以结合dom,这有点抬举它了,svg基于xml,它的结构很好,很适合修改和扩展,而如果用css3重写就是html,语义上不对不说,结构也得靠工程师自己来设计和优化,扩展性和修改能力上远不如svg。所以,复杂图形上面,svg的优势很明显,而简单的图形,可以说css3跟svg差不多。
canvas的话,因为跟js结合更紧密,所以一些逻辑操作要比css3方便太多,比如用canvas逐帧写动画和游戏,css3实现的话会更加吃力一些。
其实css3我认为依然是对css的扩展,更多是从扩展样式表现方面的考虑,从程序逻辑上来说,只能依靠模拟来实现,在这方面依附于js的canvas还是有比较大的优势。
答案对人有帮助,有参考价值
答案没帮助,是错误的答案,答非所问
4月8日 回答
canvas的操作更底层(使用WebGL的情况下) 效率更高 以后可能成为新的游戏平台
答案对人有帮助,有参考价值
答案没帮助,是错误的答案,答非所问
4月8日 回答
我 确实不知道怎么回答好
只是来逛逛 ,你们加油
「已忽略」:不符合答题要求
同步到新浪微博
欢迎来到最专业的开发者社区
终于被你注意到了 ^_^,如果你觉得这个社区还不错,记得了加入我们哦
最专业的开发者社区
最前沿的技术问答,最纯粹的技术切磋。让你不知不觉中开拓眼界,提高技能,认识更多朋友。
分享到微博?
删除理由:
删除理由:
忽略理由:
答非所问,不符合答题要求
宜作评论而非答案
带有人身攻击、辱骂、仇恨等违反条款的内容
内容质量差,或不适合在本网站出现
其他原因(请补充说明)
补充说明:
举报理由:
带有人身攻击、辱骂、仇恨等违反条款的内容
与已有问题重复
内容质量差,或不适合在本网站出现
答非所问,不符合答题要求
其他原因(请补充说明)
补充说明:html5 开发的游戏图片兼容问题,SVG?_百度知道
html5 开发的游戏图片兼容问题,SVG?
我准备用html5来开发一款游戏,主要是运行在手机上,考虑到手机频幕的不同尺寸,图片应该有什么方式呈现?SVG是矢量的,还是用普通的图片,或者&canvas&? 游戏主要是一个个不同的图片,游戏里需要放大放小图片,所以图片质量很重要!
提问者采纳
如果引用图片很多,而且有很多特效,并且需要频繁变换,那么用Canvas会比SVG好。
这个应用的主要内容就是图片,然后在应用了图片需要经常调整大小,但没什么特效的。
如果只是调整大小,那么SVG足以胜任任务要求。
提问者评价
其他类似问题
html5的相关知识
您可能关注的推广回答者:回答者:
等待您来回答
下载知道APP
随时随地咨询
出门在外也不愁在公司做项目的时候,遇到了这样一件事情。当玩家点击副本时,程序能够自动发现玩家当前的体力不够,然后弹出对话框提示玩家购买体力,当玩家成功购买体力之后,则进入刚点击的副本战斗。按照原有的设计,我需要将『玩家成功购买体力之后,进入刚点击的副本战斗』这一段逻辑封装成一个函数,将其一并传给 MsgBox——原本设计的 MsgBox 就是接受外界传入的一个回调函数,在触发 MsgBox 的点击按钮时触发。这种设计的伪代码如下:function MsgBox::SetCallBack(void fnCallBack(void*, int), void* objCallBackParam)
m_fnCallBack = fbCallB
m_objCallBackParam = objCallBackP
function MsgBox::OnClickRight()
m_fbCallBack(objCallBackParam, MsgBox::RIGHT_CLICK_EVENT);
但,在『玩家成功购买体力之后,进入刚点击的副本战斗』这段逻辑代码中,包含了一次网络协议——玩家成功购买体力,一次回调——进入刚点击的副本战斗。那么我可能会如下实现:void FightFB(int nFbId, ...)
void FightFBCallBack(void*, int)
FightFB();
struct ObjFightFBParam
int nFbId;
void BuyPower(void fnCallBack(void*, int), void* objCallBackParam)
// 发送协议,并收到服务器反馈
if (bBuySuccess)
fnCallBack(objCallBackParam);
struct ObjBuyPowerParam
void fnCallBack(void*, int);
void * objCallBackP
//---------上面是定义,下面是使用方法
ObjFightFBParam objFightFBP
ObjBuyPowerParam objBuyPowerP
objBuyPowerParam.fnCallBack = FightFBCallB
objBuyPowerParam.objCallBackParam = (void*) &objFightFBP
msgBox:SetCallBack(BuyPower, (void *)objBuyPowerParam);
所以,我直观感受上比较不喜欢,在回调中再写一次回调这种做法,于是,这样实现了一套 MsgBox 为基础的继承类。以下是伪代码:class BuyPowerMsgBox : public MsgBox
function BuyPowerMsgBox::OnClickRight()
// 玩家发送购买体力协议
// 服务器反馈购买成功(原本此处也是一个回调:)
m_fbCallBack(objCallBackParam, MsgBox::RIGHT_CLICK_EVENT);
// 这样只需照常设置一次就好
buyPowerMsgBox:SetCallBack(FightFBCallBack, (void*)&objFightFBParam);
后来,我就想的更进一步了,实际上,在 BuyPowerMsgBox 中不需要再使用 m_fbCallBack 了。我只需要将逻辑写在函数 OnClick Right() 就好。每当我需要用 MsgBox 来做操作的时候,将那些特殊的逻辑写再虚函数 OnClickRight() 中——这就是和原本传入一个回调函数不同的一种设计 MsgBox 的实现方式。回调是通过 MsgBox 本身的逻辑来保证到,外部特殊逻辑一定被调用到,而继承则是 MsgBox 本身的逻辑加上虚函数的机制。后者的问题就是,MsgBox 是否从属性上来说,就存在这样一种对象关系——这是 UI 的问题了,我自己的理解是存在的:)但尽管如此,我这样做法也还是不好,因为这样一来,MsgBox 就存在有两种使用方式,从工程学来说,并不利于代码的维护。我思考了这两种方法的在 UI 考虑之外的一些程序实现上的优缺点,总结如下:
对于 C++ 这类语言来说,继承的方式应该是更为优雅的,因为回调需要传参,不方便,每一个回调就需要配置一个回调结构体(不知道是否有更好的方式)
对存在有闭包的脚本来说,比如说 lua,可能使用 callback 更为合适,因为再多的 callback 都可使用到闭包的特性。只是比起继承,那些特殊的逻辑可能是混杂在对话框之外的逻辑中,被放在了一个并不是特别合适它们的地方。
上个月,在完成 Socket 的代码练习中,写到 size_t,突然想不起来 size_t 和 int 的异同,于是觉得有必要再看看 C++ 的头文件。在 Cplusplus 网站上,它是这样介绍自己的:The C++ library includes the same definitions as the C language library organized in the same structure of header files, with the following differences:
* Each header file has the same name as the C language version but with a "c" prefix and no extension. For example, the C++ equivalent for the C language header file &stdlib.h& is &cstdlib&.
* Every element of the library is defined within the std namespace.
上面一段话总共三个意思,合成一句话就是,C++ 的库文件结构是按照 C 语言的来做的,它们之间的差别有两点,第一是文件名由 c 开头,比如说 stdlib.h 成了 cstdlib.h;第二个差别就是,它们都包含在名为 std 的命名空间里面。下面就来综合描述一下 C++ 中的头文件(C99 标准)&cassert&: 只定义了一个 Assert()
我在 Xcode 中找到了这个头文件,它只有非常简短的一行代码,即是将一个名为 assert.h 包含进来:)。想来是这样一回事吧,Xcode 使用的是 gcc,而 gcc 中统一实现了 C 和 C++ 库,所以这样一种行为也是比较容易理解的。换句话说,assert.h 这个原本是 C 语言的头文件,里面包含了 C++ 相关的内容。
另外,这个头文件在注释中提到,它(在同一个工程中)可能被多次包含,有些包含时带有 NDEBUG 参数,而有些没有。&cctype&:它声明了一系列的函数,用于区别和转换任何字符:转换只有大小写转换:);区别则比较多,比如是否大小写,是否 blank等等……这系列函数,接受的都是一个 int 类型的字符,返回值同样是 int,如果是零,则表明不是,非零,则是。例如: int isblank( int c)&cerror&:里面定义了一个宏,在网络编程中经常取这个宏的值来判断是什么错误,所以这个头文件(以及其相关包含的头文件)中,除了定义宏之外,还定义了错误编码&cstddef&:定义了一下四个类型,ptrdiff_t -- 指针相减的结果,size_t -- unsigned int,nullptr_t -- 空指针,max_align_t -- 最大对齐,在 MAC 中是 long double。还包括有一个宏函数,offsetof,它获得一个结构体中成员在该结果体中的偏移量。以及一个宏定义,NULL。&cstdint&:定义了一系列整形变量,还包括有这些变量相关的边界宏定义和一些相关的宏函数,这些整形变量为 int8 int16 int32 int64,其中 int8 即是 char,int16 即是 short,int32 即是 int,int64 即是 long long,当然还有对应的一系列无符号型的。(这里面还有一个有趣的知识点,int 又分成了 fast 和 least 两个部分,而理由是?据说是和 CPU 相关)
在其中的宏定义中,特意又区分了 64 位和非 64 位的区别,以 SIZE_MAX 为例,在 64 位中,它的值为 UINT64_MAX,而非 64 位则是 UINT32_MAX。&cfloat&:它的内容和 cstdint 是相似的,围绕着浮点变量的相关定义代码,包括有 float double 和 long double,这一块遗憾的是没有找到 Mac 下的代码。&cstdbool&:它提供 bool 类型,并且设置好 true 和 false。&cstring&:它是关于字符串处理的头文件,除了那些字符串处理,它还包含有 memcpy 此系列函数。&ctime&:时间函数,略。&cuchar&:为了支持 UTF-16 和 UTF-32,定了 char16_t 和 char32_t 两个类型。&cstdio&:input & output,主要是通过流来管理和使用,具体的用法可能已经都非常熟悉了。&cstdlib&:杂类,诸如 string to int,随机函数,malloc 等……&cwchar&:wide 版的 stdio + stdlib + string + time :)&cwctype&:wide 版的 type&climit&:它补充了一些关于整型和浮点型参数的相关极限(边缘)的宏定义。&cmath&:数学库,主要包含 sin 这些简单的数学计算函数,还有一些浮点数的计算可以留意一下。&ctgmath&:&csignal&:这个头文件中的定义了一些信号,而这些信号大多由内核发出。头文件提供 signal 函数,允许程序员对内核发送的信息进行处理。另外,头文件还提供 raise 函数,我们可以通过它主动发出信号。&cstdarg&:这个头文件则是用来处理-- 『...』:),它定义了 va_list 类型,并提供四个宏,va_start va_rag va_end 和 va_copy,我们常用前三个来遍历整个 va_list :)&clocal&:它主要包含了 C 语言中和地域语言相关的内容,比如说,不同的输入输出符号(瞎掰)。&csetjmp&:longjmp 和 setjmp,前者是函数,后者是一个宏。程序员可以通过 SETJMP 保存当前的程序堆栈,在另外的时刻通过 longjmp 跳回至刚才保持的地方。看网上的资料,常用于异常处理,但依然未知道其用法。
cocos2d-x 的版本为 2-3-X (在 3 版本中,实现了类似 JUMPTOXX 的接口,使用起来,方便很多)首先看一段 lua 脚本代码,其中应用了 Scorll View/*************************************************************/
// 以下代码段,基本为 init 相关
// 注意,在 C++ 代码中,scroll view 并没有 GetContainerSize() 这个接口,后面会解释到此处得到 ContainerSize,实际就是 ScrollView 的 ContentSize
local nContainerSizeX, nContainerSizeY = ScrollView.GetContainerSize();
local nViewSizeX, nViewSizeY = ScrollView.GetViewSize();
// Scroll View 中有一个 Node,其名为 ContentNode,这个是我们定的,其流程类似于注释中的这段代码
CCNode nodeContent = CCNode:Create("ContentName", ...); // 创建一个名为 Content Name 的 node
ScrollView.AddChild(nodeContent); // 就这样加一个 node 给Scroll View
// **** Notice ****
// ContentNode 的主要作用有,提供一个挂载 node 的地方,有助于统一完成位置的移动
// 它并不是必须,但它有助于理解概念
local tbContentNode = ScrollView.GetChildByName("ContentNode");
// X 设为 0 ,** 是假设需求是只沿 Y 轴滚动 **
local nContentPosX, nContentPosY = 0, nContainerSizeY;
// 将内容节点放在左上角,这是方便实现我们从顶向底填充内容,默认的情况,tbContent 是在左下角
// 这个知识点呼应了 Table View 中的填充方向设置
tbContent.SetPos(nContentPosX, nContentPosY);
/*************************************************************/
// 以下代码段,是给 ScrollView 增加 child 的操作,中间记录了 content 的大小,用以实现某些需求:保持顶部出现等
tbContent.AddChild(tbChild);8
// 将新加的 child 放在 Content 的最底端,这样每个 child 就能连续出现
// ** 这里也假设了,我们从上往下的顺序往 scroll view 中添加内容 **
tbChild.SetPos(nContentWidth, nContentHight);
nContentWidth, nContentHight = 0, nContentHight + child.nContentHight
// 此处,将记录下来的 Content 的大小附给 Scroll View,后面会讲到此处修改的其实是 Container Size,它保证 container 大小时刻和 content 一致
// ** 这里假设了从上往下的填充顺序 ** ,所以修改 Height
ScrollView.SetContentSize(nContainerSizeX, nContentHight);
/*************************************************************/
// 以下代码段,实现了 Scroll View 一些常见的显示方式的需求,比如顶部显示,底部显示
// ** 这里假设了从上往下的填充顺序
// Container 和 ViewSize 的底部重合
ScrollView.SetContentOffset(0, 0)
// 用 ViewSize 减去 ContainerSize,那么呈现在 ViewSize 中的内容将从 Container 的顶部开始
ScrollView.SetContentOffset(0, nViewSizeY - nContainerSizeY);
// 用 ContentHight 减去 ContainerSize,那么 ViewSize 和 Content 的底部重合
ScrollView.SetContentOffset(0, nContentHight - nContainerSizeY);
那么,在 Scroll View 使用中会遇上的问题,多半来自 container 和 content 大小不一致的原因,当 container 大小大于 content 时,就会出现空白区域,而当 content 大于 container 时,则出现被切割。至此,可以清晰写出三个概念的定义:View,即是这个 Scroll View 可见区域的大小;Container,即是 Scroll View 中可拖动区域的大小;而 Content 则是我们脑海中的概念,是需求要显示的 item 的一个集合,它可以直接等于 Container,也可以像上述代码中那样,实体化为一个 node。特别要注意的是,不要把这个 Content 和 ScrollView 在 Cocos2d-x 中的 content 概念混淆(其实,应该是我们对 Content 取个更好的名字吧:)最后,在来看看 Scroll View 关于 View 和 Container 的实现。//这里,就不贴相关代码了,自己写伪代码
CCScrollView:public CCLayer
CCNode* m_pC
void CCScrollView::SetViewSize(CCSize size)
// 可以看到, view size 其实就是本身自己这个 layer 的大小
CCLayer::setContentSize(size);
void CCScrollView::setContentSize(CCSize size)
// 由此可见,Scroll View 重写 content size 的目的,就在于转换概念,完成『layer 对另一个 layer 的切割』这个实现原理的封装
// 也猜测,一个 layer 就是一个镜头,只有镜头才能响应消息,大概这就是 cocos2d-x 的一个原理
m_pContainer-&setContentSize(size);
void CCScorllView::addChild(CCNode* child, ...)
// 所有的 add child ,都是加再 container 上
if (m_pContainer != child) {
m_pContainer-&addChild(child, ...)
CCLayer::addChild(child)
好了,关于 ScrollView 中其他的内容,如显示上的切割,就下次再整理。
「模块」和「组件」的概念来自于实际编码过程中产生的想法。最先产生的是模块,它存在与这样一个环境之中:程序存在一个主进程,这个主进程负责程序在运行期的组建工作,它通过加/卸载不同的模块和组件,让程序呈现出不同的自身状态,以及提供不同的服务,或者同种服务中一些细节的差别(像不像操作系统?完全可以借鉴其思想)模块的特点,或者它与组件的不同之处,在于它是独立与主进程,可自己运行的一个服务进程,它需要提供给主进程一个加载和挂载的接口。在代码实现上,我将它们合并实现为 Module 接口,通过 IsSelfRun() 的返回值来判断对象是『模块』还是『组件』,为 true 即是模块。当它是组件时,则把 Breath() 加入到主进程的 Breath()(假设……不对,是主进程必然实现了Breath)中。type Module interface{
IsSelfRun() bool
从代码实现上来说,Load() 它实际上调用了 Init() 和 Run()(如果是模块的话),Unload() 实际调用了 Stop()。所以,也许并不需要这么多接口,但我是这样以为的,从程序员的角度来说,Init Run Stop 这些关键字浅显易懂,加上也还行。而 Load Unload 是从接口使用者的角度出发的,所以是必须存在的。这样的设计或许缺点就是冗余,并且对某些人会产生困扰,该不该还是看实际应用吧。还需要克服的一个问题是,单个模块和组件之间相互调用和通信的手段和实现。可以使用的方法有,系统层提供的共享内存,通信管道等;利用主进程,实现类似的消息通讯,共享内存的机制。这些暂时没有接口的设计和实现。
在网络编程中,存在服务端和客户端的设计是非常之多的(除此之外,还可以点对点,以及过滤器。后者类似与单向的中间层)。以下是我关于服务端和客户端的基本接口设计以及实现,使用的是 TCP 协议,还进行了序列化处理——GoLang 中似乎是必须进行序列化处理。在设计过程中,我还在想对于一个进程而言,它们是可以作为『模块』处理的,换句话可以自己跑自己的。相对『模块』这个概念,还有一个『挂件』——它就必须依赖于主进程。这两者我也会同样设计其接口。Server对一个 TCP Server,必须存储的是它自己的 IP 和 Port,在 GoLang 中 net.TCPAddr 包含了两者,当然,还有其他的必要数据,如端口的监听对象 net.TCPListen;以及不必要的数据,如对每一个存在的连接。具体代码见下type Server interface {
InitServer (string)
RunServer()
type EncodeServer struct {
ServerListenInfo
// 保存了我们常见的 IP 和 Port 格式:10.20.156.123:8080
ServerTCPAddr
*net.TCPAddr
ServerListen
*net.TCPListener
// 完成对端口的绑定和监听逻辑
func (es *EncodeServer) InitServer(str string)
// 处理客户端发上来连接服务端的请求
func (es* EncodeServer) RunServer()
如你所见,此处只是完成部分,主要是对服务器的初始化和处理客户端的连接请求。对于一个完成的服务器而言,还缺少处理客户端具体请求的逻辑部分——这部分逻辑将会主要围绕 net.Conn 这个结构体,并且对于客户端和服务端而言,它都扮演的一样的角色,所以我的想法是尽可能的让服务端和客户端使用同样的代码——至少,暂不在服务端表现吧。ClientTCP Client 会记住连接的 Server 的 IP 和 Port,另外,此处我将 net.Conn 直接放在此中了,需要想个办法去实现上面提到的那个想法,也就是客户端和服务端使用同一段代码。type Client interface {
InitClient(string)
ConnectServer()
type EncodeClient struct {
ClientTCPInfo
ClientConn
func (ec *EncodeClient) InitClient(server string)
func (ec *EncodeClient) ConnectServer()
虽然在结构体中包含了 net.Conn ,但在接口设计中却没有出现 Write 和 Read 对应的接口。此处尚待补足。
首先,掌握一下 Hash 一致性的背景知识,以下节选自『wiki百科』以及『百度百科』等网站。设计目标是为了解决因特网中的 Hot Spot 问题。个人理解的Hot Spot 问题是指,一个服务进程由于内容等原因,导致其负载远超其他服务进程。同样可以解决这个问题的算法还有一个 CARP ,这个算法基本思路是让多台机器共享一个IP地址,多台服务器提供的内容相同,这对Web服务也还是够用的。但与 CARP 比起来,Hash 一致性能够提供更多的便利之处。比如说热拔插。总的来说,Hash 一致性能满足/呈现出以下下条件/特性:分散性,这个是必须,它的工作环境是多个服务进程中,内容有可能会同时出现在不同的进程中,分散性即是描述这种情况出现的概率;平衡性,Hash的结果能够均匀分布到所有的空间/服务进程中;单调性,它不仅强调 Hash 结果的唯一性,而且要求满足有新的进程进入,或者有进程退出,并不影响 Hash 结果,它是 Hash 一致性的关键所在;平衡,它则是要求退出或进入的服务,能够顺利的迁移到还在运行的其他进程上,保证整个服务群能够提供完整的服务;负载,它和分散性有类似,它是指一个服务进程,被不同的用户识别为不同的内容,这也是应该避免/减低的一个参数。以下是用GoLang实现的一个满足 Hash 一致性中的平衡性和单调性的算法,而其他的则在具体的服务应用中体现。它的核心数据结构为 Circle,核心算法为 Circle 中的左右查找。代码中不包含 Hash 算法,推荐使用CRC32。HashNode每个节点知道自己左右两个节点,原本的设计会一个指向 Circle 的指针。这两个点都是可以变动的,比如说你可以知道前X个,后Y个,或者你有全局的节点数据等等。这种设计和具体的应用靠的比较紧凑,但实现的功能大致是:1、Init;2、找到附近的节点。type Node interface {
InitNode (nodeID int)
GetFrontNode () *Node
GetBehindNode () *Node
type HashNode struct {
BehindNode
func (n *HashNode) InitNode(nodeID int)
func (n *HashNode) GetFrontNode() *Node
func (n *HashNode) GetBehindNode() *Node
NodeCircleCircle 数据结构,代码实现如下,其接口 AddNode 是假设传入的 NodeID 自身保证了单调性,这个 NodeID 也就是用户生成的 Hash 值。type Circle interface {
InitCircle()
AddNode(nodeID int)
ReduceNode(nodeID int)
GetFrontNode(nodeID int) *Node
GetBehindNode(nodeID int) *Node
type NodeCircle struct {
func (c* NodeCircle) InitCircle()
func (c* NodeCircle) AddNode(nodeID int)
func (c* NodeCircle) ReduceNode(nodeID int)
func (c* NodeCircle) GetFrontNode(nodeID int) *Node
func (c* NodeCircle) GetBehindNode(nodeID int) *Node
Hash 数的生成这个就直接用 GoLang 标准库中的 Hash 就可以了:)
基础知识网络存在了很多年了,也有很多种。虽然已经定义了 OSI 模型,但最流行的网络协议栈是 TCP/IP 协议栈,IP 提供了第三层的 OSI 网络协议栈,TCP 和 UDP 提供了第四层。他们与 OSI 模型的大致关系如下:application :
OSI 5 - 7TCP/UDP
OSI 3h/w interface :
OSI 1- 2IP层 提供的是无连接的不可靠传输系统,TCP 构建在IP层之上,它是一个面向链接的协议,这个连接依赖于主机的端口号,但它依然是不可靠的,存在丢包的可能,UDP 则简单些,它依然是无连接不可靠的,但它也依赖端口号。对IP层来说,它自己的包头支持数据验证,在包头包含了源地址和目标地址,另外在由路由传导到因特网的时候,它还复杂将大包分解为小包,并在另一头(由因特网到路由)组合成大包。比起TCP和UDP而言,它的一个特点是不支持端口。IPIP 层支持的源地址和目标地址包括两种——IPv4 IPv6。这种统称为 IP 地址。IPv4 地址是一个32位无符号整数,拥有2^32个地址,它通常使用.符号分割成4个十进制数,如:127.0.0.1。其中又分为了两个部分,网段地址和网内地址。按照国际准则,网段地址只取前面一个的,称之为A类地址,而C类地址,当然是取了前三个,最后一个是网内地址,这个网络则只能容纳 2^8 台设备。为了标记出那些是网段地址,那些是网内地址,我们使用了『网络掩码』,它可以是 255.255.252.0,事实上,网段地址是可以指定到多少位的,而不是简单的根据「.」来取几位完成的。IPv6 地址的基本内容是类似的,它是由于 IPv4 没法满足需求而出现的。在 Golang 中 net 包包含了这里面的一些相关的类型和函数用于编程。// 自动支持IPv4 IPv6
type IP []byte
// IP 类型,也就是32位无符号整数
func(i * IP) String()
// 它将返回 i 的字符串形式
func ParseIP(string) IP // 依据常见的字符串写法 127.0.0.1 返回一个 IP 类型的值
type IPAddr {
// 这样写大丈夫?
// 从功能上来说,它和 ParseIP 一样,net 是'ip','ip4','ip6'中的一个,而 addr 则可以是一个因特尔网址
func ResolveIPAddr(net, addr string) (*IPAddr, os.Error)
端口通过 IP 定位主机,但主机是可以提供多个服务。对于 TCP UDP 协议,它们是通过端口号来区分,一个服务都至少有一个端口。这里也存在一些标准端口,telnet 服务使用23的 TCP 协议,DNS 使用53的 TCP 或 UDP 服务。对于 Linux 而言,这些通常都写在 /etc/services 文件中,而 Golang 则提供 func LookupPort() 函数来做相同的事情。这些小于128的端口号的服务,由IETF标准化,统称位IETF服务。TCP它就不做过多的背景解释,见代码块吧。// TCPAddr 这一套基本和 IP 类似
type TCPAddr struct {
func ResolveTCPAddr(net, addr string) (*TCPAddr, os.Error)
// 找到主机+端口后,就是该建立和服务的连接了
type TCPConn struct {
// 它是全双工通讯的 Go 类型
func (c *TCPConn) Write(b []byte) (n int, err os.Error)
func (c *TCPConn) Read(b []byte) (n int, err os.Error)
// 在客户端建立 TCPConn 的函数
// net 同上,laddr 是本地地址,通常设置为 nil ,raddr 既为连接的服务器地址
func DialTCP(net string, laddr, raddr *TCPAddr) (c *TCPConn, err os.Error)
// 在服务端上通过 TCPListener 处理客户端的请求
func ListenTCP(net string, laddr *TCPAddr) (l *TCPListener, err os.Error)
func (l *TCPListener) Accept() (c Conn, err os.Error)
// 通过 TCPListener 得到了 Conn,它和 TCPConn 都为ReadWriter,全双工
// TCP 的其他常见的控制函数
func (c *TCPConn) SetTimeOut(nsec int64) os.Error
// 设置超时 func (c *TCPConn) SetKeepAlive(keepalive bool) os.Error
// 保持存活状态(这个应用的场景?)
上面函数基本都是网络函数,所以都必须,注意是 必须 进行错误处理。UDPUDP 和 TCP 函数格式差距并不大,主要调用的函数如下func ResolveUDPAddr(net, addr string)(*UDPAddr, os.Error)
func DialUDP(net string, laddr, raddr *UDPAddr) (c *UDPConn, err os.Error)
func ListenUDP(net string, laddr *UDPAddr)(c * UDPListener, err os.Error)
func (c *UDPConn) ReadFromUDP(b []byte)(n int, addr *UDPAddr, err os.Error)
func (c *UDPConn) WriteFromUDP(b []byte, addr *UPDAddr) (n int, err os.Error)
从这些函数基本能看出来,Conn 是一个接口,TCPConn 和 UDPConn 实现了这个接口,但是UDPConn 不使用 Write Read 接口来完成数据传输。实际上,在 Net 包中提供了更为简洁的,纯粹用接口来实现 TCP 和 UDP 的函数和类型,见下。net 包中的接口Conn 接口和它相关的函数// 客户端
func Dial(net, laddr, raddr string)(c Conn, err os.Error)
func Listen(net, laddr string)(l Listener, err os.Error)
func (l Listener) Accept() (c Conn, err os.Error)
这里面非常有意思的是,其参数不再是指针了,这点和C++中的概念还是很接近的,一个接口相对于子类来说,像是一个指针其他 两点,一个是C中常见的 select,由于 goroutine 的原因,所以在 golang 中应该没有实现它(?)。二是 IP 协议也实现了。
来自于 jan.newmarch.name/go/ 其第一节中讲叙了很多网络相关的知识,他自己是这样简介的——『this chapter covers the major architectural features of distributed systems 本章涵盖了分布式系统架构的主要特性』第一段非常有意思,总得来说本文是很泛,而且应用性很广,但作者却指出它依然是有狭隘的地方——『如果你不知道你要构建什么,就无法构建一个系统,而如果你不知道哦啊它会在何种环境下工作,同样也不行。就像GUI程序不同于批量程序,游戏程序不同于商业程序一样,分布式程序也不同于独立的程序。它们都有各自的方法,常见的模式,经常出现的问题以及常用的解决办法。本章涵盖了分布式系统的上层构架,从多种角度考虑这种系统以及其依赖。』协议层什么是协议层?(对分布式系统来说)定义了与其他相关部分进行通讯方式的部分,都可称之为协议层。
协议层的由来?在分布式系统中,我们需要让多台机器协作完成一项任务。而多台机器之间的连接方式可能会很复杂,那么为了解决这个复杂的问题,我们将其分解成更小更简单的部分,也就是各种协议层。比较出名的协议层的划分有ISO OSI协议——既书中常将的7层结构;TCP/IP协议。网络首先,需要了解和区分的是LAN 和 WAN,前者是局域网,后者是广域网。前者通常是将电脑和电脑连在一起就完了,就可以定义为局域网;而广域网是物理意义上更大的一片网络。然后是互联网和内联网。互联网可以是LAN,也可以是WAN,而内联网则是某个组织的所有网络加上互联网。所以,内联网通常处于单一的管控下,它有统一的策略,而互联网不会有单一的主控,系统上的部分电脑甚至不会相互兼容,比如说,世上最大的互联网——因特网。网关/Gateways网关是一个统称,只要能连接一个或者多个网络的,那么都可以称为网关。我们介绍三种类型的网关:中继器(在物理层上,将信息从一个子网复制到另一个子网);桥接(数据层上进行复制帧);路由器(网络层上,它不仅进行复制,而且还决定了信息传输的路线)数据包封装上面讲到协议层,那么每一个层自身的信息被放置在数据表的前头,叫做包信息,每一层都会增加/解析自己那一层的数据:当包向下传时,会在头部增加头信息;当包向上传时,此层的头信息则会被移除。连接模型这个是一个概念上的存在,有两个模型:面向连接的模型;无连接模型。在面向连接的模型中,通常存在一个session,当连接断开的时候,session也就结束了,常见的是TCP无连接的模型中,消息的发送彼此独立,比如IP协议,HTTP协议。由于这是概念上的,所以在技术上,我们有时候会用一种去实现另外一种,比如说,通过IP协议来建立TCP连接;通过TCP来建立HTTP连接。不过,有一点两者相同,就是无法保证消息是按顺序到达。通讯模型这种模型并不是概念上的,而是按照(代码)使用来区分,它有两种:消息传递;远程过程调用消息传递,它通过建立好的连接发送一些数据,有时候,我们需要等待另一段接受数据后,并接受它返回消息。最出名的消息传递是Unix的管道。它也是一些非过程化语言的基础——这个倒不太明了了。远程过程调用,它也是常说RPC机制。它常见于过程语言中,实际上在任何系统中,当我们由系统的一部分到另一个部分,需要管理信息传输以及对流程控制。那么在RPC中,通常是把信息放在栈中,随着控制流程一同传入另一个部分,有时候,我们同样需要等待其返回结果——通常也是一个栈。DLL的使用就是一个典型的RPC。分布式计算模型此类模型则是从物理特性上来分类。从最上层来说,分布式系统的组件可能不是等价的,最常见的不对等关系就是:客户端-服务端关系,通常是客户端向客户端发送请求,然后服务端响应。当关系中的两者是对等的,均可以发起和响应消息,那么就是点对点系统。第三种模型是过滤器,组件A从组件B中获取数据,但是传给组件C。组件分层上面讲到组件,组件的概念太泛,很多地方都有使用,下面要讲到组件通常是从软件工程角度来说:)分解一些应用的简单方法是把它们分为三个层:表现组件;应用逻辑;数据访问。表现组件负责与用户进行交互——显示数据和采集输入。应用逻辑负责解释用户的响应,根据应用业务规则,准备查询并管理来自其他组件的响应。数据访问负责存储并检索数据,像是数据库。比上述还有更细致的分类,Gartner分类包含了五种模型:分布式数据:
由上到下,可以如下分层:表现组件;应用逻辑;本地数据;远端数据——GoogleMap远程数据:
分层:表现组件;应用逻辑;远端数据——网络文件分布式事务:
分层:表现组件;本地应用逻辑;远程应用逻辑;远端数据——php jave等web应用远程显示:
分层:表现组件;远程应用逻辑;远程数据——远程终端分布式显示:
分层:表现组件:远程表现组件;远程应用逻辑;远程数据——Linux中的X总的来说,Gartner是将三层结构中再加入一层——通常是网络。那么也是还可以添加两层,三层。中间件模型从网络和系统组件的角度来说,中间件最重要的一点是将网络和计算机系统这类底层的东西封装起来;从用户代码的角度来看,它担当着数据交换的角色,用户代码只需关心自身逻辑。总的来说,它即是各类分布式系统中的组件间的胶水层,所以如下的系统都能称为中间件:TCP/IP;RPC等等更为细致的分类activity: 键盘鼠标输入;屏幕处理;图画/声音/视频控制;命令行/目录/对话框解释;帮助处理;数据输入验证;应用逻辑;错误回复;事务构建;事务验证;数据库访问;数据库管理和存储从「键盘鼠标输入」到「帮助处理」都可以视之为「交互处理」;从「命令行解释」到「事务验证」都可以视之为「应用逻辑」;从「事务构建」到「数据库管理和存储」可视之为「数据库处理」再从常见的客户端-服务端结构来看,它亦可以如下分类:完成了以上全部层的是「host app」,也就是大部分的程序;完成到「屏幕显示」的,可称之为智能终端;完成到「命令行解释」的,称之为GUI终端;完成到「输入验证」的都称之为事务处理程序;完成到「事务构建」的,称之为「网络数据库」;完成了「数据库组织和存储的」,是文件共享程序。故障点尽管网络的情况复杂,但故障点可以简化总为以下几种:客户端/服务端崩溃;客户端硬件不兼容;客户端/服务端网卡坏掉;网络连接超时;客户端连入网络时冲突;路由器挂了;传输数据发生错误;客户端和服务端存在版本兼容问题;服务端数据库坏掉需要衡量的因素一个分布式系统,由于其复杂性,我们需要多方面进行考量,这些方面中间同样会存在某种此消彼长的关系,总得来说,如下:可靠性;性能;响应性;可扩展性;可容性;安全性。透明度分布式之所以复杂是因为它的环境复杂,但在复杂环境中,分布式却让我们能得到不少好处:访问透明度;位置透明度;迁移透明度;赋值透明度;并发透明度;扩展透明度;性能透明度;故障透明度。八个误区sun公司总结了分布式计算中常存在的八个误区:网络是可靠的;风险为零;带宽无限;网络是安全的;拓扑结果不会改变;没有管理员;传输成本为零;网络是均等。因为此,在Java的RMI中为每一个潜在的RPC调用都跑出了一个RemoteException 的异常。
Hashgo 语言中有一个Hash Packget,它提供 Hash Hash32 Hash64 三种Interface三者 Interface的接口如下:type Hash interface {
// oh…它有了io.Write的所有的接口
// 那么可以这样用 hash.Write(btyeSliece)
// io.Copy(hash, file)
// 将b添加入其中,得到 前面的输入+b 新产生的哈希的结果
Sum(b []byte) []byte
// 重置hash,也就是转为 零输入
// Sum 返回slice的长度
Size() int
// 返回hash底层表的块大小,Write能够接受任意大小的输入,但如果是这个块的整数倍,效率会得到提升
BlockSize() int
type Hash32 interface {
Sum32() uint32
type Hash64 interface {
Sum64() uint64
CRC32,CRC是一个Hash算法,32/64位循环冗余校验(常用于检查数据的完整性);类似的还有MD5,信息摘要算法第五版,SHA1,安全哈希算法。简单看这个就ok// 多项式参数,hash算法最重要的一个指标在于碰撞(另一个是时间),IEEE据说是这里面最差的一个,但它亦是使用最多的
IEEE = Oxxxxxx
Castagnoli =
var IEEETable = makeTable(IEEE)
// 用上述参数,make一个Table,用在算法之中,注意到makeTable是一个不导出函数
type Table [256]uint32
// 这里却是一个导出的函数,功效应该是一样的
func MakeTable(poly uint32) *Table
// 以下是两个快捷函数,我们希望的是,给一坨数据,然后得到结果——它们正能做到
func Checksum(data[]byte, tab *Table) uint32
func ChecksumIEEE(data []byte)
// 直接使用IEEETable
// 生成一个hash实例
func New(tab *Table) hash.Hash32
func NewIEEE() hash.Hash32
// 类似于上面Hash中的 Sum()
func Update(crc uint32, tab *Table, p []byte) uint32
具体示例,介绍两种基本用法,省去自己建Table,因为我对CRC32的原理也不太懂str := "hello ketty"
key := []byte(str)
v := crc32.ChecksumIEEE([]byte(key))
// 用法二,这种用法最接近普通hash的用法,当然,用法三几乎就是普通的了:)
h := crc32.NewIEEE()
h.Write(key)
v2 := h.Sum32()
先从变量、基础类型、关键字和控制流开始变量
变量的类型写在名称之后
变量的声明和定义是两个过程,但同样有写做一起的写法 :=,这种就是叫做短声明
const和iota,iota从零开始
b = // implicitly auto
go中的string类型较为奇葩,当你给一个字符串变量赋值,那么它就不能改变。如下,将是非法
var s string := "hello"
s[0] = 'c'
这种情况类似于C++的 const 变量,如果你需要完成上述功能,可用数组解决这个问题
s:="hello"
c:= []rune(s)
//rune 是一个关键字,同样是字符串的意思,但和string用法有差别。实际上是int32的别名,常见于遍历字符串(文档中写明支持utf8)
c[0] = 'c'
s2 := string( c ) // 由这可见,[]rune 和 string 相互转换的过程
fmt.Printf("%s\n", s2)
关键字关键字的部分和C类似,同样略过。控制结构控制结构部分中,go拥有for,switch,if还有select。
if x & 0 {
// 此中格式是固定的
//if 中还有一个特点,它接受初始化语句,就想for那样,举例:
if err := Chmod(0664); err != nil {
fmt.Printf(err)
return err
for 循环有三种形式,简单写做下
for condition {
break 和 continuebreak有了一个很棒的新用法,示例如下:
I: for i := 0; i & 10; i++ {
for j := 0; j & 10; j++{
if j & 5 {
// 这种情况下能够从双层中跳出来,不过需要的是标签好I才行
switch首先是,fallthrough 关键字,换句话就是switch 不会自动向下尝试,例子如下:
switch i {
//空的case
// 当i == 0时,它不会调用到f(), 但在 case 0: 后面添加 fallthrough 那么就可以
switch 如果没有填条件,那么它就默认是true,这时可以当作一种优雅的if else if else,例子如下:
case c == 1:
case c == 2:
一些内置函数
len/cap 它通常使用在array slices 和 map 上面,这个概念再stl中同样有,就是长度什么的
new 用于内存分配,delete,是用于map中删除一个实例
同delete一样,有很多关键字将会只是作用再array slices 和 map上:make,copy,append
close 用于关闭channel
arrayarray由 [n] 定义:var arr [10]int
arr[0] = 42
fmt.Printf("The first element is %d\n", are[0])
还可以通过复合声明arr := [3]int{1,2,3}
//或者是自动录入数组大小
arr := […]int{1,2,3}
sliceslice 它与 array 接近,但可以自动添加长度,据说slice是一个指向array的指针,它也是一个引用类型,也就是当赋值某个slice到另一个变量,两个引用指向的是同一个array。slice通过make创建,如下:sl := make([]int, 10)
因为它只是一个指针,所以必须和array成对出现,所以也可以如下写:var array[m] int
slice := array[0:n]
len(slice) == n
cap(slice) == m
len(array)==cap(array)==m
上面出现了slice 的新的复合声明,slice := a[i:j] //这里面的a 不仅仅可以是数组,也可以是另一个slice
slice有两个扩展函数,append 和 copy
append 往slice的后面追加值,当其超过原有array的长度的时候,会新建array,slice指向也就变了
s0 := []int {0, 0}
s1 := append(s0, 2)
s2 := append(s1, 3, 5, 7)
s3 := append(s2, s0…)
copy 则是复制,从一个slice 到 目标dst,其可以是array,也可以是slice
var a=[...]int{0,1,2,3,4,5,6,7}
var s = make([]int, 6)
n1 := copy(s, a[0:])
n2 := copy(s, s[2:])
map举例:ourname := map[string]int{"zero": 0, "one": 1,}
// 声明后,使用还是要make,否则就运行错误
ourname := make(mao[string]int)
fmt.print("%d\n", ourname["zero"])
// 如此增加
ourname["two"] = 2
// 如此检查是否存在,在go中更习惯使用ok这种用法
var value int
var ok bool
value, ok := ourname["three"]
// 移除元素
delete(ourname, "two")
// 遍历map : 点击这里 http://blog.golang.org/go-maps-in-action ,但map用来做遍历,应该也有问题?
函数多返回值,甚至返回值也可以有命名,这很方便defer延迟代码func ReadWrite() bool {
file.open("file")
defer file.close() // 将代码 file.close() 加入到defer列表中
if failureX {
return false // 返回,defer列表中的代码将会被自动执行
defer列表中的代码将会按照『后进先出』的原则进行执行,defer还有一个特性就是它可以修改返回值,这里就不做示例包package包是函数和数据的集合,用 package 关键字定义一个包。文件名不需要与包名一致,包名的约定是使用小写字符。Go 包可以由多个文件组成,但是使用相同的 package & name> 这一行。
包中的公有函数的名字以大写字母开头,而私有函数的名字则是以小写字母开头。
导入包的写法有两种
import fmt "fmt"
import even "even"
//前一种更为灵活,可以重新命名,比如说 import newname "fmt"
//那么就将如下使用:
newname.print()
测试包,在 Go 中为包编写单元测试应当是一种习惯。编写测试需要包含 testing 包和程序 go test。两者都有良好的文档。go test 程序调用了所有的测试函数,测试函数拥有这样的格式
func TestXxx(t *testing.T)
在函数中,可以调用几个标记函数
func (t *T) Fail()
// 失败时调用,直接退出
func (t *T) FailNow()
// 失败时调用,但继续执行
func (t *T) Log()
// 类似于print
func (t *T) Fatal()
// Log + FailNow
一段完整的包测试
package even
import "testing"
func TestEven(t *testing.T){
if !Even(2) {
t.Log("2 should be even!")
new & make 的区别make仅适用在map, slice 和 channel上,并且返回不是指针。另外,new只负责了分配内存,而make具有初始化的功效。自定义类型
type可以实现自定义类型
type NameAge struct {
name string
// 小写只能在包内使用,不导出
func main() {
a := new(NameAge)
a.name = "Peter"
a.age = 42
fmt.Printf("%v\n", a)
// 将会输出 {Peter, 42} 这真让人泪流满面
下一段代码就完成了为NameAge这个结构体提供dosomething方法(这里,方法和函数不是一个概念,方法为有接受者的函数)的任务。
func (n1 *NameAge) doSomething(n2 int)
自定义类型中,可以存在匿名字段,往往是通过它来完成『继承』这个概念,代码如下
type Mutex struct { /* Mutex 字段 */ }
func (m *Mutex) Lock() { /* Lock 实现 */ }
func (m *Mutex) Unlock() { /* Unlock 实现 */ }
type PrintableMutex struct {Mutex } //这样 PrintableMutex 也有了Lock 和 Unlocl两个方法
接口下面用一段代码来展示interfacetype S struct {i int}
func (p *S) Get() int {return p.i}
func (p *S) Put(v int) {p.i = v}
// 接着定义接口
type I interface {
// 下面是一个使用接口的示例
func f(p I){
fmt.Println(p.Get())
var s S; f(&s)
// 此时,直接使用了s这个S对象的实例
// 这种非入侵式的接口当然很酷,但也有些情况需要处理
type R struct {i int}
func (p *R) Get() int {return p.i}
func (p *R) Put(v int) {p.i = v}
// 当有两个类型都实现了接口I,但在func f2 中需要区分出类型,需要如下
func f2(p I) {
switch t := p.(type) {
// type只能在此处使用
//指针和引用都可以使用 . 操作符
//type只能在switch中使用,还有另外一个办法——『comma, ok』
if t, ok := p.(I); ok {
// 实现了接口I 的t
并发——并发是关于程序设计,并行是关于性能在Go中使用channel和goroutine开发并行程序,goroutine是Go并发能力的核心要素。
goroutine 是一个普通的函数调用,只是需要使用关键字go作为开头。它有简单的模型:它与其他goroutine并行执行,有着相同的地址空间的函数。它是轻量的,仅比分配栈空间多一点消耗。用一段代码来展示goroutine
func ready(w string, sec int) {
time.sleep(time.Duration(sec) * time.Second)
fmt.Println(w, "is ready!")
func main(){
go ready("coffee", 1)
go ready("tea", 2)
fmt.Println("waiting for time")
time.sleep(5 * time.Second)
// 5秒后才让程序结束,否则程序结束,goroutine也都kill掉了
在上述代码中使用了time.sleep()来完成等待,实际上,我们可以使用channel来做到同样的事情,高端的是,这是阻塞等待,脱离了时间这个参数
var c chan int
// 声明全局通道,并且为接受int类型,全局是为了让 goroutine 能够访问它
func ready(w string, sec int){
time.sleep(time.Duration(sec) * time.Second)
fmt.Println(w, "is ready!")
// 向通道发送整数1
func main(){
c = make(chan int)
// channel 必须使用 make 来初始化
go ready("coffee", 1)
go ready("tea", 2)
fmt.Println("waiting for time")
// 从通道中接受整数,并保存到i中
// 因为有两个 goroutine,所以接收两次
channel从概念模型上来说,很接近网络编程,而网络编程中有个好用的select函数,channel可以实现类似的功能,代码如下:
case &- c:
if i & 1 {
// 此段L for循环可替代上面的两次 &-c
使用了goroutine并不等于就使用了并行,如果不设置runtime.GOMAXPROCE(n),那么同一个时刻就只有一个goroutine在运行。
ch := make(chan bool) 这是一个特别的channel,叫做无缓冲channel,后面的type是可以随意的。关于这个channel的读写操作都会被阻塞住。那么, ch := make(chan bool, 5) 这就是一个缓冲channel,当写入第六个元素的时候(ch &- 6),代码会被阻塞住,直到有人读取数据(value := &-ch)
x, ok = &- ch 这用来检查channel是否被关闭
通讯所谓通讯是指Go与外部通讯的模块,比如说:文件目录的读写,网络通讯,运行其他程序。
文件读取,其核心为两个接口io.Reader 以及 io.Writer 看两个代码块,前者为无缓冲模式,后者有缓冲
import "os"
func main(){
buf := make([]byte, 1024)
f, _ := os.Open("/etc/passwd")
defer f.Close()
n, _ := f.Read(buf)
if n == 0 { break }
// 读到文件尾
os.Stdout.Write(buf[:n])
import ("os"; "bufio")
func main(){
buf := make([]byte, 1024)
f, _ := os.Open("/etc/passwd")
defer f.Close()
r := bufio.NewReader(f)
// NewReader()需要一个io.Reader,而 os.file 实例 f 实现了接口Read(),所以它就是一个io.Reader
w := bufio.NewWriter(os.Stdout)
// NewWirter() 和 io.Writer,以及 os.Stdout 的关系同上
defer w.Flush()
n, _ := r.Read(buf)
if n == 0 { break }
w.write(buf[0:n])
运行其他程序主要是通过执行命令完成,在os/exec包中
import "exec"
cmd := mand("/bin/ls", "-l")
buf,err := cmd.Output()
// 此时buf中就包含了ls 的输出
命令行参数,其数据都保持在slice os.Args 中,go提供了flag包来帮助写出更简洁的命令行提示等,代码如下:
// 一个DNS查询工具
// 程序接受两个命令行参数,将会赋值给dnssec 和 port,它们都是指针类型
// flag.Type 将会定义命令行参数的名称,默认值,以及提示
dnssec := flag.Bool("dnssec", false, "Request DNSSEC records")
port := flag.String("port", "53", "Set the query port")
// 重定义一下 usage 这个函数
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage: %s [options] [name …]\n", os.Args[0])
flag.PrintDefaults()
flag.Parse()
// 解析参数,并且填充到上面的 dnssec port 中
if *dnssec {
// 参数正确
// do something
网络,毫无疑问这是最重要的。所有网络相关的类型和函数都在net包中,其中最重要的函数是Dial,它抽象了网络层和传输层,TCP,UDP,IPV4,IPV6都共用这一个接口,返回Conn接口类型
conn, e := Dial("tcp", "192.0.32.10:80")
conn, e := Dial("udp", "192.0.32.10:80")
conn, e := Dial("tcp", "[d0:200::10]:80")
// 如果e变量正确(我猜应该是为空),那么此时就可以使用conn
buf := make([]byte, 1024)
conn.Read(buf)
比起上述,http包是更高层级的,例子如下:
import ("io/ioutil"; "net/http"; "fmt")
func main() {
r, err := http.Get("/robots.txt")
if err == nil {
fmt.Printf("err: %s \n", err.string)
b, err := ioutil.ReadAll(r.Body)
// 这里和http包有具体关系了,觉得应该是会很有趣的
r.Body.Close()
if err == nil {
fmt.Printf("body: %s \n", string(b))
今晚本可以很晚才回来,但我还是不打算玩下去。我想过这样不合适,但我知道回来的我才是我。或许就是这样,理性在某一刻并不是我的选择,事情本身当然没这么严重,有时理性就是唯一的选择,但生活就是这样:Ppointers and references 指针与引用pointer 和 references 有些相似的地方,它们都能指向一个对象,并能调用其的值和函数,只是所用的操作符并不同。不过,reference更为神奇些,「sizeof」和「&」这两个操作符下,reference得到结果和object是一致的,而pointer不是。这看上去不错,但reference也有些不太妙的地方,你需要投入一些心智来注意它,它初始化的时候必须指向已存在的一个对象,而且它就一直指向那个对象了,并不会移到另一个对象上,pointer初始化可以为空,它也可以指向另一个对象。(上文中提及的心智概念,来自于本月初听许世伟讲解go语言的课程中,非常有启发,它象征着代表程序员在单纯的编写环境中需要耗费的脑力,此时从工程的角度来说,无脑操作是最佳,这点和游戏相似)pass/return-by-pointer and pass/return-by-reference,所谓pass-by就是传参,return-by是指返回值。实际上,还有一种pass/return-by-value。它是最糟糕的,因为如果不是内置object,by-value会调用C++隐藏好的拷贝构造函数。在pass-by中,pass-by-reference的确最佳,它传递了value的值,但只是占用一个指针的大小,而且它也不会像指针那样怪,需要添加一个「&」符号,只是它不能视作一个function signature,因为对编译器来说,没办法区分pass-by-value。return-by中,传递pointer和reference都需要注意这样一件事情,如果它传递的是一个函数内的值/local object,那么传回的将无法预料,因为已经在变量的生存期之外,如果它传递的一个heap object——在函数内new出的对象,那么就很有可能造成memory leak。在这些情况下,似乎return-by-value是个不错的选择。// pass-by-reference and local object
string& func1()
string s("hello")
int main()
string& s = func1(); // 这种用法本身也挺奇怪的
cout << s << //无法预期string s = func1();
cout << s << // 程序一定会挂
起头还是一些零碎的东西,也是个打基础的过程。function signature/函数签名 function prototype/函数原型 和函数重载
函数签名是函数原型的组成部分,它包括有函数的名字,传入的参数还有一些其他的杂项,但是不包括返回值。同名函数只有在其函数签名不同的前提下,才能实现重载。inline function
* 它有Macros 宏的优点,而没有缺点——不会膨胀,它是在编译器在编译过程中通过自己的生成算法保证的
* 在class definition (大部分情况下,就是写在.h)中的 member functions都会视之为inline
* 当然也可以写在函数名前
* 但是决定一个函数是否是inline的还是编译器
* 在STL中有大量使用include#define 的缺点以及替代方案:const inline
它只作用于代码文件,在编译进行之前,编译器将代码中的对应字符串替换成你指定的值,例如
#define PI 3.1415
但我们还有其他的办法同样能做到这样的事情,比如说const,如下
const double PI = 3.1415
它比define的好处在于,PI这个会进入符号表格,对于查错会好一些。
这里还有其他一个知识点,就是类中间的 const 变量得声明成static,以及初始化和定义的特殊性,可参考下代码class GamePlayer {
static const int NUM_TURNS = 5;
//声明常数,in-class initialisation 只对常数中的整数类别(如int,bool,chars)才成立
int score[NUM_TURNS];
//常数就是可以这样帅
const int GamePlayer::NUM_TURNS;
// 定义,必须的,对非整数类别来说,就要在这里初始化好#define还有一个问题,它即便是对变量加上括号,在处理++符号的时候还是会有问题,以下为例:#define max(a, b) ((a) > (b) ? (a) : (b))
int a = 5, b = 0;
max(++a, b); // 此处a会++两次
a = 0; b = 5;
max(++a, b);
// 此处a只会++一次此处具有歧义,用函数就不会有问题,特别是用inline函数,同样可以获得macros的效率。
听了三天的课,是时候梳理一下,另外还要做的是一套「可用于生产环境」的开发套件,这组套件包括有服务端,客户端,代码提交,开发环境,甚至是测试。这些都要时间耗,而且要一鼓作气拿下,需要元气,镇定吧。现在,且以小碎步推进。This 指针C与C++的一点重要不同就是,C++具有类(class),它像C中的struct那样包含了成员变量,但它同时还包含有函数,并且可以如下调用struct A {
void func()
cout << a <<
a.func();这种写法的好处是显而易见的,它从语法层面上实现逻辑和数据的关联,从而完成了C++语言面向对象这个概念的基本实现。但C++是怎么做到的?我们也知道C语言是无所不能的,所以它必然也可以用一些技巧来实现上面的用法。以下就是我简化版的侯捷老师的C语言的实现typedef int (*FPTR_area) (int, int, int, int);
typedef struct tagRect
}int area (Rect* this)
return (this.right - this.left) * (this.bottom - this.top);
int r1area = r1.area(&r1)所以我们可以注意到,想要实现C++类的语法,需要实现两点,第一,是struct要能够找到自己的fuction;第二,是struct的数据要能够被function使用。前者的这种关联是通过自身记录了一个函数指针,C++就是在编译的时候在全局中存在一个类中函数表,但这一块不记在object size中。后者是通过传递了一个this指针,同样的C++也是这么做的,它在编译调用fuction的语句时,在传参列表的前端偷偷塞入了一个this指针,这个指针还是当时通过对象生成的,当然,在编译函数的时候也是偷偷留了this指针的位置,但这些在我们使用C++时,都可以不了解。既然可以不了解,那你说个鸡巴——这么粗俗不好,但我没像明白,它的意义到底何在?为何候老师要以它开篇?也许就是为了介绍C++的这种面向对象编程的本质。
写在前面:前面一篇说重构分为三部分,『何为重构』——修改代码结构;『为何重构』—— 从设计上来说,应该是追求简洁,从生产环境来说,是让代码易读,易改,易找bug;『何时重构』——当感觉代码出现坏味道时,或者就称着审查代码的时候,甚至是阅读代码的时候。其中,有一句金句,编程就是一种添加中间层的技术不考虑重构
是的,有时候我们不考虑重构,包括但不限于:
项目即将完工时:)
重构的难点有:数据库、修改接口、安全性相关的代码块
代码无法正常运行
重构和设计
重构和设计应该是互补的关系。有了重构后,并且信任重构技术,你可以在一开始的时候不去纠结细节,不依靠『预先设计』才开始行动。将『重构』和『预先设计』两者结合起来之后,你还能获得更为『简化』的软件设计。不过,简化的优势不在于灵活,也不在于性能,在于理解。为什么要追求代码的灵活?这是因为程序设计中一个重要的思想是:将多变的部分封装起来,以应对需求的变化(还有如将概念封装起来)。这种灵活的好处包括有,快速的将单线程改为多线程等。在使用重构技术之前,必须在预先设计阶段就把灵活考虑进来,然而,灵活这种需求可预见性并不强,有时候会浪费;但对于重构,我们是确切知道需要灵活时,才去改动代码,或者说,我们为了完成某种必然的需求,而去改动代码,如此产生了代码的灵活性。所以,重构好过预先设计。重构和性能
作者在书中说到,『重构必然使得性能下降,但它有助于我们调整软件使之性能优化』。这很好理解,曲线救国。通常有三种办法来解决『性能』问题:第一,对每个部分/模块的耗时有着严格的时间要求,这种情况少见,而且往往对时间要求非常严格,诸如慢来的信息都是错误的;第二,持续关切,每加入一个功能都要对性能进行追踪检查,这是非常常见的办法,不过往往会导致整个系统的性能平庸;第三,则是先统计那些代码执行的时间和消耗的资源都占多,然后针对修改。对于上述的第二和第三种,软件在代码层上能保持良好的分解度和颗粒度,都是有利的。
Fixed Function Shader
这是最简单的一种Shader,它必然含有一个pass通道,而它于后两者的区别在于……应该说,是后两者比它多出的特性使得三者有区分。以下是一个完整的例子
Shader "Tutorial/Basic" {
Properties {
_Color ("Main Color", Color) = (1,0.5,0.5,1)
SubShader {
Material {
Diffuse [_Color]
Lighting On
当然,也可以写比较复杂的Pass,如下
//...snip...
Material {
Diffuse [_Color]
Ambient [_Color]
Shininess [_Shininess]
Specular [_SpecColor]
Emission [_Emission]
Lighting On
SeparateSpecular On
SetTexture [_MainTex] {
constantColor [_Color]
Combine texture * primary DOUBLE, texture * constant
//...snip...
上面的意思是,先定好了材质(material)块,然后打开顶点光照,并允许指定一种颜色作为镜面高光,再然后在SetTexture中混合图像纹理,具体的意思不解释了,它们都是简单的Cg语言:)Vertex and Fragment Programs
从Unity3D的官方文档上,有两点值得注意:第一、当使用Vertex and Fragment Programs(顶点和片段程序,也被称为programmable pipeline ,可编程管道),显卡硬件上的fixed function pipeline(固定功能管线)将会关闭。比如说是,使用vertex program完成关闭3D转换、灯光和纹理坐标,又或者是用fragment program完成纹理混合,这样就不用使用SetTexture了。第二、其实就是第一点反过来说,当你需要写vertex and fragment programs时,就要求你对3D转换、照明和空间坐标有透彻了解。与fixed function programs不同的是,Vertex and Fragment Programs更多的使用Cg片段,其Pass的格式如下:
#pragma vertex vert
#pragma fragment frag
#include UnityCg.cginc
// the Cg code
#pragma vertex vert :说明了代码中含有了一个vertex程序:function vert()
#pragma fragment frag:说明了代码中含有了一个fragment程序:function frag()
#include UnityCg.cginc:包含一个头文件再来看看上面省略掉的 Cg Code:
struct v2f {
float4 pos : SV_POSITION;
float3 color : COLOR0;
v2f vert (appdata_base v)
o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
o.color = v.normal * 0.5 + 0.5;
half4 frag (v2f i) : COLOR
return half4 (i.color, 1);
struct v2f:其意思是Vert to Fragment,从代码中可知,颜色在Vertex函数中计算出来,传递给Fragment完成渲染。这是一个随着法线的变化而变化其自身颜色的shader。
在Cg代码段中,是可以使用ShaderLab中定义好的Properties的,不过需要在代码段中再定义一次变量,大致如下:
Properties{
_Color ("Main Color", Color) = (1, 1, 1, 0.5)
SubShader {
float4 _Color
Surface Shader
Surface Shader实际上是一种代码生成方式,它让我们更简洁容易的使用Vertex and Fragment Shader。它同样处于ShaderLap中,不过它并不使用pass通道,而是使用#pragma surface ……指令。#pragma surface surfaceFunction lightModel [optional params]
SurfaceFunction:函数名,实际上它的格式也是固定的,通常如下:void surf (Input IN, inout SurfaceOutput o),其中Input是自定义的结构。SurfaceOutput则会在后面介绍。
LightModel:光照模式,内置的有Lambert(diffuse)和BlinnPhong(specular),也是可以自己写的。
Optional params:可选的参数,包括有透明模式、阴影模式等,很复杂的,还是先看看基础知识先啦。以下则是SurfaceOutput的结构
struct SurfaceOutput {
//自发光,用于增强物体自身的亮度,使之看起来好像可以自己发光
下是一例:
Shader "Example/Diffuse Simple" {
SubShader {
Tags { "RenderType" = "Opaque" }
#pragma surface surf Lambert
struct Input {
float4 color : COLOR;
void surf (Input IN, inout SurfaceOutput o) {
o.Albedo = 1;
Fallback "Diffuse"
写在最前面:在Unity3D官方文档上有相关介绍,大致为两个模块,可以自己摸索阅读。第一部分是在『Reference Manual』中,相对简单的介绍了Shader的知识,基本等于这里的一和二;第二部分在『Components Manual』中,这里则是介绍的细致很多,讲解了ShaderLab的语法,三种Shader的写法和例子,内置Shader的说明等。在Unity3D中,shader是通过.shader文件来实现的,此脚本主要是使用了Cg/HLSL语言,这可以在nvidia上查找到关于这门语言的更多知识。尽管实际的shader代码会被包裹在统一格式的shaderLab中,但它依然还是有种类之分,在Unity3D中共有三种:surface shader(表面着色器)、Vertex and Fragment Shader(顶点和片段找色器)、Fixed Function Shader(固定功能着色器)。后面会讲三种分别。先粗略看ShaderLab
shader "name" {
[Properties]
SubShaders
[FallBack]
以上即是一个ShaderLab的基本格式。它由Properties、SubShader、FallBack三个部分组成。其中,Properties用来定义属性,包括但不限于颜色、材质这些。SubShader、FallBack两者都是着色器,此处称之为『子着色器』,在SubShader中可以写上面提及的三种着色器,不同的着色器是为了支持不同的显卡。如果当前的显卡支持不了SubShader中的任意一种,那么就会使用FallBack指定的着色器,通常这里面都是普遍支持的着色器。Properties的一般格式如下:
SubShader相对就要复杂多,大致格式如下
Subshader{
[CommonState]
[Passdef...]
tags、CommonState将会在后面说,Passdef 即是 pass通道,代码类似如 pass { [name and tags] [RenderSetup][TextureSetup]},也就是说我们可以在其中处理『渲染』和『纹理』两个部分。FallBack则简单很多,略过。
刚洗完头发,正准备留长头发,不过我是一头卷发,与其吹干还是等它自然干吧,吹干和湿着头发睡着没什么两样——等干了就都是奇形怪状的。恰好这段GitHub也逛了很多了,加了星的东西不记下来就该忘了:)
一套可以将文字描述转换为SVG流程图的js库,svg是一种基于XML的开放式的二维矢量图 。
它能够快速的建立一套网址的边栏。
似乎是一套同时运行在服务端、客户端的脚本库,当然主要是依靠HTML。
一套图钉系统,嘻嘻,不是正需要吗?
真的,一套很酷炫的JS物理引擎,当然……很酷的HTML 5
一套不错的网页模板,可以自己定义出很多不同的风格,我之所以把它加上星,我想只是我好奇吧:)
对的,这就是上面模板中所使用的编辑器,支持很多快捷键等等,非常有趣。
对了,这是一套能生成css的工具,就像是一套生成CSS的编程语言。
顾名思义,能够跑lua的JS框架,这样你就能在浏览器和Flash中使用Lua了。gerrit
目前使用的一套代码管理工具,具有最重要的review的功能,只有在主程确认之后,你的提交才正式的生效,非常的……变态,也很不错。
Particle Systems常用于烟、粉尘,而扩展一下可以做出火焰 、水、闪电,再扩展还可以做很多其他的事情,所以搞定这个还是有用的。ParticelSystems 基础基本属性
基本数值的设定,例如说是否循环,粒子存活时间,大小,速度等等。一些生僻的就从链接中找。Emission 发射器
这一块中,第二个选项可以是『Distance』,但遗憾的是到现在我还是不懂这个怎么用……单说Time模式下的吧。
第一选项,Rate——每秒或每米的粒子发射的数量。
在Time模式下,有Burts列表,在列表中添加时间和爆发数量。这可以用于模拟爆破效果。
更多。Shape 塑造器
定义发射器的形状:球形、半球体、圆锥,盒子和自定义网格。粒子所受的力,可以选择是从形状上的表面法线或是随机。更多细节。Render 粒子渲染器
选择粒子渲染的一些细节模式,主要是分两种『Billboard』和『Mesh』。Billboard模式中,这些粒子的形状都和摄像机或者XYZ轴有一定的关系,细节上暂时我也没想好可以应用在什么场景上。Mesh就显的好用一些,可以自己指定粒子的模块(但只是单一的Mesh,可不可以多种?似乎解决办法是累积几个粒子:)。其他模块
Velocity Over Lifetime Module / Limit Velocity Over Lifetime Module / Force Over Lifetime Module
这三个模块都是控制粒子的运动速度和方向Color Over Lifetime Module / Color By Speed Module
控制粒子颜色Size Over Lifetime Module / Size By Speed Module
控制粒子的大小Rotation Over Lifetime Module / Rotation By Speed Module
控制粒子旋转Collision Module
碰撞模块,但是只能做一个平面上面的碰撞检查,当然这样也足够,比如说是一个范围技能。Sub Emitter Module
子粒子发射模块。它固定了几个场景,非常好用。具体的链接地址同上。Texture Sheet Animation Module
关于动画……略过:)Particel Systems 开启、停止
在『基础属性』的面板中,可以去掉『Play On Awake』的勾选,这样就可以用脚本控制。脚本函数的地址在此:示例代码:ParticleSystem ps = GetComponentInChildren();
ps.Stop();当然,脚本不止Particel Systems的对象可以控制,但那些暂时不了解。这里我为了实现很多剑,再然后回归到一个?(这些符号真是太帅了)上,取出了一个粒子系统中的所有粒子,代码如下ParticleSystem.Particle[] psp = new ParticleSystem.Particle[100];
ParticleSystem ps = GetComponentInChildren();
ps.Stop();
int nPspLenght = ps.GetParticles(psp);
nPspLenght = nPspLenght < 100 ? nPspLenght : 100;
int i = 0;
while (i < nPspLenght){
psp[i].color=Color. // 变红,意思一下,这里应该标记为移动到目的地
}update:中午吃饭的时候,我说粒子系统是不是有点鸡肋,有时候表现可能并不如美术画出来的?展鹏答曰,它是行业标准,然后也说了它从简单到现在略为复杂的历史,大师最后总结到,粒子系统只适合做一些简单规则的事情,这样它就不是鸡肋。
显然不想使用Unity3D自己提供的网络接口(尽管它自己提供的接口还是挺有技术含量的,UDP),那么就是用C#的socket接口了,通过google得到下面几个网址应该就可以解决所有的问题:
这个网址证明了是可以用C#做到的,同时,它还指出对socket很陌生的情况下,可以从这个文档『』开始。还有一个对于基础有补充的网址这个来自官方wiki的文档却并不特别合适,因为举例中使用了windows库。
除了上面两个,该文档还给出了一个Video——,这里面有四种完成socket链接的方式,它还提供了,粗略观察了一下其中有WWWForm,这个是internet的连接呀,有点奇怪,我想当遇到问题而又baidu不到时,这里应该错不了。
最后一个链接颇有意思——C#与JSon
按理来说,C#处理起JSon来应该非常简单,事实上,C#的标准库里都带有JSon的,可是在Unity3D的Mono环境中,那个库并没有被包含进来。网上同样有个json.net的牛逼的代码库,但是……Mac的Unity3D似乎支持不了。好吧,其实自己写一个也没有太麻烦——用哈希表:)C#与Lua
在Unity3D的Assets资源库中有LuaInterface,我猜测应该时Mono环境的一个插件吧,粗略的感受了一下C#调用Lua的接口,和C的差距不大,看着不生,但同样的问题——在Mac下提示找不到Lua5.1DLL。
然后就是使用的风云在博客上提及的纯C#开发的lua库,,初看一下使用起来并不复杂——Behaviour文件下即是C#调用Lua的实例。之所以会处理到Json和Lua两个,是因为服务端传下来即是JSon或者Lua表:)
展鹏指出是两个方向的矛盾,一个是粗旷的,找不到合适的形容语句,因为这种方式就是种野蛮生长——这也是我一贯的风格,最直接的办法直到遇到南墙,然后猛改,我另外一种风格就是计划细腻到想哭。一个是解耦之后的,主要表现为逻辑的分层或是具有一定的层次,数据解决于一种模型——所以,对应的有将数据建立为一个N叉树。现有的情况,就是处于在两者之间。历史情况是这样的,原来在我的想法下,整个场景移动和拼凑都是前一种,当时脑袋里面怎么想就怎么来,粗旷的分成两个类已经是最大的框架了。而后,为了Cache,大师进行了解耦,也就成了现在代码的基本情况。昨天,则是为了完成岔路,所以我就在已解耦的情况下进行了『破坏』性的添加代码。。。实际上,我也并非没有注意到要进行一些规划,但问题在于我错误的理解了大师原有的安排,我的理解只理解到了代码层面上的,场景管理,场景,路,三者之间需要有安排,但没有注意到,其实大师改动最本质最关键的一点是,将PlayerMover和PathManager的解耦。讨论一番之后,最后确定的行动方案是,不能退回到原来的方式,同样也不用进化到三层结果,把握到最关键一点——PlayerMove和PathManager解耦即可。当然,展鹏另外两点建议也是不错的,一是Path对Player的不可见,二是,用树来描述数据。多说的两句,一个是为什么自己会有那两种模式,我想有两个原因,第一显然是经验不足,像野蛮生长的那种,靠的多半是直觉,然后遇上问题再思考,第二,也还是经验不足,在看到大师的改动之后,却未能理解到大师的意思。我记得以前我非常在意『数据』和『逻辑』的关系,而到现在却又忘了,其中……真是历经了些,因为某些时候不如意,觉得思考过度了,还不如靠直觉,直来直往,却又不想,其实是丢掉了最重要的思考成果。update:
后面又在接口下面有一些冲突,总的来说是角色定位不清晰的问题。PathManager是Tool,PlayerMove是User,那么Tool的接口要好用,User的调用要能体现出Tool的功能,细节上比较苛责。
最后还有一个Path对象,大师要求是纯逻辑的对象,我并没有考虑到。怎么说了,考虑多些总是好些。

我要回帖

更多关于 生理期适合做面膜吗 的文章

 

随机推荐