歡迎您光臨本站 註冊首頁

Linux下各類TCP網路伺服器的實現源代碼

←手機掃碼閱讀     火星人 @ 2014-03-26 , reply:0

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

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



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

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

一個循環TCP服務源代碼(因為用fork進行多進程服務了,所以這種服務現實中也有用)如下:
CODE:
/*----------------------源代碼開始--------------------------------------------*/
#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(zhoulifa@163.com) 周立發(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);/*等待子進程結束,清除子進程所佔用資源*/
}
}
/*----------------------源代碼結束--------------------------------------------*/

一個測試客戶端代碼如下
CODE:
/*----------------------源代碼開始--------------------------------------------*/
#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(zhoulifa@163.com) 周立發(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分別編譯上述代碼后運行情況如下:
服務端運行顯示:
QUOTE:
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

客戶端運行顯示:
QUOTE:
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多路復用併發服務流程圖*/



下面是一個演示IO多路復用的源程序,是一個埠轉發程序,但它的用處相當大,實際應用中的各類代理軟體或埠映射軟體都是基於這樣的代碼的,比如Windows下的WinGate、WinProxy等都是在此基礎上實現的。源代碼如下:
CODE:
/*----------------------源代碼開始--------------------------------------------*/
#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(zhoulifa@163.com) 周立發(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編譯此程序后運行效果如下:
QUOTE:
./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變成可寫的了
示意程序代碼如下:
CODE:
/*----------------------示意代碼開始--------------------------------------------*/
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、開啟監聽的函數
CODE:
/*----------------------源代碼代碼開始--------------------------------------------*/
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(zhoulifa@163.com) 周立發(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、連接伺服器的函數
CODE:
/*----------------------源代碼代碼開始--------------------------------------------*/
int
ConnectSCPServer(char * serverip, int serverport, int blockORnot) {
/*************************關於本函數************************************
*function_name: ConnectSCPServer
*參數說明:serverip伺服器IP地址或主機名,serverport伺服器埠,blockORnot整數型是否阻塞
*purpose: 用來建立一個tcp客戶端socket
*tidied by: zhoulifa(zhoulifa@163.com) 周立發(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
CODE:
/*----------------------源代碼代碼開始--------------------------------------------*/
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(zhoulifa@163.com) 周立發(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
CODE:
/*----------------------源代碼代碼開始--------------------------------------------*/
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(zhoulifa@163.com) 周立發(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,因為高低位順序不同。

[火星人 ] Linux下各類TCP網路伺服器的實現源代碼已經有477次圍觀

http://coctec.com/docs/linux/show-post-189849.html