1. 程式人生 > >套接字聯網API之二 select作用和案例

套接字聯網API之二 select作用和案例

這個系列的上一篇文章講了套接字聯網API在服務端和客戶端的幾個主要函式。 這篇文章用來實現,順便說一下select模型,也在下面的程式碼中用到。

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

select模型出現的真正原因在網上一直找不到,經過仔細思考其實只有一個,就是解決非同步的問題;而且這個select是要結合多執行緒的。下面對這些進行說明。

1) 當使用阻塞 I/0時, 當阻塞在輸入的時候無法進行讀取的監聽;

2) 當使用非阻塞I/O時,會在讀取的監聽上消耗CPU的資源。 其實這種情況即使忽略消耗的CPU資源,也是不能實現非同步的。

3) 這時可能會考慮到使用多執行緒,就是讓讀取的監聽  和  輸入放在不同的執行緒中。

比如採用select監聽一個套接字, 如果返回一個結果,就開闢一個新執行緒用來進行互動,假設這個新的執行緒阻塞在輸入上,監聽執行緒還是能夠監聽的,如果收到了一個FIN,能夠及時的終止這個新的執行緒。  這就是select的作用,但是是不是每個連線都要設定一個select?  不用的,之需要在全域性的主執行緒設定一個select就可以了,每當一個連線建立,就信開闢一個執行緒,主執行緒仍然在監聽;而它又收到監聽描述符上的資訊後(比如FIN報文),就會對該描述符對應的執行緒進行處理。

下面是select的一個模型,分為服務端和客戶端,有詳細的註釋。

服務端流程如下: 只發了一個檔案,目的是弄清流程

#include <stdio.h>
#include <stdlib.h>
#include "server.h"
#include "database.h"
#include "userdata.h"
#include "struct.h"
#include "thread.h"

int main()
{
    int x = 0;
    //關於線上使用者變數
    userlist_init(&L);
    pthread_t hThread[FD_SETSIZE];

    //套接字變數
    struct message ma;

    int i,maxi;   //maxi代表儲存最大已連線描述符的序號
    fd_set allset,curset;
    int readyfd,maxfd;    //select返回的已準備好的描述符
    int client[FD_SETSIZE];//連線描述符陣列

    int ser_sockfd,cli_sockfd,confd;
    int ser_length,cli_length;

    struct sockaddr_in server_address;
    struct sockaddr_in client_address;

    //先連上資料庫     連線的mysql的資料庫,使用者名稱和密碼均為majintao
    my_sql=mysql_init(NULL);
    if(NULL == my_sql)
    {
        printf("mysql init error\n");
        return mysqlinit;
    }
    my_sql=mysql_real_connect(my_sql,"localhost","majintao","majintao","chatroom",0,NULL,0);
    if(NULL == my_sql)
    {
        printf("connect error\n");
        return mysqlconnect;
    }


    //建立一個未命名的套接字
    ser_sockfd=socket(AF_INET, SOCK_STREAM,0);
    if(ser_sockfd < 0)
    {

        printf("create socket error\n");
        return 0;
    }
    bzero(&server_address,sizeof(server_address));
    server_address.sin_family=AF_INET;
    server_address.sin_addr.s_addr=htonl(INADDR_ANY);   //暫定
    server_address.sin_port=htons(9734);//暫定

    int bindfd=bind(ser_sockfd,(struct sockaddr*)&server_address,sizeof(server_address));
    if(bindfd < 0)
    {
        printf("bind error\n");
        return 0;
    }

    //客戶端資料處理
    bzero(&client_address,sizeof(client_address));
    //cl_echo(ser_sockfd,(struct sockaddr*)&server_address,sizeof(server_address));
    listen(ser_sockfd,LISTENQ);

    FD_ZERO(&allset);
    FD_SET(ser_sockfd,&allset);
    maxfd=ser_sockfd;

    //初始化描述符陣列
    for(i=0;i<FD_SETSIZE;i++)
    {
        client[i]=-1;
    }
    //開始select監聽套接字
    curset=allset;
    for(;;)
    {
        //
        <span style="color:#ff0000;">readyfd=select(maxfd+1,&curset,NULL,NULL,NULL);</span>

        if(FD_ISSET(ser_sockfd,&curset))
        {
                cli_length=sizeof(client_address);

                if(-1 == (confd=accept(ser_sockfd,(struct sockaddr*)&client_address,&cli_length) ) )
                {
                    printf("accept error\n");
                    exit(1);
                }
                //把描述符放到合適的位置
                for(i =0;i<FD_SETSIZE;i++)
                {
                    if(client[i] <= 0)
                    {
                        client[i] = confd;
                        break;
                    }
                }
                //客戶端連線太多
                if(i == FD_SETSIZE)
                {
                    printf("too many clients\n");
                    exit(1);
                }

                //重新設定
                FD_SET(confd,&allset);

                //如果已連線描述符大於監聽描述符,更改
                //if(confd > maxfd)
                //{
                  //  maxfd = confd;
               // }

                //更新描述符所在序號的最大值
                if(i > maxi)
                {
                    maxi = i;
                }

                //如果只有一個監聽描述符,就不檢查已連線描述符了
                /*if(--readyfd  <= 0)
                {
                    continue;
                }*/

        }   //fd_set

    //對每一個已經連線描述符做處理
    for(i = 0;i <=maxi; i++)
    {
        if( (testfd = client[i]) < 0)
            continue;

        if( FD_ISSET(testfd,&allset) )
        {
            int tThread;
            //這個時候建立一個執行緒,意思為每個已經連線的描述符建立一個執行緒
            <span style="color:#ff0000;">tThread = pthread_create(&hThread[x++], NULL, thread_ser,&testfd);</span>

            if(tThread != 0)
            {
                printf("Thread create failed\n");
                return -1;
            }
        }

        client[i] = -1;   //保證不會重複建立執行緒

        if(--readyfd <= 0)
                break;

    }//第二個for


    }//最外層for迴圈


}//main



客戶端沒有使用select,就不介紹了。