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 <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <iostream>

#include <stdlib.h>
#include <netinet/in.h>

#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 <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <stdlib.h>

#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,則表示用戶已經輸入了數據,於是發送數據。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章