windows上解决select不能同时等待键盘和套接字输入的问题

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);
 sockaddr_in servaddr;
 servaddr.sin_family = AF_INET;
 servaddr.sin_port = htons(8888);
 servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");

 bind(listenfd, (SOCKADDR*)&servaddr, sizeof(servaddr));
 listen(listenfd, 5);

 ReleaseSemaphore(hSemaphore, 1,  NULL);

 printf("t-listen!/n");
 SOCKET connfd = accept(listenfd, NULL, NULL);
 printf("t-accept!/n");

  while(fgets(buf,sizeof(buf),stdin))
 {
  ::send(connfd,buf,strlen(buf)+1,0);
  printf("t-send stdin data:%s",buf);
 }

 return 0;
}

这里有几个问题我得提醒自己记住:
1、一开始要调用WSA_Startup,这是每个线程都要调的,主线程里调了,这里也要,否则后面出错莫名其妙
2、hSemaphore是个全局信号量,这个也很重要,是为了和主线程同步。一定要等这个线程里建好监听socket,才能让主线程继续往下去,否则主线程的connect就会失败。

二、主线程如下:

int main()
{
 DWORD dwThreadId;
 HANDLE hThread;
 DWORD dwWaitResult;

 WSADATA wsaData;
 SOCKET stdinfd,listenfd,connfd;
 sockaddr_in stdserv,servaddr;

 char buf[128];

 hSemaphore=CreateSemaphore(NULL, 0, 1, NULL);
 hThread = CreateThread(NULL, 0,ThreadFunc,NULL,0, &dwThreadId);
 if(hThread == NULL)
 {
  printf("CreateThread failed." );
  return -1;
 }
 dwWaitResult = WaitForSingleObject(hSemaphore, INFINITE);
 switch (dwWaitResult)
 {
  case WAIT_OBJECT_0:
   printf("stdin-sock is established!/n");
   break;
  default:
   printf("wait the stdin-sock thread failed!/n");
   return -1;
 }

 WSAStartup(MAKEWORD(2,2), &wsaData);

 stdinfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
 stdserv.sin_family = AF_INET;
 stdserv.sin_port = htons(8888);
 stdserv.sin_addr.s_addr = inet_addr("127.0.0.1");
 
 connect(stdinfd,(SOCKADDR*)&stdserv,sizeof(stdserv));

 listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
 servaddr.sin_family = AF_INET;
 servaddr.sin_port = htons(9999);
 servaddr.sin_addr.s_addr = inet_addr("192.168.9.111");

 bind(listenfd, (SOCKADDR*)&servaddr, sizeof(servaddr));
 listen(listenfd, 5);
 printf("listen!/n");
 SOCKET connfd = accept(listenfd, NULL, NULL);
 printf("accept!/n");

 fd_set fsets;
 timeval t;
 memset(&t,0,sizeof(t));
 t.tv_sec=60;
 while(1)
 {
  FD_ZERO(&fsets);
  FD_SET(stdinfd,&fsets);
  FD_SET(connfd,&fsets);

  if(select(0,&fsets,NULL,NULL,&t)==SOCKET_ERROR)
  {
   printf("err:%d/n",WSAGetLastError());
  };

  if(FD_ISSET(stdinfd,&fsets))
  {
   int n=recv(stdinfd,buf,sizeof(buf),0);
   buf[n]=0;
   printf("get stdin input:");
   for(int k=0;k<n;k++) putchar(buf[k]);
  }

  if(FD_ISSET(connfd,&fsets))
  {
   int n=recv(connfd,buf,sizeof(buf),0);
   buf[n]=0;
   printf("get socket input:%s/n",buf);
  }

 }
 return 0;
}

 

这里就很简单了,一个要注意的是for(int k=0;k<n;k++) putchar(buf[k]); 为什么一个一个输出,因为从键盘上输入了两行时,第一行后面的/0很恶心,假始刚开始主线程卡在对外网的accept上,而键盘上已经输入了两个命令进去,此时select还没开始。等到外网有连接进来,主线程马上就会执行select并返回且被告知键盘有输入,它这时去取回这两行,如果用一个printf("%s",buf)打出来,那么碰到第一行末尾的/0时就认为没了!当然这个问题纯属细节,没必要深究了。

现在主线程就可以像在unix上一样同时等待多个输入了,而且改动很小,就是加一个stdinfd进去而已,可以用#ifdef开关来控制源代码。

2006.4.26

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值