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

Posted 海枫

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux socke编程实例:一个简单的echo服务器程序相关的知识,希望对你有一定的参考价值。

    在文章 <<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] == -1continue;
            
if( (n = recv(clientArray[i], buff, 1000)) > 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 socke编程实例:一个简单的echo服务器程序的主要内容,如果未能解决你的问题,请参考以下文章

网络编程基础day08:socke编程入门

linux之Shell编程--Shell输出命令echo和printf

linux shell脚本读取用户输入的参数

Linux echo命令和查看环境变量实例

求问关于linux子shell的问题!

linux shell 自定义加法函数 急求