海阔天空

I'm on my way!
随笔 - 17, 文章 - 69, 评论 - 21, 引用 - 0
数据加载中……

socket编程


今天只想谈一谈关于阻塞和非阻塞的socket。

调用socket函数得到的socket的文件标示符,默认情况下是一个阻塞的socket,从应用角度,就是每当调用accept,recv,send 等函数的时候,如果对方没有相应,那么进程会阻塞在那里,直到对方相应。这在应用中有很多不便,尤其是在windows环境中的编程,如果进程被阻塞那么 看上去有一点像死机的感觉。

其实把一个socket设置为非阻塞的也很简单。但是,应用select函数,阻塞socket也可以到达类似的效果。一会详细讨论。

先考虑一个TCP的连接,当服务器程序listen以后,如果直接调用accept,那么进程会阻塞在那里,一直到被client端connect,然后 开启一个新的线程处理客户的服务,主线程继续监听请求。这样可以实现同时处理多个客户端连接,但是这样是会阻塞主线程,不是一个好的办法。其实,我当然也 可添加一个线程,在“后台”accept,但是我觉得这里面有一个多线程操作同一个文件描述符socket的问题。也不是很好。也就是说,如果只用到线程 机制,并不能很好的解决阻塞的问题。

select函数可以对一套文件描述符操作(windows里叫handle,我觉得没有什么本 质的区别。都是进程空间里面的一个整数而已),观察这套文件描述符的可读或可写的情况。这样就给我们提供这样一个思路,只有当一个socket是可读的, 也就是有远程连接请求connect或是对套接口write,那么才去调用accept或是read,这样就可以避免阻塞了。

以准备accept的套接口为例,如果调用socket函数得到的文件描述符为s=3,那么,我可以调用select监视所有4以下的文件描述符的读写特 性。 select(s+1,&fd_readset,&fd_writeset,&fd_errorset,&tv_time). 这个函数是内核等待特定事件的函数,并不是只有套接口可以用。原型是

int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout);

第一个参数表示观察的最大的文件描述符,一般是你想要观察的socket+1, 第二个参数是可读文件描述符set,第三个是可写set,第四个参数是error set,第五个是超时的时间。如果在超时的时间内没有观察过任何改动,那么函数会清空这几个set(我个人理解),并且返回0;如果出错,会返回-1。否 则返回>0,并且只把可读(可写)的文件描述符保留在对应的set内。所以只要在调用select以前,把等待连接的套接口放到可读set中,如果 发生connect,select会返回>0,这时候可以观察一下套接口是否还在这个set中,如果是,则说明有请求。这个方法可以用于多个套接口 同时监听多个端口的情况。也可以用于一个已经连接的套接口等待对方发送的消息,比如write。

对于set的操作,有如下几个宏:FD_ZERO,FD_SET,FD_ISSET,FD_CLR.

fd_set myset; //定义描述字集数据类型

FD_ZERO (&myset); //对描述字集初始化

FD_SET(s, &myset); //打开描述字的第s位,也即把套接口加入set中

FD_ISSET(s, &myest) //测试描述字的第s位,这个宏一般是在select之后才会用到。

FD_CLR(s, &myset) // //关闭描述字的第s位,就是在set中清除s.

这个函数的使用方法,可以参见下面的程序。就不再细诉了。

FD_ZERO(&fs_ReadSet);
FD_SET(iSockets, &fs_ReadSet);
iSockNum = iSockets+1;

while(1)
{
FD_ZERO(&fs_WriteSet);
for (iSocketz = 0; iSocketz<iSockNum; iSocketz++)
{
if (FD_ISSET(iSocketz, &fs_ReadSet))
FD_SET(iSocketz, &fs_WriteSet);
}

tv_time.tv_sec = 2;
tv_time.tv_usec = 500000;
iRtn = select(iSockNum, &fs_WriteSet, NULL, NULL, &tv_time);
if (iRtn == -1)
{
printf("function select error\n");
goto error;
}
else if(iRtn == 0)
{
continue;
}

if (FD_ISSET(iSockets, &fs_WriteSet))
{
iSockLen = sizeof(sa_client);
iSocketz = accept(iSockets, (struct sockaddr*)&sa_client, &iSockLen);
if (iSocketz == -1)
{
printf("function accept error \n");
goto error;
}
printf("z : %d\n", iSocketz);

if (iSocketz >=MAX_CLIENT)
{
close(iSocketz);
continue;
}
if (iSockNum < iSocketz+1)
iSockNum = iSocketz+1;
FD_SET(iSocketz, &fs_ReadSet);

}

for (iSocketz=iSockets+1; iSocketz < iSockNum; iSocketz++)
{

if (FD_ISSET(iSocketz, &fs_WriteSet))
{
memset(pRcvBuf, 0, BUFFERSIZE);
memset(pSndBuf, 0, BUFFERSIZE);
iRtn = read(iSocketz, pRcvBuf, BUFFERSIZE);
if (iRtn == -1)
{
printf("function read error \n");
goto error;
}

printf("%s\n", pRcvBuf);

pSndBuf[0] = 0;
strcpy(pSndBuf, "fuck");
iBufLen = sizeof("fuck");

iRtn = write(iSocketz, pSndBuf, iBufLen);
if (iRtn == -1)
{
printf("function write error \n");
goto error;
}

if(strcmp(pRcvBuf,"exit")==0)
{
FD_CLR(iSocketz, &fs_ReadSet);
close(iSocketz);
}
}

}
for (iSocketz = iSockNum-1; (iSocketz >iSockets&&!FD_ISSET(iSocketz, &fs_ReadSet)); iSocketz = iSockNum-1)
{
iSockNum = iSocketz;
}
//printf("isockNum: %d\n",iSockNum);
}

至于套接口的通信,我还想说一点,套接口和文件管道差不多,一方读一方写,read会阻塞到对方write,而write不会阻塞。应该是这样了。如果我个人对这个部分的理解有错误。会在以后改正。

posted on 2009-12-14 11:35 石头@ 阅读(468) 评论(0)  编辑  收藏 所属分类: Tcp/Ip


只有注册用户登录后才能发表评论。


网站导航: