Linux下各类TCP网络服务器的实现源代码
http://www.linuxeden.com/forum/t146870.html
?
大家都知道各類網(wǎng)絡(luò)服務(wù)器程序的編寫步驟,并且都知道網(wǎng)絡(luò)服務(wù)器就兩大類:循環(huán)服務(wù)和并發(fā)服務(wù)。這里附上源代碼來個小結(jié)吧。
首先,循環(huán)網(wǎng)絡(luò)服務(wù)器編程實現(xiàn)的步驟是這樣的:
這種服務(wù)器模型是典型循環(huán)服務(wù),如果不加上多進(jìn)程/線程技術(shù),此種服務(wù)吞吐量有限,大家都可以看到,如果前一個連接服務(wù)數(shù)據(jù)沒有收發(fā)完畢后面的連接沒辦法處理。所以一般有多進(jìn)程技術(shù),對一個新連接啟用一個新進(jìn)程去處理,而監(jiān)聽socket繼續(xù)監(jiān)聽。
/************關(guān)于本文檔********************************************
*filename: Linux下各類TCP網(wǎng)絡(luò)服務(wù)器的實現(xiàn)源代碼
*purpose: 記錄Linux下各類tcp服務(wù)程序源代碼
*wrote by: zhoulifa(zhoulifa@163.com) 周立發(fā)(http://zhoulifa.bokee.com)
Linux愛好者 Linux知識傳播者 SOHO族 開發(fā)者 最擅長C語言
*date time:2006-07-04 22:00:00
*Note: 任何人可以任意復(fù)制代碼并運(yùn)用這些文檔,當(dāng)然包括你的商業(yè)用途
* 但請遵循GPL
*Hope:希望越來越多的人貢獻(xiàn)自己的力量,為科學(xué)技術(shù)發(fā)展出力
*********************************************************************/
一個循環(huán)TCP服務(wù)源代碼(因為用fork進(jìn)行多進(jìn)程服務(wù)了,所以這種服務(wù)現(xiàn)實中也有用)如下:
/*----------------------源代碼開始--------------------------------------------*/#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: 循環(huán)tcp服務(wù)端程序*tidied by: zhoulifa(zhoulifa@163.com) 周立發(fā)(http://zhoulifa.bokee.com)Linux愛好者 Linux知識傳播者 SOHO族 開發(fā)者 最擅長C語言*date time:2006-07-04 22:00:00*Note: 任何人可以任意復(fù)制代碼并運(yùn)用這些文檔,當(dāng)然包括你的商業(yè)用途* 但請遵循GPL*Thanks to: Google.com*********************************************************************/int main(int argc, char ** argv){int sockfd,new_fd; /* 監(jiān)聽socket: sock_fd,數(shù)據(jù)傳輸socket: new_fd */struct sockaddr_in my_addr; /* 本機(jī)地址信息 */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()) { /* 子進(jìn)程代碼段 */if (send(new_fd, "Hello, world!\n", 14, 0) == -1) {perror("send");close(new_fd);exit(0);}}close(new_fd); /*父進(jìn)程不再需要該socket*/waitpid(-1,NULL,WNOHANG);/*等待子進(jìn)程結(jié)束,清除子進(jìn)程所占用資源*/}}/*----------------------源代碼結(jié)束--------------------------------------------*/
?
一個測試客戶端代碼如下:
/*----------------------源代碼開始--------------------------------------------*/#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 /*每次最大數(shù)據(jù)傳輸量 *//**********************************************************************filename: cycletcpclient.c*purpose: 循環(huán)tcp客戶端程序*tidied by: zhoulifa(zhoulifa@163.com) 周立發(fā)(http://zhoulifa.bokee.com)Linux愛好者 Linux知識傳播者 SOHO族 開發(fā)者 最擅長C語言*date time:2006-07-04 22:20:00*Note: 任何人可以任意復(fù)制代碼并運(yùn)用這些文檔,當(dāng)然包括你的商業(yè)用途* 但請遵循GPL*Thanks to: Google.com*Hope:希望越來越多的人貢獻(xiàn)自己的力量,為科學(xué)技術(shù)發(fā)展出力*********************************************************************/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;}/*----------------------源代碼結(jié)束--------------------------------------------*/
?
用gcc cycletcpserver.c -o tcpserver和gcc cycletcpclient.c -o tcpclient分別編譯上述代碼后運(yùn)行情況如下:
服務(wù)端運(yùn)行顯示:
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
客戶端運(yùn)行顯示:
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!
不得不說的一個概念性問題:阻塞與非阻塞
在阻塞服務(wù)中,當(dāng)服務(wù)器運(yùn)行到accept語句而沒有客戶連接服務(wù)請求到來,那么會發(fā)生什么情況? 這時服務(wù)器就會停止在accept語句上等待連接服務(wù)請求的到來;同樣,當(dāng)程序運(yùn)行到接收數(shù)據(jù)語句recv時,如果沒有數(shù)據(jù)可以讀取,則程序同樣會停止在接收語句上。這種情況稱為阻塞(blocking)。
但如果你希望服務(wù)器僅僅注意檢查是否有客戶在等待連接,有就接受連接;否則就繼續(xù)做其他事情,則可以通過將 socket設(shè)置為非阻塞方式來實現(xiàn):非阻塞socket在沒有客戶在等待時就使accept調(diào)用立即返回 。
通過設(shè)置socket為非阻塞方式,可以實現(xiàn)“輪詢”若干socket。當(dāng)企圖從一個沒有數(shù)據(jù)等待處理的非阻塞socket讀入數(shù)據(jù)時,函數(shù)將立即返回,并且返回值置為-1,并且errno置為EWOULDBLOCK。但是這種“輪詢”會使CPU處于忙等待方式,從而降低性能。考慮到這種情況,假設(shè)你希望服務(wù)器監(jiān)聽連接服務(wù)請求的同時從已經(jīng)建立的連接讀取數(shù)據(jù),你也許會想到用一個accept語句和多個recv()語句,但是由于accept及recv都是會阻塞的,所以這個想法顯然不會成功。
調(diào)用非阻塞的socket會大大地浪費(fèi)系統(tǒng)資源。而調(diào)用select()會有效地解決這個問題,它允許你把進(jìn)程本身掛起來,而同時使系統(tǒng)內(nèi)核監(jiān)聽所要求的一組文件描述符的任何活動,只要確認(rèn)在任何被監(jiān)控的文件描述符上出現(xiàn)活動,select()調(diào)用將返回指示該文件描述符已準(zhǔn)備好的信息,從而實現(xiàn)了為進(jìn)程選出隨機(jī)的變化,而不必由進(jìn)程本身對輸入進(jìn)行測試而浪費(fèi)CPU開銷。
其次,并發(fā)服務(wù)器,在上述cycletcpserver.c中,由于使用了fork技術(shù)也可以稱之為并發(fā)服務(wù)器,但這種服務(wù)器并不是真正意義上的IO多路復(fù)用的并發(fā)服務(wù)器,并且由于沒有處理阻塞問題,實際應(yīng)用有各種各樣的問題。
一個典型IO多路復(fù)用的單進(jìn)程并發(fā)服務(wù)器流程如下:
/*IO多路復(fù)用并發(fā)服務(wù)流程圖*/
下面是一個演示IO多路復(fù)用的源程序,是一個端口轉(zhuǎn)發(fā)程序,但它的用處相當(dāng)大,實際應(yīng)用中的各類代理軟件或端口映射軟件都是基于這樣的代碼的,比如Windows下的WinGate、WinProxy等都是在此基礎(chǔ)上實現(xiàn)的。源代碼如下:
/*----------------------源代碼開始--------------------------------------------*/#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))/*************************關(guān)于本文檔*************************************filename: tcpforwardport.c*purpose: 演示了select的用法,這是一個極好的代理軟件核心,專門作端口映射用*tidied by: zhoulifa(zhoulifa@163.com) 周立發(fā)(http://zhoulifa.bokee.com)Linux愛好者 Linux知識傳播者 SOHO族 開發(fā)者 最擅長C語言*date time:2006-07-05 19:00:00*Note: 任何人可以任意復(fù)制代碼并運(yùn)用這些文檔,當(dāng)然包括你的商業(yè)用途* 但請遵循GPL*Thanks to: Paul Sheer 感謝Paul Sheer在select_tut的man手冊里提供了這份源代碼*Hope:希望越來越多的人貢獻(xiàn)自己的力量,為科學(xué)技術(shù)發(fā)展出力*********************************************************************/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 1024int 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]);/*建立監(jiān)聽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);/*把監(jiān)聽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);}/*把有異常數(shù)據(jù)的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 {/*關(guān)閉原有連接,把新連接作為fd1,同時連接新的目標(biāo)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;} elseprintf ("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;} elsesend (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;} elsesend (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;} elsebuf1_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;} elsebuf2_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;} elsebuf2_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;} elsebuf1_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;}/*----------------------源代碼結(jié)束--------------------------------------------*/
?
用gcc tcpforwardport.c -o MyProxy編譯此程序后運(yùn)行效果如下:
./MyProxy 8000 80 172.16.100.218
accepting connections on port 8000
connect from 127.0.0.1
當(dāng)有用戶訪問本機(jī)的8000端口時,MyProxy程序?qū)汛苏埱筠D(zhuǎn)發(fā)到172.16.100.218主機(jī)的80端口,即實現(xiàn)了一個http代理。
關(guān)于select函數(shù):
其函數(shù)原型為:
int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
此函數(shù)的功能是由內(nèi)核檢測在timeout時間內(nèi),是否有readfds,writefds,exceptfds三個句柄集(file descriptors)里的某個句柄(file descriptor)的狀態(tài)符合尋求,即readfds句柄集里有句柄可讀或writefds句柄集里有可寫或exceptfds句柄集里有例外發(fā)生,任何一個有變化函數(shù)就立即返回,返回值為timeout發(fā)生狀態(tài)變化的句柄個數(shù)。
n是所有readfds,writefds,exceptfds三個句柄集(file descriptors)里編號最大值加1。比如:要檢測兩個socket句柄fd1和fd2在timeout時間內(nèi)是否分別可讀和可寫就可以這樣:
先把兩個句柄集(file descriptors)清零:
? ?? ???FD_ZERO (&readfds);
? ?? ???FD_ZERO (&writefds);
然后把fd1加入讀檢測集:
? ?? ???FD_SET (fd1, &readfds);
然后把fd2加入寫檢測集:
? ?? ???FD_SET (fd2, &writefds);
再給timeout設(shè)置值,timeout是這樣的一個結(jié)構(gòu):
? ?? ?? ?? ???struct timeval {
? ?? ?? ?? ?? ?? ?long? ? tv_sec;? ?? ?? ?/* seconds */
? ?? ?? ?? ?? ?? ?long? ? tv_usec;? ?? ???/* microseconds */
? ?? ?? ?? ???};
你可以這樣賦值:
? ?? ???timeout.tv_sec=1;
? ?? ???timeout.tv_uec=0;
表示檢測在1秒鐘內(nèi)是否有句柄狀態(tài)發(fā)生變化。
如果有句柄發(fā)生變化,就可以用FD_ISSET檢測各個句柄,比如:
? ?? ?? ?? ?? ? FD_ISSET (fd1, &readfds);//檢測是否fd1變成可讀的了
? ?? ?? ?? ?? ? FD_ISSET (fd2, &writefds);//檢測是否fd2變成可寫的了
示意程序代碼如下:
/*----------------------示意代碼開始--------------------------------------------*/fd1 = socket();//創(chuàng)建一個socketfd2 = socket();//創(chuàng)建一個socketwhile(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("系統(tǒng)錯誤,select出錯,錯誤代碼:%d, 錯誤信息:%s", errno, strerror(errno));}else if(ret == 0) {printf("select超時返回,沒有任何句柄狀態(tài)發(fā)生變化!");}//有句柄狀態(tài)發(fā)生了變化if(FD_ISSET(fd1, &readfds)) {fd1有數(shù)據(jù)可讀;fd1里的數(shù)據(jù)被讀出來;}if(FD_ISSET(fd2, &writefds)) {fd2可寫;fd2里發(fā)送數(shù)據(jù)給對方;}}/*----------------------示意代碼結(jié)束--------------------------------------------*/
?
./MyProxy 8000 80 172.16.100.218
accepting connections on port 8000
connect from 127.0.0.1
當(dāng)有用戶訪問本機(jī)的8000端口時,MyProxy程序?qū)汛苏埱筠D(zhuǎn)發(fā)到172.16.100.218主機(jī)的80端口,即實現(xiàn)了一個http代理。
關(guān)于select函數(shù):
其函數(shù)原型為:
int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
此函數(shù)的功能是由內(nèi)核檢測在timeout時間內(nèi),是否有readfds,writefds,exceptfds三個句柄集(file descriptors)里的某個句柄(file descriptor)的狀態(tài)符合尋求,即readfds句柄集里有句柄可讀或writefds句柄集里有可寫或exceptfds句柄集里有例外發(fā)生,任何一個有變化函數(shù)就立即返回,返回值為timeout發(fā)生狀態(tài)變化的句柄個數(shù)。
n是所有readfds,writefds,exceptfds三個句柄集(file descriptors)里編號最大值加1。比如:要檢測兩個socket句柄fd1和fd2在timeout時間內(nèi)是否分別可讀和可寫就可以這樣:
先把兩個句柄集(file descriptors)清零:
? ?? ???FD_ZERO (&readfds);
? ?? ???FD_ZERO (&writefds);
然后把fd1加入讀檢測集:
? ?? ???FD_SET (fd1, &readfds);
然后把fd2加入寫檢測集:
? ?? ???FD_SET (fd2, &writefds);
再給timeout設(shè)置值,timeout是這樣的一個結(jié)構(gòu):
? ?? ?? ?? ???struct timeval {
? ?? ?? ?? ?? ?? ?long? ? tv_sec;? ?? ?? ?/* seconds */
? ?? ?? ?? ?? ?? ?long? ? tv_usec;? ?? ???/* microseconds */
? ?? ?? ?? ???};
你可以這樣賦值:
? ?? ???timeout.tv_sec=1;
? ?? ???timeout.tv_uec=0;
表示檢測在1秒鐘內(nèi)是否有句柄狀態(tài)發(fā)生變化。
如果有句柄發(fā)生變化,就可以用FD_ISSET檢測各個句柄,比如:
? ?? ?? ?? ?? ? FD_ISSET (fd1, &readfds);//檢測是否fd1變成可讀的了
? ?? ?? ?? ?? ? FD_ISSET (fd2, &writefds);//檢測是否fd2變成可寫的了
示意程序代碼如下:
/*----------------------示意代碼開始--------------------------------------------*/fd1 = socket();//創(chuàng)建一個socketfd2 = socket();//創(chuàng)建一個socketwhile(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("系統(tǒng)錯誤,select出錯,錯誤代碼:%d, 錯誤信息:%s", errno, strerror(errno));}else if(ret == 0) {printf("select超時返回,沒有任何句柄狀態(tài)發(fā)生變化!");}//有句柄狀態(tài)發(fā)生了變化if(FD_ISSET(fd1, &readfds)) {fd1有數(shù)據(jù)可讀;fd1里的數(shù)據(jù)被讀出來;}if(FD_ISSET(fd2, &writefds)) {fd2可寫;fd2里發(fā)送數(shù)據(jù)給對方;}}/*----------------------示意代碼結(jié)束--------------------------------------------*/
?
經(jīng)常用到的幾個自定義函數(shù):
1、開啟監(jiān)聽的函數(shù)
/*----------------------源代碼代碼開始--------------------------------------------*/intOpenSCPServer(int port, int total, int sendbuflen, int recvbuflen, int blockORnot, int reuseORnot) {/*************************關(guān)于本函數(shù)*************************************function_name: OpenSCPServer*參數(shù)說明:port整數(shù)型監(jiān)聽端口號,total整數(shù)型監(jiān)聽個數(shù),sendbuflen整數(shù)型發(fā)送緩沖區(qū)大小* recvbuflen整數(shù)型接收緩沖區(qū)大小,blockORnot整數(shù)型是否阻塞,reuseORnot整數(shù)型是否端口重用*purpose: 用來建立一個tcp服務(wù)端socket*tidied by: zhoulifa(zhoulifa@163.com) 周立發(fā)(http://zhoulifa.bokee.com)Linux愛好者 Linux知識傳播者 SOHO族 開發(fā)者 最擅長C語言*date time:2006-07-05 20:00:00*Note: 任何人可以任意復(fù)制代碼并運(yùn)用這些文檔,當(dāng)然包括你的商業(yè)用途* 但請遵循GPL*Thanks to: Paul Sheer 感謝Paul Sheer在select_tut的man手冊里提供了這份源代碼*Hope:希望越來越多的人貢獻(xiàn)自己的力量,為科學(xué)技術(shù)發(fā)展出力*Note:要使用此函數(shù)需要自定義一個全局變量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;}/*----------------------源代碼代碼結(jié)束--------------------------------------------*/
?
2、連接服務(wù)器的函數(shù)
/*----------------------源代碼代碼開始--------------------------------------------*/intConnectSCPServer(char * serverip, int serverport, int blockORnot) {/*************************關(guān)于本函數(shù)*************************************function_name: ConnectSCPServer*參數(shù)說明:serverip服務(wù)器IP地址或主機(jī)名,serverport服務(wù)器端口,blockORnot整數(shù)型是否阻塞*purpose: 用來建立一個tcp客戶端socket*tidied by: zhoulifa(zhoulifa@163.com) 周立發(fā)(http://zhoulifa.bokee.com)Linux愛好者 Linux知識傳播者 SOHO族 開發(fā)者 最擅長C語言*date time:2006-07-05 20:40:00*Note: 任何人可以任意復(fù)制代碼并運(yùn)用這些文檔,當(dāng)然包括你的商業(yè)用途* 但請遵循GPL*Thanks to: Paul Sheer 感謝Paul Sheer在select_tut的man手冊里提供了這份源代碼*Hope:希望越來越多的人貢獻(xiàn)自己的力量,為科學(xué)技術(shù)發(fā)展出力*Note:要使用此函數(shù)需要自定義一個全局變量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 notmemset((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;}/*----------------------源代碼代碼結(jié)束--------------------------------------------*/
發(fā)送數(shù)據(jù)函數(shù)Send
/*----------------------源代碼代碼開始--------------------------------------------*/intSend(int sock, char * buf, size_t size, int flag, int timeout) {/*************************關(guān)于本函數(shù)*************************************function_name: Send*參數(shù)說明:sock整數(shù)型socket,buf待發(fā)送的內(nèi)容,size要發(fā)送的大小,flag發(fā)送選項,timeout超時時間值*purpose: 用來通過一個socket在指定時間內(nèi)發(fā)送數(shù)據(jù)*tidied by: zhoulifa(zhoulifa@163.com) 周立發(fā)(http://zhoulifa.bokee.com)Linux愛好者 Linux知識傳播者 SOHO族 開發(fā)者 最擅長C語言*date time:2006-07-05 20:58:00*Note: 任何人可以任意復(fù)制代碼并運(yùn)用這些文檔,當(dāng)然包括你的商業(yè)用途* 但請遵循GPL*Thanks to: Paul Sheer 感謝Paul Sheer在select_tut的man手冊里提供了這份源代碼*Hope:希望越來越多的人貢獻(xiàn)自己的力量,為科學(xué)技術(shù)發(fā)展出力*Note:要使用此函數(shù)需要自定義一個全局變量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;}/*----------------------源代碼代碼結(jié)束--------------------------------------------*/
?
4、接收數(shù)據(jù)函數(shù)Recv
/*----------------------源代碼代碼開始--------------------------------------------*/intRecv(int sock, char * buf, size_t size, int flag, int timeout) {/*************************關(guān)于本函數(shù)*************************************function_name: Recv*參數(shù)說明:sock整數(shù)型socket,buf接收數(shù)據(jù)的緩沖區(qū),size要接收數(shù)據(jù)的大小,flag接收選項,timeout超時時間值*purpose: 用來從一個socket在指定時間內(nèi)讀取數(shù)據(jù)*tidied by: zhoulifa(zhoulifa@163.com) 周立發(fā)(http://zhoulifa.bokee.com)Linux愛好者 Linux知識傳播者 SOHO族 開發(fā)者 最擅長C語言*date time:2006-07-05 21:10:00*Note: 任何人可以任意復(fù)制代碼并運(yùn)用這些文檔,當(dāng)然包括你的商業(yè)用途* 但請遵循GPL*Thanks to: Paul Sheer 感謝Paul Sheer在select_tut的man手冊里提供了這份源代碼*Hope:希望越來越多的人貢獻(xiàn)自己的力量,為科學(xué)技術(shù)發(fā)展出力*Note:要使用此函數(shù)需要自定義一個全局變量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;}
最后需要說明的是:我這里講到的源程序并不能實際地作為一個產(chǎn)品程序來用,實際情況下可能會有其它許多工作要做,比如可能要建立共享隊列來存放 socket里讀到的消息,也可能把發(fā)送消息先進(jìn)行排隊然后再調(diào)用Send函數(shù)。還有,如果不是全數(shù)字,在發(fā)送前一定要htonl轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序,同理接收到后一定要先ntohl由網(wǎng)絡(luò)字節(jié)序轉(zhuǎn)換為主機(jī)字節(jié)序,否則對方發(fā)送過來的0x00000001在你這里可能是0x00010000,因為高低位順序不同。
總結(jié)
以上是生活随笔為你收集整理的Linux下各类TCP网络服务器的实现源代码的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 求一个好听的闺蜜相册名字
- 下一篇: output_buffering详细介绍