linux socke编程实例:一个简单的echo服务器程序(2)

    在文章 <<linux socke编程实例:一个简单的echo服务器程序>>简单地介绍了如何在linix使用socket进行网络编程,并且在文中给出相应的程序和说明,以使大家对socket编程有初步的了解。现在我们解决文中出现的问题:不能同时与多个客户进行通信,只能第一个客户通信结束事才能与第二个客户进行通信,依次...直到有的客户连接超时,或者没有客户进行连接。通常一个服务器程序应能同时与多个客户端进行通信,能及时对它的请求作出回复。
   其实这个问题有很多解方案,最明显的一种就是采用多线程或者采用多进程。显然多进程是不可取的,因此有多线程的存。采用多线程技术时,每当有客户请求连接,服务器都是新建一个线程与之通信,通信完成后将终止该线程。这样的情况适合于大量IO操作的服务器程程,或者服务器的是多核的。否则,这样未免太浪费了,系统的开销也会很大。因此我们致力于采用单进程单线的解决方案。
   其实,我们对C/S通信方式中的服务器端会有这样的直觉:服务器等待客户的到来,如果在一定的时间内(10ms),如果没有收到请求,那就不等了,马上回来处量已连接上的客户端,并与之通信。与已连接上的客户端进行通信,也采用这样的方式,对于任一客户端,看它有没有数据到来,有则处理,没则轮到下一个客户,采用相同的处理方式。依次进行下去,就不需要使用多进程或多线程技术,可以如何实现呢?

    其实IO可以分为阻塞IO和非阻塞IO,当读阻塞IO的数据时,直到有数据时才返回,否则一直阻塞;非阻塞IO却不同,读非阻塞IO的数据时,不管有没有数据到来,它都会及时返回,有 没 有数据到来依据返回值进行判断。无论我们在代码中使用open还是socket函数返回的文件描述符,它都是阻塞的,要使它变为非阻塞,则必须使用fcntl函数对它的属性进行更改。fcntl函数说明如下:

int  fcntl( int fd int  cmd,... /*  int arg * / ) ;

fd为文件描述符,cmd表示命令,可变参数的个数和类型根据cmd值的不同而不同。

f c n t l函数有五种功能:
复制一个现存的描述符(cmd=F_DUPFD)。
获得/设置文件描述符标记(cmd = F_GETFD或F_SETFD)。
获得/设置文件状态标志(cmd = F_GETFL或F_SETFL)。
获得/设置异步I / O有权(cmd = F_GETOWN或F_SETOWN)。
获得/设置记录锁(cmd = F_GETLK , F_SETLK或F_SETLKW)。
要设置文件的非阻塞属生,先要用cmd=F_GETFD获得属性值,然后把属性值为非阻塞的即可。

为了方便设置文件的属生,我们定义了如下的函数:
void  set_fl( int  fd,  int  flags)
{
   
int val;
    //获取属性
   
if((val = fcntl(fd, F_GETFL, 0)) < 0)
    
{
        printf(
"get socket property error ");
        exit(
0);
    }
    //更改属性
    val = val |flags;
    //更改属性后再设置,以使它使效
   
if(fcntl(fd, F_SETFL, val) < 0)
    
{
        printf(
"set socket property error ");
        exit(
1);
    }

}

在函数set_fl中,可以对文件fd设置属性flags,如果我们要把描述符socketfd设置为非阻塞,只需如下语句即可:
set_fl(socketfd, O_NONBLOCK);

下面是采用非阻塞IO机制来实现的代码,与原来的相比,结构和原理上没有改变,只是把所有的IO都设置为非阻塞的。

#include  < netdb.h >
#include 
< sys / socket.h >
#include 
< errno.h >
#include 
< stdio.h >
#include 
< unistd.h >
#include 
< fcntl.h >

#define  EHCO_PORT    8080
#define  MAX_CLIENT_NUM        100

void  set_fl( int  fd,  int  flags)
{
     int  val;
    
if ((val  =  fcntl(fd, F_GETFL,  0 ))  <   0 )
    {
        printf(
" get socket property error " );
        exit(
0 );
    }
    val 
=  val  | flags;
    
if (fcntl(fd, F_SETFL, val)  <   0 )
    {
        printf(
" set socket property error " );
        exit(
1 );
    }
}

int  main()
{
    
int  socketfd;
    socketfd 
=  socket(AF_INET, SOCK_STREAM,  0 );
       
    
if (socketfd  ==   - 1 )
    {
        printf(
" errno=%d " , errno);
        exit(
1 );
    }
    
else
    {
        printf(
" socket create successfully " );
    }

    
struct  sockaddr_in sa;
    bzero(
& sa,  sizeof (sa));
    sa.sin_family 
=  AF_INET;
    sa.sin_port 
=  htons(EHCO_PORT);
    sa.sin_addr.s_addr 
=  htons(INADDR_ANY);
    bzero(
& (sa.sin_zero),  8 );

    
if (bind(socketfd, ( struct  sockaddr  * ) & sa,  sizeof (sa)) !=   0 )
    {
        printf(
" bind failed " );
        printf(
" errno=%d " , errno);
        exit(
1 );
    }
    
else
    {
        printf(
" bind successfully " );
    }

    
// listen
     if (listen(socketfd ,MAX_CLIENT_NUM)  !=   0 )
    {
        printf(
" listen error " );
        exit(
1 );
    }
    
else
    {
        printf(
" listen successfully " );
    }

    
int  clientfd;
    
struct  sockaddr_in clientAdd;
    socklen_t len 
=   sizeof (clientAdd);

    
char  buff[ 100 ];
    
int  closing  = 0 ;
    
int  clientArray[MAX_CLIENT_NUM];
    
int  i;
    
for (i  =   0 ; i  <  MAX_CLIENT_NUM;  ++ i)
    {
        clientArray[i] 
=   - 1 ;     //  -1 indicates no using
    }
   
    // set no blocking for server, and the accept function will not block.
    set_fl(socketfd, O_NONBLOCK);

    
while ( closing  ==   0   )
    {
        clientfd 
=  accept(socketfd, ( struct  sockaddr  * ) & clientAdd,  & len);
        
        
if (clientfd  >   0 )
        {
            printf(
" receive connection " );
            //set no blocking for client, in that the read function will no block.
            set_fl(clientfd, O_NONBLOCK);
            
for (i  =   0 ; i  <  MAX_CLIENT_NUM;  ++ i)
            {
                
if (clientArray[i]  ==   - 1 )
                {
                    clientArray[i] 
=  clientfd;
                    
break ;
                }
            }
            
if (i  ==  MAX_CLIENT_NUM)
            {
                
// there is not enough sockets for client, so close it
                close(clientfd);
            }
        }
        
        
int  n;

        
for (i  =   0 ; i  <  MAX_CLIENT_NUM;  ++ i)
        {
            
if (clientArray[i]  ==   - 1 continue ;
            
if ( (n  =  recv(clientArray[i], buff,  100 0 ))  >   0 )
            {
                printf(
" number of receive bytes = %d " , n);
                write(STDOUT_FILENO, buff, n);
                send(clientArray[i],buff, n, 
0 );
                buff[n] 
= 0 ;
                
if (strcmp(buff,  " quit/r/n " ==   0 )
                {
                    close(clientArray[i]);
                    clientArray[i] 
=   - 1 ;
                    
break ;
                }
                
else   if (strcmp(buff,  " close /r/n" ==   0 )
                {
                    
// server closing
                    closing  =   1 ;
                    printf(
" server is closing " );
                    
break ;
                }
            }
        }
        
// printf("receive finsihed ");
        
    }

    
for (i  =   0 ; i  <  MAX_CLIENT_NUM;  ++ i)
    {
        
if (clientArray[i]  !=   - 1 )
        {
            close(clientArray[i]);    
        }
    }
    close(socketfd);

    
return   0 ;
}

    值得一提的是,当文件为非阻塞时,accept会回返回它接受连接的客户端描述符,如没有客户请求连接,则返回-1;同理,recv会返回该客户端发送过来的数据的字节数,如没有数据到来,则返回-1。因此我们可以根据这两个函数的返回值进行相应的操作。accept返回非负数的情况,说明有新的客户请求连接,因此要把它记录下来,以下次能收到它发送过来的信息。对于recv也一样,如果返回值为正数,则说明有数所到来,对数据作相应的处理返回复。
    经此一役,使用单进程的通信方式仍然可以实现服务器程序。可以使用上文中介绍的方法(telnet)对它进行测试。

    在测试的过程中,我们依然可以发现:服务器采用了非阻塞的方式进行读,当没有数据准备好的时候,它不停地循环,这样浪费了大量的CPU时间。因此,本文的方案仍不是一个最好的方案。
   如果客户端的通信量很少,那么服务器会不停地循环,这样就会占用很多CPU时间,使得其它进程使用的时间很少。
   如果解决这个问题呢?在下一篇章和大家一起探讨一下解决的方法。

你可能感兴趣的:(多线程,编程,linux,socket,服务器,cmd)