UNIX環境高級編程學習之第十六章網絡IPC:套接字 - 非阻塞的Socket通信EPoll模型(多路複用), 實用Socket通信模板

UNIX環境高級編程學習之第十六章網絡IPC:套接字 - 非阻塞的Socket通信EPoll模型(多路複用), 實用Socket通信模板

 

/* User:Lixiujie         
* Date:20101207
* Desc:Unix(Linux)非阻塞的Socket通信EPoll模型,多路複用,TCP服務器端, 向客戶端發送響應信息。
* File:tcp_server_epoll.c  
* System:Ubuntu 64bit  
* gcc tcp_server_epoll.c -o  tcp_server_epoll
* tcp_server_epoll 7878
*
EPoll 函數介紹
epoll是Linux內核爲處理大批量句柄而作了改進的poll,是Linux下多路複用IO接口select/poll的增強版本,它能顯著減少程序在大量併發連接中只有少量活躍的情況下的系統CPU利用率。

EPoll 優點
1、它保留了poll的兩個相對與select的優點
2、epoll_wait的參數events作爲出參,直接返回了有事件發生的fd,epoll_wait的返回值既是發生事件的個數,省略了poll中返回之後的循環操作。
3、不再象select、poll一樣將標識符侷限於fd,epoll中可以將標識符擴大爲指針,大大增加了epoll模型下的靈活性。

EPoll 使用說明事項
1、如果fd被註冊到兩個epoll中時,如果有時間發生則兩個epoll都會觸發事件。
2、如果註冊到epoll中的fd被關閉,則其會自動被清除出epoll監聽列表。注意:關閉自動清除,不用手機清除
3、如果多個事件同時觸發epoll,則多個事件會被聯合在一起返回。
4、epoll_wait會一直監聽epollhup事件發生,所以其不需要添加到events中。
5、爲了避免大數據量io時,et模式下只處理一個fd,其他fd被餓死的情況發生。linux建議可以在fd聯繫到的結構中增加ready位,然後epoll_wait觸發事件之後僅將其置位爲ready模式,然後在下邊輪詢ready fd列表。
6、epoll_ctl epoll的事件註冊函數,其events參數可以是以下宏的集合:
        EPOLLIN:    表示對應的文件描述符可以讀(包括對端SOCKET正常關閉);
        EPOLLOUT:   表示對應的文件描述符可以寫;
        EPOLLPRI:   表示對應的文件描述符有緊急的數據可讀(這裏應該表示有帶外數據到來);
        EPOLLERR:   表示對應的文件描述符發生錯誤;寫已關閉socket pipe broken 
        EPOLLHUP:   表示對應的文件描述符被掛斷;譬如收到RST包。在註冊事件的時候這個事件是默認添加。
        EPOLLRDHUP: 表示對應的文件描述符對端socket關閉事件,主要應用於ET模式下。
        在水平觸發模式下,如果對端socket關閉,則會一直觸發epollin事件,驅動去處理client socket。
        在邊沿觸發模式下,如果client首先發送協議然後shutdown寫端。則會觸發epollin事件。但是如果處理程序只進行一次recv操作時,根據recv收取到得數據長度來判讀後邊是否還有需要處理的協議時,將丟失客戶端關閉事件。

        EPOLLET: 將EPOLL設爲邊緣觸發(Edge Triggered)模式,這是相對於水平觸發(Level Triggered)來說的。
        EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之後,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL隊列裏

EPoll 工作模式
1、水平觸發Level Triggered (LT) 是EPoll的缺省的工作方式,並且同時支持block和no-block socket.在這種做法中,內核告訴你一個文件描述符是否就緒了,然後你可以對這個就緒的fd進行IO操作。如果你不作任何操作,內核還是會繼續通知你 的,所以,這種模式編程出錯誤可能性要小一點。傳統的select/poll都是這種模型的代表.

2、邊緣觸發Edge  Triggered (ET) 是EPoll的高速工作方式,只支持no-block socket。在這種模式下,當描述符從未就緒變爲就緒時,內核通過epoll告訴你。然後它會假設你知道文件描述符已經就緒,並且不會再爲那個文件描述 符發送更多的就緒通知,直到你做了某些操作導致那個文件描述符不再爲就緒狀態了(比如,你在發送,接收或者接收請求,或者發送接收的數據少於一定量時導致 了一個EWOULDBLOCK 錯誤)。但是請注意,如果一直不對這個fd作IO操作(從而導致它再次變成未就緒),內核不會發送更多的通知(only once),不過在TCP協議中,ET模式的加速效用仍需要更多的benchmark確認。
內核的讀buffer有內核態主動變化時,內核會通知你, 無需再去mod。寫事件是給用戶使用的,最開始add之後,內核都不會通知你了,你可以強制寫數據(直到EAGAIN或者實際字節數小於 需要寫的字節數),當然你可以主動mod OUT,此時如果句柄可以寫了(send buffer有空間),內核就通知你。 
這裏內核態主動的意思是:內核從網絡接收了數據放入了讀buffer(會通知用戶IN事件,即用戶可以recv數據) 
並且這種通知只會通知一次,如果這次處理(recv)沒有到剛纔說的兩種情況(EAGIN或者實際字節數小於 需要讀寫的字節數),則該事件會被丟棄,直到下次buffer發生變化。 
與LT的差別就在這裏體現,LT在這種情況下,事件不會丟棄,而是隻要讀buffer裏面有數據可以讓用戶讀,則不斷的通知你。

EPoll 函數使用介紹
1、EPoll 創建epoll句柄函數。
int epoll_create(int size);
        參數size:用來告訴內核要監聽的數目一共有多少個。
        返回值:成功時,返回一個非負整數的文件描述符,作爲創建好的epoll句柄。調用失敗時,返回-1,錯誤信息可以通過errno獲得。
    說明:創建一個epoll句柄,size用來告訴內核這個監聽的數目一共有多大。這個參數不同於select()中的第一個參數,給出最大監聽的fd+1的值。需要注意的是,當創建好epoll句柄後,它就是會佔用一個fd值,所以在使用完epoll後,必須調用close()關閉,否則可能導致fd被耗盡。

2、EPoll 註冊修改刪除文件描述符到epoll句柄函數
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    參數epfd:epoll_create()函數返回的epoll句柄。
        參數op:操作選項。
        參數fd:要進行操作的目標文件描述符。
        參數event:struct epoll_event結構指針,將fd和要進行的操作關聯起來。
        返回值:成功時,返回0,作爲創建好的epoll句柄。調用失敗時,返回-1,錯誤信息可以通過errno獲得。
        說明:epoll的事件註冊函數,它不同與select()是在監聽事件時告訴內核要監聽什麼類型的事件,而是在這裏先註冊要監聽的事件類型。
        參數op的可選值有以下3個:
        EPOLL_CTL_ADD:註冊新的fd到epfd中;
        EPOLL_CTL_MOD:修改已經註冊的fd的監聽事件;
        EPOLL_CTL_DEL:從epfd中刪除一個fd;

        events可以是以下幾個宏的集合:
        EPOLLIN :表示對應的文件描述符可以讀(包括對端SOCKET正常關閉);
        EPOLLOUT:表示對應的文件描述符可以寫;
        EPOLLPRI:表示對應的文件描述符有緊急的數據可讀(這裏應該表示有帶外數據到來);
        EPOLLERR:表示對應的文件描述符發生錯誤;
        EPOLLHUP:表示對應的文件描述符被掛斷;
        EPOLLET: 將EPOLL設爲邊緣觸發(Edge Triggered)模式,這是相對於水平觸發(Level Triggered)來說的。
        EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之後,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL隊列裏

3、EPoll 等待事件的產生函數
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
        參數epfd:epoll_create()函數返回的epoll句柄。
        參數events:struct epoll_event結構指針,用來從內核得到事件的集合。
        參數 maxevents:告訴內核這個events有多大
        參數 timeout: 等待時的超時時間,以毫秒爲單位。
        返回值:成功時,返回需要處理的事件數目。調用失敗時,返回0,表示等待超時。
        說明:等待事件的產生。
        timeout值 說明 
        -1 永遠等待 
        0 立即返回,不阻塞進程 
        >0 等待指定數目的毫秒數 

EPoll 注意事項
        建立連接的時候epoll_add IN和OUT事件, 後面就不需要管了 
        每次read/write的時候,到兩種情況下結束: 
        1 發生EAGAIN 
        2 read/write的實際字節數小於 需要讀寫的字節數 
        對於第二點需要注意兩點: 
        A:如果是UDP服務,處理就不完全是這樣,必須要recv到發生EAGAIN爲止,否則就丟失事件了 
        因爲UDP和TCP不同,是有邊界的,每次接收一定是一個完整的UDP包,當然recv的buffer需要至少大於一個UDP包的大小 
        隨便再說一下,一個UDP包到底應該多大? 
        對於internet,由於MTU的限制,UDP包的大小不要超過576個字節,否則容易被分包,對於公司的IDC環境,建議不要超過1472,否則也比較容易分包。 

        B 如果發送方發送完數據以後,就close連接,這個時候如果recv到數據是實際字節數小於讀寫字節數,根據開始所述就認爲到EAGIN了從而直接返回,等待下一次事件,這樣是有問題的,close事件丟失了! 
        因此如果依賴這種關閉邏輯的服務,必須接收數據到EAGIN爲止。


*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>

#include <sys/types.h> 
#include <sys/socket.h> /* socket bind listen connect accept send recv */
#include <arpa/inet.h>  /* htons ntohs htonl ntohl inet_addr inet_ntoa */
#include <netinet/in.h> /* sockaddr_in */

#include <sys/epoll.h>
#include <unistd.h>
#include <fcntl.h>


#define BUFLEN 1024
#define QLEN 20

/* 傳送信息結構體 */
typedef struct _MyMsg{
        char szCmd[16];/* message command 
                                        * RE_LINK:test link request
                                        * RESP_LINK:test link response
                                        * RE_EXIT: exit request
                                        * RESP_TEXT: exit response
                                        * RE_DATA: data request
                                        * RESP_DATA: data response
                                        */
        int  iLen;     /* message data length*/
        char szData[0];/* message data */
}MyMsg;

/* 信息處理 */
MyMsg * MsgProcess(MyMsg *pMsgIn){
        char szBuf[BUFLEN] = { 0x00 };
        char szTmp[BUFLEN] = { 0x00 };
        MyMsg *pMsg = NULL;
        FILE *fp;
        if (strcmp(pMsgIn->szCmd, "RE_LINK") == 0){
                pMsg = (MyMsg *)malloc(sizeof(MyMsg));
                memset(pMsg, 0, sizeof(MyMsg));
                strcpy(pMsg->szCmd, "RESP_LINK");
        }else if (strcmp(pMsgIn->szCmd, "RESP_LINK") == 0){
        }else if (strcmp(pMsgIn->szCmd, "RE_EXIT") == 0){
                pMsg = (MyMsg *)malloc(sizeof(MyMsg));
                memset(pMsg, 0, sizeof(MyMsg));
                strcpy(pMsg->szCmd, "RESP_TEXT");
        }else if (strcmp(pMsgIn->szCmd, "RESP_TEXT") == 0){
        }else if (strcmp(pMsgIn->szCmd, "RE_DATA") == 0){
                memset(szBuf, 0, BUFLEN);
                strncpy(szBuf, pMsgIn->szData, pMsgIn->iLen);
                if ((fp = popen(szBuf, "r")) == NULL){
                        memset(szBuf, 0, BUFLEN);
                        sprintf(szBuf, "error: %s\n", strerror(errno));
                }else{
                        memset(szTmp, 0, BUFLEN);
                        while (fgets(szTmp, BUFLEN, fp) != NULL){
                                strcat(szBuf, szTmp);
                                memset(szTmp, 0, BUFLEN);
                        }
                        pclose(fp);
                }
                pMsg = (MyMsg *)malloc(sizeof(MyMsg)+ strlen(szBuf)+1);
                memset(pMsg, 0, sizeof(MyMsg));
                strcpy(pMsg->szCmd, "RESP_DATA");
                pMsg->iLen = strlen(szBuf)+1;
                strcpy(pMsg->szData, szBuf);
        }else if (strcmp(pMsgIn->szCmd, "RESP_DATA") == 0){
        }
        return pMsg;
}

/* Socket結構體 */
typedef struct _SockNode{
        int sock;
        struct sockaddr_in addr;
        struct _SockNode *pNext;
}SockNode;

/* create SockNode */
SockNode* createSockNode(int sock, struct sockaddr_in *pAddr){
        SockNode *p = NULL;
        if ((p = (SockNode *)malloc(sizeof(SockNode))) != NULL){
                p->sock = sock;
                memcpy(&(p->addr), pAddr, sizeof(struct sockaddr_in));
                p->pNext = NULL;
        }
        return p;
}

/* add SockNode from list */
void addSockNodeList(SockNode *head, SockNode *node){
        SockNode *p = head;
        while (p->pNext != NULL){
                p = p->pNext;
        }
        p->pNext = node;
}

/* delete SockNode from list 
* return head
*/
SockNode* deleteSockNodeList(SockNode *head, int sock){
        SockNode *p = head, *pPrevious = p;
        while (p != NULL){
                if (p->sock == sock){
                        if (p != pPrevious){
                                pPrevious->pNext = p->pNext;
                        }else{
                                head = p->pNext;
                        }
                        free(p);
                        break;
                }
                pPrevious = p;
                p = p->pNext;
        }
        return head;
}
/* select SockNode from list 
* return head
*/
SockNode* selectSockNodeList(SockNode *head, int sock){
        SockNode *p = head, *pPrevious = p;
        while (p != NULL){
                if (p->sock == sock){
                        return p;
                }
                p = p->pNext;
        }
        return NULL;
}

/* maximumly sock from list */
int maxSockNodeList(SockNode *head){
        SockNode *p = head;
        int maxsock = -1;
        while (p != NULL){
                maxsock = maxsock > p->sock ? maxsock : p->sock;
                p = p->pNext;
        }
        return maxsock;
}

/* create tcp server */
int initserver(int type, const struct sockaddr *addr, socklen_t alen, int qlen){
        int fd;
        int err = 0, iSockAttrOn = 1;
        
        /* 創建 */
        if ((fd = socket(addr->sa_family, type, 0)) < 0){
                return -1;
        }
        /* 端口複用 */
        if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &iSockAttrOn, sizeof(iSockAttrOn) ) < 0){
                err = errno;
                goto errout;
        }
        /* 綁定 */
        if (bind(fd, addr, alen) < 0){
                err = errno;
                goto errout;
        }
        /* 監聽數 */
        if (SOCK_STREAM == type || SOCK_SEQPACKET == type){
                if (listen(fd, qlen) < 0) {
                        err = errno;
                        goto errout;
                }
        }
        return fd;
errout:
        close(fd);
        errno = err;
        return -1;
}
/* 設置爲非阻塞模式 */
void setnonblocking(int sock){   
    int opts;   
    opts=fcntl(sock,F_GETFL);   
    if(opts<0){   
        perror("fcntl(sock,GETFL)");   
        exit(1);   
    }   
    opts = opts|O_NONBLOCK;   
    if(fcntl(sock,F_SETFL,opts)<0){   
        perror("fcntl(sock,SETFL,opts)");   
        exit(1);   
    }   
}
/* EPoll ET模式下讀取數據函數, 保證 szBuf空間足夠大, 否則數據丟失 */
int epoolRecv(int fd, char *szBuf, int nBuflen){
        int recvTotalLen = 0, recvLen = 0;
        char szTmp[1024] = { 0x00 };
        while (1) {
                memset(szTmp, 0x00, sizeof(szTmp));
                recvLen = recv(fd, szTmp, sizeof(szTmp), 0);
                if (recvLen < 0){
                        if (EAGAIN == errno) {
                                return recvTotalLen; /* 讀取結束,正常返回 */
                        } else {
                                perror("Err:epoolRecv recv err!");
                                return -1;
                        }
                }else if (0 == recvLen) {
                        return 0; /* 對方Socket正常關閉 */
                }
                if (recvTotalLen + recvLen <= nBuflen){
                        memcpy(szBuf + recvTotalLen, szTmp, recvLen);
                }
                recvTotalLen += recvLen;
                if (recvLen < sizeof(szTmp)){ /* TCP 可以用這種模式, UDP 可能報EAGAIN才能結束 */
                        return recvTotalLen; /* 讀取結束,正常返回 */
                }
        }
        return recvTotalLen;
}
/* EPoll ET模式下發送數據函數 */
int epollSend(int fd, const char *szBuf, int nBuflen){
        int sendTotalLen = 0, sendLen = 0;
        char szTmp[1024] = { 0x00 };
        while (1) {
                sendLen = send(fd, szBuf + sendTotalLen, nBuflen - sendTotalLen, 0);
                if (sendLen < 0) {
                        /* 當socket是非阻塞時,如返回此錯誤,表示寫緩衝隊列已滿,在這裏做延時後再重試. */
                        if (errno == EAGAIN) {
                                usleep(1000);
                                continue;
                        }
                        return -1;
                }else if (0 == sendLen) {
                        return 0;
                }
                sendTotalLen += sendLen;
                if(sendTotalLen == nBuflen)
                        return sendTotalLen;
        }
        return sendTotalLen;
} 

int main(int argc, char *argv[]){
        if (argc != 2){
                printf("arg err!\n");
                return -1;
        }
        int sefd, clfd, ret, len;
        char szBuf[BUFLEN];
        SockNode *head = NULL,*node = NULL; /* socket 監聽鏈表 */

        struct sockaddr_in se_addr,cl_addr;
        socklen_t alen = sizeof(struct sockaddr);
        /* 設置服務IP和端口 */
        memset((void *)&se_addr, 0, alen);
        se_addr.sin_family = AF_INET;
        se_addr.sin_addr.s_addr = INADDR_ANY;// inet_addr("0.0.0.0");
        se_addr.sin_port = htons(atoi(argv[1]));

        if ((sefd = initserver(SOCK_STREAM, (struct sockaddr *)&se_addr, alen, QLEN)) < 0){
                printf("initserver err=%s!\n", strerror(errno));
                return -1;
        }
        printf("initserver OK !\n");
        head = createSockNode(sefd, &se_addr);

        int epfd = epoll_create(256);/* 創建一個epoll句柄,size用來告訴內核這個監聽的數目一共有多大 */
        struct epoll_event ev, events[QLEN];/* 聲明epoll_event結構體的變量,ev用於註冊事件,數組用於回傳要處理的事件 */
        /* 註冊Server socket事件到EPoll */
        ev.data.fd = sefd;
        ev.events = EPOLLIN|EPOLLET; /* 讀事件、ET模式  */
        epoll_ctl(epfd, EPOLL_CTL_ADD, sefd, &ev); /* 註冊epoll事件 */
        int i, nevs;
        while (1){
                printf("epoll_wait before OK !\n");
                nevs = epoll_wait(epfd, events, QLEN, -1); 
                printf("epoll_wait after OK !ret = %d\n", ret);
                if (nevs < 0){
					if(errno == EINTR && epfd > 0){
						usleep(10*1000);
						continue;
					}
					printf("epoll_wait err=%s!\n", strerror(errno));
					while (head != NULL){
							node = head;
							head = head->pNext;
							close(node->sock);
							free(node);
					}
					return -1;
                }else if (0 == nevs) { /* 不可能出現  */
                        printf("epoll_wait timeout!\n");
                        sleep(1);
                        continue;
                }
                for (i = 0; i < nevs; i++){        
                        if (events[i].data.fd == sefd){ /* Server Socket */
                                alen = sizeof(struct sockaddr);
                                memset((void *)&cl_addr, 0 , alen);
                                clfd = accept(events[i].data.fd, (struct sockaddr*)&cl_addr, &alen);
                                if (clfd < 0){
                                        printf("accept err=%s!\n", strerror(errno));
                                        while (head != NULL){
                                                node = head;
                                                head = head->pNext;
                                                close(node->sock);
                                                free(node);
                                        }
                                        return -1;
                                }
                                printf("Client connect:ip=%s, port=%d \n", inet_ntoa(cl_addr.sin_addr), 
                                        ntohs(cl_addr.sin_port));
                                addSockNodeList(head, createSockNode(clfd, &cl_addr));

                                setnonblocking(clfd); /* 設置客戶端爲非阻塞模式 */
                                ev.data.fd = clfd;
                                ev.events = EPOLLIN|EPOLLET; /* 讀事件、ET模式  */
                                epoll_ctl(epfd, EPOLL_CTL_ADD, clfd, &ev); /* 註冊epoll事件 */

                        }else if (events[i].events | EPOLLIN){
                                node = selectSockNodeList(head, events[i].data.fd);
                                if (NULL == node){
                                        continue;
                                }
                                memset(szBuf, 0, BUFLEN);
                                /* ret = recv(node->sock, szBuf, BUFLEN, 0); */
                                ret = epoolRecv(node->sock, szBuf, BUFLEN);
                                if (ret < 0){
                                        printf("epoolRecv Client err=%s, ip=%s, port=%d!\n", strerror(errno), 
                                                inet_ntoa(node->addr.sin_addr), ntohs(node->addr.sin_port));
                                        close(node->sock);
                                        events[i].data.fd = -1;   
                                        head =  deleteSockNodeList(head, node->sock);
                                } else if (0 == ret){
                                        printf("epoolRecv Client exit, ip=%s, port=%d!\n", 
                                                inet_ntoa(node->addr.sin_addr), ntohs(node->addr.sin_port));
                                        close(node->sock);
                                        events[i].data.fd = -1;
                                        head = deleteSockNodeList(head, node->sock);

                                } else {
                                        MyMsg *msgRecv = (MyMsg *)szBuf;
                                        msgRecv->iLen = ntohl(msgRecv->iLen);/* 轉換成本機字節序 */
                                        MyMsg *msgSend = NULL;
                                        /* 預處理 */
                                        if (strcmp(msgRecv->szCmd, "RE_LINK") == 0){
                                                printf("epoolRecv Client RE_LINK, ip=%s, port=%d!\n", 
                                                        inet_ntoa(node->addr.sin_addr), ntohs(node->addr.sin_port));
                                                msgSend = MsgProcess(msgRecv); /* 實際處理 */
                                                if (msgSend != NULL){
                                                        len = msgSend->iLen;
                                                        msgSend->iLen = htonl(msgSend->iLen); /* 轉換成網絡字節序 */
                                                        memset(szBuf, 0, BUFLEN);
                                                        memcpy(szBuf, msgSend, sizeof(MyMsg) + len);
                                                        epollSend(node->sock, szBuf, sizeof(MyMsg) + len);
                                                        printf("epollSend Client %s, ip=%s, port=%d!\n", msgSend->szCmd, 
                                                                inet_ntoa(node->addr.sin_addr), ntohs(node->addr.sin_port));
                                                        free(msgSend);
                                                        msgSend = NULL;
                                                }
                                        }else if (strcmp(msgRecv->szCmd, "RESP_LINK") == 0){
                                                printf("epoolRecv Client RESP_LINK, ip=%s, port=%d!\n", 
                                                        inet_ntoa(node->addr.sin_addr), ntohs(node->addr.sin_port));
                                        }else if (strcmp(msgRecv->szCmd, "RE_EXIT") == 0){
                                                printf("epoolRecv Client RE_EXIT, ip=%s, port=%d!\n", 
                                                        inet_ntoa(node->addr.sin_addr), ntohs(node->addr.sin_port));
                                                msgSend = MsgProcess(msgRecv); /* 實際處理 */
                                                if (msgSend != NULL){
                                                        len = msgSend->iLen;
                                                        msgSend->iLen = htonl(msgSend->iLen); /* 轉換成網絡字節序 */
                                                        memset(szBuf, 0, BUFLEN);
                                                        memcpy(szBuf, msgSend, sizeof(MyMsg) + len);
                                                        epollSend(node->sock, szBuf, sizeof(MyMsg) + len);
                                                        printf("epollSend Client %s, ip=%s, port=%d!\n", msgSend->szCmd, 
                                                                inet_ntoa(node->addr.sin_addr), ntohs(node->addr.sin_port));
                                                        free(msgSend);
                                                        msgSend = NULL;
                                                        
                                                }
                                                close(node->sock);
                                                events[i].data.fd = -1;
                                                head = deleteSockNodeList(head, node->sock);
                                        }else if (strcmp(msgRecv->szCmd, "RESP_TEXT") == 0){
                                                printf("epoolRecv Client RESP_TEXT, ip=%s, port=%d!\n", 
                                                        inet_ntoa(node->addr.sin_addr), ntohs(node->addr.sin_port));
                                                close(node->sock);
                                                events[i].data.fd = -1;
                                                head = deleteSockNodeList(head, node->sock);
                                        }else if (strcmp(msgRecv->szCmd, "RE_DATA") == 0){
                                                printf("epoolRecv Client RE_DATA, ip=%s, port=%d, data:%s!\n", 
                                                        inet_ntoa(node->addr.sin_addr), ntohs(node->addr.sin_port), msgRecv->szData);
                                                msgSend = MsgProcess(msgRecv); /* 實際處理 */
                                                if (msgSend != NULL){
                                                        len = msgSend->iLen;
                                                        msgSend->iLen = htonl(msgSend->iLen); /* 轉換成網絡字節序 */
                                                        memset(szBuf, 0, BUFLEN);
                                                        memcpy(szBuf, msgSend, sizeof(MyMsg) + len);
                                                        epollSend(node->sock, szBuf, sizeof(MyMsg) + len);
                                                        printf("epollSend Client %s, ip=%s, port=%d, data:%s!\n", msgSend->szCmd, 
                                                                inet_ntoa(node->addr.sin_addr), ntohs(node->addr.sin_port), 
                                                                len > 0 ? msgSend->szData : "");
                                                        free(msgSend);
                                                        msgSend = NULL;
                                                }
                                        }else if (strcmp(msgRecv->szCmd, "RESP_DATA") == 0){
                                                printf("epoolRecv Client RESP_DATA, ip=%s, port=%d, data:%s!\n", 
                                                        inet_ntoa(node->addr.sin_addr), ntohs(node->addr.sin_port), msgRecv->szData);
                                        }
                                }/* recv */
                        }/* if i = 0 */
                }/*for */
        }/* while 1 */

        while (head != NULL){
                node = head;
                head = head->pNext;
                close(node->sock);
                free(node);
        }
        return 0;
}

/* User:Lixiujie         
* Date:20101207
* Desc:Unix(Linux)非阻塞的Socket通信EPoll模型,多路複用,TCP客戶端, 向服務端發送請求信息,接收響應信息。
* 可以在發送ls uptime pwd 等簡單的顯示命令
* File:tcp_client_epoll.c  
* System:Ubuntu 64bit  
* gcc tcp_client_epoll.c -o  tcp_client_epoll
* tcp_client_epoll 127.0.0.1 7878
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>

#include <sys/types.h> 
#include <sys/socket.h> /* socket bind listen connect accept send recv */
#include <arpa/inet.h>  /* htons ntohs htonl ntohl inet_addr inet_ntoa */
#include <netinet/in.h> /* sockaddr_in */

#include <pthread.h> /* multithreading  */

#define BUFLEN 1024

/* 傳送信息結構體 */
typedef struct _MyMsg{
        char szCmd[16];/* message command 
                                        * RE_LINK:test link request
                                        * RESP_LINK:test link response
                                        * RE_EXIT: exit request
                                        * RESP_TEXT: exit response
                                        * RE_DATA: data request
                                        * RESP_DATA: data response
                                        */
        int  iLen;     /* message data length*/
        char szData[0];/* message data */
}MyMsg;

/* 信息處理 */
MyMsg * MsgProcess(MyMsg *pMsgIn){
        char szBuf[BUFLEN] = { 0x00 };
        char szTmp[BUFLEN] = { 0x00 };
        MyMsg *pMsg = NULL;
        FILE *fp;
        if (strcmp(pMsgIn->szCmd, "RE_LINK") == 0){
                pMsg = (MyMsg *)malloc(sizeof(MyMsg));
                memset(pMsg, 0, sizeof(MyMsg));
                strcpy(pMsg->szCmd, "RESP_LINK");
        }else if (strcmp(pMsgIn->szCmd, "RESP_LINK") == 0){
        }else if (strcmp(pMsgIn->szCmd, "RE_EXIT") == 0){
                pMsg = (MyMsg *)malloc(sizeof(MyMsg));
                memset(pMsg, 0, sizeof(MyMsg));
                strcpy(pMsg->szCmd, "RESP_TEXT");
        }else if (strcmp(pMsgIn->szCmd, "RESP_TEXT") == 0){
        }else if (strcmp(pMsgIn->szCmd, "RE_DATA") == 0){
                memset(szBuf, 0, BUFLEN);
                strncpy(szBuf, pMsgIn->szData, pMsgIn->iLen);
                if ((fp = popen(szBuf, "r")) == NULL){
                        memset(szBuf, 0, BUFLEN);
                        sprintf(szBuf, "error: %s\n", strerror(errno));
                }else{
                        memset(szTmp, 0, BUFLEN);
                        while (fgets(szTmp, BUFLEN, fp) != NULL){
                                strcat(szBuf, szTmp);
                                memset(szTmp, 0, BUFLEN);
                        }
                        pclose(fp);
                }
                pMsg = (MyMsg *)malloc(sizeof(MyMsg)+ strlen(szBuf)+1);
                memset(pMsg, 0, sizeof(MyMsg));
                strcpy(pMsg->szCmd, "RESP_DATA");
                pMsg->iLen = strlen(szBuf)+1;
                strcpy(pMsg->szData, szBuf);
        }else if (strcmp(pMsgIn->szCmd, "RESP_DATA") == 0){
        }
        return pMsg;
}
int recvProcess(int sefd){
        fd_set rdset;
        struct timeval timeout = {5, 0};
        char szBuf[BUFLEN] = { 0x00 };
        int ret, len;
        FD_ZERO(&rdset);
        FD_SET(sefd, &rdset);
        ret = select(sefd + 1, &rdset, NULL, NULL, &timeout);

        if (ret < 0){
                printf("select err:%s\n", strerror(errno));
                exit(-1);
        }else if (0 == ret){
                memset(szBuf, 0, BUFLEN);
                strcpy(szBuf, "RE_LINK");
                send(sefd, szBuf, strlen(szBuf) + sizeof(MyMsg), 0);
                printf("select timeout send: RE_LINK\n");
                recvProcess(sefd);
                return -1;
        }else{
                memset(szBuf, 0, BUFLEN);
                ret = recv(sefd, szBuf, BUFLEN, 0);
                if (ret < 0){
                        printf("recv err:%s\n", strerror(errno));
                        close(sefd);
                        exit(-1);
                } else if (0 == ret){
                        printf("recv server close!\n");
                        close(sefd);
                        exit(-1);
                } else {
                        MyMsg *msgRecv = (MyMsg *)szBuf;
                        msgRecv->iLen = ntohl(msgRecv->iLen);/* 轉換成本機字節序 */
                        MyMsg *msgSend = NULL;
                        /* 預處理 */
                        if (strcmp(msgRecv->szCmd, "RE_LINK") == 0){
                                printf("recv Server RE_LINK!\n");
                                msgSend = MsgProcess(msgRecv); /* 實際處理 */
                                if (msgSend != NULL){
                                        len = msgSend->iLen;
                                        msgSend->iLen = htonl(msgSend->iLen); /* 轉換成網絡字節序 */
                                        memset(szBuf, 0, BUFLEN);
                                        memcpy(szBuf, msgSend, sizeof(MyMsg) + len);
                                        send(sefd, szBuf, sizeof(MyMsg) + len, 0);
                                        printf("send Server %s,\n", msgSend->szCmd);
                                        free(msgSend);
                                        msgSend = NULL;
                                }
                        }else if (strcmp(msgRecv->szCmd, "RESP_LINK") == 0){
                                printf("recv Server RESP_LINK!\n");
                        }else if (strcmp(msgRecv->szCmd, "RE_EXIT") == 0){
                                printf("recv Server RE_EXIT!\n");
                                msgSend = MsgProcess(msgRecv); /* 實際處理 */
                                if (msgSend != NULL){
                                        len = msgSend->iLen;
                                        msgSend->iLen = htonl(msgSend->iLen); /* 轉換成網絡字節序 */
                                        memset(szBuf, 0, BUFLEN);
                                        memcpy(szBuf, msgSend, sizeof(MyMsg) + len);
                                        send(sefd, szBuf, sizeof(MyMsg) + len, 0);
                                        printf("send Server %s!\n", msgSend->szCmd);
                                        free(msgSend);
                                        msgSend = NULL;
                                }
                                close(sefd);
                                exit(0);
                        }else if (strcmp(msgRecv->szCmd, "RESP_TEXT") == 0){
                                printf("recv Server RESP_TEXT!\n");
                                close(sefd);
                                exit(0);
                        }else if (strcmp(msgRecv->szCmd, "RE_DATA") == 0){
                                printf("recv Server RE_DATA, data:%s!\n", msgRecv->szData);
                                msgSend = MsgProcess(msgRecv); /* 實際處理 */
                                if (msgSend != NULL){
                                        len = msgSend->iLen;
                                        msgSend->iLen = htonl(msgSend->iLen); /* 轉換成網絡字節序 */
                                        memset(szBuf, 0, BUFLEN);
                                        memcpy(szBuf, msgSend, sizeof(MyMsg) + len);
                                        send(sefd, szBuf, sizeof(MyMsg) + len, 0);
                                        printf("send Server %s, data:%s!\n", msgSend->szCmd, 
                                                len > 0 ? msgSend->szData : "");
                                        free(msgSend);
                                        msgSend = NULL;
                                }
                        }else if (strcmp(msgRecv->szCmd, "RESP_DATA") == 0){
                                printf("recv Server RESP_DATA, data:%s!\n", msgRecv->szData);
                        }
                }
        }
        return 0;
}

int main(int argc, char *argv[]){
        if (argc != 3){
                printf("arg err!\n");
                return -1;
        }
        int sefd, ret, len;
        char szBuf[BUFLEN];
        struct sockaddr_in se_addr, my_addr;
        MyMsg *pMsg;
        socklen_t alen = sizeof(struct sockaddr);
        /* 設置服務端的IP和端口 */
        memset((void *)&se_addr, 0, alen);
        se_addr.sin_family = AF_INET;
        se_addr.sin_addr.s_addr = inet_addr(argv[1]);
        se_addr.sin_port = htons(atoi(argv[2]));

        if ((sefd = socket(se_addr.sin_family, SOCK_STREAM, 0)) < 0){
                printf("socket err:%s\n", strerror(errno));
                return -1;
        }
        if (connect(sefd, (struct sockaddr *)&se_addr, alen) < 0){
                printf("connect err:%s\n", strerror(errno));
                return -1;
        }
        alen = sizeof(struct sockaddr);
        memset((void *)&my_addr, 0, alen);
        getsockname(sefd, (struct sockaddr *)&my_addr, &alen);
        printf("connect OK, 本機IP:%s, Port:%d\n", inet_ntoa(my_addr.sin_addr), ntohs(my_addr.sin_port));

        while (1){
                memset(szBuf, 0, BUFLEN);
                printf("Input data:");
                gets(szBuf);
                if (strncmp(szBuf, "link", 4) == 0){
                        memset(szBuf, 0, BUFLEN);
                        strcpy(szBuf, "RE_LINK");
                        send(sefd, szBuf, strlen(szBuf) + sizeof(MyMsg), 0);
                        printf("Send Server CMD: RE_LINK\n");
                        recvProcess(sefd);
                }else if (strncmp(szBuf, "exit", 4) == 0){
                        memset(szBuf, 0, BUFLEN);
                        strcpy(szBuf, "RE_EXIT");
                        send(sefd, szBuf, strlen(szBuf) + sizeof(MyMsg), 0);
                        printf("Send Server CMD: RE_EXIT\n");
                        recvProcess(sefd);
                }else if (strlen(szBuf) > 0){
                        pMsg = (MyMsg *)malloc(sizeof(MyMsg) + strlen(szBuf)+1);
                        strcpy(pMsg->szCmd, "RE_DATA");
                        pMsg->iLen = strlen(szBuf)+1;
                        strcpy(pMsg->szData, szBuf);
                        len = pMsg->iLen;
                        pMsg->iLen = htonl(pMsg->iLen); /* 轉換成網絡字節序 */

                        memset(szBuf, 0, BUFLEN);
                        memcpy(szBuf, pMsg, sizeof(MyMsg) + len);
                        send(sefd, szBuf, sizeof(MyMsg) + len, 0);
                        printf("Send Server CMD: RE_DATA, data:%s\n", pMsg->szData);
                        free(pMsg);
                        recvProcess(sefd);
                }else{
                        printf("Error: Input err!\n"); 
                }
        }
        return 0;
}


 


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