手机端基于select/poll的非阻塞Socket

来源:互联网 发布:linux桌面安装包 编辑:程序博客网 时间:2024/03/29 03:03
20161227:
不同于阻塞线程的I/O模型,这种I/O模型是在主线程里面调用send/recv函数,不需要考虑线程安全和加锁,但当手机端从前台切换到后台时,虽然网络连接保持着,网卡上依然有数据到达,但不会调用recv函数读取网卡上的数据,而当从后台又切换回前台时,则会读取到休眠期间到达网卡的大量数据。
关于日志标题中的“非阻塞”:
struct timeval timeout={3,0};//select等待3秒,3秒轮询,要非阻塞就置0
ClientSock::update
timeval TimeoutValue={0,0};
//接收测试,即使掉线也可以有为1的情况
nRetCode=m_pISocketStrem->CheckCanRecv(&TimeoutValue);
//timeout:指定为了完成选择而阻塞当前进程的最大时间间隔,如果不需要,将其设置为NULL。
ClientSock::_SendOrRecvBuffer
const timeval *pcTimeout=NULL;
nRetCode=_CheckCanSend(nSocket,pcTimeout);
基于Epoll模型的消息推送研究与实现
摘要:在Linux系统下的epoll模型是一种优秀的通信模型,它优于select模型和poll模型,在连接数量上限制很小,并且对CPU的负担比较小。
当前使用比较多的消息推送机制(message push)有轮询与长连接2种:1、轮询(polling);2、长连接。
Linux主要支持的I/O模型有select、poll和epoll 3种模型。
epoll没有最大并发连接的限制,连接上限与系统内存有关,即内存越大能并发的连接就越多。
epoll的另一个优点在于它只管“活跃”地并发连接数量,而不用去管连接总数的多少。
epoll对信息的提取使用了共享内存,省略了内存拷贝,从而避免了巨大的资源消耗。
epoll模型的接口主要有以下3个函数:
epoll-create函数可创建一个epoll的句柄。
epoll-ctl函数是epoll的事件注册函数。
epoll-wait函数是等待监听的事件发生。epoll模型有如下2种工作方式:
1、水平触发方式,该方式在套接字下可以支持阻塞与非阻塞工作模式;
2、边缘触发方式,该方式只能支持处理非阻塞工作模式。
网路I/O方式的比较与选择
打通服务器的任督二脉
Linux下高性能网络I/O解决方案分析
摘要:Linux下网络I/O的高性能,主要是指当Socket文件描述符上有大量事件发生时,系统可以快速响应,且其处理能力具有可扩展性。
Linux2.6内核最初是在2002年末开发出来。
随着Linux内核的出现,epoll通过新的机制从根本上解决了select、poll的局限性。
20160612添加:
Cocos2d-x缺了什么:
底层通信模块(TCP/UDP、HTTP)
协议解析
文件跨平台读写
消息推送
游戏付费
第三方平台集成

如何扩展跨平台模块:
定义统一接口
分平台实现底层组件
隔离业务与底层组件的耦合
Android下通过JNI+Java实现平台功能扩展
类工厂模式隔离第三方平台的SDK差异

20160607添加:
chap10 HTTP编程
关于使用HTTP协议来进行通信,首先会简单介绍下libcurl库和pthread库的基础知识,然后从实现基本的数据通信、文件的下载、上传3个方面来讲解。在2.x版本中,线程库使用的是pthread;在3.x版本中,引擎丢弃了pthread库,改成C++ 11标准中的std:thread。
10.2 pthread基础知识
ans\AnsThread.h
typedef void * sp_thread_result_t;
//命名前缀pthread_mutex_表示互斥量
typedef pthread_mutex_t sp_thread_mutex_t;
//命名前缀pthread_cond_表示条件变量
typedef pthread_cond_t  sp_thread_cond_t;
//命名前缀pthread_表示条件变量线程标识和其他的子程序
typedef pthread_t       sp_thread_t;
//命名前缀pthread_attr_表示线程对象的属性
typedef pthread_attr_t  sp_thread_attr_t;

//互斥操作函数:初始化互斥锁
#define sp_thread_mutex_init     pthread_mutex_init
//互斥操作函数:销毁互斥锁
#define sp_thread_mutex_destroy  pthread_mutex_destroy
//互斥操作函数:占有互斥锁(阻塞操作)
#define sp_thread_mutex_lock     pthread_mutex_lock
//互斥操作函数:释放互斥锁
#define sp_thread_mutex_unlock   pthread_mutex_unlock
chap10 POSIX Socket API:本地通信
10.5运行本地Sockets示例
10.6异步I/O
大部分socket APIs阻塞函数调用。这些函数挂起调用进程直到满足某些条件,例如读操作时socket上有可读数据。socket通过select函数提供异步I/O。与其他在给定的时间只能操作一个socket描述符的socket APIs不同,select函数可以操作多个socket描述符并同时监控他们的状态。如果监控的一个事件发生或者到了指定的时限,则函数阻塞。要使用select函数,需要先包含sys/select.h头文件。
_CheckCanSend
int ret=select(nSocket+1,NULL,&fds,NULL,pcTimeout);
if(ret==-1)
{
return -1;//select错误,退出程序
}
if(ret==0)
{
return 0;//没有数据到达
}
if(FD_ISSET(nSocket,&fds))//测试socket是否可写,即是否网络上有数据
{
return 1;//成功
}
_CheckCanRecv
int ret=select(nSocket+1,&fds,NULL,NULL,pcTimeout);
if(ret==-1)
{
return -1;//select错误,退出程序
}
if(ret==0)
{
return 0;//没有数据到达
}
if(FD_ISSET(nSocket,&fds))//测试socket是否可读,即是否网络上有数据
{
return 1;//成功
}
chap11 Socket编程
11.1、select介绍
注释:
  本函数用于确定一个或多个套接口的状态。对每一个套接口,调用者可查询它的可读性、可写性及错误状态信息。用fd_set结构来表示一组等待检查的套接口。在调用返回时,这个结构存有满足一定条件的套接口组的子集,并且select()返回满足条件的套接口的数目。有一组宏可用于对fd_set的操作,这些宏与Berkeley Unix软件中的兼容,但内部的表达是完全不同的。
  readfds参数标识等待可读性检查的套接口。如果该套接口正处于监听listen()状态,则若有连接请求到达,该套接口便被标识为可读,这样一个accept()调用保证可以无阻塞完成。对其他套接口而言,可读性意味着有排队数据供读取。或者对于SOCK_STREAM类型套接口来说,相对于该套接口的虚套接口已关闭,于是recv()或recvfrom()操作均能无阻塞完成。如果虚电路被“优雅地”中止,则recv()不读取数据立即返回;如果虚电路被强制复位,则recv()将以WSAECONNRESET错误立即返回。如果SO_OOBINLINE选项被设置,则将检查带外数据是否存在(参见setsockopt())。
  writefds参数标识等待可写性检查的套接口。如果一个套接口正在connect()连接(非阻塞),可写性意味着连接顺利建立。如果套接口并未处于connect()调用中,可写性意味着send()和sendto()调用将无阻塞完成。〔但并未指出这个保证在多长时间内有效,特别是在多线程环境中〕。
  exceptfds参数标识等待带外数据存在性或意味错误条件检查的套接口。请注意如果设置了SO_OOBINLINE选项为假FALSE,则只能用这种方法来检查带外数据的存在与否。对于SO_STREAM类型套接口,远端造成的连接中止和KEEPALIVE错误都将被作为意味出错。如果套接口正在进行连接connect()(非阻塞方式),则连接试图的失败将会表现在exceptfds参数中。
  如果对readfds、writefds或exceptfds中任一个组类不感兴趣,可将它置为空NULL。
  在winsock.h头文件中共定义了四个宏来操作描述字集。FD_SETSIZE变量用于确定一个集合中最多有多少描述字(FD_SETSIZE缺省值为64,可在包含winsock.h前用#define FD_SETSIZE来改变该值)。对于内部表示,fd_set被表示成一个套接口的队列,最后一个有效元素的后续元素为INVAL_SOCKET。宏为:
  FD_CLR(s,*set):从集合set中删除描述字s。
  FD_ISSET(s,*set):若s为集合中一员,非零;否则为零。
  FD_SET(s,*set):向集合添加描述字s。
  FD_ZERO(*set):将set初始化为空集NULL。
  timeout参数控制select()完成的时间。若timeout参数为空指针,则select()将一直阻塞到有一个描述字满足条件。否则的话,timeout指向一个timeval结构,其中指定了select()调用在返回前等待多长时间。如果timeval为{0,0},则select()立即返回,这可用于探询所选套接口的状态。如果处于这种状态,则select()调用可认为是非阻塞的,且一切适用于非阻塞调用的假设都适用于它。举例来说,阻塞钩子函数不应被调用,且WINDOWS套接口实现不应yield。
返回值:
  select()调用返回处于就绪状态并且已经包含在fd_set结构中的描述字总数;如果超时则返回0;否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()获取相应错误代码。
错误代码:
  WSANOTINITIALISED:在使用此API之前应首先成功地调用WSAStartup()。
  WSAENETDOWN:WINDOWS套接口实现检测到网络子系统失效。
  WSAEINVAL:超时时间值非法。
  WSAEINTR:通过一个WSACancelBlockingCall()来取消一个(阻塞的)调用。
  WSAEINPROGRESS:一个阻塞的WINDOWS套接口调用正在运行中。
  WSAENOTSOCK:描述字集合中包含有非套接口的元素。
第1处:
 //struct timeval timeout={3,0};  //select等待3秒,3秒轮询,要非阻塞就置0
 FD_ZERO(&fds);      //每次循环都要清空集合,否则不能检测描述符变化
 FD_SET(nSocket,&fds);    //添加描述符
 
 int ret = select(nSocket + 1,&fds,NULL,NULL,pcTimeout);   //select使用
 if (ret == -1)
 {
  return -1;   //select错误,退出程序
 }
第2处:
 FD_SET(nSocket,&fds);    //添加描述符
 
 int ret = select(nSocket + 1,&fds,NULL,NULL,pcTimeout);   //select使用
 if (ret == -1)
 {
  return -1;   //select错误,退出程序
 }
 if (ret == 0)
 {
  return 0;   //没有数据到达
第3处:
// return -1: error, 0: timeout, 1: success
static inline int _CheckCanSend(int nSocket, const timeval *pcTimeout)
{
#ifdef WIN32
 struct fd_set fds;
 //struct timeval timeout={3,0};  //select等待3秒,3秒轮询,要非阻塞就置0
 FD_ZERO(&fds);      //每次循环都要清空集合,否则不能检测描述符变化
 FD_SET(nSocket,&fds);    //添加描述符
 int ret = select(nSocket + 1,NULL,&fds,NULL,pcTimeout);   //select使用
网络字节序:网络字节顺序是TCP/IP中规定好的一种数据表示方式,它与具体的CPU类型、操作系统等无关。网络字节序采用big endian排序方式,低字节存低地址,高字节存高地址。
ntohs把unsigned short类型从网络序转为主机序,2dx项目中没有使用
ntohl把unsigned long类型从网络序转为主机序,2dx项目中没有使用
htonl把unsigned long类型从主机序转为网络序,2dx项目中没有使用
htons把unsigned short类型从主机序转为网络序,2dx项目中用到了
WSAStartup函数
    int WSAStartup(
      WORD wVersionRequested, 
      LPWSADATA lpWSAData 
    );
WIN32使用Socket之前必须调用WSAStartup函数。该函数的第一个参数指明程序请求使用的Socket版本,其中高位字节指明副版本、低位字节指明主版本;操作系统利用第二个参数返回请求的Socket的版本信息。当一个应用程序调用WSAStartup函数时,操作系统根据请求的Socket版本来搜索相应的Socket库,然后绑定找到的Socket库到该应用程序中。以后应用程序就可以调用所请求的Socket库中的其它Socket函数了。该函数执行成功后返回0。
2dx项目中使用2.0版本的Socket:
net\GC_Socket.cpp
#ifdef WIN32
 WSADATA wsaData;
 WORD version = MAKEWORD(2, 0);
 int ret = WSAStartup(version, &wsaData);//win sock start up
 if ( ret )
 {
  return NULL;
 }
2dx项目中没有调用WSACleanup函数
int WSACleanup (void);
应用程序在完成对请求的Socket库的使用后,要调用WSACleanup函数来解除与Socket库的绑定并且释放Socket库所占用的系统资源。

11.2 Socket常用函数
11.2.1、socket函数
   SOCKET socket(
      int af,      
      int type,    
      int protocol 
    );
应用程序调用socket函数来创建一个能够进行网络通信的套接字。第一个参数指定应用程序使用的通信协议的协议族,对于TCP/IP协议族,该参数置PF_INET;第二个参数指定要创建的套接字类型,流套接字类型为SOCK_STREAM、数据报套接字类型为SOCK_DGRAM;第三个参数指定应用程序所使用的通信协议。该函数如果调用成功就返回新创建的套接字的描述符,如果失败就返回INVALID_SOCKET。套接字描述符是一个整数类型的值。每个进程的进程空间里都有一个套接字描述符表,该表中存放着套接字描述符和套接字数据结构的对应关系。该表中有一个字段存放新创建的套接字的描述符,另一个字段存放套接字数据结构的地址,因此根据套接字描述符就可以找到其对应的套接字数据结构。每个进程在自己的进程空间里都有一个套接字描述符表但是套接字数据结构都是在操作系统的内核缓冲里。
net\GC_Socket.cpp
第1处:
 nSocket = (int)socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
 KG_PROCESS_ERROR(nSocket != -1);
 pHost = gethostbyname(cszIPAddress);
 if (NULL == pHost)
 {
第2处:
 memset((void *)&ServerAddr, 0, sizeof(sockaddr_in));
 ServerAddr.sin_family       = AF_INET;
 ServerAddr.sin_addr.s_addr  = *(unsigned long *)pHost->h_addr_list[0];
 ServerAddr.sin_port         = htons(nPort);
 nSocket = (int)socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
 KG_PROCESS_ERROR(nSocket != -1);
 nRetCode = bind(nSocket, (struct sockaddr *)&m_BindLoaclAddr, sizeof(sockaddr_in));
 KG_PROCESS_ERROR(nRetCode != -1);

11.2.2、bind函数
   int bind(
      SOCKET s,                         
      const struct sockaddr FAR *name,  
      int namelen                       
    );
    当创建了一个Socket以后,套接字数据结构中有一个默认的IP地址和默认的端口号。一个服务程序必须调用bind函数来给其绑定一个IP地址和一个特定的端口号。客户程序一般不必调用bind函数来为其Socket绑定IP地址和断口号。该函数的第一个参数指定待绑定的Socket描述符;第二个参数指定一个sockaddr结构,该结构是这样定义的:
        struct sockaddr {
            u_short sa_family;
            char sa_data[14];
        };
   sa_family指定地址族,对于TCP/IP协议族的套接字,给其置AF_INET。当对TCP/IP协议族的套接字进行绑定时,我们通常使用另一个地址结构:
        struct sockaddr_in {
            short   sin_family;
            u_short sin_port;
            struct  in_addr sin_addr;
            char    sin_zero[8];
        };
    其中sin_family置AF_INET;sin_port指明端口号;sin_addr结构体中只有一个唯一的字段s_addr,表示IP地址,该字段是一个整数,一般用函数inet_addr()把字符串形式的IP地址转换成unsigned long型的整数值后再置给s_addr。有的服务器是多宿主机,至少有两个网卡,那么运行在这样的服务器上的服务程序在为其Socket绑定IP地址时可以把htonl(INADDR_ANY)置给s_addr,这样做的好处是不论哪个网段上的客户程序都能与该服务程序通信;如果只给运行在多宿主机上的服务程序的Socket绑定一个固定的IP地址,那么就只有与该IP地址处于同一个网段上的客户程序才能与该服务程序通信。我们用0来填充sin_zero数组,目的是让sockaddr_in结构的大小与sockaddr结构的大小一致。
2dx项目中bind函数调用的例子:
 ServerAddr.sin_port         = htons(nPort);
 nSocket = (int)socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
 KG_PROCESS_ERROR(nSocket != -1);
 nRetCode = bind(nSocket, (struct sockaddr *)&m_BindLoaclAddr, sizeof(sockaddr_in));
 KG_PROCESS_ERROR(nRetCode != -1);
 {
  // 屏蔽SIGPIPE信号。其实不需要每次都做这个操作,但是Connector没有Init函数,所以只能暂时先放这里了。
  typedef void (*SignalHandlerPointer)(int);

11.2.3、2dx项目中没有用到listen函数
    int listen( SOCKET s, int backlog );
服务程序可以调用listen函数使其流套接字s处于监听状态。处于监听状态的流套接字s将维护一个客户连接请求队列,该队列最多容纳backlog个客户连接请求。假如该函数执行成功,则返回0;如果执行失败,则返回SOCKET_ERROR。

11.2.4、2dx项目中没有用到accept函数
    SOCKET accept(
      SOCKET s,
      struct sockaddr FAR *addr, 
      int FAR *addrlen 
    );
服务程序调用accept函数从处于监听状态的流套接字s的客户连接请求队列中取出排在最前的一个客户请求,并且创建一个新的套接字来与客户套接字创建连接通道,如果连接成功,就返回新创建的套接字的描述符,以后与客户套接字交换数据的是新创建的套接字;如果失败就返回INVALID_SOCKET。该函数的第一个参数指定处于监听状态的流套接字;操作系统利用第二个参数来返回新创建的套接字的地址结构;操作系统利用第三个参数来返回新创建的套接字的地址结构的长度。

11.2.5、connect函数
    int connect(
      SOCKET s,                         
      const struct sockaddr FAR *name, 
      int namelen                       
    );
客户程序调用connect函数来使客户Socket s与监听于name所指定的计算机的特定端口上的服务Socket进行连接。如果连接成功,connect返回0;如果失败则返回SOCKET_ERROR。
第1处:
 //struct sockaddr_in svraddr;
 ServerAddr.sin_family = AF_INET;
 ServerAddr.sin_addr.s_addr = *(unsigned long *)pHost->h_addr_list[0];//inet_addr(cszIPAddress);
 ServerAddr.sin_port = htons(nPort);
 ret = connect(nSocket, (struct sockaddr*)&ServerAddr, sizeof(ServerAddr));
 
 if ( ret == SOCKET_ERROR )
 {
  return NULL;
 }
第2处:
 nRetCode = setsockopt(nSocket, SOL_SOCKET, SO_SNDTIMEO, (const char*)(&TimeOutConnect), sizeof(TimeOutConnect));
 KG_PROCESS_ERROR(nRetCode ==0 );
 nRetCode = connect(nSocket, (struct sockaddr *)&ServerAddr, sizeof(sockaddr_in));
 KG_PROCESS_ERROR(nRetCode != -1);
 TimeOutConnect.tv_sec = 75;
 TimeOutConnect.tv_usec = 0;
 nRetCode = setsockopt(nSocket, SOL_SOCKET, SO_SNDTIMEO, (const char*)(&TimeOutConnect), sizeof(TimeOutConnect));

11.2.6、send函数
   int send(
      SOCKET s,             
      const char FAR *buf, 
      int len,              
      int flags             
    );
不论是客户还是服务器应用程序都用send函数来向TCP连接的另一端发送数据。客户程序一般用send函数向服务器发送请求,而服务器则通常用send函数来向客户程序发送应答。该函数的第一个参数指定发送端套接字描述符;第二个参数指明一个存放应用程序要发送数据的缓冲区;第三个参数指明实际要发送的数据的字节数;第四个参数一般置0。这里只描述同步Socket的send函数的执行流程。当调用该函数时,send先比较待发送数据的长度len和套接字s的发送缓冲区的长度,如果len大于s的发送缓冲区的长度,该函数返回SOCKET_ERROR;如果len小于或者等于s的发送缓冲区的长度,那么send先检查协议是否正在发送s的发送缓冲中的数据,如果是就等待协议把数据发送完,如果协议还没有开始发送s的发送缓冲中的数据或者s的发送缓冲中没有数据,那么send就比较s的发送缓冲区的剩余空间和len,如果len大于剩余空间大小send就一直等待协议把s的发送缓冲中的数据发送完,如果len小于剩余空间大小send就仅仅把buf中的数据copy到剩余空间里(注意并不是send把s的发送缓冲中的数据传到连接的另一端的,而是协议传的,send仅仅是把buf中的数据copy到s的发送缓冲区的剩余空间里)。如果send函数copy数据成功,就返回实际copy的字节数,如果send在copy数据时出现错误,那么send就返回SOCKET_ERROR;如果send在等待协议传送数据时网络断开的话,那么send函数也返回SOCKET_ERROR。要注意send函数把buf中的数据成功copy到s的发送缓冲的剩余空间里后它就返回了,但是此时这些数据并不一定马上被传到连接的另一端。如果协议在后续的传送过程中出现网络错误的话,那么下一个Socket函数就会返回SOCKET_ERROR。(每一个除send外的Socket函数在执行的最开始总要先等待套接字的发送缓冲中的数据被协议传送完毕才能继续,如果在等待时出现网络错误,那么该Socket函数就返回SOCKET_ERROR)
注意:在Unix系统下,如果send在等待协议传送数据时网络断开的话,调用send的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。
第1处:
    if (nRetCode)   // if can restore then continue
     continue;
    goto Exit0;
   }
   nRetCode = send(nSocket, (char *)pbyBuffer, *nSize, 0);
  }
  else    // recv
  {
   //when timeout disabled,pTimeout was set to NULL.
   nRetCode = _CheckCanRecv(nSocket, pcTimeout);
第2处:
 volatile int nData = false; // no use

 KG_PROCESS_ERROR(m_nSocketHandle != -1);
 nRetCode = send(m_nSocketHandle, (char *)&nData, 0, 0);
 KG_PROCESS_ERROR(nRetCode != -1);
 nRetsult = true;
Exit0:
 return nRetsult;

11.2.7、recv函数
    int recv(
      SOCKET s,      
      char FAR *buf, 
      int len,       
      int flags      
    );
不论是客户还是服务器应用程序都用recv函数从TCP连接的另一端接收数据。该函数的第一个参数指定接收端套接字描述符;第二个参数指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;第三个参数指明buf的长度;第四个参数一般置0。这里只描述同步Socket的recv函数的执行流程。当应用程序调用recv函数时,recv先等待s的发送缓冲中的数据被协议传送完毕,如果协议在传送s的发送缓冲中的数据时出现网络错误,那么recv函数返回SOCKET_ERROR,如果s的发送缓冲中没有数据或者数据被协议成功发送完毕后,recv先检查套接字s的接收缓冲区,如果s接收缓冲区中没有数据或者协议正在接收数据,那么recv就一直等待,只到协议把数据接收完毕。当协议把数据接收完毕,recv函数就把s的接收缓冲中的数据copy到buf中(注意协议接收到的数据可能大于buf的长度,所以在这种情况下要调用几次recv函数才能把s的接收缓冲中的数据copy完。recv函数仅仅是copy数据,真正的接收数据是协议来完成的),recv函数返回其实际copy的字节数。如果recv在copy时出错,那么它返回SOCKET_ERROR;如果recv函数在等待协议接收数据时网络中断了,那么它返回0。
注意:在Unix系统下,如果recv函数在等待协议接收数据时网络断开了,那么调用recv的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。
    if (nRetCode)   // if can restore then continue
     continue;
    goto Exit0;
   }
   nRetCode = recv(nSocket, (char *)pbyBuffer, *nSize, 0);
   *nSize = nRetCode;
  }
  if (nRetCode == 0)  // Disconnected
   goto Exit0;
11.2.8、2dx项目中没有用到sendto函数
11.2.9、2dx项目中没有用到recvfrom函数
11.2.10、closesocket函数
   int closesocket(
      SOCKET s 
    );
closesocket函数用来关闭一个描述符为s套接字。由于每个进程中都有一个套接字描述符表,表中的每个套接字描述符都对应了一个位于操作系统缓冲区中的套接字数据结构,因此有可能有几个套接字描述符指向同一个套接字数据结构。套接字数据结构中专门有一个字段存放该结构的被引用次数,即有多少个套接字描述符指向该结构。当调用closesocket函数时,操作系统先检查套接字数据结构中的该字段的值,如果为1,就表明只有一个套接字描述符指向它,因此操作系统就先把s在套接字描述符表中对应的那条表项清除,并且释放s对应的套接字数据结构;如果该字段大于1,那么操作系统仅仅清除s在套接字描述符表中的对应表项,并且把s对应的套接字数据结构的引用次数减1。
closesocket函数如果执行成功就返回0,否则返回SOCKET_ERROR。
static inline int _CloseSocket(int nSocket)
{
#ifdef WIN32
 return closesocket(nSocket);
#else
 struct linger lingerStruct;
 lingerStruct.l_onoff = 1;
 lingerStruct.l_linger = 0;
11.2.11、2dx项目中没有用到shutdown函数
11.2.12、2dx项目中没有用到gethostname函数
11.2.13、gethostbyname函数返回对应于给定主机名的主机信息
根据主机名称返回相应的信息
#include <winsock.h>
[声明]
struct hostent FAR * PASCAL FAR gethostbyname(const char FAR * name);
struct hostent FAR * gethostbyname ( const char FAR *name );
[参数]
name - 指向主机名字符串的指针
[返回值]
当函数成功调用时返回主机信息
失败时返回NULL(空值)
struct hostent FAR *PASCAL FAR gethostbyname(const char
FAR * name);
Public Declare Function gethostbyname Lib "WSOCK32.DLL" _
   (ByVal szHost As String) As Long
name:指向主机名的指针。
注释:
    gethostbyname()返回对应于给定主机名的包含主机名字和地址信息的hostent结构指针。结构的声明与gethostaddr()中一致。
返回的指针指向一个由Windows Sockets实现分配的结构。应用程序不应该试图修改这个结构或者释放它的任何部分。此外,每一线程仅有一份这个结构的拷贝,所以应用程序应该在发出其他Windows Scokets API调用前,把自己所需的信息拷贝下来。
gethostbyname()实现没有必要识别传送给它的IP地址串。对于这样的请求,应该把IP地址串当作一个未知主机名同样处理。如果应用程序有IP地址串需要处理,它应该使用inet_addr()函数把地址串转换为IP地址,然后调用gethostbyaddr()来得到hostent结构。
返回值:
    如果没有错误发生,gethostbyname()返回如上所述的一个指向hostent结构的指针,否则,返回一个空指针。应用程序可以通过WSAGetLastError()来得到一个特定的错误代码。
错误代码:
WSANOTINTIALISED  在应用这个API前,必须成功地调用WSAStartup()。
WSAENTDOWN        Windows Sockets实现检测到了网络子系统的错误。
WSAHOST_NOT_FOUND 没有找到授权应答主机。
WSATRY_AGAIN      没有找到非授权主机,或者SERVERFAIL。
WSANO_RECOVERY    无法恢复的错误,FORMERR,REFUSED,NOTIMP。
WSANO_DATA        有效的名字,但没有关于请求类型的数据记录。
WSAEINPROGRESS    一个阻塞的Windows Sockets操作正在进行。
WSAEINTR          阻塞调用被WSACancelBlockingCall()取消了.
第1处:
 nSocket = (int)socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
 KG_PROCESS_ERROR(nSocket != -1);
 pHost = gethostbyname(cszIPAddress);
 if (NULL == pHost)
 {
  return NULL;
 }
第2处:
#else
 
 
 pHost = gethostbyname(cszIPAddress);
 KG_PROCESS_ERROR(pHost);
 memset((void *)&ServerAddr, 0, sizeof(sockaddr_in));
 ServerAddr.sin_family       = AF_INET;
 ServerAddr.sin_addr.s_addr  = *(unsigned long *)pHost->h_addr_list[0];

11.2.14、2dx项目中没有用到getpeername函数
11.2.15、setsockopt函数
int PASCAL FAR setsockopt (SOCKET s,
int level,
int optname,
const char FAR * optval,
int optlen);
注释:
  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处:
 struct linger lingerStruct;
 lingerStruct.l_onoff = 1;
 lingerStruct.l_linger = 0;
 setsockopt(
  nSocket,
  SOL_SOCKET, SO_LINGER,
  (char *)&lingerStruct, sizeof(lingerStruct)
  );
第2处:
  pSignalHandler = signal(SIGPIPE, SIG_IGN);
  KG_PROCESS_ERROR(pSignalHandler != SIG_ERR);  // write when connection reset.
 }

 nRetCode = setsockopt(nSocket, SOL_SOCKET, SO_SNDTIMEO, (const char*)(&TimeOutConnect), sizeof(TimeOutConnect));
 KG_PROCESS_ERROR(nRetCode ==0 );
 nRetCode = connect(nSocket, (struct sockaddr *)&ServerAddr, sizeof(sockaddr_in));
 KG_PROCESS_ERROR(nRetCode != -1);
第3处:
 nRetCode = connect(nSocket, (struct sockaddr *)&ServerAddr, sizeof(sockaddr_in));
 KG_PROCESS_ERROR(nRetCode != -1);
 TimeOutConnect.tv_sec = 75;
 TimeOutConnect.tv_usec = 0;
 nRetCode = setsockopt(nSocket, SOL_SOCKET, SO_SNDTIMEO, (const char*)(&TimeOutConnect), sizeof(TimeOutConnect));
 KG_PROCESS_ERROR(nRetCode ==0 );
#endif
 piResult = new GC_SocketStream(nSocket, &ServerAddr);
 KG_PROCESS_ERROR(piResult);
11.2.16、2dx项目中没有用到getsockopt函数







0 0
原创粉丝点击