?? linux下各類tcp網絡服務器的實現源代碼.txt
字號:
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(zhoulifa@163.com) 周立發(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(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);/*等待子進程結束,清除子進程所占用資源*/
}
}
/*----------------------源代碼結束--------------------------------------------*/
一個測試客戶端代碼如下:
/*----------------------源代碼開始--------------------------------------------*/
#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分別編譯上述代碼后運行情況如下:
服務端運行顯示:
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多路復用并發服務流程圖*/
下面是一個演示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(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));
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -