2013年6月1日


本文主要讨论如何将一个win32项目,移植到android上面,网上很多文章说的不一样,因为cocos2d-x的android项目配置方法好像修改过几次(好像?我没用过老版的),本文提供一种简单的方法。

第零步:配置android工程生成器
#1用ue之类的编辑器打开cocos2d-x目录下的create-android-project.bat

#2编辑这三个变量

意思太明白了,就不解释了


第一步:建立android工程

#1运行cocos2d-x目录下的create-android-project.bat

#2然后首先输入包名和项目名。
注意有坑!这会删除和覆盖你在cocos2d-x安装目录下同名文件夹,如果你建立了一个同名项目(有的话一般是win32的)的话。
本地图片,请重新上传
#3然后输入支持系统版本,android是向下兼容的。我这里写5,也就是android2.1-update1
本地图片,请重新上传
然后项目就建立好了,你会发现在cocos2d-x安装目录下,多了个Test文件夹

第二步:导入代码和资源

#1打开Test

本地图片,请重新上传
#2我们看到有三个文件夹,其中android就是android项目文件夹
把你win32项目里的Classes拷贝到当前Classes下(注意删除Classes里面的多余的cpp和h,就是项目自动生成的那些)
把你win32项目里的Resources拷贝到当前Resources下(注意删除Classes里面的多余资源,就是项目自动生成的那些)

第三步:编辑mk

#1注意有坑!android项目里有一大堆mk,而且不同的教程说的mk位置还不一样!你编辑错误了,是无效的(跟你实际使用的mk也有关系)。在这个例子中,我们使用的是Classes下的mk

本地图片,请重新上传
#2要修改的地方如下
LOCAL_SRC_FILES:在这里加入你Classes下的cpp文件
LOCAL_C_INCLUDES:在这里添加你使用的库的h文件,如果有的话
LOCAL_LDLIBS:在这里添加你使用的库的lib文件,如果有的话

这里我使用的是,之前的“是男人就坚持20秒”那个例子的代码

注意,第三方库和额外的库都是要自己添加的。默认生成的mk里没那么全。

第四步:运行build_native.sh脚本,编辑so库

#1运行你的cygwin安装目录下的Cygwin.bat

#2进入当前Test\android路径下
本地图片,请重新上传
注意cygwin下的命令写法,和win下不同
#3运行build_native.sh脚本
头一次编译会长些,当然图省事你也可以把自己及其他的android项目里面的so复制进来(应该可以把,哈哈哈)
编译完然后你会看到,在android文件夹下面生成了一大堆东西。

第五步:导入到eclipse

#1注意有坑!使用新建android项目下的Create project from existing source,如果你使用的是“导入”有可能无法识别。
#2导入成功,然后就是编译执行了。cocos2d-x作者推荐用sh编译so,再用eclipse编译成apk。
本地图片,请重新上传

其他注意事项:


#1.win32项目对资源文件大小写不敏感,android敏感,所以如果出错了,把给你提供资源的人给打一顿
#2.可以直接把win32项目里的工程文件,以及win32文件夹复制进来,项目就可以和VS共用了,当然cocos2d-x也是这么干的
#3.vs用的是gb码,android用的是utf-8,解决方法在vs下用iconv,另外最好全都转成utf-8,网上有很多编码批量转换工具
#4.android的横竖屏问题错乱,在android下把
pDirector->setDeviceOrientation(kCCDeviceOrientationLandscapeLeft);
这句用条件编译宏给关掉

 

 

 

另外,关于cocos2d-x在android上添加第三方库的问题,6群网友塞风朔雪写了一个文档,很有参考价值。我已把它上传到附件中。


posted @ 2013-06-01 10:04 郑兴锋 阅读(941) | 评论 (0)编辑 收藏

环境winxp + android sdk + ndk r8b+ cygwin 1.7.16-1 + cocos2d-1.0.1-x-0.12.0

1.下载android sdk、ndk、cygwin

http://dl.google.com/android/android-sdk_r20.0.1-windows.zip

http://dl.google.com/android/ndk/android-ndk-r8b-windows.zip

http://cygwin.com/setup.exe

2.android sdk的安装就不多说了,网上多的是。

将ndk解压到不含空格的目录下,下文用<ndk_dir>来表示解压后的ndk根目录。

下载好cygwin后,运行setup.exe。需要安装的组件有:

autoconf automake binutils gcc-core gcc-g++ gcc4-core gcc4-g++ gdb pcre pcre-devel gawk make

可以在上方search处进行查找安装,下文用<cyg_dir>表示cygwin的安装目录。

3.cygwin安装好后,在windows下编辑<cyg_dir>\home\Administrator\.bash_profile文件

在文件最后添加如下内容

 

  1. NDK=/cygdrive/d/<这里是ndk路径>/android-ndk-r8b  
  2. export NDK  
NDK=/cygdrive/d/<这里是ndk路径>/android-ndk-r8bexport NDK
编辑cocos2dx目录下的create-android-project.bat文件,分别修改如下几个变量的值

 

 

  1. set _CYGBIN=C:\cygwin\bin  
  2. set _ANDROIDTOOLS=C:\android-sdk-windows\tools  
  3. set _NDKROOT=D:\Tools\Developer\Android\android-ndk-r8b  
set _CYGBIN=C:\cygwin\binset _ANDROIDTOOLS=C:\android-sdk-windows\toolsset _NDKROOT=D:\Tools\Developer\Android\android-ndk-r8b
上面是我的系统中使用的路径,同学们需要根据自己的实际情况进行修改。

 

这样,环境基本上就搭建好了,下面需要建一个hello world工程来验证一下环境是否可用。

1.运行cocos2dx目录下的create-android-project.bat文件,根据提示输入包名(例如:cn.wey.android)、项目名称(例如:hello2dx)、所使用的android sdk版本。

2.运行cygwin,在命令窗口中进入刚刚新建的hello2dx目录下的android目录,运行命令

 

  1. ./build_native.sh  
./build_native.sh
对工程进行编译,直到最后正常结束。

 

3.打开eclipse,导入hello2dx项目,编译并运行。即可看到经典的cocos2dx的hello world界面

posted @ 2013-06-01 10:02 郑兴锋 阅读(1743) | 评论 (0)编辑 收藏

2011年11月21日

  http://blog.csdn.net/xiaominghimi?viewmode=contents 
  http://www.cnblogs.com/andyque/tag/iphone

posted @ 2011-11-21 10:39 郑兴锋 阅读(233) | 评论 (0)编辑 收藏

2011年11月20日

  最近在用VC++开发一个小工具,平时用惯了.NET,用起VC++最郁闷的就是字符串处理。当然最最让人难于琢磨的就是字符集,编码之间的转换。通过这几天的研究,终于明白了Unicode和UTF-8之间编码的区别。Unicode是一个字符集,而UTF-8是Unicode的其中一种,Unicode是定长的都为双字节,而UTF-8是可变的,对于汉字来说Unicode占有的字节比UTF-8占用的字节少1个字节。Unicode为双字节,而UTF-8中汉字占三个字节。
                        网魂小兵 http://xdotnet.cnblogs.com
    UTF-8编码字符理论上可以最多到6个字节长,然而16位BMP(Basic Multilingual Plane)字符最多只用到3字节长。下面看一下UTF-8编码表:

        U-00000000 - U-0000007F: 0xxxxxxx
        U-00000080 - U-000007FF: 110xxxxx 10xxxxxx
        U-00000800 - U-0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx
        U-00010000 - U-001FFFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
        U-00200000 - U-03FFFFFF: 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
        U-04000000 - U-7FFFFFFF: 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

    xxx 的位置由字符编码数的二进制表示的位填入, 越靠右的 x 具有越少的特殊意义,只用最短的那个足够表达一个字符编码数的多字节串。 注意在多字节串中, 第一个字节的开头"1"的数目就是整个串中字节的数目。而第一行中以0开头,是为了兼容ASCII编码,为一个字节,第二行就为双字节字符串,第三行为3字节,如汉字就属于这种,以此类推。(个人认为:其实我们可以简单的把前面的1的个数看成字节数)
                         网魂小兵 http://xdotnet.cnblogs.com
    为了要将Unicode转换为UTF-8,当然要知道他们的区别到底在什么地方。下面来看一下,在Unicode中的编码是怎样转换成UTF-8的,在UTF-8中,如果一个字符的字节小于0x80(128)则为ASCII字符,占一个字节,可以不用转换,因为UTF-8兼容ASCII编码。假如在Unicode中汉字“你”的编码为“u4F60”,把它转换为二进制为100111101100000,然后按照UTF-8的方法进行转换。可以将Unicode二进制从地位往高位取出二进制数字,每次取6位,如上述的二进制就可以分别取出为如下所示的格式,前面按格式填补,不足8位用0填补。

      
           unicode:  100111101100000                  4F60

           utf-8:    11100100,10111101,10100000       E4BDA0


    从上面就可以很直观的看出Unicode到UTF-8之间的转换,当然知道了UTF-8的格式后,就可以进行逆运算,就是按照格式把它在二进制中的相应位置上取出,然后在转换就是所得到的Unicode字符了(这个运算可以通过“位移”来完成)。
                      网魂小兵 http://xdotnet.cnblogs.com
    如上述的“你”的转换,由于其值大于0x800小于0x10000,因此可以判断为三字节存储,则最高位需要向右移“12”位再根据三字节格式的最高位为11100000(0xE0)求或(|)就可以得到最高位的值了。同理第二位则是右移“6”位,则还剩下最高位和第二位的二进制值,可以通过与111111(0x3F)求按位于(&)操作,再和11000000(0x80)求或(|)。第三位就不用移位了,只要直接取最后六位(与111111(ox3F)取&),在与11000000(0x80)求或(|)。OK了,转换成功!在VC++中的代码如下所示(Unicode到UTF-8的转换)。

        1 const wchar_t pUnicode = L"你";
        2 char utf8[3+1];
        3 memset(utf8,0,4);
        4 utf8[0] = 0xE0|(pUnicode>>12);
        5 utf8[1] = 0x80|((pUnicode>>6)&0x3F);
        6 utf8[2] = 0x80|(pUnicode&0x3F);
        7 utf8[3] = "\0";
        8 //char[4]就是UTF-8的字符“你”了。

    当然在UTF-8到Unicode的转换也是通过移位等来完成的,就是把UTF-8那些格式相应的位置的二进制数给揪出来。在上述例子中“你”为三个字节,因此要每个字节进行处理,有高位到低位进行处理。在UTF-8中“你”为11100100,10111101,10100000。从高位起即第一个字节11100100就是把其中的"0100"给取出来,这个很简单只要和11111(0x1F)取与(&),由三字节可以得知最到位肯定位于12位之前,因为每次取六位。所以还要将得到的结果左移12位,最高位也就这样完成了0100,000000,000000。而第二位则是要把“111101”给取出来,则只需将第二字节10111101和111111(0x3F)取与(&)。在将所得到的结果左移6位与最高字节所得的结果取或(|),第二位就这样完成了,得到的结果为0100,111101,000000。以此类推最后一位直接与111111(0x3F)取与(&),再与前面所得的结果取或(|)即可得到结果0100,111101,100000。OK,转换成功!在VC++中的代码如下所示(UTF-8到Unicode的转换)。

    1 //UTF-8格式的字符串
    2 const char* utf8 = "你";
    3 wchar_t unicode;
    4 unicode = (utf8[0] & 0x1F) << 12;
    5 unicode |= (utf8[1] & 0x3F) << 6;
    6 unicode |= (utf8[2] & 0x3F);
    7 //unicode is ok!
                            网魂小兵 http://xdotnet.cnblogs.com
    当然在编程过程中不可能只转换一个字符,这里需要注意的是字符的长度一定要算清楚,不然会带来...以上就是我这几天研究的结果,至于Unicode的转换为GB2312在MFC中Windows有自带的API(WideCharToMultiByte)可以转换。这样也就能够将UTF-8格式转换为GB2312了,这里就不再赘述,如果大家有更好的方法希望指教。

posted @ 2011-11-20 13:52 郑兴锋 阅读(170) | 评论 (0)编辑 收藏

2011年11月16日

1.closesocket(一般不会立即关闭而经历TIME_WAIT的过程)后想继续重用该socket:
BOOL bReuseaddr=TRUE;
setsockopt(s,SOL_SOCKET ,SO_REUSEADDR,(const char*)&bReuseaddr,sizeof(BOOL));
2. 如果要已经处于连接状态的soket在调用closesocket后强制关闭,不经历
TIME_WAIT的过程:
BOOL bDontLinger = FALSE;
setsockopt(s,SOL_SOCKET,SO_DONTLINGER,(const char*)&bDontLinger,sizeof(BOOL));
3.在send(),recv()过程中有时由于网络状况等原因,发收不能预期进行,而设置收发时限:
int nNetTimeout=1000;//1秒
//发送时限
setsockopt(socket,SOL_S0CKET,SO_SNDTIMEO,(char *)&nNetTimeout,sizeof(int));
//接收时限
setsockopt(socket,SOL_S0CKET,SO_RCVTIMEO,(char *)&nNetTimeout,sizeof(int));
4.在send()的时候,返回的是实际发送出去的字节(同步)或发送到socket缓冲区的字节
(异步);系统默认的状态发送和接收一次为8688字节(约为8.5K);在实际的过程中发送数据
和接收数据量比较大,可以设置socket缓冲区,而避免了send(),recv()不断的循环收发:
// 接收缓冲区
int nRecvBuf=32*1024;//设置为32K
setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));
//发送缓冲区
int nSendBuf=32*1024;//设置为32K
setsockopt(s,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));
5. 如果在发送数据的时,希望不经历由系统缓冲区到socket缓冲区的拷贝而影响
程序的性能:
int nZero=0;
setsockopt(socket,SOL_S0CKET,SO_SNDBUF,(char *)&nZero,sizeof(nZero));
6.同上在recv()完成上述功能(默认情况是将socket缓冲区的内容拷贝到系统缓冲区):
int nZero=0;
setsockopt(socket,SOL_S0CKET,SO_RCVBUF,(char *)&nZero,sizeof(int));
7.一般在发送UDP数据报的时候,希望该socket发送的数据具有广播特性:
BOOL bBroadcast=TRUE;
setsockopt(s,SOL_SOCKET,SO_BROADCAST,(const char*)&bBroadcast,sizeof(BOOL));
8.在client连接服务器过程中,如果处于非阻塞模式下的socket在connect()的过程中可
以设置connect()延时,直到accpet()被呼叫(本函数设置只有在非阻塞的过程中有显著的
作用,在阻塞的函数调用中作用不大)
BOOL bConditionalAccept=TRUE;
setsockopt(s,SOL_SOCKET,SO_CONDITIONAL_ACCEPT,(const char*)&bConditionalAccept,sizeof(BOOL));
9.如果在发送数据的过程中(send()没有完成,还有数据没发送)而调用了closesocket(),以前我们
一般采取的措施是"从容关闭"shutdown(s,SD_BOTH),但是数据是肯定丢失了,如何设置让程序满足具体
应用的要求(即让没发完的数据发送出去后在关闭socket)?
struct linger {
u_short l_onoff;
u_short l_linger;
};
linger m_sLinger;
m_sLinger.l_onoff=1;//(在closesocket()调用,但是还有数据没发送完毕的时候容许逗留)
// 如果m_sLinger.l_onoff=0;则功能和2.)作用相同;
m_sLinger.l_linger=5;//(容许逗留的时间为5秒)
setsockopt(s,SOL_SOCKET,SO_LINGER,(const char*)&m_sLinger,sizeof(linger));
 
设置套接口的选项。
   #include <winsock.h>
   int PASCAL FAR setsockopt( SOCKET s, int level, int optname,
   const char FAR* optval, int optlen);
   s:标识一个套接口的描述字。
   level:选项定义的层次;目前仅支持SOL_SOCKET和IPPROTO_TCP层次。
   optname:需设置的选项。
   optval:指针,指向存放选项值的缓冲区。
   optlen:optval缓冲区的长度。
注释:
setsockopt()函数用于任意类型、任意状态套接口的设置选项值。尽管在不同协议层上存在选项,但本函数仅定义了最高的“套接口”层次上的选项。选项影响套接口的操作,诸如加急数据是否在普通数据流中接收,广播数据是否可以从套接口发送等等。
   有两种套接口的选项:一种是布尔型选项,允许或禁止一种特性;另一种是整形或结构选项。允许一个布尔型选项,则将optval指向非零整形数;禁止一个选项optval指向一个等于零的整形数。对于布尔型选项,optlen应等于sizeof(int);对其他选项,optval指向包含所需选项的整形数或结构,而optlen则为整形数或结构的长度。SO_LINGER选项用于控制下述情况的行动:套接口上有排队的待发送数据,且 closesocket()调用已执行。参见closesocket()函数中关于SO_LINGER选项对closesocket()语义的影响。应用程序通过创建一个linger结构来设置相应的操作特性:
   struct linger {
int l_onoff;
int l_linger;
   };
   为了允许SO_LINGER,应用程序应将l_onoff设为非零,将l_linger设为零或需要的超时值(以秒为单位),然后调用setsockopt()。为了允许SO_DONTLINGER(亦即禁止SO_LINGER),l_onoff应设为零,然后调用setsockopt()。
   缺省条件下,一个套接口不能与一个已在使用中的本地地址捆绑(参见bind())。但有时会需要“重用”地址。因为每一个连接都由本地地址和远端地址的组合唯一确定,所以只要远端地址不同,两个套接口与一个地址捆绑并无大碍。为了通知WINDOWS套接口实现不要因为一个地址已被一个套接口使用就不让它与另一个套接口捆绑,应用程序可在bind()调用前先设置SO_REUSEADDR选项。请注意仅在bind()调用时该选项才被解释;故此无需(但也无害)将一个不会共用地址的套接口设置该选项,或者在bind()对这个或其他套接口无影响情况下设置或清除这一选项。
   一个应用程序可以通过打开SO_KEEPALIVE选项,使得WINDOWS套接口实现在TCP连接情况下允许使用“保持活动”包。一个WINDOWS套接口实现并不是必需支持“保持活动”,但是如果支持的话,具体的语义将与实现有关,应遵守RFC1122“Internet主机要求-通讯层”中第 4.2.3.6节的规范。如果有关连接由于“保持活动”而失效,则进行中的任何对该套接口的调用都将以WSAENETRESET错误返回,后续的任何调用将以WSAENOTCONN错误返回。
   TCP_NODELAY选项禁止Nagle算法。Nagle算法通过将未确认的数据存入缓冲区直到蓄足一个包一起发送的方法,来减少主机发送的零碎小数据包的数目。但对于某些应用来说,这种算法将降低系统性能。所以TCP_NODELAY可用来将此算法关闭。应用程序编写者只有在确切了解它的效果并确实需要的情况下,才设置TCP_NODELAY选项,因为设置后对网络性能有明显的负面影响。TCP_NODELAY是唯一使用IPPROTO_TCP层的选项,其他所有选项都使用SOL_SOCKET层。
   如果设置了SO_DEBUG选项,WINDOWS套接口供应商被鼓励(但不是必需)提供输出相应的调试信息。但产生调试信息的机制以及调试信息的形式已超出本规范的讨论范围。
setsockopt()支持下列选项。其中“类型”表明optval所指数据的类型。
选项        类型   意义
SO_BROADCAST BOOL 允许套接口传送广播信息。
SO_DEBUG BOOL 记录调试信息。
SO_DONTLINER BOOL 不要因为数据未发送就阻塞关闭操作。设置本选项相当于将SO_LINGER的l_onoff元素置为零。
SO_DONTROUTE BOOL 禁止选径;直接传送。
SO_KEEPALIVE BOOL 发送“保持活动”包。
SO_LINGER struct linger FAR*   如关闭时有未发送数据,则逗留。
SO_OOBINLINE BOOL 在常规数据流中接收带外数据。
SO_RCVBUF int 为接收确定缓冲区大小。
SO_REUSEADDR BOOL 允许套接口和一个已在使用中的地址捆绑(参见bind())。
SO_SNDBUF int 指定发送缓冲区大小。
TCP_NODELAY BOOL 禁止发送合并的Nagle算法。
setsockopt()不支持的BSD选项有:
选项名    类型 意义
SO_ACCEPTCONN BOOL 套接口在监听。
SO_ERROR int 获取错误状态并清除。
SO_RCVLOWAT int 接收低级水印。
SO_RCVTIMEO int 接收超时。
SO_SNDLOWAT int 发送低级水印。
SO_SNDTIMEO int 发送超时。
SO_TYPE     int 套接口类型。
IP_OPTIONS    在IP头中设置选项。
返回值:
   若无错误发生,setsockopt()返回0。否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()获取相应错误代码。
错误代码:
   WSANOTINITIALISED:在使用此API之前应首先成功地调用WSAStartup()。
   WSAENETDOWN:WINDOWS套接口实现检测到网络子系统失效。
   WSAEFAULT:optval不是进程地址空间中的一个有效部分。
   WSAEINPROGRESS:一个阻塞的WINDOWS套接口调用正在运行中。
   WSAEINVAL:level值非法,或optval中的信息非法。
   WSAENETRESET:当SO_KEEPALIVE设置后连接超时。
   WSAENOPROTOOPT:未知或不支持选项。其中,SOCK_STREAM类型的套接口不支持SO_BROADCAST选项,SOCK_DGRAM 类型的套接口不支持SO_DONTLINGER 、SO_KEEPALIVE、SO_LINGER和SO_OOBINLINE选项。
   WSAENOTCONN:当设置SO_KEEPALIVE后连接被复位。
   WSAENOTSOCK:描述字不是一个套接口。

posted @ 2011-11-16 20:57 郑兴锋 阅读(228) | 评论 (0)编辑 收藏

2011年3月22日

       关于网游服务器的分类和布局的问题是在网络游戏中比较关键的部分, 首先我们讨论网络游戏服务器的分类问题: 一般情况下游戏服务器分为: 登陆服务器, 网关服务器, 游戏服务器和数据库服务器四类, 有的服务器可能是多台并存的,如游戏服务器, 其中游戏服务器包括: 地图服务器和逻辑服务器.
下面这个图描述了网关服务器和其他服务器之间的关系:
     
网关服务器的功能是将: 游戏服务器, 客户端和数据库服务器之间的消息进行转发, 所以它负责管理客户端的连接和服务端的连接, 并转发他们之间的消息, 同时他还连接到数据库服务器上,等需要数据读取是对数据库进行操作,并转发给相应的请求者.  (其实在对数据库的连接这个问题上,有游戏服务器来连接的,网关服务只负责转发和简单的逻辑处理)。
    我们现在来讨论下网关服务器是怎么实现的:网关服务器要管理两个事情,一个是游戏服务器的连接,另一个玩家的连接。其实对于网关服务器而言,这两个种类型的连接差别,并不是很大,可以用一个网路模型来处理收到的消息,并将其转发给相应的接受者。
    游戏服务器中的地图服务器:地图服务器相对于网关服务器而言,它更像是一个客户端,它在收到消息的时候处理消息,并将消息结果返回给网关,网关把消息转给相应的连接。只是说因为地图服务器相对来说是处理那些长时间连接,需要不断处理的逻辑的,如用户的移动,状态,补给等,需要实时的消息处理。
    逻辑服务器的功能是实现如打怪,组队等一些,不经常交互的逻辑的。其实对于一组游戏服务器而言,大量的聊天信息也是很消耗系统资源的,有时候要单独建立一个聊天服务器。
   数据库服务器基本上是存放数据库的,游戏服务器,逻辑服务器,在需要是读取数据,进行逻辑处理。

   
  














 

posted @ 2011-03-22 17:38 郑兴锋 阅读(577) | 评论 (0)编辑 收藏

2011年2月15日

Windows系统编程之进程间通信
北极星2003 当前离线


Windows 的IPC(进程间通信)机制主要是异步管道和命名管道。(至于其他的IPC方式,例如内存映射、邮槽等这里就不介绍了)
管道(pipe)是用于进程间通信的共享内存区域。创建管道的进程称为管道服务器,而连接到这个管道的进程称为管道客户端。一个进程向管道写入信息,而另外一个进程从管道读取信息。
异步管道是基于字符和半双工的(即单向),一般用于程序输入输出的重定向;命名管道则强大地多,它们是面向消息和全双工的,同时还允许网络通信,用于创建客户端/服务器系统。
一、异步管道(实现比较简单,直接通过实例来讲解)
实验目标:当前有sample.cpp, sample.exe, sample.in这三个文件,sample.exe为sample.cpp的执行程序,sample.cpp只是一个简单的程序示例(简单求和),如下:
代码:
#include <iostream.h>
int main()
{
  int a, b ;
  while ( cin >> a >> b && ( a || b ) )
    cout << a + b << endl ;
  return 0;
}
Sample.in文件是输入文件,内容:
32 433
542 657
0 0
要求根据sample.exe和它的输入数据,把输出数据重定向到sample.out
流程分析:实际这个实验中包含两个部分,把输入数据重定向到sample.exe 和把输出数据重定向到sample.out。在命令行下可以很简单的实现这个功能“sample <sample.in >sample.out”,这个命令也是利用管道特性实现的,现在我们就根据异步管道的实现原理自己来实现这个功能。
管道是基于半双工(单向)的,这里有两个重定向的过程,显然需要创建两个管道,下面给出流程图:
 
异步管道实现的流程图说明:
1)。父进程是我们需要实现的,其中需要创建管道A,管道B,和子进程,整个实现流程分为4个操作。
2)。管道A:输入管道
3)。管道B:输出管道
4)。操作A:把输入文件sample.in的数据写入输入管道(管道A)
5)。操作B:子进程从输入管道中读取数据,作为该进程的加工原料。通常,程序的输入数据由标准的输入设备输入,这里实现输入重定向,即把输入管道作为输入设备。
6)。操作C:子进程把加工后的成品(输出数据)输出到输出管道。通常,程序的输出数据会输出到标准的输出设备,一般为屏幕,这里实现输出重定向,即把输出管道作为输出设备。
7)。操作D:把输出管道的数据写入输出文件
需要注意的是,管道的本质只是一个共享的内存区域。这个实验中,管道区域处于父进程的地址空间中,父进程的作用是提供环境和资源,并协调子进程进行加工。
程序源码:
代码:
#include <windows.h> 
#include <iostream.h>
const int BUFSIZE = 4096 ; 
HANDLE  hChildStdinRd, hChildStdinWr, hChildStdinWrDup, 
       hChildStdoutRd,hChildStdoutWr,hChildStdoutRdDup, 
    hSaveStdin,    hSaveStdout; 
BOOL CreateChildProcess(LPTSTR); 
VOID WriteToPipe(LPTSTR); 
VOID ReadFromPipe(LPTSTR); 
VOID ErrorExit(LPTSTR); 
VOID ErrMsg(LPTSTR, BOOL); 
void main( int argc, char *argv[] ) 
{  
  // 处理输入参数
  if ( argc != 4 )
    return ;
  // 分别用来保存命令行,输入文件名(CPP/C),输出文件名(保存编译信息)
  LPTSTR lpProgram = new char[ strlen(argv[1]) ] ;
  strcpy ( lpProgram, argv[1] ) ;
  LPTSTR lpInputFile = new char[ strlen(argv[2]) ];
  strcpy ( lpInputFile, argv[2] ) ;
  LPTSTR lpOutputFile = new char[ strlen(argv[3]) ] ;
  strcpy ( lpOutputFile, argv[3] ) ;    
  
  SECURITY_ATTRIBUTES saAttr; 
  saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 
  saAttr.bInheritHandle = TRUE; 
  saAttr.lpSecurityDescriptor = NULL; 
   
  /************************************************
   *    redirecting child process's STDOUT  *
   ************************************************/
  hSaveStdout = GetStdHandle(STD_OUTPUT_HANDLE); 
  
  if (! CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &saAttr, 0)) 
    ErrorExit("Stdout pipe creation failed\n"); 
    
  if (! SetStdHandle(STD_OUTPUT_HANDLE, hChildStdoutWr)) 
    ErrorExit("Redirecting STDOUT failed"); 
  
  BOOL fSuccess = DuplicateHandle(
    GetCurrentProcess(), 
    hChildStdoutRd,
        GetCurrentProcess(), 
    &hChildStdoutRdDup ,
    0,
        FALSE,
        DUPLICATE_SAME_ACCESS);
    if( !fSuccess )
        ErrorExit("DuplicateHandle failed");
    CloseHandle(hChildStdoutRd);
  
  /************************************************
   *    redirecting child process's STDIN    *
   ************************************************/
  hSaveStdin = GetStdHandle(STD_INPUT_HANDLE); 
  if (! CreatePipe(&hChildStdinRd, &hChildStdinWr, &saAttr, 0)) 
    ErrorExit("Stdin pipe creation failed\n"); 
  
  if (! SetStdHandle(STD_INPUT_HANDLE, hChildStdinRd)) 
    ErrorExit("Redirecting Stdin failed"); 
  
  fSuccess = DuplicateHandle(
    GetCurrentProcess(), 
    hChildStdinWr, 
    GetCurrentProcess(),
    &hChildStdinWrDup, 
    0, 
    FALSE,                 
    DUPLICATE_SAME_ACCESS); 
  if (! fSuccess) 
    ErrorExit("DuplicateHandle failed"); 
  CloseHandle(hChildStdinWr);   
  /************************************************
   *      创建子进程(即启动SAMPLE.EXE)    *
   ************************************************/
  fSuccess = CreateChildProcess( lpProgram );
  if ( !fSuccess ) 
    ErrorExit("Create process failed"); 
  
  // 父进程输入输出流的还原设置
  if (! SetStdHandle(STD_INPUT_HANDLE, hSaveStdin)) 
    ErrorExit("Re-redirecting Stdin failed\n"); 
  if (! SetStdHandle(STD_OUTPUT_HANDLE, hSaveStdout)) 
    ErrorExit("Re-redirecting Stdout failed\n"); 
  WriteToPipe( lpInputFile ) ;
  ReadFromPipe( lpOutputFile ); 
          delete lpProgram ;
          delete lpInputFile ;
          delete lpOutputFile ;
} 
BOOL CreateChildProcess( LPTSTR lpProgram ) 
{ 
  PROCESS_INFORMATION piProcInfo; 
  STARTUPINFO siStartInfo;
  BOOL bFuncRetn = FALSE; 
  
  ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) );
  ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
  siStartInfo.cb = sizeof(STARTUPINFO); 
  
  bFuncRetn = CreateProcess ( NULL, lpProgram, NULL, NULL, TRUE, \
                0, NULL, NULL, &siStartInfo, &piProcInfo);
  if (bFuncRetn == 0) 
  {
    ErrorExit("CreateProcess failed\n");
    return 0;
  } 
  else 
  {
    CloseHandle(piProcInfo.hProcess);
    CloseHandle(piProcInfo.hThread);
    return bFuncRetn;
  }
}
VOID WriteToPipe( LPTSTR lpInputFile ) 
{ 
  HANDLE hInputFile = CreateFile(lpInputFile, GENERIC_READ, 0, NULL, 
    OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL); 
  if (hInputFile == INVALID_HANDLE_VALUE) 
    return ;
  BOOL fSuccess ;
  DWORD dwRead, dwWritten; 
  CHAR chBuf[BUFSIZE] = {0} ; 
  
  for (;;) 
  { 
    fSuccess = ReadFile( hInputFile, chBuf, BUFSIZE, &dwRead, NULL) ;
    if ( !fSuccess || dwRead == 0)
      break; 
    fSuccess = WriteFile( hChildStdinWrDup, chBuf, dwRead, &dwWritten, NULL) ;
    if ( !fSuccess ) 
      break; 
  } 
    
  if (! CloseHandle(hChildStdinWrDup)) 
    ErrorExit("Close pipe failed\n"); 
  CloseHandle ( hInputFile ) ;
} 
VOID ReadFromPipe( LPTSTR lpOutputFile ) 
{ 
  HANDLE hOutputFile = CreateFile( lpOutputFile, GENERIC_READ|GENERIC_WRITE, 
    FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); 
  if (hOutputFile == INVALID_HANDLE_VALUE) 
    return ;
  BOOL fSuccess ;
  DWORD dwRead, dwWritten; 
  CHAR chBuf[BUFSIZE] = { 0 }; 
  
  if (!CloseHandle(hChildStdoutWr)) 
    ErrorExit("Closing handle failed"); 
  
  for (;;) 
  { 
    fSuccess = ReadFile( hChildStdoutRdDup, chBuf, BUFSIZE, &dwRead, NULL) ;
    if( !fSuccess || dwRead == 0) 
    {
      break; 
    }
    fSuccess = WriteFile( hOutputFile, chBuf, dwRead, &dwWritten, NULL) ;
    if ( !fSuccess ) 
      break; 
  } 
  CloseHandle ( hOutputFile ) ;
} 
VOID ErrorExit (LPTSTR lpszMessage) 
{ 
  MessageBox( 0, lpszMessage, 0, 0 ); 
}
二、命名管道
命名管道具有以下几个特征:
(1)命名管道是双向的,所以两个进程可以通过同一管道进行交互。
(2)命名管道不但可以面向字节流,还可以面向消息,所以读取进程可以读取写进程发送的不同长度的消息。
(3)多个独立的管道实例可以用一个名称来命名。例如几个客户端可以使用名称相同的管道与同一个服务器进行并发通信。
(4)命名管道可以用于网络间两个进程的通信,而其实现的过程与本地进程通信完全一致。
实验目标:在客户端输入数据a和b,然后发送到服务器并计算a+b,然后把计算结果发送到客户端。可以多个客户端与同一个服务器并行通信。
界面设计:
 http://bbs.pediy.com/upload/2006/41/image/namedpipe.gif 
难点所在:
实现的过程比较简单,但有一个难点。原本当服务端使用ConnectNamedPipe函数后,如果有客户端连接,就可以直接进行交互。原来我在实现过程中,当管道空闲时,管道的线程函数会无限(INFINITE)阻塞。若现在需要停止服务,就必须结束所有的线程,TernimateThread可以作为一个结束线程的方法,但我基本不用这个函数。一旦使用这个函数之后,目标线程就会立即结束,但如果此时的目标线程正在操作互斥资源、内核调用、或者是操作共享DLL的全局变量,可能会出现互斥资源无法释放、内核异常等现象。这里我用重叠I/0来解决这个问题,在创建PIPE时使用FILE_FLAG_OVERLAPPED标志,这样使用ConnectNamedPipe后会立即返回,但线程的阻塞由等待函数WaitForSingleObject来实现,等待OVERLAPPED结构的事件对象被设置。
客户端主要代码:
代码:
void CMyDlg::OnSubmit() 
{
  // 打开管道
  HANDLE hPipe = CreateFile("\\\\.\\Pipe\\NamedPipe", GENERIC_READ | GENERIC_WRITE, \
    0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL) ;
  if ( hPipe == INVALID_HANDLE_VALUE )
  {
    this->MessageBox ( "打开管道失败,服务器尚未启动,或者客户端数量过多" ) ;
    return ;
  }
  DWORD nReadByte, nWriteByte ;
  char szBuf[1024] = {0} ;
  // 把两个整数(a,b)格式化为字符串
  sprintf ( szBuf, "%d %d", this->nFirst, this->nSecond ) ;
  // 把数据写入管道
  WriteFile ( hPipe, szBuf, strlen(szBuf), &nWriteByte, NULL ) ;
  memset ( szBuf, 0, sizeof(szBuf) ) ;
  // 读取服务器的反馈信息
  ReadFile ( hPipe, szBuf, 1024, &nReadByte, NULL ) ;
  // 把返回信息格式化为整数
  sscanf ( szBuf, "%d", &(this->nResValue) ) ;
  this->UpdateData ( false ) ;
  CloseHandle ( hPipe ) ;
}
服务端主要代码:
代码:
// 启动服务
void CMyDlg::OnStart() 
{
  CString lpPipeName = "\\\\.\\Pipe\\NamedPipe" ;
  for ( UINT i = 0; i < nMaxConn; i++ )
  {
    // 创建管道实例
    PipeInst[i].hPipe =  CreateNamedPipe ( lpPipeName, PIPE_ACCESS_DUPLEX|FILE_FLAG_OVERLAPPED, \
          PIPE_TYPE_BYTE|PIPE_READMODE_BYTE|PIPE_WAIT, nMaxConn, 0, 0, 1000, NULL ) ;
    if ( PipeInst[i].hPipe == INVALID_HANDLE_VALUE )
    {
      DWORD dwErrorCode = GetLastError () ;
      this->MessageBox ( "创建管道错误!" ) ;
      return ;
    }
    // 为每个管道实例创建一个事件对象,用于实现重叠IO
    PipeInst[i].hEvent  =  CreateEvent ( NULL, false, false, false ) ;
    // 为每个管道实例分配一个线程,用于响应客户端的请求
    PipeInst[i].hTread = AfxBeginThread ( ServerThread, &PipeInst[i], THREAD_PRIORITY_NORMAL ) ;
  }
  
  this->SetWindowText ( "命名管道实例之服务器(运行)" ) ;
  this->MessageBox ( "服务启动成功" ) ;
}
// 停止服务
void CMyDlg::OnStop() 
{
  DWORD dwNewMode = PIPE_TYPE_BYTE|PIPE_READMODE_BYTE|PIPE_NOWAIT ;
  for ( UINT i = 0; i < nMaxConn; i++ )
  {
    SetEvent ( PipeInst[i].hEvent ) ;
    CloseHandle ( PipeInst[i].hTread ) ;
    CloseHandle ( PipeInst[i].hPipe ) ;
  }
    
  this->SetWindowText ( "命名管道实例之服务器" ) ;
  this->MessageBox ( "停止启动成功" ) ;
}
// 线程服务函数
UINT ServerThread ( LPVOID lpParameter )
{
  DWORD  nReadByte = 0, nWriteByte = 0, dwByte = 0 ;  
  char  szBuf[MAX_BUFFER_SIZE] = {0} ;
  PIPE_INSTRUCT  CurPipeInst = *(PIPE_INSTRUCT*)lpParameter ;
  OVERLAPPED OverLapStruct = { 0, 0, 0, 0, CurPipeInst.hEvent } ;
  while ( true )
  {
    memset ( szBuf, 0, sizeof(szBuf) ) ;  
    // 命名管道的连接函数,等待客户端的连接(只针对NT)
    ConnectNamedPipe ( CurPipeInst.hPipe, &OverLapStruct ) ;
    // 实现重叠I/0,等待OVERLAPPED结构的事件对象
    WaitForSingleObject ( CurPipeInst.hEvent, INFINITE ) ;
    // 检测I/0是否已经完成,如果未完成,意味着该事件对象是人工设置,即服务需要停止
    if ( !GetOverlappedResult ( CurPipeInst.hPipe, &OverLapStruct, &dwByte, true ) )
      break ;
    // 从管道中读取客户端的请求信息
    if ( !ReadFile ( CurPipeInst.hPipe, szBuf, MAX_BUFFER_SIZE, &nReadByte, NULL ) )
    {
      MessageBox ( 0, "读取管道错误!", 0, 0 ) ;
      break ;
    }
    
    int a, b ;
    sscanf ( szBuf, "%d %d", &a, &b ) ;
    pMyDlg->nFirst    = a ;
    pMyDlg->nSecond    = b ;
    pMyDlg->nResValue  = a + b ;
    memset ( szBuf, 0, sizeof(szBuf) ) ;
    sprintf ( szBuf, "%d", pMyDlg->nResValue ) ;
    // 把反馈信息写入管道
    WriteFile ( CurPipeInst.hPipe, szBuf, strlen(szBuf), &nWriteByte, NULL ) ;
    pMyDlg->SetDlgItemInt ( IDC_FIRST, a, true ) ;
    pMyDlg->SetDlgItemInt ( IDC_SECOND, b, true ) ;
    pMyDlg->SetDlgItemInt ( IDC_RESULT, pMyDlg->nResValue, true ) ;
    // 断开客户端的连接,以便等待下一客户的到来
    DisconnectNamedPipe ( CurPipeInst.hPipe ) ;
  }
  return 0 ;
}

posted @ 2011-02-15 12:30 郑兴锋 阅读(582) | 评论 (0)编辑 收藏

2010年11月4日

setsockopt()

目录

简述:
注释:
返回值:
用法

简述:

  设置套接口的选项。
  #include <winsock.h>
  int PASCAL FAR setsockopt( SOCKET s, int level, int optname,
  const char FAR* optval, int optlen);
  s:标识一个套接口的描述字。
  level:选项定义的层次;目前仅支持SOL_SOCKET和IPPROTO_TCP层次。
  optname:需设置的选项。
  optval:指针,指向存放选项值的缓冲区。
  optlen:optval缓冲区的长度。

注释:

  setsockopt()函数用于任意类型、任意状态套接口的设置选项值。尽管在不同协议层上存在选项,但本函数仅定义了最高的“套接口”层次上的选项。选项影响套接口的操作,诸如加急数据是否在普通数据流中接收,广播数据是否可以从套接口发送等等。
  有两种套接口的选项:一种是布尔型选项,允许或禁止一种特性;另一种是整形或结构选项。允许一个布尔型选项,则将optval指向非零整形数;禁止一个选项optval指向一个等于零的整形数。对于布尔型选项,optlen应等于sizeof(int);对其他选项,optval指向包含所需选项的整形数或结构,而optlen则为整形数或结构的长度。SO_LINGER选项用于控制下述情况的行动:套接口上有排队的待发送数据,且closesocket()调用已执行。参见closesocket()函数中关于SO_LINGER选项对closesocket()语义的影响。应用程序通过创建一个linger结构来设置相应的操作特性:
  struct linger {
  int l_onoff;
  int l_linger;
  };
  为了允许SO_LINGER,应用程序应将l_onoff设为非零,将l_linger设为零或需要的超时值(以秒为单位),然后调用setsockopt()。为了允许SO_DONTLINGER(亦即禁止SO_LINGER),l_onoff应设为零,然后调用setsockopt()。
  缺省条件下,一个套接口不能与一个已在使用中的本地地址捆绑(参见bind())。但有时会需要“重用”地址。因为每一个连接都由本地地址和远端地址的组合唯一确定,所以只要远端地址不同,两个套接口与一个地址捆绑并无大碍。为了通知WINDOWS套接口实现不要因为一个地址已被一个套接口使用就不让它与另一个套接口捆绑,应用程序可在bind()调用前先设置SO_REUSEADDR选项。请注意仅在bind()调用时该选项才被解释;故此无需(但也无害)将一个不会共用地址的套接口设置该选项,或者在bind()对这个或其他套接口无影响情况下设置或清除这一选项。
  一个应用程序可以通过打开SO_KEEPALIVE选项,使得WINDOWS套接口实现在TCP连接情况下允许使用“保持活动”包。一个WINDOWS套接口实现并不是必需支持“保持活动”,但是如果支持的话,具体的语义将与实现有关,应遵守RFC1122“Internet主机要求-通讯层”中第4.2.3.6节的规范。如果有关连接由于“保持活动”而失效,则进行中的任何对该套接口的调用都将以WSAENETRESET错误返回,后续的任何调用将以WSAENOTCONN错误返回。
  TCP_NODELAY选项禁止Nagle算法。Nagle算法通过将未确认的数据存入缓冲区直到蓄足一个包一起发送的方法,来减少主机发送的零碎小数据包的数目。但对于某些应用来说,这种算法将降低系统性能。所以TCP_NODELAY可用来将此算法关闭。应用程序编写者只有在确切了解它的效果并确实需要的情况下,才设置TCP_NODELAY选项,因为设置后对网络性能有明显的负面影响。TCP_NODELAY是唯一使用IPPROTO_TCP层的选项,其他所有选项都使用SOL_SOCKET层。
  如果设置了SO_DEBUG选项,WINDOWS套接口供应商被鼓励(但不是必需)提供输出相应的调试信息。但产生调试信息的机制以及调试信息的形式已超出本规范的讨论范围。
  setsockopt()支持下列选项。其中“类型”表明optval所指数据的类型。
  选项 类型 意义
  SO_BROADCAST BOOL 允许套接口传送广播信息。
  SO_DEBUG BOOL 记录调试信息。
  SO_DONTLINER BOOL 不要因为数据未发送就阻塞关闭操作。设置本选项相当于将SO_LINGER的l_onoff元素置为零。
  SO_DONTROUTE BOOL 禁止选径;直接传送。
  SO_KEEPALIVE BOOL 发送“保持活动”包。
  SO_LINGER struct linger FAR* 如关闭时有未发送数据,则逗留。
  SO_OOBINLINE BOOL 在常规数据流中接收带外数据。
  SO_RCVBUF int 为接收确定缓冲区大小。
  SO_REUSEADDR BOOL 允许套接口和一个已在使用中的地址捆绑(参见bind())。
  SO_SNDBUF int 指定发送缓冲区大小。
  TCP_NODELAY BOOL 禁止发送合并的Nagle算法。
  setsockopt()不支持的BSD选项有:
  选项名 类型 意义
  SO_ACCEPTCONN BOOL 套接口在监听。
  SO_ERROR int 获取错误状态并清除。
  SO_RCVLOWAT int 接收低级水印。
  SO_RCVTIMEO int 接收超时。
  SO_SNDLOWAT int 发送低级水印。
  SO_SNDTIMEO int 发送超时。
  SO_TYPE int 套接口类型。
  IP_OPTIONS 在IP头中设置选项。

返回值:

  若无错误发生,setsockopt()返回0。否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()获取相应错误代码。
  错误代码:
  WSANOTINITIALISED:在使用此API之前应首先成功地调用WSAStartup()。
  WSAENETDOWN:WINDOWS套接口实现检测到网络子系统失效。
  WSAEFAULT:optval不是进程地址空间中的一个有效部分。
  WSAEINPROGRESS:一个阻塞的WINDOWS套接口调用正在运行中。
  WSAEINVAL:level值非法,或optval中的信息非法。
  WSAENETRESET:当SO_KEEPALIVE设置后连接超时。
  WSAENOPROTOOPT:未知或不支持选项。其中,SOCK_STREAM类型的套接口不支持SO_BROADCAST选项,SOCK_DGRAM类型的套接口不支持SO_DONTLINGER 、SO_KEEPALIVE、SO_LINGER和SO_OOBINLINE选项。
  WSAENOTCONN:当设置SO_KEEPALIVE后连接被复位。
  WSAENOTSOCK:描述字不是一个套接口。

用法

  1.设置调用closesocket()后,仍可继续重用该socket。调用closesocket()一般不会立即关闭socket,而经历TIME_WAIT的过程。
  BOOL bReuseaddr = TRUE;
  setsockopt( s, SOL_SOCKET, SO_REUSEADDR, ( const char* )&bReuseaddr, sizeof( BOOL ) );
  2. 如果要已经处于连接状态的soket在调用closesocket()后强制关闭,不经历TIME_WAIT的过程:
  BOOL bDontLinger = FALSE;
  setsockopt( s, SOL_SOCKET, SO_DONTLINGER, ( const char* )&bDontLinger, sizeof( BOOL ) );
  3.在send(),recv()过程中有时由于网络状况等原因,收发不能预期进行,可以设置收发时限:
  int nNetTimeout = 1000; //1秒
  //发送时限
  setsockopt( socket, SOL_SOCKET, SO_SNDTIMEO, ( char * )&nNetTimeout, sizeof( int ) );
  //接收时限
  setsockopt( socket, SOL_SOCKET, SO_RCVTIMEO, ( char * )&nNetTimeout, sizeof( int ) );
  4.在send()的时候,返回的是实际发送出去的字节(同步)或发送到socket缓冲区的字节(异步);系统默认的状态发送和接收一次为8688字节(约
  为8.5K);在实际的过程中如果发送或是接收的数据量比较大,可以设置socket缓冲区,避免send(),recv()不断的循环收发:
  // 接收缓冲区
  int nRecvBuf = 32 * 1024; //设置为32K
  setsockopt( s, SOL_SOCKET, SO_RCVBUF, ( const char* )&nRecvBuf, sizeof( int ) );
  //发送缓冲区
  int nSendBuf = 32*1024; //设置为32K
  setsockopt( s, SOL_SOCKET, SO_SNDBUF, ( const char* )&nSendBuf, sizeof( int ) );
  5.在发送数据的时,不执行由系统缓冲区到socket缓冲区的拷贝,以提高程序的性能:
  int nZero = 0;
  setsockopt( socket, SOL_S0CKET, SO_SNDBUF, ( char * )&nZero, sizeof( nZero ) );
  6.在接收数据时,不执行将socket缓冲区的内容拷贝到系统缓冲区:
  int nZero = 0;
  setsockopt( s, SOL_S0CKET, SO_RCVBUF, ( char * )&nZero, sizeof( int ) );
  7.一般在发送UDP数据报的时候,希望该socket发送的数据具有广播特性:
  BOOL bBroadcast = TRUE;
  setsockopt( s, SOL_SOCKET, SO_BROADCAST, ( const char* )&bBroadcast, sizeof( BOOL ) );
  8.在client连接服务器过程中,如果处于非阻塞模式下的socket在connect()的过程中可以设置connect()延时,直到accpet()被调用(此设置只
  有在非阻塞的过程中有显著的作用,在阻塞的函数调用中作用不大)
  BOOL bConditionalAccept = TRUE;
  setsockopt( s, SOL_SOCKET, SO_CONDITIONAL_ACCEPT, ( const char* )&bConditionalAccept, sizeof( BOOL ) );
  9.如果在发送数据的过程中send()没有完成,还有数据没发送,而调用了closesocket(),以前一般采取的措施是shutdown(s,SD_BOTH),但是数
  据将会丢失。
  某些具体程序要求待未发送完的数据发送出去后再关闭socket,可通过设置让程序满足要求:
  struct linger {
  u_short l_onoff;
  u_short l_linger;
  };
  linger m_sLinger;
  m_sLinger.l_onoff = 1; //在调用closesocket()时还有数据未发送完,允许等待
  // 若m_sLinger.l_onoff=0;则调用closesocket()后强制关闭
  m_sLinger.l_linger = 5; //设置等待时间为5秒
  setsockopt( s, SOL_SOCKET, SO_LINGER, ( const char* )&m_sLinger, sizeof( linger ) );

posted @ 2010-11-04 14:32 郑兴锋 阅读(436) | 评论 (0)编辑 收藏

控制套接口的模式。   
#include <winsock.h>   int PASCAL FAR ioctlsocket( SOCKET s, long cmd, u_long FAR* argp);   
s:一个标识套接口的描述字。   
cmd:对套接口s的操作命令。   
argp:指向cmd命令所带参数的指针。   
注释:   本函数可用于任一状态的任一套接口。它用于获取与套接口相关的操作参数,而与具体协议或通讯子系统无关。支持下列命令:   FIONBIO:允许或禁止套接口s的非阻塞模式。argp指向一个无符号长整型。如允许非阻塞模式则非零,如禁止非阻塞模式则为零。当创建一个套接口时,它就处于阻塞模式(也就是说非阻塞模式被禁止)。这与BSD套接口是一致的。WSAAsynSelect()函数将套接口自动设置为非阻塞模式。如果已对一个套接口进行了WSAAsynSelect() 操作,则任何用ioctlsocket()来把套接口重新设置成阻塞模式的试图将以WSAEINVAL失败。为了把套接口重新设置成阻塞模式,应用程序必须首先用WSAAsynSelect()调用(IEvent参数置为0)来禁至WSAAsynSelect()。   FIONREAD:确定套接口s自动读入的数据量。argp指向一个无符号长整型,其中存有ioctlsocket()的返回值。如果s是SOCKET_STREAM类型,则FIONREAD返回在一次recv()中所接收的所有数据量。这通常与套接口中排队的数据总量相同。如果S是SOCK_DGRAM 型,则FIONREAD返回套接口上排队的第一个数据报大小。   SIOCATMARK:确实是否所有的带外数据都已被读入。这个命令仅适用于SOCK_STREAM类型的套接口,且该套接口已被设置为可以在线接收带外数据(SO_OOBINLINE)。如无带外数据等待读入,则该操作返回TRUE真。否则的话返回FALSE假,下一个recv()或recvfrom()操作将检索“标记”前一些或所有数据。应用程序可用SIOCATMARK操作来确定是否有数据剩下。如果在“紧急”(带外)数据前有常规数据,则按序接收这些数据(请注意,recv()和recvfrom()操作不会在一次调用中混淆常规数据与带外数据)。argp指向一个BOOL型数,ioctlsocket()在其中存入返回值。   兼容性:   本函数为Berkeley套接口函数ioctl()的一个子集。其中没有与FIOASYNC等价的命令,SIOCATMARK是套接口层次支持的唯一命令。   返回值:   成功后,ioctlsocket()返回0。否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()获取相应错误代码。   错误代码:   WSANOTINITIALISED:在使用此API之前应首先成功地调用WSAStartup()。   WSAENETDOWN:WINDOWS套接口实现检测到网络子系统失效。   WSAEINVAL:cmd为非法命令,或者argp所指参数不适用于该cmd命令,或者该命令   不适用于此种类型的套接口。   WSAEINPROGRESS:一个阻塞的WINDOWS套接口调用正在运行中。   WSAENOTSOCK:描述字不是一个套接口。   参见:   socket(), setsockopt(), getsockopt(), WSAAsyncSelect().   该命令     不适用于此种类型的套接口。     WSAEINPROGRESS:一个阻塞的WINDOWS套接口调用正在运行中。     WSAENOTSOCK:描述字不是一个套接口。

posted @ 2010-11-04 12:13 郑兴锋 阅读(306) | 评论 (0)编辑 收藏

2010年10月21日

 
通信模块图

  
  通信关系图
 
其中有不足的地方, 希望朋友们提出意见.

posted @ 2010-10-21 17:51 郑兴锋 阅读(187) | 评论 (0)编辑 收藏

仅列出标题  下一页