TCP/IP网络编程——IO复用

引入复用技术,可以减少进程数,无论连接多少客户端,提供服务的进程只有一个。

select函数可以实现IO复用,它可以将多个文件描述符集中到一起统一监视:

是否存在套接字接收数据?无需阻塞传输数据的套接字有哪些?哪些套接字发生了异常?

下面是具体用例:

//复用:时分复用;频分复用
//使用select函数完成IO复用
//1.设置文件描述符;指定监视范围;设置超时
//2.调用select函数
//3.查看调用结果
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/select.h>

#define BUF_SIZE 30

int main(void)
{
  fd_set reads, temps;
  int result, str_len;
  char buf[BUF_SIZE];
  struct timeval timeout;
  
  FD_ZERO(&reads);
  FD_SET(0, &reads);	//0表示标准输入描述符
  
  while(1)
  {
    //调用select函数后,除了发生变化的描述符对应位以外剩下所有位将
    //初始化为0,因此应记住初始值reads。
    temps=reads;
    //timeout设置应在循环里,因为调用select后timeout会被替换为超时前剩余时间
    timeout.tv_sec=5;
    timeout.tv_usec=0;
    //调用select函数,监控范围是1,timeout是5s
    result=select(1, &temps, 0, 0, &timeout);
    if(result==-1)
    {
      puts("select error");
      break;
    }
    else if(result==0)
    {
      puts("time out");
    }
    else
    {
      //验证第0位是否为1,即是否有标准输入
      if(FD_ISSET(0, &temps))
      {
	str_len=read(0, buf, BUF_SIZE);
	buf[str_len]=0;
	printf("message from console: %s \n", buf);
      }
    }
  }
  
  return 0;
}

实现IO复用的服务器端:

//使用select函数实现IO复用回声服务器
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/select.h>

#define BUF_SIZE 30

void error_handling(char *message);

int main(int argc, char *argv[])
{
  int serv_sock;
  int clnt_sock;
  struct sockaddr_in serv_addr;
  struct sockaddr_in clnt_addr;
  socklen_t clnt_addr_size;
  int str_len;
  char buf[BUF_SIZE];
  
  fd_set reads, cpy_reads;
  struct timeval timeout;
  int fd_max, fd_num;
  
  if(argc!=2)
  {
    exit(1);
  }
  
  //TCP socket
  serv_sock=socket(PF_INET, SOCK_STREAM, 0);
  if(serv_sock == -1)
    error_handling("socket error!");

  memset(&serv_addr, 0, sizeof(serv_addr));
  serv_addr.sin_family = AF_INET;	//IPV4协议族
  serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);	//主机字节序(host)转换成网络字节序(net)(大端序)
  serv_addr.sin_port = htons(atoi(argv[1]));	//端口号

  if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1)
    error_handling("bind error");

  if(listen(serv_sock, 5) == -1)
    error_handling("listen error");

  FD_ZERO(&reads);
  FD_SET(serv_sock, &reads);
  fd_max=serv_sock;
  
  while(1)
  {
    cpy_reads=reads;
    timeout.tv_sec=5;
    timeout.tv_usec=5000;
    
    if((fd_num=select(fd_max+1, &cpy_reads, 0, 0, &timeout))==-1)
      break;
    if(fd_num==0)
      continue;
    
    int i;
    for(i=0;i<fd_max+1;i++)
    {
      if(FD_ISSET(i, &cpy_reads))
      {
	if(i==serv_sock)	//发生连接请求
	{
	  clnt_addr_size=sizeof(clnt_addr);
	  clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
	  //将新的客户端套接字加入监控
	  FD_SET(clnt_sock, &reads);
	  if(fd_max<clnt_sock)
	    fd_max=clnt_sock;
	  printf("connected client: %d \n", clnt_sock);
	}
	else	//读取消息
	{
	  str_len=read(i, buf, BUF_SIZE);
	  if(str_len==0)	//如果接收到EOF结束符要关闭套接字
	  {
	    FD_CLR(i, &reads);	//将监控位置0
	    close(i);
	    printf("close client: %d \n", i);
	  }
	  else
	  {
	    write(i, buf, str_len);
	  }
	}
      }
    }
  }
  
  close(serv_sock);
  
  return 0;
}

void error_handling(char *message)
{
  fputs(message, stderr);
  fputc('\n', stderr);
  exit(1);
}

猜你喜欢

转载自blog.csdn.net/u012411498/article/details/80499561