select在freebsd上可以同时等待多个fd的读写通知,因为freebsd将socket和file当作同一对象,这有一点有时很有用,比如,当进程进入某个状态,需要既可以等待网络输入,又能等待键盘输入(比如取消指令)时,就可把该socket和stdin一起加到一个fd_set中,用select来监视。
但是windows上的select却没有此功能,如果把非socket型的描述符加到fd_set里,很快就会报错。虽然有很多办法可以用来解决我们的需要(即同时等待两种输入),但是如果已经有一份在unix上跑得很好的代码,只是想把它移植到win上时,就有个比较简单的办法,不用改变程序的大致结构。
方法就是:在主线程里打开一个额外的socket如取名为“stdinfd”,它就在本机上监听,而另开一个线程去获取键盘输入,然后把这些内容通过socket发给本机上的stdinfd。这样一来,相当于变相地把键盘输入变成了网络输入,于是fd_set中获得了一致的形式,这对于在unix上本来就是使用select机制的程序来说,改动非常小。
也许觉得在本机上多开两个socket有点麻烦,既然只是要异步获取键盘输入,为何不用键盘钩子?这个想法我也想过,而且实践证明不行,因为钩子是基于消息机制的,既然我们是想把unix上的程序移过来,那里面就根本不会使用windows的消息循环,钩子也就无从着手了。我调了半个小时一直都没反应,后来才翻然醒悟此理,真蠢!
具体步骤如下:
一、先做一个stdin获取线程函数:
DWORD WINAPI ThreadFunc( LPVOID lpParam ) { WSADATA wsaData; WSAStartup(MAKEWORD(2,2), &wsaData); SOCKET listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); bind(listenfd, (SOCKADDR*)&servaddr, sizeof(servaddr)); ReleaseSemaphore(hSemaphore, 1, NULL); printf("t-listen!/n"); while(fgets(buf,sizeof(buf),stdin)) return 0; |
这里有几个问题我得提醒自己记住:
1、一开始要调用WSA_Startup,这是每个线程都要调的,主线程里调了,这里也要,否则后面出错莫名其妙
2、hSemaphore是个全局信号量,这个也很重要,是为了和主线程同步。一定要等这个线程里建好监听socket,才能让主线程继续往下去,否则主线程的connect就会失败。
二、主线程如下:
int main() WSADATA wsaData; char buf[128]; hSemaphore=CreateSemaphore(NULL, 0, 1, NULL); WSAStartup(MAKEWORD(2,2), &wsaData); stdinfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); bind(listenfd, (SOCKADDR*)&servaddr, sizeof(servaddr)); fd_set fsets; if(select(0,&fsets,NULL,NULL,&t)==SOCKET_ERROR) if(FD_ISSET(stdinfd,&fsets)) if(FD_ISSET(connfd,&fsets)) }
|
这里就很简单了,一个要注意的是for(int k=0;k 现在主线程就可以像在unix上一样同时等待多个输入了,而且改动很小,就是加一个stdinfd进去而已,可以用#ifdef开关来控制源代码。 2006.4.26