TCP套接字編程(二)

篇一:TCP套接字編程(一)
篇二:TCP套接字編程(二)
前面通過多進程,多線程,支持處理多個客戶端通信,本篇將講述用select這個IO複用來實現。

IO模型

首先連接一下IO模型,unix下有5種可用IO模型,分別是:阻塞式IO、非阻塞式IO、IO複用、信號驅動式IO。


這裏寫圖片描述

5種IO模型比較

1、阻塞式IO

阻塞式IO是最常用的IO模式,它就是沒有數據就阻塞,一直等到有數據位置,期間啥也不幹,就一門心思等數據。

2、非阻塞式IO

非阻塞式IO,就是不斷輪詢,問“數據有沒有準備好啊”,“數據有沒有準備好啊”….。數據準備好了,返回數據。不斷詢問,會耗費大量CPU時間。

3、IO複用模型

slect和pool就是IO複用。它是阻塞在系統調用,而不是阻塞在真正的IO系統調用上。學會轉移矛盾,避免呢傷害了,自己很忙,不能阻塞自己,讓別人幫忙等待(系統調用)。

4、信號驅動式IO模型

信號是個好東西,可以讓內核在描述符就緒了,用信號(SIGIO)通知自己呀,這樣不就工作學習兩不誤了。

5、異步IO模型

信號驅動IO是由內核通知我們何時可以啓動一個IO操作,這個不通知我們何時啓動,而是直接替我們工作,並由內核告訴我們何時可以完成。一個是何時開始,一個是何時完成。

select函數

slelect函數原型爲:

int select(int maxfdp1,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);

參數含義:

  • maxfdp1:指定待預測的描述符個數,它的值是待測試的最大描述符加1,描述符0,1,2,… ,maxfdp1-1;
  • readfds,writefds,exceptfds:指定我們要讓內核測試讀、寫和異常條件的描述符;
  • timeout:告知內核等待就緒描述符可花多長時間(不等,等一段時間,永久等待)。

描述符操作:

void FD_ZERO(fd_set *fdset); //initialize the set:all bits off
void FD_SET(int fd,fd_set *fdset);  //turn on the bit for fd in dfset
void FD_CLR(int fd,fd_set *fdset);  //turn off the bit for fd in dfset
int  FD_ISSET(int fd,fd_set *fdset);  // is the bit for fd on in fdset

瞭解了select的使用,下面就來用select實現echo服務器。

server select版

/**
    Handle multiple socket connections with select and fd_set on Linux
*/

#include <stdio.h>
#include <string.h>   //strlen
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>   //close
#include <arpa/inet.h>    //close
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <sys/select.h> //FD_SET, FD_ISSET, FD_ZERO macros

#define TRUE   1
#define FALSE  0
#define PORT 8888
#define BUF_SIZE 1205
#define MAX_ENQ 1024

int main(int argc , char *argv[])
{
    int opt = TRUE;
    int listenfd ,connfd,max_fd,sockfd, activity,i ,valread ;
    int client[FD_SETSIZE],max_client_index;

    socklen_t socklen=sizeof(struct sockaddr_in);

    char buffer[BUF_SIZE];  //data buffer of 1K

    struct sockaddr_in servaddr,cliaddr;


    //set of socket descriptors
    fd_set rset,allset;

    //initialise all client_socket[] to 0 so not checked
    for (i = 0; i < FD_SETSIZE; i++) 
    {
        client[i] = -1;
    }
    max_client_index=-1;

    //create a master socket
    if( (listenfd = socket(AF_INET , SOCK_STREAM , 0)) <0) 
    {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    //set master socket to allow multiple connections , this is just a good habit, it will work without this
    if( setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt)) < 0 )
    {
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }

    //type of socket created
    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = INADDR_ANY;
    servaddr.sin_port = htons( PORT );

    //bind the socket to localhost port 8888
    if (bind(listenfd, (struct sockaddr *)&servaddr, socklen)<0) 
    {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    //try to specify maximum of 3 pending connections for the master socket
    if (listen(listenfd, MAX_ENQ) < 0)
    {
        perror("listen");
        exit(EXIT_FAILURE);
    }

    //accept the incoming connection
    printf("echo server use select startup, listen on port %d\n", PORT);
    printf("max connection: %d\n", FD_SETSIZE);
    puts("Waiting for connections ...");  

    //clear the socket set
    FD_ZERO(&allset);    
    //add master socket to set
    FD_SET(listenfd, &allset); 
    max_fd = listenfd;

    while(TRUE) 
    {
        rset=allset;
        //wait for an activity on one of the sockets , timeout is NULL , so wait indefinitely
        activity = select( max_fd + 1 , &rset , NULL , NULL , NULL);   
        if ((activity < 0) && (errno!=EINTR)) 
        {
            printf("select error");
        }

        //If something happened on the master socket , then its an incoming connection
        if (FD_ISSET(listenfd, &rset)) 
        {          
            if ((connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &socklen))<0)
            {
                perror("accept");
                exit(EXIT_FAILURE);
            }

            //inform user of socket number - used in send and receive commands
            printf("New connection , socket fd is %d , ip is : %s , port : %d \n" , connfd , inet_ntoa(cliaddr.sin_addr) , ntohs(cliaddr.sin_port));

            //send new connection greeting message
            //a message
            char *message = "good ,connect successfully \r\n";
            if( send(connfd, message, strlen(message), 0) != strlen(message) ) 
            {
                perror("send");
            }

            //add new socket to array of sockets
            for (i = 0; i < FD_SETSIZE; i++) 
            {
                if( client[i] ==-1)
                {
                    client[i] = connfd;                   
                    break;
                }
            }

            if (i == FD_SETSIZE)
            {
                fprintf(stderr, "too many connection, more than %d\n", FD_SETSIZE);
                close(connfd);
                continue;
            }

            if (connfd>max_fd)
               max_fd=connfd;

           if(i>max_client_index)
               max_client_index=i;

            FD_SET(connfd, &allset);  //一定不能丟,一定不能丟
            if(--activity<=0)
                continue;
        }


        //else its some IO operation on some other socket :)
        for (i = 0; i <= max_client_index; i++) 
        {
            if((sockfd = client[i])!=-1)
            {
                if (FD_ISSET( sockfd , &rset)) 
                {
                    //Check if it was for closing , and also read the incoming message
                    if ((valread = read( sockfd , buffer, BUF_SIZE)) == 0)
                    {
                        //Somebody disconnected , get his details and print
                        getpeername(sockfd , (struct sockaddr*)&cliaddr , &socklen);
                        printf("Host disconnected , ip %s , port %d \n" , inet_ntoa(cliaddr.sin_addr) , ntohs(cliaddr.sin_port));

                        //Close the socket and mark as 0 in list for reuse
                        close(sockfd);
                        FD_CLR(sockfd, &allset);
                        client[i] = -1;
                        continue;
                    }
                    else if (valread<0)
                    {
                        perror("read error");
                        close(sockfd);
                        FD_CLR(sockfd, &allset);
                        client[i] = -1;
                        continue;
                    }

                    //Echo back the message that came in
                    else 
                    {
                        printf("get message form client %d,meeasge is %s\n" , i,buffer);
                        //set the string terminating NULL byte on the end of the data read
                        buffer[valread] = '\0';
                        send(sockfd , buffer , strlen(buffer) , 0 );
                    }

                    if(--activity<=0)
                        break;
                }

            }                
        }
    }

    return 0;
} 

真是不容易,花了一個早上才調試通。測試結果如下:
服務器:

客戶端1:

客戶端2:

客戶端3:

select學習就到這裏了,接下來試着用pool和epool實現服務器,加油!!!!!

發佈了79 篇原創文章 · 獲贊 127 · 訪問量 39萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章