关于tcpc socket tcp非阻塞读的问题:应用如何在指定时间内读取指定长度的数据!

socket recv函数接受长度的问题
[问题点数:30分,结帖人keyboard330]
本版专家分:30
结帖率 100%
CSDN今日推荐
本版专家分:5427
2012年11月 其他开发语言大版内专家分月排行榜第一2011年5月 其他开发语言大版内专家分月排行榜第一2010年6月 其他开发语言大版内专家分月排行榜第一2010年5月 其他开发语言大版内专家分月排行榜第一2010年4月 其他开发语言大版内专家分月排行榜第一2008年1月 其他开发语言大版内专家分月排行榜第一2007年12月 其他开发语言大版内专家分月排行榜第一2007年11月 其他开发语言大版内专家分月排行榜第一2007年4月 其他开发语言大版内专家分月排行榜第一2006年12月 其他开发语言大版内专家分月排行榜第一2006年11月 其他开发语言大版内专家分月排行榜第一2006年10月 其他开发语言大版内专家分月排行榜第一2006年7月 其他开发语言大版内专家分月排行榜第一2006年6月 其他开发语言大版内专家分月排行榜第一2005年11月 其他开发语言大版内专家分月排行榜第一2005年10月 其他开发语言大版内专家分月排行榜第一2005年9月 其他开发语言大版内专家分月排行榜第一2005年6月 其他开发语言大版内专家分月排行榜第一2005年5月 其他开发语言大版内专家分月排行榜第一2005年3月 其他开发语言大版内专家分月排行榜第一2005年2月 其他开发语言大版内专家分月排行榜第一2004年10月 其他开发语言大版内专家分月排行榜第一2004年9月 其他开发语言大版内专家分月排行榜第一2005年2月 硬件使用大版内专家分月排行榜第一2004年8月 硬件/嵌入开发大版内专家分月排行榜第一
2012年10月 其他开发语言大版内专家分月排行榜第二2011年7月 其他开发语言大版内专家分月排行榜第二2010年3月 其他开发语言大版内专家分月排行榜第二2007年10月 其他开发语言大版内专家分月排行榜第二2007年9月 其他开发语言大版内专家分月排行榜第二2005年3月 Windows专区大版内专家分月排行榜第二2005年2月 Windows专区大版内专家分月排行榜第二2005年6月 扩充话题大版内专家分月排行榜第二2006年9月 其他开发语言大版内专家分月排行榜第二2006年5月 其他开发语言大版内专家分月排行榜第二2006年3月 其他开发语言大版内专家分月排行榜第二2006年2月 其他开发语言大版内专家分月排行榜第二2005年12月 其他开发语言大版内专家分月排行榜第二2005年4月 其他开发语言大版内专家分月排行榜第二2004年11月 其他开发语言大版内专家分月排行榜第二2005年3月 硬件使用大版内专家分月排行榜第二
2011年11月 其他开发语言大版内专家分月排行榜第三2011年8月 其他开发语言大版内专家分月排行榜第三2008年10月 其他开发语言大版内专家分月排行榜第三2004年9月 硬件/嵌入开发大版内专家分月排行榜第三
本版专家分:10355
本版专家分:10355
本版专家分:10355
本版专家分:203
本版专家分:10355
本版专家分:44101
2018年6月 C/C++大版内专家分月排行榜第二2018年1月 C/C++大版内专家分月排行榜第二2017年12月 C/C++大版内专家分月排行榜第二2017年8月 C/C++大版内专家分月排行榜第二
2018年5月 C/C++大版内专家分月排行榜第三2018年4月 C/C++大版内专家分月排行榜第三2018年3月 C/C++大版内专家分月排行榜第三2018年2月 C/C++大版内专家分月排行榜第三2017年11月 C/C++大版内专家分月排行榜第三2017年10月 C/C++大版内专家分月排行榜第三2017年9月 C/C++大版内专家分月排行榜第三2017年6月 C/C++大版内专家分月排行榜第三2017年5月 C/C++大版内专家分月排行榜第三2017年4月 C/C++大版内专家分月排行榜第三2017年3月 C/C++大版内专家分月排行榜第三
本版专家分:30
结帖率 100%
本版专家分:368047
2017年 总版技术专家分年内排行榜第一
2014年 总版技术专家分年内排行榜第二
2013年 总版技术专家分年内排行榜第三
2012年 总版技术专家分年内排行榜第七
匿名用户不能发表回复!|
CSDN今日推荐嵌入式 软硬件
socket 设置阻塞和超时
1669人阅读
为阻塞或非阻塞
1,ioctlsocket()
#include &winsock.h&
This function controls the I/O mode of a socket.
int ioctlsocket(
u_long FAR* argp
Parameters
s[in] Descriptor identifying a socket.cmd[in] Command to perform on socket s.argp[in, out] Pointer to a parameter for cmd.
Return Values
If no error occurs, this function returns zero. If an error occurs, a value of SOCKET_ERROR is returned, and a specific error code can be retrieved by calling .
The following table shows a list of possible error codes.
u_long mode = 0;
ioctlsocket(s,FIONBIO,&mode);
控制为阻塞方式。
u_long mode = 1;
ioctlsocket(s,FIONBIO,&mode);
控制为非阻塞方式。
本函数可用于任一状态的任一套接口。它用于获取与套接口相关的操作参数,而与具体协议或通讯子系统无关。支持下列命令:
  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()在其中存入返回值。
2,setsockopt()  简述:
Call this member function to set a socket option.
BOOL SetSockOpt(
int nOptionName,
const void* lpOptionValue,
int nOptionLen,
int nLevel = SOL_SOCKET
nOptionName
The socket option for which the value is to be set.
lpOptionValue
A pointer to the buffer in which the value for the requested option is supplied.
nOptionLen
The size of the lpOptionValue buffer in bytes.
The level at which t the only supported levels are SOL_SOCKET and IPPROTO_TCP.
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_
  int l_
  为了允许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_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.在接收数据时,不执行将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_
  u_short l_
  linger m_sL
  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 ) );
没有更多推荐了,
加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!socket发送和接收固定长度数据的函数
#include "stdafx.h"
#include "ATLComTime.h"
#include "Functions.h"
comment(lib,
"rpcrt4.lib ")
CRITICAL_SECTION _criticalL
extern string _ExeP
//接收數據包
int SocketRecvFunc(SOCKET s, char *buf, int len, int timeout)
{fd_int iRecvlen, iRead, iRecEint iResult=0;LONG lRecvSize =timeval time={timeout,0};//超时时长5秒 while( s != INVALID_SOCKET ){FD_ZERO(&fdread);FD_SET(s,&fdread);iRead = select( 0, &fdread, NULL, NULL, &time );if( iRead == 0 )//接收超时if( iRead == SOCKET_ERROR ){iResult = iR}if( iRead & 0 ){if( FD_ISSET(s, &fdread) ){iRecvlen = recv( s, buf+(len-lRecvSize), lRecvSize, NULL );if( iRecvlen == 0 ){iResult = iR}if( iRecvlen == SOCKET_ERROR ){iRecError = WSAGetLastError();if( (iRecError == WSAENOTCONN)//(WSABASEERR+57)|| (iRecError == WSAESHUTDOWN)//(WSABASEERR+58)|| (iRecError == WSAENOTSOCK)
|| (iRecError == WSAENETDOWN)
|| (iRecError == WSAECONNABORTED)//(WSABASEERR+53)|| (iRecError == WSAETIMEDOUT)//(WSABASEERR+60)|| (iRecError == WSAECONNRESET) )//(WSABASEERR+54){iResult = iR}}lRecvSize -= iRiResult += iR//接收到完整数据包后再处理(因为每次recv的数据长度不一定等于发送包的长度)if (lRecvSize &= 0)}}}return iR
//发送数据包
int SocketSendFunc(SOCKET s, const char *buf, int len)
{fd_int iSint lSendSize=0;int iResult=0;int iWtimeval timeout={2,0};//超时时长
do{FD_ZERO(&fdwrite);FD_SET(s,&fdwrite);iWrite = select(0, NULL, &fdwrite, NULL, &timeout);if (iWrite == 0){iResult = iW}if (iWrite == SOCKET_ERROR){iResult = iW}if (iWrite & 0)if (FD_ISSET(s,&fdwrite)){iSendlen = send(s, buf+lSendSize, len-lSendSize, NULL);if (iSendlen == SOCKET_ERROR){iResult = iS}elselSendSize += iSiResult = lSendS}}while((lSendSize & len) && (s != INVALID_SOCKET));//判断是否发送完毕return iR
int SocketSendFunc(SOCKET s, const char *buf, int len, bool* blockflag)
{fd_int iSint lSendSize=0;int iResult=0;int iWtimeval timeout={2,0};//超时时长
do{FD_ZERO(&fdwrite);FD_SET(s,&fdwrite);iWrite = select(0, NULL, &fdwrite, NULL, &timeout);if (iWrite == 0){if (*blockflag)iResult = iW}if (iWrite == SOCKET_ERROR){iResult = iW}if (iWrite & 0)if (FD_ISSET(s,&fdwrite)){iSendlen = send(s, buf+lSendSize, len-lSendSize, NULL);if (iSendlen == SOCKET_ERROR){iResult = iS}elselSendSize += iSiResult = lSendS}}while((lSendSize & len) && (s != INVALID_SOCKET));//判断是否发送完毕return iR
UINT ConnectToServer(char* szServer, WORD nPort)
{SOCKET sSstruct sockaddr_FD_SETu_long value=1;TIMEVALsSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);if (sSocket == INVALID_SOCKET){sSocket = 0;}else{serveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(nPort);serveraddr.sin_addr.s_addr = inet_addr(szServer);ioctlsocket(sSocket,FIONBIO,&value);//设置为非阻塞connect(sSocket,(struct sockaddr *)&serveraddr,sizeof(serveraddr));timeout.tv_sec=2;timeout.tv_usec=0;FD_ZERO(&mask);FD_SET(sSocket,&mask);value=select(NULL,NULL,&mask,NULL,&timeout);if(value && value!=SOCKET_ERROR){ //连接成功value = 0;ioctlsocket(sSocket,FIONBIO,&value);//设置为阻塞}else{shutdown(sSocket,SD_BOTH);closesocket(sSocket);sSocket = 0;}}return sS
void WriteLogFile(char* szLog)
{COleDateTime t(COleDateTime::GetCurrentTime());FILE *file=NULL;errno_char szTxt[128]={};LockCS lock( &_criticalLog );sprintf_s( szTxt, sizeof(szTxt), "%d-%d-%d %d:%d:%d
", t.GetYear(), t.GetMonth(), t.GetDay(), t.GetHour(), t.GetMinute(), t.GetSecond() );
if( 0 == (err
= fopen_s( &file, "MRVideoServer.log", "a+" ))){if( file != NULL ){fprintf_s( file, "%s", szTxt );fflush(file);fprintf_s( file, "%s", szLog );fflush(file);fprintf_s( file, "\r\n" );fflush(file);fclose(file);}}
void WriteLogFileEx(char* szLog,char* szPath)
{COleDateTime t(COleDateTime::GetCurrentTime());FILE *file=NULL;errno_char szTxt[128]={};LockCS lock( &_criticalLog );sprintf_s( szTxt, sizeof(szTxt), "%d-%d-%d %d:%d:%d
", t.GetYear(), t.GetMonth(), t.GetDay(), t.GetHour(), t.GetMinute(), t.GetSecond() );
if( 0 == (err
= fopen_s( &file, szPath, "a+" ))){if( file != NULL ){fprintf_s( file, "%s", szTxt );fflush(file);fprintf_s( file, "%s", szLog );fflush(file);fprintf_s( file, "\r\n" );fflush(file);fclose(file);}}
int MakeVideoID(short ConnectID,short SubID)
{int iResult = 0;if ((0 & ConnectID ) || (0 & SubID))iResult = -1;elseiResult = (ConnectID && 16) | (SubID);return iR
void AnalysisID(int iID,short* pConnectID,short* pSubID)
{if (0 & iID){*pConnectID = -1;*pSubID = -1;}else{*pConnectID = (iID && 16);*pSubID = (short)iID;}
void DwordIPToChar(DWORD dwIP,char* szIP)
{char* szTempIP = NULL;struct sockaddr_addr.sin_family = AF_INET;addr.sin_addr.S_un.S_addr = dwIP;szTempIP = inet_ntoa(addr.sin_addr);strcpy_s(szIP,128,szTempIP);
DWORD CharIPToDWORD(const char* szIP)
{return inet_addr(szIP);
bool compare(const GUID &guidl, const GUID &guidr)
{char *pszGuid_l = NULL;char *pszGuid_r = NULL;string guid_l = "";string guid_r = "";UuidToStringA(&guidl,(RPC_CSTR*)&pszGuid_l);UuidToStringA(&guidr,(RPC_CSTR*)&pszGuid_r);guid_l = pszGuid_l;guid_r = pszGuid_r;if(guid_l.compare(guid_r) & 0)else
bool MyIsEqualGuid(const GUID &guid1, const GUID &guid2)
{return ((guid1.Data1 == guid2.Data1) && (guid1.Data2 == guid2.Data2));
bool IsFileExists(const char* lpszFileName)
WIN32_FIND_DATAA
hFind = FindFirstFileA(lpszFileName,
= (hFind != INVALID_HANDLE_VALUE);
FindClose(hFind);
没有更多推荐了,
加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!看到很多PHP程序员职业规划的文章,都是直接上来就提Linux、PHP、MySQL、Nginx、Redis、Memcache、jQuery这些,然后就直接上手搭环境、做项目,中级就是学习各种PHP框架和类库,高级阶段就是MySQL优化、PHP内核与扩展、架构设计这些了。
这些文章都存在一个严重的缺陷,不重视基础。就好比练武功,只求速成,不修炼内功和心法,只练各种招式,这样能高到哪里去?我所见过的PHP大牛每一个都是具备非常扎实的基础,他们之所以能成为大牛,是因为基础足够好。基础不稳,面对技术复杂的系统,如同盲人摸象、管中窥豹,只得其门不得其法。而且如果基础不扎实,也没办法进入大公司。国外的Google、Facebook,国内的腾讯、阿里、百度、滴滴、京东、新浪等知名互联网企业,无论哪一家公司面试必然会考验应聘者的技术功底。无法进入一个拥有大规模并发请求的项目中得到历练,不坚持提升自己,那也只能在小公司混日子了。
我最开始工作也是在2家小公司,后来加入腾讯阿里,主要原因还是我坚持学习基础知识,从而得倒了这个机会。有几个方面的基础知识,我建议每一位PHP程序员都应该好好学习一下。我推荐几本书给大家,包括深入理解计算机系统、现代操作系统、C程序设计语言、C语言数据结构和算法、Unix环境高级编程、TCP/IP网络通信详解。另外我建议大家学习一下面向对象方面知识,PHP这方面的书不太多,建议看Java面向对象编程、Java编程思想、J2EE这些书。PHP语言基础方面,建议认真地把PHP5权威编程这本书好好读完。另外不光要读,还要照着书中的讲解动手去编程实践。
总之有一个好的基础,再去学LAMP、Redis、PHP框架、前端,这样取得的成就更大。这与年龄无关、与学历无关、与智力无关,与天赋也无关。只要肯努力学习,人人可以成为技术大牛。
JS程序员总是嘲笑PHP没有闭包,今天抽空写一篇文章来专门介绍一下PHP的闭包。从5.3版本开始PHP就增加了匿名函数支持,经过数个版本迭代到现在的PHP5.6、PHP7,PHP语言的闭包已经非常完善了。再结合Swoole提供的事件驱动支持,PHP的闭包功能非常强大而且很优雅。
匿名函数是闭包的核心,匿名函数在PHP里实际上是一个Closure类的对象(请注意是对象)。与普通的面向对象编程方式不同,匿名函数的代码是直接写在调用处的,不需要额外写一个类,编写方法的代码。这样的好处就是更直接。下面的示例是设置一个定时器,每2秒输出hello world。
function timer () {
echo "hello world";
Swoole\Timer::tick(2000, 'timer');
Swoole\Timer::tick(2000, function () {
echo "hello world";
非闭包的传统写法,先要声明一个函数,再转入函数名称字符串。两段代码是分离的,不够直观。而闭包的写法把定时器的声明和定时器要执行的代码写在了一起,逻辑非常清晰直观。使用闭包语法可以很方便编写回调函数。在事件驱动编程、排序、array_walk等需要用户传入一段执行代码的场景中,闭包的写法非常优雅。
闭包更强大的地方在于它可以直接在调用处引入外部变量。PHP中实现的方法就是use关键词。
如果刚才的定时器需要传入一个变量,传统的写法只能通过全局变量来实现。与JS不同,PHP的变量引入是显式的,如果要引用外部变量必须使用use来声明。而JS是隐式的,匿名函数内部可以随意操作外部变量,无需声明。这样好处是少写了一点代码,缺点是存在风险和混乱。
$str = "hello world";
function timer () {
Swoole\Timer::tick(2000, 'timer');
$str = "hello world";
Swoole\Timer::tick(2000, function () use ($str) {
闭包写法使用use直接引入了当前的$str变量,而不需要使用global全局变量。另外如果是在swoole的事件驱动编程模式,使用global就无法实现异步并发了,因为global全局变量只有1个,如果同时有多个客户端请求,每个请求要查询数据库,输出不同的内容,传统的编程方法就不太容易实现,需要使用全局变量数组,以客户端的ID为KEY保存各自的数据。
$requestArray = array();
$dbResultArray = array();
function my_request($request, $response) {
global $dbResultArray, $requestA
$queryId = $db->query($sql, 'get_result');
$requestArray[$request->fd] = array($request, $response);
$dbResultArray[$queryId] = $request->
function get_result($queryId, $queryResult) {
global $dbResultArray, $requestA
list($request, $response) = $requestArray[$dbResultArray[$queryId]];
$response->end($queryResult);
$server-&on('request', 'my_request');
$server->on('request', function ($request, $response) {
$queryId = $db->query($sql, function ($queryId, $queryResult) use ($request, $response) {
$response->end($queryResult);
传统的写法非常复杂,需要反复多次从全局数组保存/提取数据。而闭包的写法非常简洁优雅,只用了几行代码就实现了同样的功能。闭包写法非常适合用来编写异步非阻塞回调模式的服务器程序。目前热门的编程语言中只有PHP和JS具备这种能力。
闭包更多特性
在类的方法中使用匿名函数,5.4以上的版本无需使用use引入$this,直接可以在匿名函数中使用$this来调用当前对象的方法。在swoole编程中,可以利用此特性减少$serv对象的use引入传递。
class Server extends Swoole\Server {
function onReceive($serv, $fd, $reactorId, $data) {
$db->query($sql, function ($queryId, $queryResult) use ($fd) {
$this->send($fd, $queryResult);
另外如果希望在闭包函数中修改外部变量,可以在use时为变量增加&引用符号即可。注意对象类型不需要加&,因为在PHP中对象默认就是传引用而非传值。
Swoole扩展自带的Task进程功能非常强大,可以用来实现各种复杂的业务逻辑。本文主要介绍使用task/finish功能实现程序内的Map-Reduce并发任务处理。一个聊天服务经常会有群聊需求,我的群组和群组内成员,另外群组内成员需要按照积分排序,类似与这样的功能就可以使用Swoole简单实现。
传统多线程方案
创建2个全局变量Map,group_map以group_id为Key,存储成员set。user_map以uid为Key存储当前用户加入的所有group。
多线程环境下实际上不能直接操作这2个Map,必须要加锁。当添加用户到一个组或者用户退出一个组时需要操作这2个map,必须要加锁。如果操作很频繁,实际上锁的碰撞是很严重的,这部分操作就会变成串行的。同时只有一个线程可以对map进行操作。锁的争抢也会带来大量线程切换浪费很多CPU资源。
lock.lock();
group_map[group_id].append([uid, score]);
user_map[uid].append(group_id);
group_map.sortByScore();
lock.unlock();
基于Swoole的Task功能
基于Swoole的Task功能,可以将任务切片,然后hash投递到不同的Task进程,完成任务。排序功能可以直接使用PHP提供的SplHeap实现,时间复杂度为O(logn),如果要实现查询功能,如根据UID查询用户加入的所有群组,根据GroupId查询有哪些成员。可以先计算Hash找到对应Task进程,然后通过task/taskwait发送指令,直接读取进程的变量查找到信息。
$serv->set(array("task_worker_num" => 24));
$serv->task(array("cmd" => "user", "uid" => $uid, "gid" => $gid, "score" => $score), $gid % $task_worker_num);
$serv->task(array("cmd" => "group", "uid" => $uid, "gid" => $gid), $uid % $task_worker_num);
class MyMaxHeap extends SplHeap
public function compare($value1, $value2)
return ($value1['score'] - $value2['score']);
function onTask($serv, $taskId, $srcWorkerId, $data) {
static $userMap = array();
static $groupMap = array();
if ($data['cmd'] == 'group')
if (!isset($groupMap[$data['gid']]))
$groupMap[$data['gid']] = new MyMaxHeap();
$heap = $groupMap[$data['gid']];
$heap->insert(array("uid" => $data['uid'], "score" => $data['score']));
elseif ($data['cmd'] == 'user')
$userMap[$data['uid']][] = $data['gid'];
由于Task进程只有数组操作,所以是非阻塞的,只需要开启与CPU核数相同的进程数量即可。进程间无任何加锁争抢,性能非常好。Swoole的Task进程通信使用UnixSocket,是内核提供的全内存通信方式无任何IO,一写一读单进程可达100万/秒。虽然没有直接读变量的速度快,但性能也足够了。
————–伟大的分割线—————
PHP饭米粒(phpfamily) 由一群靠谱的人建立,愿为PHPer带来一些值得细细品味的精神食粮!
本文由 rango 独家授权 php饭米粒发布,转载请注明本来源信息和以下的二维码(长按可识别二维码关注):
并发 IO 问题一直是服务器端编程中的技术难题,从最早的同步阻塞直接 Fork 进程,到 Worker 进程池/线程池,到现在的异步IO、协程。PHP 程序员因为有强大的 LAMP 框架,对这类底层方面的知识知之甚少,本文目的就是详细介绍 PHP 进行并发 IO 编程的各种尝试,最后再介绍 Swoole 的使用,深入浅出全面解析并发 IO 问题。
多进程/多线程同步阻塞
最早的服务器端程序都是通过多进程、多线程来解决并发IO的问题。进程模型出现的最早,从 Unix 系统诞生就开始有了进程的概念。最早的服务器端程序一般都是 Accept 一个客户端连接就创建一个进程,然后子进程进入循环同步阻塞地与客户端连接进行交互,收发处理数据。
多线程模式出现要晚一些,线程与进程相比更轻量,而且线程之间是共享内存堆栈的,所以不同的线程之间交互非常容易实现。比如聊天室这样的程序,客户端连接之间可以交互,比聊天室中的玩家可以任意的其他人发消息。用多线程模式实现非常简单,线程中可以直接向某一个客户端连接发送数据。而多进程模式就要用到管道、消息队列、共享内存,统称进程间通信(IPC)复杂的技术才能实现。
代码实例:
多进程/线程模型的流程是
创建一个 socket,绑定服务器端口(bind),监听端口(listen),在PHP中用stream_socket_server一个函数就能完成上面3个步骤,当然也可以使用更底层的sockets扩展分别实现。
进入while循环,阻塞在accept操作上,等待客户端连接进入。此时程序会进入睡眠状态,直到有新的客户端发起connect到服务器,操作系统会唤醒此进程。accept函数返回客户端连接的socket
主进程在多进程模型下通过fork(php: pcntl_fork)创建子进程,多线程模型下使用pthread_create(php: new Thread)创建子线程。下文如无特殊声明将使用进程同时表示进程/线程。
子进程创建成功后进入while循环,阻塞在recv(php: fread)调用上,等待客户端向服务器发送数据。收到数据后服务器程序进行处理然后使用send(php: fwrite)向客户端发送响应。长连接的服务会持续与客户端交互,而短连接服务一般收到响应就会close。
当客户端连接关闭时,子进程退出并销毁所有资源。主进程会回收掉此子进程。
这种模式最大的问题是,进程/线程创建和销毁的开销很大。所以上面的模式没办法应用于非常繁忙的服务器程序。对应的改进版解决了此问题,这就是经典的 Leader-Follower 模型。
代码实例:
它的特点是程序启动后就会创建N个进程。每个子进程进入 Accept,等待新的连接进入。当客户端连接到服务器时,其中一个子进程会被唤醒,开始处理客户端请求,并且不再接受新的TCP连接。当此连接关闭时,子进程会释放,重新进入 Accept ,参与处理新的连接。
这个模型的优势是完全可以复用进程,没有额外消耗,性能非常好。很多常见的服务器程序都是基于此模型的,比如 Apache 、PHP-FPM。
多进程模型也有一些缺点。
这种模型严重依赖进程的数量解决并发问题,一个客户端连接就需要占用一个进程,工作进程的数量有多少,并发处理能力就有多少。操作系统可以创建的进程数量是有限的。
启动大量进程会带来额外的进程调度消耗。数百个进程时可能进程上下文切换调度消耗占CPU不到1%可以忽略不计,如果启动数千甚至数万个进程,消耗就会直线上升。调度消耗可能占到 CPU 的百分之几十甚至 100%。
另外有一些场景多进程模型无法解决,比如即时聊天程序(IM),一台服务器要同时维持上万甚至几十万上百万的连接(经典的C10K问题),多进程模型就力不从心了。
还有一种场景也是多进程模型的软肋。通常Web服务器启动100个进程,如果一个请求消耗100ms,100个进程可以提供1000qps,这样的处理能力还是不错的。但是如果请求内要调用外网Http接口,像QQ、微博登录,耗时会很长,一个请求需要10s。那一个进程1秒只能处理0.1个请求,100个进程只能达到10qps,这样的处理能力就太差了。
有没有一种技术可以在一个进程内处理所有并发IO呢?答案是有,这就是IO复用技术。
IO复用/事件循环/异步非阻塞
其实IO复用的历史和多进程一样长,Linux很早就提供了 select 系统调用,可以在一个进程内维持1024个连接。后来又加入了poll系统调用,poll做了一些改进,解决了 1024 限制的问题,可以维持任意数量的连接。但select/poll还有一个问题就是,它需要循环检测连接是否有事件。这样问题就来了,如果服务器有100万个连接,在某一时间只有一个连接向服务器发送了数据,select/poll需要做循环100万次,其中只有1次是命中的,剩下的99万9999次都是无效的,白白浪费了CPU资源。
直到Linux 2.6内核提供了新的epoll系统调用,可以维持无限数量的连接,而且无需轮询,这才真正解决了 C10K 问题。现在各种高并发异步IO的服务器程序都是基于epoll实现的,比如Nginx、Node.js、Erlang、Golang。像 Node.js 这样单进程单线程的程序,都可以维持超过1百万TCP连接,全部归功于epoll技术。
IO复用异步非阻塞程序使用经典的Reactor模型,Reactor顾名思义就是反应堆的意思,它本身不处理任何数据收发。只是可以监视一个socket句柄的事件变化。
Reactor有4个核心的操作:
add添加socket监听到reactor,可以是listen socket也可以使客户端socket,也可以是管道、eventfd、信号等
set修改事件监听,可以设置监听的类型,如可读、可写。可读很好理解,对于listen socket就是有新客户端连接到来了需要accept。对于客户端连接就是收到数据,需要recv。可写事件比较难理解一些。一个SOCKET是有缓存区的,如果要向客户端连接发送2M的数据,一次性是发不出去的,操作系统默认TCP缓存区只有256K。一次性只能发256K,缓存区满了之后send就会返回EAGAIN错误。这时候就要监听可写事件,在纯异步的编程中,必须去监听可写才能保证send操作是完全非阻塞的。
del从reactor中移除,不再监听事件
callback就是事件发生后对应的处理逻辑,一般在add/set时制定。C语言用函数指针实现,JS可以用匿名函数,PHP可以用匿名函数、对象方法数组、字符串函数名。
Reactor只是一个事件发生器,实际对socket句柄的操作,如connect/accept、send/recv、close是在callback中完成的。具体编码可参考下面的伪代码:
Reactor模型还可以与多进程、多线程结合起来用,既实现异步非阻塞IO,又利用到多核。目前流行的异步服务器程序都是这样的方式:如
Nginx:多进程Reactor
Nginx+Lua:多进程Reactor+协程
Golang:单线程Reactor+多线程协程
Swoole:多线程Reactor+多进程Worker
协程是什么
协程从底层技术角度看实际上还是异步IO Reactor模型,应用层自行实现了任务调度,借助Reactor切换各个当前执行的用户态线程,但用户代码中完全感知不到Reactor的存在。
PHP并发IO编程实践
PHP相关扩展
Stream:PHP内核提供的socket封装
Sockets:对底层Socket API的封装
Libevent:对libevent库的封装
Event:基于Libevent更高级的封装,提供了面向对象接口、定时器、信号处理的支持
Pcntl/Posix:多进程、信号、进程管理的支持
Pthread:多线程、线程管理、锁的支持
PHP还有共享内存、信号量、消息队列的相关扩展
PECL:PHP的扩展库,包括系统底层、数据分析、算法、驱动、科学计算、图形等都有。如果PHP标准库中没有找到,可以在PECL寻找想要的功能。
PHP语言的优劣势
PHP的优点:
第一个是简单,PHP比其他任何的语言都要简单,入门的话PHP真的是可以一周就入门。C++有一本书叫做《21天深入学习C++》,其实21天根本不可能学会,甚至可以说C++没有3-5年不可能深入掌握。但是PHP绝对可以7天入门。所以PHP程序员的数量非常多,招聘比其他语言更容易。
PHP的功能非常强大,因为PHP官方的标准库和扩展库里提供了做服务器编程能用到的99%的东西。PHP的PECL扩展库里你想要的任何的功能。
另外PHP有超过20年的历史,生态圈是非常大的,在Github可以找到很多代码。
PHP的缺点:
性能比较差,因为毕竟是动态脚本,不适合做密集运算,如果同样的 PHP 程序使用 C/C++ 来写,PHP 版本要比它差一百倍。
函数命名规范差,这一点大家都是了解的,PHP更讲究实用性,没有一些规范。一些函数的命名是很混乱的,所以每次你必须去翻PHP的手册。
提供的数据结构和函数的接口粒度比较粗。PHP只有一个Array数据结构,底层基于HashTable。PHP的Array集合了Map,Set,Vector,Queue,Stack,Heap等数据结构的功能。另外PHP有一个SPL提供了其他数据结构的类封装。
PHP更适合偏实际应用层面的程序,业务开发、快速实现的利器
PHP不适合开发底层软件
使用C/C++、JAVA、Golang等静态编译语言作为PHP的补充,动静结合
借助IDE工具实现自动补全、语法提示
PHP的Swoole扩展
基于上面的扩展使用纯PHP就可以完全实现异步网络服务器和客户端程序。但是想实现一个类似于多IO线程,还是有很多繁琐的编程工作要做,包括如何来管理连接,如何来保证数据的收发原子性,网络协议的处理。另外PHP代码在协议处理部分性能是比较差的,所以我启动了一个新的开源项目Swoole,使用C语言和PHP结合来完成了这项工作。灵活多变的业务模块使用PHP开发效率高,基础的底层和协议处理部分用C语言实现,保证了高性能。它以扩展的方式加载到了PHP中,提供了一个完整的网络通信的框架,然后PHP的代码去写一些业务。它的模型是基于多线程Reactor+多进程Worker,既支持全异步,也支持半异步半同步。
Swoole的一些特点:
Accept线程,解决Accept性能瓶颈和惊群问题
多IO线程,可以更好地利用多核
提供了全异步和半同步半异步2种模式
处理高并发IO的部分用异步模式
复杂的业务逻辑部分用同步模式
底层支持了遍历所有连接、互发数据、自动合并拆分数据包、数据发送原子性。
Swoole的进程/线程模型:
Swoole程序的执行流程:
使用PHP+Swoole扩展实现异步通信编程
实例代码在https://github.com/swoole/swoole-src 主页查看。
TCP服务器与客户端
异步TCP服务器:
在这里new swoole_server对象,然后参数传入监听的HOST和PORT,然后设置了3个回调函数,分别是onConnect有新的连接进入、onReceive收到了某一个客户端的数据、onClose某个客户端关闭了连接。最后调用start启动服务器程序。swoole底层会根据当前机器有多少CPU核数,启动对应数量的Reactor线程和Worker进程。
异步客户端:
客户端的使用方法和服务器类似只是回调事件有4个,onConnect成功连接到服务器,这时可以去发送数据到服务器。onError连接服务器失败。onReceive服务器向客户端连接发送了数据。onClose连接关闭。
设置完事件回调后,发起connect到服务器,参数是服务器的IP,PORT和超时时间。
同步客户端:
同步客户端不需要设置任何事件回调,它没有Reactor监听,是阻塞串行的。等待IO完成才会进入下一步。
异步任务:
异步任务功能用于在一个纯异步的Server程序中去执行一个耗时的或者阻塞的函数。底层实现使用进程池,任务完成后会触发onFinish,程序中可以得到任务处理的结果。比如一个IM需要广播,如果直接在异步代码中广播可能会影响其他事件的处理。另外文件读写也可以使用异步任务实现,因为文件句柄没办法像socket一样使用Reactor监听。因为文件句柄总是可读的,直接读取文件可能会使服务器程序阻塞,使用异步任务是非常好的选择。
异步毫秒定时器
这2个接口实现了类似JS的setInterval、setTimeout函数功能,可以设置在n毫秒间隔实现一个函数或 n毫秒后执行一个函数。
异步MySQL客户端
swoole还提供一个内置连接池的MySQL异步客户端,可以设定最大使用MySQL连接数。并发SQL请求可以复用这些连接,而不是重复创建,这样可以保护MySQL避免连接资源被耗尽。
异步Redis客户端
异步的Web程序
程序的逻辑是从Redis中读取一个数据,然后显示HTML页面。使用ab压测性能如下:
同样的逻辑在php-fpm下的性能测试结果如下:
WebSocket程序
swoole内置了websocket服务器,可以基于此实现Web页面主动推送的功能,比如WebIM。有一个开源项目可以作为参考。https://github.com/matyhtf/php-webim
PHP+Swoole协程
异步编程一般使用回调方式,如果遇到非常复杂的逻辑,可能会层层嵌套回调函数。协程就可以解决此问题,可以顺序编写代码,但运行时是异步非阻塞的。腾讯的工程师基于Swoole扩展和PHP5.5的Yield/Generator语法实现类似于Golang的协程,项目名称为TSF(Tencent Server Framework),开源项目地址:https://github.com/tencent-php/tsf。目前在腾讯公司的企业QQ、QQ公众号项目以及车轮忽略的查违章项目有大规模应用 。
TSF使用也非常简单,下面调用了3个IO操作,完全是串行的写法。但实际上是异步非阻塞执行的。TSF底层调度器接管了程序的执行,在对应的IO完成后才会向下继续执行。
在树莓派上使用PHP+Swoole
PHP和Swoole都可以在ARM平台上编译运行,所以在树莓派系统上也可以使用PHP+Swoole来开发网络通信的程序。
12月1日发布了swoole-1.7.21版本,修复了很多BUG。这是swoole-1.7最后一个版本了,以后不再更新。我们启动了雄心勃勃的swoole-1.8开发计划。在介绍1.8的计划前,先讲一段小插曲。
性能测试游戏
昨天在1.7.21发布后,正好赶上PHP7也发布,忍不住想要做下性能测试。安装了一下HHVM,准备对比PHP5.6、PHP7、HHVM3.9的性能。对WordPress程序的测试中,PHP7确实已经与HHVM很接近了。不过这不是重点,接下来我又测试了一下新写的PHP版本WebSocket客户端。这一测问题就来了。我发现PHP在这个场景下性能非常差。当时的数据是:
5.6.9 cost 419ms
5.6.99-hhvm cost 905ms
7.0.0RC7 cost 777ms
压测10万次WebSocket协议打包,竟然消耗了23秒,PHP7比PHP5.6提升了1倍性能,但和HHVM还是有较大的差距。这个结果倒是在预料之中,毕竟Zend PHP没有JIT,这样纯密集计算的场景下性能不会太好。我顺手加了一个C版本的websocket_pack。再次压测,结果震惊了。
5.6.9 cost 419ms
5.6.99-hhvm cost 905ms
7.0.0RC7 cost 777ms
C-websocket_pack cost 57.ms
C语言版本只用了57ms就完成了10万次的打包。比PHP版本性能高出300多倍。即使是有JIT的HHVM也无法和C语言版本的websocket_pack相提并论。好了接下来就聊聊swoole-1.8的开发计划。
Swoole-1.8的开发计划
异步模式下reload/max_request的支持
swoole-1.7下如果Server是纯异步非阻塞的模式,使用reload/max_request可能会导致某些事件丢失。所以WIKI中明确写了异步模式不应该设置max_request,reload会出现一些错误。1.8会解决此问题,对异步模式下Worker进程退出逻辑将发生一些修改。底层会确保Worker进程中的事件全部处理完后才会退出。
网络协议部分的调整
swoole-1.8将增加Http异步客户端(同步阻塞模式直接用CURL就可以了)、WebSocket同步/异步客户端。1.8还将增加http2.0、mqtt协议的支持。
另外一个重要调整就是,1.8将支持Server多协议。目前1.7版本一个swoole_server只能使用一种网络协议,比如swoole_http_server就只能处理HTTP协议,无法再支持固定包头+包体、EOF协议。有一些场景下一个Server需要做多件事情。新的版本会支持混合多协议。
正是因为上面的性能测试,让我下定决定在swoole内逐步开始支持绝大部分通用的网络协议。C语言实现的客户端在性能上远远超过其他语言。Swoole要让PHP在网络通信方面超过其他语言,比如Java、Node.js、Golang、Erlang等,只有C语言实现一条路可选。
扩展内RPC的支持
RPC作为服务器端程序中很重要的一项功能,Swoole没有理由不去支持它。1.8版本将会内置RPC的基础模块,包括Server端和Client端,Client端会同时支持异步和同步,并且支持php-fpm下调用。
配套工具的支持
经常有PHPer向我反馈想要有一套命令行工具可以简单地实现Server程序的start、stop、reload、status,以及通过inotify监控实现自动reload,新的swoole-1.8开发计划中将包含这些工具,我们会提供一些通用的、非侵入式的工具。
Swoole-1.8什么时候发布
我们已经启动了开发,新特性会逐个在子版本上线,预计2016年7月份会上齐所有特性。
昨天和一个前同事聊天,各种吐槽PHP,吐槽Swoole,他认为PHP到处是坑,PHP局限很大。PHP+Swoole不适合做高并发服务器,C+Swoole才是最好的方案。C++有各种数据结构,C++可以开线程,C++可以共享对象。看来有必要好好得说明一下了。
PHP比C/C++或Java少了什么?多线程,多线程,多线程……
是的。PHP比C/C++、Java少了多线程。PHP只有多进程的方案,所以PHP里的全局变量和对象不是共享的、数据结构也不能跨进程操作、Socket文件描述符不能共享等等。所以PHP有局限?
多线程看似比多进程要强大很多,实际上我可以负责任的告诉你,多线程带来的坑更多。
数据同步问题会让你崩溃的。要么就牺牲性能到处加锁,要么就用地狱难度的无锁并发编程,据我所知目前国内能掌握此项技能的人凤毛麟角。
不要以为加锁就万事大吉了,你会在死锁问题上栽个大跟头。当你的程序逻辑复杂后,锁越来越难控制了,一旦死锁你的程序基本上就完了。
某个线程挂了那所有线程都会退出
反而在看多进程,其实就简单的多了。
配合进程间通信,基本上你可以实现任意的数据共享。比如利用一个进程专门存数据结构和对象,其他进程的数据操作全部投递到此进程来
多进程不需要锁
多进程可以使用共享内存的数据结构实现一些多线程的功能。如Swoole提供的Table、Atomic可以实现数据共享,但成本很低。未来还会加入共享内存队列
所谓PHP限制了Swoole,这完全是无稽之谈。合理利用Swoole提供的Table、Atomic、SendMessage/PipeMessage、Task完全可以实现异步非阻塞的代码逻辑。
C++写出来的程序性能更好?
这完全是盲目的迷信,密集计算的程序C++确实是有优势的。而并发服务器核心是IO,并非大规模密集运算。C++从语言层面来看并没有什么优势。另外C++中的大部分数据结构在PHP中都有对应的实现,实在不行自己写个专门的扩展也能解决之。
高并发的服务器单机能维持10W连接、每秒可处理3-5W笔消息收发。这种性能水准已经可以应用在BAT的核心系统上了。
开发效率快的意义是什么?
这位同事还说PHP开发Server虽然比C++快了,但是追求性能的极致还是要用C++。我要告诉你效率高了究竟意义何在。开发一套好程序不是一件容易的事情,需要程序员投入大量时间和精力。开发效率提升的意义并不是简单的我可以更少时间完工,而是剩下的时间你可以增加单元测试、修复BUG、提升用户体验、完善细节、提供配套工具、优化性能、增加关键日志、增加监控报警、增加容灾方案。
TCP协议在底层机制上解决了UDP协议的顺序和丢包重传问题。但相比UDP又带来了新的问题,TCP协议是流式的,数据包没有边界。应用程序使用TCP通信就会面临这些难题。一些程序在本机测试是正确的,上线后就出现各种奇怪的BUG。如下面的伪代码,客户端向服务器端发送一个json字符串,服务器端接收此字符串。在慢速网络中Server无法正确接收完整的JSON字符串。
$client-&send(json_encode('a' =& $data_10k, 'b' =& $data_5k));
$pkg = $server-&recv(); //Server收到的数据只有一小部分
$client-&send("hello1");
$client-&send("hello2");
$client-&send("hello3");
$pkg = $server-&recv(); //Server会一次性收到3个数据包
因为TCP通信是流式的,在接收1个大数据包时,可能会被拆分成多个数据包发送。多次Send底层也可能会合并成一次进行发送。这里就需要2个操作来解决:
分包:Server收到了多个数据包,需要拆分数据包
合包:Server收到的数据只是包的一部分,需要缓存数据,合并成完整的包
具体编码实现这里就不讲了,这是一个比较复杂的编程过程,稍有不慎就会出现严重的BUG
Swoole如何解决此问题
swoole提供了通用协议的支持,如Http和WebSocket。自定义协议可以使用Length/EOF 2种协议解析方式来完美解决此问题。从1.7.18版本开始,Swoole的Server/Client都支持了Length/EOF的协议处理方式,应用层代码只需要配置一下参数,就无需关注底层分包合包了。每次onReceive收到的数据包总是完整的。
Server/Client的配置是相同的
Http/WebSocket
swoole内置对http/websocket 2种协议的支持,如果要实现一个http服务或者websocket服务,直接用swoole_http_server和swoole_websocket_server即可。
$server-&set(array('open_eof_split' =& true, 'package_eof' =& "\r\n"));
EOF协议处理的原理是每个数据包结尾加一串特殊字符表示包已结束。如memcache、ftp、stmp都使用\r\n作为结束符。发送数据时只需要在包末尾增加\r\n即可。使用EOF协议处理,一定要确保数据包中间不会出现EOF,否则会造成分包错误。
$server-&set(array(
'open_length_check' =& true,
'package_max_length' =& 81920,
'package_length_type' =& 'n', //see php pack()
'package_length_offset' =& 0,
'package_body_offset' =& 2,
固定包头的协议非常通用,在BAT的服务器程序中经常能看到。这种协议的特点是一个数据包总是由包头+包体2部分组成。包头由一个字段指定了包体或整个包的长度,长度一般是使用2字节/4字节整数来表示。服务器收到包头后,可以根据长度值来精确控制需要再接收多少数据就时完整的数据包。Swoole的配置可以很好的支持这种协议,可以灵活地设置4项参数应对所有情况。
Swoole的Server和异步Client都是在onReceive回调函数中处理数据包,当设置了协议处理后,只有收到一个完整数据包时才会触发onReceive事件。同步客户端在设置了协议处理后,调用 $client-&recv() 不再需要传入长度,recv函数在收到完整数据包或发生错误后返回。
最近PHP官方终于发布了传说中的PHP7,虽然只是alpha版。PHP7号称是新一代的PHP,官方开发组对Zend引擎底层做了大量修改来优化PHP的性能。可以说PHP7这个版本的主题就是性能优化。
在过去PHP一直以开发效率快著称,而语言本身的性能较差(当然比Python,Ruby还是要快一些的)。普通的Web网站都是IO密集型的程序,瓶颈在MySQL上,所以体现不出PHP的性能劣势。但在密集计算方面比C/C++、Java等静态编译语言差几十倍甚至上百倍。另外使用设计非常复杂的开发框架,如Symfony、Laravel等,程序性能也会明显下降。
现在随着PHP越来越流行,像Facebook、新浪微博这样超大型规模的网站都在使用PHP。PHP语言性能问题就越来越严重了。Facebook有几十万台服务器,如果现有的PHP程序可以提升一部分性能,将会节约大量的服务器资源。所以就有了HHVM、Hack。Hack为PHP增加了类型,HHVM是一个重新设计的PHP引擎,实际项目中使用HHVM可以提近70%的性能。实际项目70%性能提升这是一个什么概念?腾讯QQ农场最初使用PHP开发,后因为性能问题使用C语言重构,完成后性能提升了100%。
PHP官方也注意到了这个问题,所以就有了PHP7的开发计划。最新公布的PHP7-alpha在WordPress项目中测试的表现已经超越了HHVM。未来PHP将会同时具备极高的开发效率和极高的性能,再结合Swoole做异步编程,PHP势必会更加流行。
本文简单介绍一下PHP7做了哪些优化,可以提升如此多性能。
zval使用栈内存
在Zend引擎和扩展中,经常要创建一个PHP的变量,底层就是一个zval指针。之前的版本都是通过MAKE_STD_ZVAL动态的从堆上分配一个zval内存。而PHP7可以直接使用栈内存。PHP代码中创建的变量也进行了优化,PHP7直接在栈内存上预分配zval。这样节约了大量内存分配和内存管理的操作。
zval * MAKE_STD_ZVAL(val);
二 zend_string存储hash值,array查询不再需要重复计算hash
PHP7为字符串单独创建了新类型叫做zend_string,除了char *指针和长度之外,增加了一个hash字段,用于保存字符串的hash值。PHP中array是核心数据结构,PHP程序中往往都有大量的$array[$key]操作,虽然hashtable查找的时间复杂度是O(1),但$key要转为hash值是要经过计算的。不仅仅是array操作,实际上PHP底层对于类属性、类方法、函数,访问时都要先通过hashtable查找到对应的指针,再执行对应的操作。PHP7之前Zend引擎会有大量的CPU时间用于计算hash值。
实际上PHP程序运行起来之后,大部分情况下$key的值都是不变的。PHP7干脆将这个hash值保存起来,下次直接使用,这样就节省了大量的hash计算操作,PHP的hashtable与C数组的性能一致。
从实际项目进行callgrind性能分析,会发现alloc和hash 2项操作就占用了相当大比例的CPU时间。PHP7优化之后这2项操作占用的CPU时间降低了非常多。(注:zend_hash仍然占12%,因为整体CPU降低了,所以总的耗时降低了不少)
三 hashtable桶内直接存数据
PHP5的hashtable每个元素都是一个 Bucket *,而PHP7直接存Bucket,减少了内存申请次数,提升了Cache命中率和内存访问速度。
四 zend_parse_parameters改为宏实现
PHP的C扩展函数与PHP中的变量进行参数输入时,要使用zend_parse_parameters()函数,这个函数根据一个字符串参数找到对应PHP的zval指针,然后进行赋值。 这个函数实际上有一定的性能消耗。PHP7直接使用宏替换了zend_parse_parameters函数,C扩展中不再需要使用zend_parse_parameters进行逐个参数的查找,宏展开后自动会实现参数赋值。仅此一项就提升了5%的性能。
五 新增加4种OPCODE
很多PHP程序中会大量使用call_user_function, is_int/string/array,
strlen , defined 函数。PHP5 都是以扩展函数的方式提供,PHP7中这4类函数改成ZendVM的OPCODE指令,执行更快。
六 其他更多优化
除了上面5个主要优化点之外,PHP7还有其他更多的细节性能优化。如基础类型int、float、bool等改为直接进行值拷贝,排序算法改进,PCRE with JIT,execute_data和opline使用全局寄存器等等。PHP7对性能的优化会继续进行下去。
PHP7-alpha相比PHP5.6性能提升了近3倍。下面是WordPress在PHP7上的表现:
PHP7的新特性
除了性能优化外,PHP7新增加了2项重要的新特性。
1. 变量类型
PHP7版本函数的参数和返回值增加了类型限定。为什么PHP要加入类型,实际上此项特性是为了PHP7.1版本的JIT特性做准备,增加类型后PHP JIT可以准确判断变量类型,生成最佳的机器指令。
function test(int $a, string $b, array $c) : int {
2. 错误异常
PHP程序出错后过去Zend引擎会发生致命错误并终止程序运行,PHP7可以使用try/catch捕获错误。底层使用Exeception代替了Fatal Error。这个特性表示PHP语言正在向一个更加规范的方向发展。应用层与底层在错误抛出的方式全部统一为异常。
non_exists_func();
} catch (EngineException $e) {
echo "Exception: {$e-&getMessage()}\n";
$test = new class("Hello World") {
public function __construct($greeting) {
$this-&greeting = $
最初PHP7性能优化的方向并不是以上所讲的,而是JIT。JIT是just in time的缩写,表示运行时将指令转为二进制机器码。Java语言的JVM引擎底层就是使用JIT将Java字节码编译为二进制机器码执行。PHP7开发过程中有一个中间版本是基于JIT,后来开发组发现使用JIT后,对于实际项目并没有有太大的性能提升,所以PHP7最终放弃了JIT方案,PHP7.0-final版本不会携带JIT特性。
但如果是密集计算类程序就不同了,使用JIT将PHP OpCode编译为机器码,运算的性能会大幅提升。PHP官方开发组在2014年底重启了JIT的开发工作。
PHP的异步网络通信扩展Swoole
PHP在大部分程序员印象中都是用来做Web网站的。PHP没有像Python的Twisted、Tornado,Java的Netty、Mina,JavaScript的Node.js等框架,无法实现异步网络通信程序。PHP的Swoole扩展就是为了弥补此项缺陷而诞生的开源项目。Swoole是一个标准的PHP扩展,为PHP提供了一系列异步IO、事件驱动、并行数据结构功能。
Swoole与Node.js非常相似,不同之处是Swoole在并行提供了底层支持。Node.js是一个单进程单线程的程序,在多核服务器上无法发挥全部CPU核的计算能力。需要程序员自行使用child_process/cluster扩展或者启动多实例,使程序能够利用到多核优势。而Swoole在底层就支持了多线程/多进程,程序启动后就会创建好多个IO线程和多个Worker进程。程序员仅需配置线程/进程数量即可。
使用Swoole开发的TCP服务器程序:
$serv = new swoole_server("127.0.0.1", 9501);
$serv-&on('connect', function ($serv, $fd){
echo "Client:Connect.\n";
$serv-&on('receive', function ($serv, $fd, $from_id, $data) {
$serv-&send($fd, $data);
$serv-&on('close', function ($serv, $fd) {
echo "Client: Close.\n";
$serv-&start();
Swoole同样也内置了http_server和WebSocket服务器的支持。swoole_http_server与传统的php-fpm不同,它是在PHP内进行事件循环的,基于swoole_http_server完全可以开发出类似Java应用服务器一样,可以控制完整对象生命周期的程序。swoole_http_server天然支持异步IO,可以很方便的实现支持大量TCP连接的Comet服务。swoole_websocket_server可以用来实现支持Web实时推送的程序。
使用Swoole的Web服务器程序:
$http = new swoole_http_server("0.0.0.0", 9501);
$http-&on('request', function ($request, $response) {
$response-&header("Content-Type", "text/ charset=utf-8");
$response-&end("
Hello Swoole. #".rand()."
$http-&start();
可以预见PHP语言未来会在性能方面有明显的提升,越来越接近C/C++、Java等静态编译语言。再加上Swoole扩展,PHP的使用范围可以扩展到移动通信、云计算、网络游戏、物联网、车联网、智能家居等领域。
PHP虽然未必是最好的编程语言,但PHP在向着这个方向在发展。
swoole-1.7.14增加了tick定时器,解决了addtimer存在的各种问题。
swoole_timer_tick(1000, function ($timer_id) {
echo "tick-1000ms\n";
swoole_timer_clear($timer_id);
swoole_timer_tick(2000, function () {
echo "tick-2000ms\n";
swoole_timer_tick和swoole_timer_after原理是相同的,区别是after只执行一次就会销毁。tick定时器会持续执行,直到调用了swoole_timer_clear清除定时器。
与PHP代码中的pcntl_alarm不同,swoole的定时器是基于EventLoop实现的,不依赖系统信号和PHP的tick机制,所以效率非常高。即使添加大量定时器,CPU消耗也是很低的。PHP在过去没有像JavaScript这样方便的定时器功能,现在有swoole就可以很好地解决。
在网络游戏程序中经常要用定时器,PHP+swoole将会成为最佳的技术方案。
最近群里很多朋友询问我是如何学习编程的,今天抽个时间大致讲讲。
和所有人一样,上学学的第一门编程语言是C语言。我记得我当时学的很认真,非常认真。和别人最大的不同是我买了一本老外写的C语言编程书,没被谭浩强误导。另外还买了一本《现代操作系统》一书看了很久。学习期间写了不少读书笔记。真正要说学到什么了,这个还真不好说,到最后我对C语言也是似懂非懂的状态。
用学物理、数学的方法学编程语言是行不通的,编程需要的是实践。
学Java的目的很明确,为了找一份好工作。面向对象、J2EE、Java设计模式、SSH的书买了好几本。最后还是没能够入门。原因我后来分析主要是2点,1、Java太复杂了,根本就不适合初学者。2、犯了和学习C语言同样的错误,读书太多,实践太少(主要是不会搭建Java的开发环境)。
偶然的机会了解到了PHP语言,按照网上的教程一搞,写了一个MySQL的网页程序,竟然运行成功了。这实在太神奇了。你们无法理解当时我的心情,可以说到了 高兴地3天不用吃饭的程度。呵呵,当时我就下定决心以后就是它了。PHP让我进入了一个新的境界,我可以大量地实践,写出各种程序。而且也顺利找到了工作,干的风生水起,一个接一个地做了很多项目。后来又进入腾讯,在腾讯有机会去开发维护一个超过2亿用户,日活超过8千万,机器规模超过1000多台的超大系统。我的人生的各种成果都归功于PHP这门伟大的编程语言。
重新回到C语言
当然后面也逐渐认识到PHP的不足,不过很快我就发现可以写C扩展来弥补。重新学起C语言,我发现竟然很顺利。原因应该是我在写PHP程序的过程中,工程实践能力已经磨练到非同一般。其实不只是C语言,只要我愿意,C++,Java,Python,Golang任何语言都可以快速掌握。然后就开始搞Swoole这个项目,边学边做。经过这2年的时间,现在可以说我已经掌握C语言了。
为什么会回到C语言,原因很简单:所有底层软件都是C语言写的,包括操作系统、底层库、驱动、其他编程语言本身。C语言一切现代软件技术的根源。
我的个人感悟
我的编程之路轨迹就是 C语言(起始) -& Java(过客)-& PHP(主)-& C语言(终点)。
编程语言专注于一个就行了,Java、PHP、Python、Ruby 任意一个都行。只要能把一门编程语言学精通,其他语言学起来也很容易。
工作时选择的编程语言,应该是一主多辅。做Web得了解Javascript,做APP需要掌握Android、Object-C,写桌面程序需要学习C++。带着问题去学就好了。
不要选择难或者复杂的编程语言作为入门,易于上手的编程语言更容易成功。公司有一个搞Java 9年的人,技术等级很低。很好的例子。另外一个只工作了5年的PHPer现在已经是T3-2的技术Leader了。
编程最核心的是动手实践
多看一看别的编程语言和技术,开阔一下视野
编程做到最后还是会回到C语言的,当然很多人中途就已经离开编程岗位了
分类: , , ,

我要回帖

更多关于 tcp ip socket 的文章

 

随机推荐