Linux下各類TCP服務實現的源代碼

Linux下各類TCP網絡服務器的實現源代碼 轉自互聯網

大家都知道各類網絡服務器程序的編寫步驟,並且都知道網絡服務器就兩大類:循環服務和併發服務。這裏附上源代碼來個小結吧。

首先,循環網絡服務器編程實現的步驟是這樣的:

                              建立socket(這裏用到socket()函數及函數setsockopt())
                                                            |
                                                            |
                                                           \|/
                              把socket和IP地址及端口綁定(這裏用到bind函數)
                                                            |
                                                            |
                                                           \|/
                              開始監聽(這裏用到listen()函數)
                                                            |
                                                            |
                                                           /\
                                                       /        \
                                               \     /           \
                    -----------------------  | 有連接|
                    |                           /   \             /
                    |                                 \         /
                    |                                     \ /
                    |                                       | 
                    |          接受新的連接(這裏用到accept()函數)
                    |                                       |  /___________________________________________________
                    |                                       |  \                                                                                                                            |
                    |                                     \|/                                                                                                                              |
                    |         從連接裏讀取數據(這裏用到recv()系統函數,當然也可以是read()函數)                          |
                    |                                        |                                                                                                                                |
                    |                                       |                                                                                                                                |
                    |                                     \|/                                                                                                                             |
                    |          返回信息給連接(這裏用到send()系統函數,當然也可以是write()函        數)                    |
                    |                                       |                                                                                                                                |
                    |                                       |                                                                                                                                |
                    |                                      /\                                                                                                                             |
                    |                                  /        \                                                                                                                         |
                    |                               /               \                                                                                                                      |
                    |                              | 還有數據 |-Y-------------------------------------------------------
                    |                              \               /
                    |                                 \         /
                    |                                     \ /
                    |________________|

這種服務器模型是典型循環服務,如果不加上多進程/線程技術,此種服務吞吐量有限,大家都可以看到,如果前一個連接服務數據沒有收發完畢後面的連接沒辦法處理。所以一般有多進程技術,對一個新連接啓用一個新進程去處理,而監聽socket繼續監聽。

/************關於本文檔********************************************
*filename: Linux下各類TCP網絡服務器的實現源代碼
*purpose: 記錄Linux下各類tcp服務程序源代碼
*wrote by: zhoulifa([email protected]) 周立發(http://zhoulifa.bokee.com)
Linux愛好者 Linux知識傳播者 SOHO族 開發者 最擅長C語言
*date time:2006-07-04 22:00:00
*Note: 任何人可以任意複製代碼並運用這些文檔,當然包括你的商業用途
* 但請遵循GPL
*Hope:希望越來越多的人貢獻自己的力量,爲科學技術發展出力
*********************************************************************/

一個循環TCP服務源代碼(因爲用fork進行多進程服務了,所以這種服務現實中也有用)如下:
/*----------------------源代碼開始--------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
/*********************************************************************
*filename: cycletcpserver.c
*purpose: 循環tcp服務端程序
*tidied by: zhoulifa([email protected]) 周立發(http://zhoulifa.bokee.com)
Linux愛好者 Linux知識傳播者 SOHO族 開發者 最擅長C語言
*date time:2006-07-04 22:00:00
*Note: 任何人可以任意複製代碼並運用這些文檔,當然包括你的商業用途
* 但請遵循GPL
*Thanks to: Google.com
*********************************************************************/
int main(int argc, char ** argv)
{
    int sockfd,new_fd; /* 監聽socket: sock_fd,數據傳輸socket: new_fd */
    struct sockaddr_in my_addr; /* 本機地址信息 */
    struct sockaddr_in their_addr; /* 客戶地址信息 */
    unsigned int sin_size, myport, lisnum;

    if(argv[1])  myport = atoi(argv[1]);
    else myport = 7838;

    if(argv[2])  lisnum = atoi(argv[2]);
    else lisnum = 2;

    if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
        perror("socket"); 
        exit(1); 
    }
    my_addr.sin_family=PF_INET;
    my_addr.sin_port=htons(myport);
    my_addr.sin_addr.s_addr = INADDR_ANY;
    bzero(&(my_addr.sin_zero), 0);
    if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1) {
        perror("bind");
        exit(1);
    }

    if (listen(sockfd, lisnum) == -1) {
        perror("listen");
        exit(1);
    }
    while(1) {
        sin_size = sizeof(struct sockaddr_in);
        if ((new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size)) == -1) {
            perror("accept");
            continue;
        }
        printf("server: got connection from %s\n",inet_ntoa(their_addr.sin_addr));
        if (!fork()) { /* 子進程代碼段 */
            if (send(new_fd, "Hello, world!\n", 14, 0) == -1) {
                perror("send");
                close(new_fd);
                exit(0);
            }
        }
        close(new_fd); /*父進程不再需要該socket*/
        waitpid(-1,NULL,WNOHANG);/*等待子進程結束,清除子進程所佔用資源*/
    }
}
/*----------------------源代碼結束--------------------------------------------*/

 


一個測試客戶端代碼如下:
/*----------------------源代碼開始--------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define MAXDATASIZE 100 /*每次最大數據傳輸量 */
/*********************************************************************
*filename: cycletcpclient.c
*purpose: 循環tcp客戶端程序
*tidied by: zhoulifa([email protected]) 周立發(http://zhoulifa.bokee.com)
Linux愛好者 Linux知識傳播者 SOHO族 開發者 最擅長C語言
*date time:2006-07-04 22:20:00
*Note: 任何人可以任意複製代碼並運用這些文檔,當然包括你的商業用途
* 但請遵循GPL
*Thanks to: Google.com
*Hope:希望越來越多的人貢獻自己的力量,爲科學技術發展出力
*********************************************************************/

int main(int argc, char *argv[])
{
    int sockfd, numbytes;
    char buf[MAXDATASIZE];
    struct hostent *he;
    struct sockaddr_in their_addr;
    unsigned int myport;

    if(argv[2]) myport = atoi(argv[2]);
    else myport = 7838;

    if (argc != 3) {
        fprintf(stderr,"usage: %s hostname port\n", argv[0]); 
        exit(1);
    }
    if((he=gethostbyname(argv[1]))==NULL) {
        herror("gethostbyname");
        exit(1);
    }
    if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
        perror("socket");
        exit(1);
    }
    their_addr.sin_family=PF_INET;
    their_addr.sin_port=htons(myport);
    their_addr.sin_addr = *((struct in_addr *)he->h_addr);
    bzero(&(their_addr.sin_zero),0);
    if (connect(sockfd, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) == -1) {
        perror("connect");
        exit(1);
    }
    if ((numbytes=recv(sockfd, buf, MAXDATASIZE, 0)) == -1) {
        perror("recv");
        exit(1);
    }
    buf[numbytes] = 0;
    printf("Received: %s\n",buf);
    close(sockfd);
    return 0;
}
/*----------------------源代碼結束--------------------------------------------*/

 
用gcc cycletcpserver.c -o tcpserver和gcc cycletcpclient.c -o tcpclient分別編譯上述代碼後運行情況如下:
服務端運行顯示:
administrator@ubuzlf:/data/example/c$ ./tcpserver
server: got connection from 127.0.0.1
server: got connection from 127.0.0.1
server: got connection from 127.0.0.1
 
客戶端運行顯示:
administrator@ubuzlf:/data/example/c$ ./tcpclient 127.0.0.1 7838
Received: Hello, world!

administrator@ubuzlf:/data/example/c$ ./tcpclient 127.0.0.1 7838
Received: Hello, world!

administrator@ubuzlf:/data/example/c$ ./tcpclient 127.0.0.1 7838
Received: Hello, world!

 

不得不說的一個概念性問題:阻塞與非阻塞
在阻塞服務中,當服務器運行到accept語句而沒有客戶連接服務請求到來,那麼會發生什麼情況?這時服務器就會停止在accept語句上等待連接服務請求的到來;同樣,當程序運行到接收數據語句recv時,如果沒有數據可以讀取,則程序同樣會停止在接收語句上。這種情況稱爲阻塞(blocking)。
但如果你希望服務器僅僅注意檢查是否有客戶在等待連接,有就接受連接;否則就繼續做其他事情,則可以通過將 socket設置爲非阻塞方式來實現:非阻塞socket在沒有客戶在等待時就使accept調用立即返回 。
通過設置socket爲非阻塞方式,可以實現“輪詢”若干socket。當企圖從一個沒有數據等待處理的非阻塞socket讀入數據時,函數將立即返回,並且返回值置爲-1,並且errno置爲EWOULDBLOCK。但是這種“輪詢”會使CPU處於忙等待方式,從而降低性能。考慮到這種情況,假設你希望服務器監聽連接服務請求的同時從已經建立的連接讀取數據,你也許會想到用一個accept語句和多個recv()語句,但是由於accept及recv都是會阻塞的,所以這個想法顯然不會成功。
調用非阻塞的socket會大大地浪費系統資源。而調用select()會有效地解決這個問題,它允許你把進程本身掛起來,而同時使系統內核監聽所要求的一組文件描述符的任何活動,只要確認在任何被監控的文件描述符上出現活動,select()調用將返回指示該文件描述符已準備好的信息,從而實現了爲進程選出隨機的變化,而不必由進程本身對輸入進行測試而浪費CPU開銷。

其次,併發服務器,在上述cycletcpserver.c中,由於使用了fork技術也可以稱之爲併發服務器,但這種服務器並不是真正意義上的IO多路複用的併發服務器,並且由於沒有處理阻塞問題,實際應用有各種各樣的問題。

一個典型IO多路複用的單進程併發服務器流程如下:
/*IO多路複用併發服務流程圖*/

Linux下各類TCP服務實現的源代碼 - 風過無痕 - 靜
下面是一個演示IO多路複用的源程序,是一個端口轉發程序,但它的用處相當大,實際應用中的各類代理軟件或端口映射軟件都是基於這樣的代碼的,比如Windows下的WinGate、WinProxy等都是在此基礎上實現的。源代碼如下:
/*----------------------源代碼開始--------------------------------------------*/
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <string.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>


static int forward_port;

#undef max
#define max(x,y) ((x) > (y) ? (x) : (y))

/*************************關於本文檔************************************
*filename: tcpforwardport.c
*purpose: 演示了select的用法,這是一個極好的代理軟件核心,專門作端口映射用
*tidied by: zhoulifa([email protected]) 周立發(http://zhoulifa.bokee.com)
Linux愛好者 Linux知識傳播者 SOHO族 開發者 最擅長C語言
*date time:2006-07-05 19:00:00
*Note: 任何人可以任意複製代碼並運用這些文檔,當然包括你的商業用途
* 但請遵循GPL
*Thanks to: Paul Sheer 感謝Paul Sheer在select_tut的man手冊裏提供了這份源代碼
*Hope:希望越來越多的人貢獻自己的力量,爲科學技術發展出力
*********************************************************************/

static int listen_socket (int listen_port) {
    struct sockaddr_in a;
    int s;
    int yes;
    if ((s = socket (AF_INET, SOCK_STREAM, 0)) < 0) {
        perror ("socket");
        return -1;
    }
    yes = 1;
    if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &yes, sizeof (yes)) <
0) {
        perror ("setsockopt");
        close (s);
        return -1;
    }
    memset (&a, 0, sizeof (a));
    a.sin_port = htons (listen_port);
    a.sin_family = AF_INET;
    if (bind(s, (struct sockaddr *) &a, sizeof (a)) < 0) {
        perror ("bind");
        close (s);
        return -1;
    }
    printf ("accepting connections on port %d\n", (int) listen_port);
    listen (s, 10);
    return s;
}

static int connect_socket (int connect_port, char *address) {
    struct sockaddr_in a;
    int s;
    if ((s = socket (AF_INET, SOCK_STREAM, 0)) < 0) {
        perror ("socket");
        close (s);
        return -1;
    }

    memset (&a, 0, sizeof (a));
    a.sin_port = htons (connect_port);
    a.sin_family = AF_INET;

    if (!inet_aton(address, (struct in_addr *) &a.sin_addr.s_addr)) {
        perror ("bad IP address format");
        close (s);
        return -1;
    }

    if (connect(s, (struct sockaddr *) &a, sizeof (a)) < 0) {
        perror ("connect()");
        shutdown (s, SHUT_RDWR);
        close (s);
        return -1;
    }
    return s;
}

#define SHUT_FD1 { \
    if (fd1 >= 0) {   \
        shutdown (fd1, SHUT_RDWR);  \
        close (fd1);  \
        fd1 = -1;     \
    }   \
}

#define SHUT_FD2 { \
    if (fd2 >= 0) {   \
        shutdown (fd2, SHUT_RDWR);  \
        close (fd2);  \
        fd2 = -1;     \
    }   \
}

#define BUF_SIZE 1024

int main (int argc, char **argv) {
    int h;
    int fd1 = -1, fd2 = -1;
    char buf1[BUF_SIZE], buf2[BUF_SIZE];
    int buf1_avail, buf1_written;
    int buf2_avail, buf2_written;

    if (argc != 4) {
        fprintf (stderr, "Usage\n\tfwd   \n");
        exit (1);
    }

    signal (SIGPIPE, SIG_IGN);

    forward_port = atoi (argv[2]);

    /*建立監聽socket*/
    h = listen_socket (atoi (argv[1]));
    if (h < 0) exit (1);

    for (;;) {
        int r, nfds = 0;
        fd_set rd, wr, er;
        FD_ZERO (&rd);
        FD_ZERO (&wr);
        FD_ZERO (&er);
        FD_SET (h, &rd);

        /*把監聽socket和可讀socket三個一起放入select的可讀句柄列表裏*/
        nfds = max (nfds, h);
        if (fd1 > 0 && buf1_avail < BUF_SIZE) {
            FD_SET (fd1, &rd);
            nfds = max (nfds, fd1);
        }
        if (fd2 > 0 && buf2_avail < BUF_SIZE) {
            FD_SET (fd2, &rd);
            nfds = max (nfds, fd2);
        }

        /*把可寫socket兩個一起放入select的可寫句柄列表裏*/
        if (fd1 > 0 && buf2_avail - buf2_written > 0) {
            FD_SET (fd1, &wr);
            nfds = max (nfds, fd1);
        }
        if (fd2 > 0 && buf1_avail - buf1_written > 0) {
            FD_SET (fd2, &wr);
            nfds = max (nfds, fd2);
        }

        /*把有異常數據的socket兩個一起放入select的異常句柄列表裏*/
        if (fd1 > 0) {
            FD_SET (fd1, &er);
            nfds = max (nfds, fd1);
        }
        if (fd2 > 0) {
            FD_SET (fd2, &er);
            nfds = max (nfds, fd2);
        }

        /*開始select*/
        r = select (nfds + 1, &rd, &wr, &er, NULL);

        if (r == -1 && errno == EINTR) continue;
        if (r < 0) {
            perror ("select()");
            exit (1);
        }

        /*處理新連接*/
        if (FD_ISSET (h, &rd)) {
            unsigned int l;
            struct sockaddr_in client_address;
            memset (&client_address, 0, l = sizeof (client_address));
            r = accept (h, (struct sockaddr *)&client_address, &l);
            if (r < 0) {
                perror ("accept()");
            } else {
                /*關閉原有連接,把新連接作爲fd1,同時連接新的目標fd2*/
                SHUT_FD1;
                SHUT_FD2;
                buf1_avail = buf1_written = 0;
                buf2_avail = buf2_written = 0;
                fd1 = r;
                fd2 = connect_socket (forward_port, argv[3]);
                if (fd2 < 0) {
                    SHUT_FD1;
                } else
                    printf ("connect from %s\n", inet_ntoa(client_address.sin_addr));
            }
        }

        /* NB: read oob data before normal reads */
        if (fd1 > 0)
        if (FD_ISSET (fd1, &er)) {
            char c;
            errno = 0;
            r = recv (fd1, &c, 1, MSG_OOB);
            if (r < 1) {
                SHUT_FD1;
            } else
                send (fd2, &c, 1, MSG_OOB);
        }

        if (fd2 > 0)
        if (FD_ISSET (fd2, &er)) {
            char c;
            errno = 0;
            r = recv (fd2, &c, 1, MSG_OOB);
            if (r < 1) {
                SHUT_FD1;
            } else
                send (fd1, &c, 1, MSG_OOB);
        }

        /* NB: read data from fd1 */
        if (fd1 > 0)
        if (FD_ISSET (fd1, &rd)) {
            r = read (fd1, buf1 + buf1_avail, BUF_SIZE - buf1_avail);
            if (r < 1) {
                SHUT_FD1;
            } else
                buf1_avail += r;
        }

        /* NB: read data from fd2 */
        if (fd2 > 0)
        if (FD_ISSET (fd2, &rd)) {
            r = read (fd2, buf2 + buf2_avail, BUF_SIZE - buf2_avail);
            if (r < 1) {
                SHUT_FD2;
            } else
                buf2_avail += r;
        }

        /* NB: write data to fd1 */
        if (fd1 > 0)
        if (FD_ISSET (fd1, &wr)) {
            r = write (fd1, buf2 + buf2_written, buf2_avail - buf2_written);
            if (r < 1) {
                SHUT_FD1;
            } else
                buf2_written += r;
        }

        /* NB: write data to fd1 */
        if (fd2 > 0)
        if (FD_ISSET (fd2, &wr)) {
            r = write (fd2, buf1 + buf1_written, buf1_avail - buf1_written);
            if (r < 1) {
                SHUT_FD2;
            } else
                buf1_written += r;
        }

        /* check if write data has caught read data */
        if (buf1_written == buf1_avail) buf1_written = buf1_avail = 0;
        if (buf2_written == buf2_avail) buf2_written = buf2_avail = 0;

        /* one side has closed the connection, keep writing to the other side until empty */
        if (fd1 < 0 && buf1_avail - buf1_written == 0) {
            SHUT_FD2;
        }
        if (fd2 < 0 && buf2_avail - buf2_written == 0) {
            SHUT_FD1;
        }
    }
    return 0;
}
/*----------------------源代碼結束--------------------------------------------*/

 


用gcc tcpforwardport.c -o MyProxy編譯此程序後運行效果如下:
./MyProxy 8000 80 172.16.100.218
accepting connections on port 8000
connect from 127.0.0.1
 

當有用戶訪問本機的8000端口時,MyProxy程序將把此請求轉發到172.16.100.218主機的80端口,即實現了一個http代理。

關於select函數:
其函數原型爲:
int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
此函數的功能是由內核檢測在timeout時間內,是否有readfds,writefds,exceptfds三個句柄集(file descriptors)裏的某個句柄(file descriptor)的狀態符合尋求,即readfds句柄集裏有句柄可讀或writefds句柄集裏有可寫或exceptfds句柄集裏有例外發生,任何一個有變化函數就立即返回,返回值爲timeout發生狀態變化的句柄個數。
n是所有readfds,writefds,exceptfds三個句柄集(file descriptors)裏編號最大值加1。比如:要檢測兩個socket句柄fd1和fd2在timeout時間內是否分別可讀和可寫就可以這樣:
先把兩個句柄集(file descriptors)清零:
        FD_ZERO (&readfds);
        FD_ZERO (&writefds);
然後把fd1加入讀檢測集:
        FD_SET (fd1, &readfds);
然後把fd2加入寫檢測集:
        FD_SET (fd2, &writefds);
再給timeout設置值,timeout是這樣的一個結構:
              struct timeval {
                  long    tv_sec;         /* seconds */
                  long    tv_usec;        /* microseconds */
              };
你可以這樣賦值:
        timeout.tv_sec=1;
        timeout.tv_uec=0;
表示檢測在1秒鐘內是否有句柄狀態發生變化。
如果有句柄發生變化,就可以用FD_ISSET檢測各個句柄,比如:
                FD_ISSET (fd1, &readfds);//檢測是否fd1變成可讀的了
                FD_ISSET (fd2, &writefds);//檢測是否fd2變成可寫的了
示意程序代碼如下:
/*----------------------示意代碼開始--------------------------------------------*/
    fd1 = socket();//創建一個socket
    fd2 = socket();//創建一個socket
    while(1)  {
        FD_ZERO (&readfds);
        FD_ZERO (&writefds);
        FD_SET (fd1, &readfds);
        FD_SET (fd2, &writefds);
        timeout.tv_sec=1;
        timeout.tv_uec=0;
        ret = select(fd1>fd2?(fd1+1):(fd2+1), &readfds, &writefds, NULL, &timeout);
        if(ret < 0) {printf("系統錯誤,select出錯,錯誤代碼:%d, 錯誤信息:%s", errno, strerror(errno));}
        else if(ret == 0) {printf("select超時返回,沒有任何句柄狀態發生變化!");}
        //有句柄狀態發生了變化
        if(FD_ISSET(fd1, &readfds)) {
            fd1有數據可讀;
            fd1裏的數據被讀出來;
        }
        if(FD_ISSET(fd2, &writefds)) {
            fd2可寫;
            fd2裏發送數據給對方;
        }
    }
/*----------------------示意代碼結束--------------------------------------------*/
 


經常用到的幾個自定義函數:
1、開啓監聽的函數
/*----------------------源代碼代碼開始--------------------------------------------*/
int
OpenSCPServer(int port, int total, int sendbuflen, int recvbuflen, int blockORnot, int reuseORnot)    {
/*************************關於本函數************************************
*function_name: OpenSCPServer
*參數說明:port整數型監聽端口號,total整數型監聽個數,sendbuflen整數型發送緩衝區大小
*          recvbuflen整數型接收緩衝區大小,blockORnot整數型是否阻塞,reuseORnot整數型是否端口重用
*purpose: 用來建立一個tcp服務端socket
*tidied by: zhoulifa([email protected]) 周立發(http://zhoulifa.bokee.com)
Linux愛好者 Linux知識傳播者 SOHO族 開發者 最擅長C語言
*date time:2006-07-05 20:00:00
*Note: 任何人可以任意複製代碼並運用這些文檔,當然包括你的商業用途
* 但請遵循GPL
*Thanks to: Paul Sheer 感謝Paul Sheer在select_tut的man手冊裏提供了這份源代碼
*Hope:希望越來越多的人貢獻自己的力量,爲科學技術發展出力
*Note:要使用此函數需要自定義一個全局變量char errorMessage[1024];幷包含GetCurrentTime.h頭文件
*********************************************************************/
    int    sockfd = 0, ret = 0, opt = 0, flags=1;
    struct sockaddr_in    laddr;

    ret = sockfd = socket(PF_INET, SOCK_STREAM, 0);
    if(ret < 0)    {
        sprintf(errorMessage, "OpenTCPServer socket() error! return:%d, errno=%d, errortext:'%s' %s", ret, errno, strerror(errno), GetCurrentTime(0, 0));
        return -1;
    }

    ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuseORnot, sizeof(int));
    if(ret < 0)    {
        sprintf(errorMessage, "OpenTCPServer setsockopt() reuse error! return:%d, errno=%d, errortext:'%s' %s", ret, errno, strerror(errno), GetCurrentTime(0, 0));
        return -2;
    }

    ret = setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &recvbuflen, sizeof(int));
    if ( ret < 0)    {
        sprintf(errorMessage, "OpenTCPServer setsockopt() recvbuf error! return:%d, errno=%d, errortext:'%s' %s", ret, errno, strerror(errno), GetCurrentTime(0, 0));
        return -3;
    }

    ret = setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &sendbuflen, sizeof(int));
    if (ret < 0)    {
        sprintf(errorMessage, "OpenTCPServer setsockopt() sendbuf error! return:%d, errno=%d, errortext:'%s' %s", ret, errno, strerror(errno), GetCurrentTime(0, 0));
        return -4;
    }

    ioctl(sockfd,FIONBIO,&blockORnot);/*block or not*/

    laddr.sin_family = PF_INET;
    laddr.sin_port = htons(port);
    laddr.sin_addr.s_addr = INADDR_ANY;
    bzero(&(laddr.sin_zero), 8);

    ret = bind(sockfd, (struct sockaddr *)&laddr, sizeof(struct sockaddr));
    if(ret < 0)    {
        sprintf(errorMessage, "OpenTCPServer bind() error! return:%d, errno=%d, errortext:'%s' %s", ret, errno, strerror(errno), GetCurrentTime(0, 0));
        close(sockfd);
        return -5;
    }
    ret = listen(sockfd, total);
    if(ret < 0)    {
        sprintf(errorMessage, "OpenTCPServer listen() error! return:%d, errno=%d, errortext:'%s' %s", ret, errno, strerror(errno), GetCurrentTime(0, 0));
        close(sockfd);
        return -6;
    }
    sprintf(errorMessage, "OpenTCPServer opened on port.%d(%d) OK, socket(%d), buf(%d:%d)! %s", port, total, sockfd, sendbuflen, recvbuflen, GetCurrentTime(0, 0));
    return sockfd;
}
/*----------------------源代碼代碼結束--------------------------------------------*/

 
2、連接服務器的函數
/*----------------------源代碼代碼開始--------------------------------------------*/
int
ConnectSCPServer(char * serverip, int serverport, int blockORnot)    {
/*************************關於本函數************************************
*function_name: ConnectSCPServer
*參數說明:serverip服務器IP地址或主機名,serverport服務器端口,blockORnot整數型是否阻塞
*purpose: 用來建立一個tcp客戶端socket
*tidied by: zhoulifa([email protected]) 周立發(http://zhoulifa.bokee.com)
Linux愛好者 Linux知識傳播者 SOHO族 開發者 最擅長C語言
*date time:2006-07-05 20:40:00
*Note: 任何人可以任意複製代碼並運用這些文檔,當然包括你的商業用途
* 但請遵循GPL
*Thanks to: Paul Sheer 感謝Paul Sheer在select_tut的man手冊裏提供了這份源代碼
*Hope:希望越來越多的人貢獻自己的力量,爲科學技術發展出力
*Note:要使用此函數需要自定義一個全局變量char errorMessage[1024];幷包含自己編寫的GetCurrentTime.h頭文件
*********************************************************************/
    int    serversock = 0, ret = 0;
    unsigned long    addr;
    struct sockaddr_in    sin;
    struct hostent *he;

    if((he=gethostbyname(serverip))== 0) {
        sprintf(errorMessage, "ConnectSCPServer IP address '%s' error! return:-1 %s", serverip, GetCurrentTime(0, 0));
        return -1;
    }

    serversock = socket(PF_INET, SOCK_STREAM, 0);
    if(serversock == -1)    {
        sprintf(errorMessage, "ConnectSCPServer socket() error! return:-2, errno=%d, errortext:'%s' %s", errno, strerror(errno), GetCurrentTime(0, 0));
        return -2;
    }

    ioctl(serversock, FIONBIO, &blockORnot);  //block or not

    memset((char*)&sin, 0, sizeof(struct sockaddr_in));
    sin.sin_family = PF_INET;
    sin.sin_port = htons(serverport);
    sin.sin_addr = *((struct in_addr *)he->h_addr);

    ret = connect(serversock, (struct sockaddr *)&sin, sizeof(sin));

    if(ret == -1)    {
        sprintf(errorMessage, "ConnectSCPServer connect() error! return:-3, errno=%d, errortext:'%s' %s", errno, strerror(errno), GetCurrentTime(0, 0));
        close(serversock);
        return -3;
    }

    return serversock;
}
/*----------------------源代碼代碼結束--------------------------------------------*/

 
3、發送數據函數Send
/*----------------------源代碼代碼開始--------------------------------------------*/
int
Send(int sock, char * buf, size_t size, int flag, int timeout)    {
/*************************關於本函數************************************
*function_name: Send
*參數說明:sock整數型socket,buf待發送的內容,size要發送的大小,flag發送選項,timeout超時時間值
*purpose: 用來通過一個socket在指定時間內發送數據
*tidied by: zhoulifa([email protected]) 周立發(http://zhoulifa.bokee.com)
Linux愛好者 Linux知識傳播者 SOHO族 開發者 最擅長C語言
*date time:2006-07-05 20:58:00
*Note: 任何人可以任意複製代碼並運用這些文檔,當然包括你的商業用途
* 但請遵循GPL
*Thanks to: Paul Sheer 感謝Paul Sheer在select_tut的man手冊裏提供了這份源代碼
*Hope:希望越來越多的人貢獻自己的力量,爲科學技術發展出力
*Note:要使用此函數需要自定義一個全局變量char errorMessage[1024];幷包含自己編寫的GetCurrentTime.h頭文件
*********************************************************************/
    int i = 0, ret = 0, intretry = 0;

    struct timeval tival;
    fd_set writefds;
    int maxfds = 0;

    tival.tv_sec = timeout;
    tival.tv_usec = 0;

    FD_ZERO(&writefds);

    if(sock > 0) {
        FD_SET(sock, &writefds);
        maxfds=((sock > maxfds)?sock:maxfds);
    }
    else    {
        sprintf(errorMessage, "Send socket:%d error! return:-2 %s", sock, GetCurrentTime(0, 0));
        return -2;
    }

    ret = select(maxfds + 1, NULL, &writefds, NULL, &tival);
    if(ret <= 0) {
        if(ret < 0)    sprintf(errorMessage, "Send socket:%d select() error! return:%d, errno=%d, errortext:'%s' %s", sock, ret, errno, strerror(errno), GetCurrentTime(0, 0));
        else sprintf(errorMessage, "Send socket:%d select timeout(%d)! %s", sock, timeout, GetCurrentTime(0, 0));
        close(sock);
        return -3;
    }
    if(!(FD_ISSET(sock, &writefds)))    {
        sprintf(errorMessage, "Send socket:%d not in writefds! %s", sock, GetCurrentTime(0, 0));
        close(sock);
        return -4;
    }

    while(i < size)    {
        ret = send(sock, buf + i, size - i, flag);
        if(ret <= 0)    {
            sprintf(errorMessage, "Send socket:%d send() error! return:%d, errno=%d, errortext:'%s' %s", sock, ret, errno, strerror(errno), GetCurrentTime(0, 0));

            if (EINTR == errno)
              if(intretry < 10)  {intretry++;continue;}
              else sprintf(errorMessage, "Send socket:%d send() error!EINTR 10 times! %s", sock, GetCurrentTime(0, 0));

            close(sock);
            return -1;
        }
        else i += ret;
    }
    sprintf(errorMessage, "Send socket:%d send() OK! %d/%d bytes sent! %s", sock, i, size, GetCurrentTime(0, 0));
    return i;
}
/*----------------------源代碼代碼結束--------------------------------------------*/

 
4、接收數據函數Recv
/*----------------------源代碼代碼開始--------------------------------------------*/
int
Recv(int sock, char * buf, size_t size, int flag, int timeout)    {
/*************************關於本函數************************************
*function_name: Recv
*參數說明:sock整數型socket,buf接收數據的緩衝區,size要接收數據的大小,flag接收選項,timeout超時時間值
*purpose: 用來從一個socket在指定時間內讀取數據
*tidied by: zhoulifa([email protected]) 周立發(http://zhoulifa.bokee.com)
Linux愛好者 Linux知識傳播者 SOHO族 開發者 最擅長C語言
*date time:2006-07-05 21:10:00
*Note: 任何人可以任意複製代碼並運用這些文檔,當然包括你的商業用途
* 但請遵循GPL
*Thanks to: Paul Sheer 感謝Paul Sheer在select_tut的man手冊裏提供了這份源代碼
*Hope:希望越來越多的人貢獻自己的力量,爲科學技術發展出力
*Note:要使用此函數需要自定義一個全局變量char errorMessage[1024];幷包含自己編寫的GetCurrentTime.h頭文件
*********************************************************************/
    int i = 0, ret = 0, intretry = 0;

    struct timeval tival;
    fd_set readfds;
    int maxfds = 0;

    tival.tv_sec = timeout;
    tival.tv_usec = 0;

    FD_ZERO(&readfds);

    if(sock > 0) {
        FD_SET(sock, &readfds);
        maxfds=((sock > maxfds)?sock:maxfds);
    }
    else    {
        sprintf(errorMessage, "Recv socket:%d error! return:-2 %s", sock, GetCurrentTime(0, 0));
        return -2;
    }

    ret = select(maxfds + 1, &readfds, NULL, NULL, &tival);
    if(ret <= 0) {
        if(ret < 0)    sprintf(errorMessage, "Recv socket:%d select() error! return:%d, errno=%d, errortext:'%s' %s", sock, ret, errno, strerror(errno), GetCurrentTime(0, 0));
        else sprintf(errorMessage, "Recv socket:%d select timeout(%d)! %s", sock, timeout, GetCurrentTime(0, 0));
        close(sock);
        return -3;
    }
    if(!(FD_ISSET(sock, &readfds)))    {
        sprintf(errorMessage, "Recv socket:%d not in readfds! %s", sock, GetCurrentTime(0, 0));
        close(sock);
        return -4;
    }
    while(i < size)    {
        ret = recv(sock, buf + i, size - i, flag);
        if(ret <= 0){
            sprintf(errorMessage, "Recv socket:%d recv() error! return:%d, errno=%d, errortext:'%s' %s", sock, ret, errno, strerror(errno), GetCurrentTime(0, 0));
            if(errno == EINTR)    
                if(intretry < 10)  {intretry++;continue;}
                else sprintf(errorMessage, "Recv socket:%d recv() error! EINTR 10 times! %s", sock, GetCurrentTime(0, 0));
            close(sock);
            return -1;
        }
        else i += ret;
    }
    sprintf(errorMessage, "Recv socket:%d recv() OK! %d/%d bytes received! %s", sock, i, size, GetCurrentTime(0, 0));
    return i;
}
 
最後需要說明的是:我這裏講到的源程序並不能實際地作爲一個產品程序來用,實際情況下可能會有其它許多工作要做,比如可能要建立共享隊列來存放socket裏讀到的消息,也可能把發送消息先進行排隊然後再調用Send函數。還有,如果不是全數字,在發送前一定要htonl轉換爲網絡字節序,同理接收到後一定要先ntohl由網絡字節序轉換爲主機字節序,否則對方發送過來的0x00000001在你這裏可能是0x00010000,因爲高低位順序不同。

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