1. 程式人生 > >Linux網路程式設計:使用select函式實現socket 收發資料

Linux網路程式設計:使用select函式實現socket 收發資料

 所謂的回射是指:客戶端A向服務端B傳送資料,服務端B接收到資料之後,再將接收到的資料傳送回客戶端B。所謂的迭代伺服器,是指伺服器端只用一個程序處理或執行緒處理所有客戶端的請求。與之對應的是併發伺服器,併發伺服器是指對於每一一個客戶端的請求,服務端都分配一個程序或是執行緒獨立來處理客戶端的處理。下面介紹使用select函式實現TCP回射迭代服務。直接上程式碼:

服務端程式:

/*=============================================================================
#     FileName: tcpservselect.c
#         Desc: receive client data and then send they back.
#       Author: Licaibiao
#   LastChange: 2017-02-12 
=============================================================================*/
#include<stdio.h>  
#include<sys/types.h>  
#include<sys/socket.h>  
#include<unistd.h>  
#include<stdlib.h>  
#include<errno.h>  
#include<arpa/inet.h>  
#include<netinet/in.h>  
#include<string.h>  
#include<signal.h>
#define MAXLINE      1024
#define LISTENLEN 10
#define SERV_PORT 6666
 
int main(int argc, char **argv)
{
    int                    i, maxi, maxfd, listenfd, connfd, sockfd;
    int                    nready, client[FD_SETSIZE];
    ssize_t                n;
    fd_set                rset, allset;
    char                buf[MAXLINE];
    socklen_t            clilen;
    struct sockaddr_in    cliaddr, servaddr;
 
    listenfd = socket(AF_INET, SOCK_STREAM, 0);
 
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family      = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port        = htons(SERV_PORT);
 
    bind(listenfd, (struct sockaddr*) &servaddr, sizeof(servaddr));
 
    listen(listenfd, LISTENLEN);
 
    maxfd = listenfd;            /* initialize */
    maxi = -1;                    /* index into client[] array */
    for (i = 0; i < FD_SETSIZE; i++)
        client[i] = -1;            /* -1 indicates available entry */
    FD_ZERO(&allset);
    FD_SET(listenfd, &allset);
 
    for ( ; ; ) 
    {
        rset = allset;        /* structure assignment */
        nready = select(maxfd+1, &rset, NULL, NULL, NULL);
 
        if (FD_ISSET(listenfd, &rset)) /* new client connection */
        {    
            clilen = sizeof(cliaddr);
            connfd = accept(listenfd, (struct sockaddr*) &cliaddr, &clilen);
#ifdef    NOTDEF
            printf("new client: %s, port %d\n",
                    inet_ntop(AF_INET, &cliaddr.sin_addr, 4, NULL),
                    ntohs(cliaddr.sin_port));
#endif
 
            for (i = 0; i < FD_SETSIZE; i++)
                if (client[i] < 0) {
                    client[i] = connfd;    /* save descriptor */
                    break;
                }
            if (i == FD_SETSIZE)
            {
                printf("too many clients");
                exit(0);
            }
 
            FD_SET(connfd, &allset);    /* add new descriptor to set */
            if (connfd > maxfd)
                maxfd = connfd;            /* for select */
            if (i > maxi)
                maxi = i;                /* max index in client[] array */
 
            if (--nready <= 0)
                continue;                /* no more readable descriptors */
        }
 
        for (i = 0; i <= maxi; i++)     /* check all clients for data */
        {    
            if ( (sockfd = client[i]) < 0)
                continue;
            if (FD_ISSET(sockfd, &rset)) 
            {
                if ( (n = read(sockfd, buf, MAXLINE)) == 0)/* connection closed by client */ 
                {
                    close(sockfd);
                    FD_CLR(sockfd, &allset);
                    client[i] = -1;
                } else
                    write(sockfd, buf, n);
 
                if (--nready <= 0)
                    break;                /* no more readable descriptors */
            }
        }
    }
}
 
在服務端的程式中,我們使用select 來處理任意個客戶的單程序程式,而不是派生一個子程式。
客戶端程式:

/*=============================================================================
#     FileName: tcpcliselect.c
#         Desc: send data to server and receive data from server
#       Author: Licaibiao
#   LastChange: 2017-02-12 
=============================================================================*/
#include<stdio.h>  
#include<sys/types.h>  
#include<sys/socket.h>  
#include<unistd.h>  
#include<stdlib.h>  
#include<errno.h>  
#include<arpa/inet.h>  
#include<netinet/in.h>  
#include<string.h>  
#include<signal.h>
#define MAXLINE      1024
#define LISTENLEN 10
#define SERV_PORT 6666
 
int max(int a, int b)
{
    return a>b ? a : b;
}
 
void str_cli(FILE *fp, int sockfd)
{
    int            maxfdp1, stdineof;
    fd_set        rset;
    char        buf[MAXLINE];
    int        n;
 
    stdineof = 0;
    FD_ZERO(&rset);
    for ( ; ; ) 
    {
        if (stdineof == 0)
            FD_SET(fileno(fp), &rset);
        FD_SET(sockfd, &rset);
        maxfdp1 = max(fileno(fp), sockfd) + 1;
        select(maxfdp1, &rset, NULL, NULL, NULL);
 
        if (FD_ISSET(sockfd, &rset)) 
        {    
            if ( (n = read(sockfd, buf, MAXLINE)) == 0) /* socket is readable */
            {
                if (stdineof == 1)
                    return;        /* normal termination */
                else
                    printf("str_cli: server terminated prematurely");
            }
            write(fileno(stdout), buf, n);
        }
 
        if (FD_ISSET(fileno(fp), &rset))  /* input is readable */
        {  
            if ( (n = read(fileno(fp), buf, MAXLINE)) == 0) 
            {
                stdineof = 1;
                shutdown(sockfd, SHUT_WR);    /* send FIN */
                FD_CLR(fileno(fp), &rset);
                continue;
            }
 
            write(sockfd, buf, n);
        }
    }
}
 
int main(int argc, char **argv)
{
    int    sockfd;
    struct sockaddr_in    servaddr;
 
    if (argc != 2)
    {
        printf("usage: tcpcli <IPaddress>");
        exit(0);
    }
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
 
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERV_PORT);
    inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
 
    connect(sockfd, (struct sockaddr*) &servaddr, sizeof(servaddr));
 
    str_cli(stdin, sockfd);        /* do it all */
 
    exit(0);
}
 
 
    在第60行我們使用了shutdown函式。我們知道,TCP是全雙工工作,在我們做批量輸入批量輸出的時候,我們客戶端已經把資料傳送完畢,這個時候並不能直接關閉描述符,因為可能還有資料在從服務端傳送回來的路上。close函式是直接終止讀和寫兩個方向的資料傳送。但是使用shutdown可以單方向關閉資料傳輸。
    在客戶端,我們使用Ctrl + d 來結束客戶端程式。Ctrl + d 會發送一個exit 。在TCP傳輸中,如果對端TCP傳送一個FIN(finish 對端程序終止),那麼該套接字變為可讀,並且read返回0(EOF)

執行結果:

執行服務端程式

root@ubuntu:/home/share/test# ./strserselect
另外一個終端執行客戶端程式:
root@ubuntu:/home/share/test# ./strcliselect 127.0.0.1
china                        /*傳送*/
china                        /*接收*/

注意:上面的程式存在一個問題,如果有一個惡意客戶端只發送一個位元組資料(不是換行符)後進入睡眠,伺服器呼叫read讀入一個位元組,後面就阻塞在read函式以等待其他的資料,這樣一來服務端就阻塞在一個客戶端,不能再處理其他客戶端的請求(拒絕服務型攻擊)
解決上面問題有下面的幾種方法:

(a)使用非阻塞式IO

(b)對IO操作設定一個超時

(c)讓每個客戶由單獨的控制執行緒提供服務


--------------------- 
作者:li_wen01 
來源:CSDN 
原文:https://blog.csdn.net/li_wen01/article/details/55004918 
版權宣告:本文為博主原創文章,轉載請