linux 下socket 服务器和客户端异步通信

我们知道用socket进行通信时,发送数据和接收数据所使用的recv/send函数会阻塞进程,只有收到或发送数据后才能返回值,导致是socket通信只能实现服务器和客户端交替收发数据,而使用select可以很好地解决这个问题。
诸如connect、accept、recv或recvfrom这样的阻塞程序(所谓阻塞方式block,顾名思义,就是进程或是线程执行到这些函数时必须等待某个事件的发生,如果事件没有发生,进程或线程就被阻塞,函数不能立即返回)。可是使用Select就可以完成非阻塞(所谓非阻塞方式non-block,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高)方式工作的程序,它能够监视我们需要监视的文件描述符的变化情况——读写或是异常。
select函数的原型:int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval*timeout); 关于select函数的用法和作用,大家看看这个帖子

http://blog.csdn.net/piaojun_pj/article/details/5991968/

这个帖子已经讲的很详细了,大家可以看一看,关于select如何解决socket的异步通信问题,个人的理解:select的作用在于,以轮询的方式 同时对“自己有无发送数据” “对方有无发送数据”同时进行监控。而每次检测的时间限制为 struct timeval * timeout 中自己设置的时间,在检测的这段时间内,进程/线程会阻塞。这样就可以不断地交替对“ 是否发送了数据”和“是否接收到数据”进行不断交替检测,这样,进程就不会被recv或是send阻塞,从而实现了异步通信。
代码如下:
服务器 server.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 

#define PORT 7000
#define QUEUE 20

int main() {
    fd_set rfds;
    struct timeval tv;
    int retval, maxfd;
    int listenfd= socket(AF_INET, SOCK_STREAM, 0);

    //设置地址可重复方式
    int opt = SO_REUSEADDR;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    struct sockaddr_in server_sockaddr;
    server_sockaddr.sin_family = AF_INET;
    server_sockaddr.sin_port = htons(PORT);
    server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);

    if(bind(listenfd, (struct sockaddr* ) &server_sockaddr, sizeof(server_sockaddr))==-1) {
        perror("bind");
        exit(1);
    }
    if(listen(listenfd, QUEUE) == -1) {
        perror("listen");
        exit(1);
    }

    struct sockaddr_in client_addr;
    socklen_t length = sizeof(client_addr);

    int fd = accept(listenfd, (struct sockaddr*)&client_addr, &length);
    if( fd < 0 ) {
        perror("connect");
        exit(1);
    }

    printf("You got connected from:%s\n",inet_ntoa(client_addr.sin_addr));

    while(1) {

        /*把可读文件描述符的集合清空*/
        FD_ZERO(&rfds);
        /*把 标准输入 的文件描述符加入到集合中*/
        FD_SET(0, &rfds);
        maxfd = 0;
        /*把当前连接的文件描述符加入到集合中*/
        FD_SET(fd, &rfds);
        /*找出文件描述符集合中最大的文件描述符*/    
        if(maxfd < fd)
            maxfd = fd;
        /*设置超时时间*/
        tv.tv_sec = 5;
        tv.tv_usec = 0;
        /*等待聊天*/
        retval = select(maxfd+1, &rfds, NULL, NULL, &tv);
        if(retval == -1){
            printf("select出错,客户端程序退出\n");
            break;
        }else if(retval == 0){
            continue;
        }else{
            /*客户端发来了消息*/
            if(FD_ISSET(fd,&rfds)){
                char buffer[1024];    
                memset(buffer, 0 ,sizeof(buffer));
                int len = recv(fd, buffer, sizeof(buffer), 0);
                if(strcmp(buffer, "exit\n") == 0) exit(0);
                printf("From client:%s", buffer);
            }
            /*用户输入信息了,开始处理信息并发送*/
            if(FD_ISSET(0, &rfds)){
                char buf[1024];
                fgets(buf, sizeof(buf), stdin);

                send(fd, buf, sizeof(buf), 0);  
                if(strcmp(buf, "exit\n") == 0) exit(0);

            }
        }
    }
    close(fd);
    close(listenfd);
    return 0;
}

客户端 client.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define PORT  7000
#define BUFFER_SIZE 1024
int main()
{
    int fd;
    fd_set rfds;
    struct timeval tv;
    int retval, maxfd;


    fd = socket(AF_INET,SOCK_STREAM, 0);

    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(PORT);  
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");


    if (connect(fd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
    {
        perror("connect");
        exit(1);
    }

    while(1){
        /*把可读文件描述符的集合清空*/
        FD_ZERO(&rfds);
        /*把标准输入的文件描述符加入到集合中*/
        FD_SET(0, &rfds);
        maxfd = 0;
        /*把当前连接的文件描述符加入到集合中*/
        FD_SET(fd, &rfds);
        /*找出文件描述符集合中最大的文件描述符*/    
        if(maxfd < fd)
            maxfd = fd;
        /*设置超时时间*/
        tv.tv_sec = 5;
        tv.tv_usec = 0;
        /*等待聊天*/
        retval = select(maxfd+1, &rfds, NULL, NULL, &tv);
        if(retval == -1){
            printf("select出错,客户端程序退出\n");
            break;
        }else if(retval == 0){
            continue;
        }else{
            /*服务器发来了消息*/
            if(FD_ISSET(fd,&rfds)){
                char recvbuf[BUFFER_SIZE];
                int len;
                len = recv(fd, recvbuf, sizeof(recvbuf),0);
                printf("From server:%s", recvbuf);
                if(strcmp(recvbuf,"exit\n")==0) exit(0);
                memset(recvbuf, 0, sizeof(recvbuf));
            }
            /*用户输入信息了,开始处理信息并发送*/
            if(FD_ISSET(0, &rfds)){
                char sendbuf[BUFFER_SIZE];
                fgets(sendbuf, sizeof(sendbuf), stdin);
                send(fd, sendbuf, strlen(sendbuf),0); 
                if(strcmp(sendbuf,"exit\n")==0) exit(0);
                memset(sendbuf, 0, sizeof(sendbuf));
                            }
        }
    }

    close(fd);
    return 0;
}

个人理解:以客户端为例,在上述代码中,若FD_ISSET(fd,&rfds)不为0,则表示接收到了数据,这是因为socket的数据传输是通过读写文件描述符来实现的,所以服务器发送数据时,select可以检测到文件描述符已被读写,于是接收数据。而select中0 1 2分别表示标准输入,标准输出,标准错误。符若FD_ISSET(0,&rfds)不为0,则表示用户已经输入了数据,于是发送数据。

你可能感兴趣的:(socket网络编程)