1. 程式人生 > >Linux網路程式設計 -- select實現多路IO轉接伺服器

Linux網路程式設計 -- select實現多路IO轉接伺服器

server.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <ctype.h>

#include "wrap.h"

#define SERV_PORT 8888

int main()
{
    int opt = 1;
    int i, j, n, maxi;
    int nready, client[FD_SETSIZE];   //自定義陣列client, 防止遍歷1024個檔案描述符  FD_SETSIZE預設為1024
int maxfd, listenfd, connfd, sockfd; char buf[BUFSIZ], clie_IP[INET_ADDRSTRLEN]; struct sockaddr_in clie_addr, serv_addr; socklen_t clie_addr_len; fd_set read_set, allset; //read_set 讀事件檔案描述符集合 allset用來暫存 listenfd = Socket(AF_INET, SOCK_STREAM, 0); setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof
(opt)); bzero(&serv_addr, sizeof(serv_addr)); serv_addr.sin_family= AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port= htons(SERV_PORT); Bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); Listen(listenfd, 128); maxfd = listenfd; maxi = -1
; //指向陣列首地址的前一個 for (i = 0; i < FD_SETSIZE; ++i) { client[i] = -1; //將存放客戶端的檔案描述符的陣列初始化為 -1 } FD_ZERO(&allset); //將allset清空 FD_SET(listenfd, &allset); //將listenfd 放入allset中 while (1) { read_set = allset; //select 返回所有監聽的文教描述符滿足條件的總個數 //引數1:所監聽的所有檔案描述符中,最大的檔案描述符+1 //引數2/3/4:所監聽的檔案描述符 可讀、可寫、異常時間 nready = select(maxfd+1, &read_set, NULL, NULL, NULL); if (nready < 0) { perr_exit("select error"); } if (FD_ISSET(listenfd, &read_set)) //說明有新的客戶端連線請求 { clie_addr_len = sizeof(clie_addr); connfd = Accept(listenfd, (struct sockaddr *)&clie_addr, &clie_addr_len); printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &clie_addr.sin_addr, clie_IP, sizeof(clie_IP)), ntohs(clie_addr.sin_port)); for (i = 0; i < FD_SETSIZE; ++i) { if (client[i] < 0) //定位到client陣列中沒有存入檔案描述符的位置 { client[i] = connfd; //將新連線的客戶端的檔案描述符儲存如client break; } } if (FD_SETSIZE == i) //防止檔案描述符的個數超過陣列的容量 { printf("client numbers over\n"); exit(1); } FD_SET(connfd, &allset); //將conf儲存到allset集合中 if (connfd > maxfd) //更新最大的檔案描述符 { maxfd = connfd; } if (i > maxi) //更新client陣列的內容的長度 { maxi = i; } if (--nready == 0) { continue; } } for (i = 0; i <= maxi; ++i) ///檢測client陣列中有哪些檔案描述符滿足了條件 */ { if ((sockfd = client[i]) < 0) //該元素還沒有儲存檔案描述符 { continue; } if (FD_ISSET(sockfd, &read_set)) //該文教描述符是否滿足讀事件 { n = Read(sockfd, buf, sizeof(buf)); if (0 == n) ///表示該客戶端關閉連結,此時,伺服器端也要關閉對應連結 { Close(sockfd); FD_CLR(sockfd, &allset); //將該檔案描述符從allset集合中清除 client[i] = -1; } else if (n > 0) { for (j = 0; j < n; ++j) { buf[j] = toupper(buf[j]); } Write(sockfd, buf, n); //將處理後的資料傳送給客戶端 Write(STDOUT_FILENO, buf, n); //在服務端顯示處理的資料 } if (--nready == 0) { break; } } } } Close(listenfd); return 0; }

wrap.h

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>

//輸出錯誤資訊並退出
void perr_exit(const char *str)
{
    perror(str);
    exit(-1);
}    

//建立套接字 帶出錯處理
int Socket(int family, int type, int protocol)
{
    int n;

    n = socket(family, type, protocol);
    if (n < 0)
    {
        perr_exit("socket error");
    }

    return n;
}


int Bind(int fd, const struct sockaddr *sa, socklen_t salen)
{
    int n;

    n = bind(fd, sa, salen);
    if (n < 0)
    {
        perr_exit("bing error");
    }

    return n;
}

int Listen(int fd, int backlog)
{
    int n;

    n = listen(fd, backlog);
    if (n < 0)
    {
        perr_exit("listen error");
    }

    return n;
}

int Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{
    int n;

    n = connect(fd, sa, salen);
    if (n < 0)
    {
        perr_exit("connect error");
    }

    return n;
}

int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
{
    int n;

again:
    n = accept(fd, sa, salenptr);
    if (n < 0)
    {
        if (errno == ECONNABORTED || errno == EINTR)
        {
            goto again;
        }
        else
        {
            perr_exit("accept error");
        }
    }

    return n;
}

ssize_t Read(int fd, void *ptr, size_t nbytes)
{
    ssize_t n;

again:
    n = read(fd, ptr, nbytes);
    if (-1 == n)
    {
        if (EINTR == errno)
        {
            goto again;
        }
        else
        {
            return -1;
        }
    }

    return n;
}

ssize_t Write(int fd, const void *ptr, size_t nbytes)
{
    ssize_t n;

again:
    n = write(fd, ptr, nbytes);
    if (-1 == n)
    {
        if (EINTR == errno)
        {
            goto again;
        }
        else
        {
            return -1;
        }
    }

    return n;
}

int Close(int fd)
{
    int n;

    n = close(fd);
    if (-1 == n)
    {
        perr_exit("close error");
    }

    return n;
}