1. 程式人生 > >linux 網路程式設計:epoll 的例項

linux 網路程式設計:epoll 的例項

       在前面已經經過了PPC、TPC、select之類( TPC就是使用程序處理data,TPC就是使用執行緒處理 ),前面兩個的缺點大家應該都是知道的是吧,對於select( 其實poll和他差不多 ),缺點是能同時連線的fd是在是不多,在linux中一般是1024/2048,對於很大的伺服器來說是不夠的!當然我們可以自己修改其值!但是效率上就會下降!

       對於改進poll的epoll來說:支援一個程序開啟大數目的socket描述符,也就是說與本機的記憶體是有關係的!( 一般伺服器的都是很大的! )

       下面是我的小PC機上的顯示:

       [email protected]

:~$ cat /proc/sys/fs/file-max
       391658
       達到了391658個,那麼對於伺服器而言,顯然,嘿嘿嘿~~~

      epoll的基礎知識吧大家在網上到處都能找到,不就是epoll_creat 、epoll_ctl、epoll_wait 3函式!大家自己搜去,我也是在學習。。。

      此處主要是貼上自己的測試的一些垃圾程式碼,與大家共勉!呵呵呵~

     哦,忘了要注意一下:

     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佇列裡


server端:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>			/* socket類定義需要*/
#include <sys/epoll.h>			/* epoll標頭檔案 */
#include <fcntl.h>	                /* nonblocking需要 */
#include <sys/resource.h>		/* 設定最大的連線數需要setrlimit */

#define	MAXEPOLL	10000	/* 對於伺服器來說,這個值可以很大的! */
#define	MAXLINE		1024
#define 	PORT			6000
#define	MAXBACK	1000

//!> 設定非阻塞
//!> 
int setnonblocking( int fd )
{
	if( fcntl( fd, F_SETFL, fcntl( fd, F_GETFD, 0 )|O_NONBLOCK ) == -1 )
	{
		printf("Set blocking error : %d\n", errno);
		return -1;
	}
	return 0;
}

int main( int argc, char ** argv )
{
	int 		listen_fd;
	int 		conn_fd;
	int 		epoll_fd;
	int 		nread;
	int 		cur_fds;				//!> 當前已經存在的數量
	int 		wait_fds;				//!> epoll_wait 的返回值
	int		i;
	struct sockaddr_in servaddr;
	struct sockaddr_in cliaddr;
	struct 	epoll_event	ev;
	struct 	epoll_event	evs[MAXEPOLL];
	struct 	rlimit	rlt;		//!> 設定連線數所需
	char 	buf[MAXLINE];
	socklen_t	len = sizeof( struct sockaddr_in );

	//!> 設定每個程序允許開啟的最大檔案數
	//!> 每個主機是不一樣的哦,一般伺服器應該很大吧!
	//!> 
	rlt.rlim_max = rlt.rlim_cur = MAXEPOLL;
	if( setrlimit( RLIMIT_NOFILE, &rlt ) == -1 )	
	{
		printf("Setrlimit Error : %d\n", errno);
		exit( EXIT_FAILURE );
	}
	
	//!> server 套介面
	//!> 
	bzero( &servaddr, sizeof( servaddr ) );
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl( INADDR_ANY );
	servaddr.sin_port = htons( PORT );
	
	//!> 建立套接字
	if( ( listen_fd = socket( AF_INET, SOCK_STREAM, 0 ) ) == -1 )
	{
		printf("Socket Error...\n" , errno );
		exit( EXIT_FAILURE );
	}
	
	//!> 設定非阻塞模式
	//!> 
	if( setnonblocking( listen_fd ) == -1 )
	{
		printf("Setnonblocking Error : %d\n", errno);
		exit( EXIT_FAILURE );
	}
	
	//!> 繫結
	//!>
	if( bind( listen_fd, ( struct sockaddr *)&servaddr, sizeof( struct sockaddr ) ) == -1 )
	{
		printf("Bind Error : %d\n", errno);
		exit( EXIT_FAILURE );
	}

	//!> 監聽
	//!> 
	if( listen( listen_fd, MAXBACK ) == -1 )
	{
		printf("Listen Error : %d\n", errno);
		exit( EXIT_FAILURE );
	}
	
	//!> 建立epoll
	//!> 
	epoll_fd = epoll_create( MAXEPOLL );	//!> create
	ev.events = EPOLLIN | EPOLLET;		//!> accept Read!
	ev.data.fd = listen_fd;					//!> 將listen_fd 加入
	if( epoll_ctl( epoll_fd, EPOLL_CTL_ADD, listen_fd, &ev ) < 0 )
	{
		printf("Epoll Error : %d\n", errno);
		exit( EXIT_FAILURE );
	}
	cur_fds = 1;
	
	while( 1 )
	{
		if( ( wait_fds = epoll_wait( epoll_fd, evs, cur_fds, -1 ) ) == -1 )
		{
			printf( "Epoll Wait Error : %d\n", errno );
			exit( EXIT_FAILURE );
		}

		for( i = 0; i < wait_fds; i++ )
		{
			if( evs[i].data.fd == listen_fd && cur_fds < MAXEPOLL )	
													//!> if是監聽埠有事
			{
				if( ( conn_fd = accept( listen_fd, (struct sockaddr *)&cliaddr, &len ) ) == -1 )
				{
					printf("Accept Error : %d\n", errno);
					exit( EXIT_FAILURE );
				}
				
				printf( "Server get from client !\n"/*,  inet_ntoa(cliaddr.sin_addr), cliaddr.sin_port */);
				
				ev.events = EPOLLIN | EPOLLET;		//!> accept Read!
				ev.data.fd = conn_fd;					//!> 將conn_fd 加入
				if( epoll_ctl( epoll_fd, EPOLL_CTL_ADD, conn_fd, &ev ) < 0 )
				{
					printf("Epoll Error : %d\n", errno);
					exit( EXIT_FAILURE );
				}
				++cur_fds; 
				continue;		
			}
			
			//!> 下面處理資料
			//!> 
			nread = read( evs[i].data.fd, buf, sizeof( buf ) );
			if( nread <= 0 )						//!> 結束後者出錯
			{
				close( evs[i].data.fd );
				epoll_ctl( epoll_fd, EPOLL_CTL_DEL, evs[i].data.fd, &ev );	//!> 刪除計入的fd
				--cur_fds;					//!> 減少一個唄!
				continue;
			}
			
			write( evs[i].data.fd, buf, nread );			//!> 回寫
			
		}
	}
	
	close( listen_fd );
	return 0;
}




對於client:

由於本人比較懶,所以就使用上一次的select的client吧,一樣的,呵呵:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include  <arpa/inet.h>
#include <sys/select.h>

#define MAXLINE 1024
#define SERV_PORT 6000

//!> 注意輸入是由stdin,接受是由server傳送過來
//!> 所以在client端也是需要select進行處理的
void send_and_recv( int connfd )
{
	FILE * fp = stdin;
	int   lens;
	char send[MAXLINE];
	char recv[MAXLINE];
	fd_set rset;
	FD_ZERO( &rset );
	int maxfd = ( fileno( fp ) > connfd ? fileno( fp ) : connfd  + 1 );	
							        //!> 輸入和輸出的最大值
	int n;
	
	while( 1 )
	{
		FD_SET( fileno( fp ), &rset );
		FD_SET( connfd, &rset );			//!> 注意不要把rset看作是簡單的一個變數
								//!> 注意它其實是可以包含一組套接字的哦,
								//!> 相當於是封裝的陣列!每次都要是新的哦!
		
		if( select( maxfd, &rset, NULL, NULL, NULL ) == -1 )
		{
			printf("Client Select Error..\n");
			exit(EXIT_FAILURE  );
		}
		
		//!> if 連線口有資訊
		if( FD_ISSET( connfd, &rset ) )	//!> if 連線埠有資訊
		{
			printf( "client get from server ...\n" );
			memset( recv, 0, sizeof( recv ) );
			n = read( connfd, recv, MAXLINE );
			if( n == 0 )
			{
				printf("Recv ok...\n");
				break;
			}
			else if( n == -1 )
			{
				printf("Recv error...\n");
				break;
			}
			else
			{
				lens = strlen( recv );
				recv[lens] = '\0';
				//!> 寫到stdout
				write( STDOUT_FILENO, recv, MAXLINE );
				printf("\n");
			}

		}
		
		//!> if 有stdin輸入
		if( FD_ISSET( fileno( fp ), &rset ) )	//!> if 有輸入
		{
			//!> printf("client stdin ...\n");
			
			memset( send, 0, sizeof( send ) );
			if( fgets( send, MAXLINE, fp ) == NULL )
			{
				printf("End...\n");
				exit( EXIT_FAILURE );
			}
			else
			{
				//!>if( str )
				lens = strlen( send );
				send[lens-1] = '\0';		//!> 減一的原因是不要回車字元
								//!> 經驗值:這一步非常重要的哦!!!!!!!!
				if( strcmp( send, "q" ) == 0 )
				{
					printf( "Bye..\n" );
					return;
				}
				
				printf("Client send : %s\n", send);
				write( connfd, send, strlen( send ) );
			}
		}
		
	}
	
}

int main( int argc, char ** argv )
{
	//!> char * SERV_IP = "10.30.97.188";
	char 	buf[MAXLINE];
	int   	connfd;
	struct sockaddr_in servaddr;
	
	if( argc != 2 )
	{
		printf("Input server ip !\n");
		exit( EXIT_FAILURE );
	}
	
	//!> 建立套接字
	if( ( connfd = socket( AF_INET, SOCK_STREAM, 0 ) ) == -1 )
	{
		printf("Socket Error...\n" , errno );
		exit( EXIT_FAILURE );
	}

	//!> 套接字資訊
	bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERV_PORT);
    inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
	
	//!> 連結server
	if( connect( connfd, ( struct sockaddr *  )&servaddr, sizeof( servaddr ) ) < 0 )
	{
		printf("Connect error..\n");
		exit(EXIT_FAILURE);
	}	
	/*else
	{
		printf("Connet ok..\n");
	}*/

	//!>
	//!> send and recv
	send_and_recv( connfd );
	
	//!> 

	close( connfd );
	printf("Exit\n");
	
	return 0;
}

編譯執行:

gcc -o server server.c

gcc -o client client.c

./server

./client

END

相關推薦

linux 網路程式設計epoll例項

       在前面已經經過了PPC、TPC、select之類( TPC就是使用程序處理data,TPC就是使用執行緒處理 ),前面兩個的缺點大家應該都是知道的是吧,對於select( 其實poll和他差不多 ),缺點是能同時連線的fd是在是不多,在linux中一般是102

Linux網路程式設計TCP客戶/伺服器模型及基本socket函式

TCP客戶/伺服器模型 TCP連線的分組交換 在使用socket API的時候應該清楚應用程式和TCP協議棧是如何互動的: 呼叫connect()會發出SYN段(SYN是TCP報文段頭部的一個標誌位,置為1) 阻塞的read()函式返回0就表明收到了FIN段 客戶端呼叫c

Linux網路程式設計socket程式設計簡介、網路位元組序及相關函式

Socket(套接字) socket可以看成是使用者程序與核心網路協議棧的程式設計介面(API函式)。 socket不僅可以用於本機的程序間通訊,還可以用於網路上不同主機的程序間通訊。 IPv4套接字地址結構 IPv4套接字地址結構通常也稱為“網際套接字地址結構”,它以

網路程式設計epoll、accept觸發模式及阻塞方式的選擇

select(),poll()模型都是水平觸發模式,訊號驅動IO是邊緣觸發模式,epoll()模型即支援水平觸發,也支援邊緣觸發,預設是水平觸發 從表象看epoll的效能最好,但是在連線數少,並且連線都十分活躍的情況下,select和poll的效能可能比ep

Linux網路程式設計--使用epoll模型同時處理tcp和udp服務

在實際工作中,伺服器需要同時監聽和處理tcp和udp的套接字,同時監聽N多的埠。根據bind系統呼叫來講,一個socket只能監聽一個埠,因此要建立多個socket並繫結到各個埠上。當然同一個埠可以同時繫結tcp和udp的socket,但是要建立兩個socket

Linux網路程式設計原始套接字的魔力【上】

原文:http://blog.chinaunix.net/uid-23069658-id-3280895.html 基於原始套接字程式設計        在開發面向連線的TCP和麵向無連線的UDP程式時,我們所關心的核心問題在於資料收發層面,資料的傳輸特性由TCP或UDP來

Linux網路程式設計使用select函式實現socket 收發資料

 所謂的回射是指:客戶端A向服務端B傳送資料,服務端B接收到資料之後,再將接收到的資料傳送回客戶端B。所謂的迭代伺服器,

Linux網路程式設計——原始套接字例項MAC 頭部報文分析

通過《Linux網路程式設計——原始套接字程式設計》得知,我們可以通過原始套接字以及 recvfrom( ) 可以獲取鏈路層的資料包,那我們接收的鏈路層資料包到底長什麼樣的呢? MAC 頭部(有線區域網) 注意:CRC、PAD 在組包時可以忽略 鏈路層資料包的其中一

Linux 網路程式設計——原始套接字例項MAC 地址掃描器

如果 A (192.168.1.1 )向 B (192.168.1.2 )傳送一個數據包,那麼需要的條件有 ip、port、使用的協議(TCP/UDP)之外還需要 MAC 地址,因為在乙太網資料包中 MAC 地址是必須要有的。那麼怎樣才能知道對方的 MAC 地址?答案是:它通

Linux 網路程式設計——原始套接字例項傳送 UDP 資料包

乙太網(Ethernet)報文格式(MAC頭部報文格式): IP 報文格式: UDP 報文格式: 校驗和函式: /******************************************************* 功能:     

Linux網路程式設計--epoll 模型原理詳解以及例項

1.簡介 Linux I/O多路複用技術在比較多的TCP網路伺服器中有使用,即比較多的用到select函式。Linux 2.6核心中有提高網路I/O效能的新方法,即epoll 。 epoll是什麼?按照man手冊的說法是為處理大批量控制代碼而作了改進

Linux學習之網路程式設計epoll的用法)

言之者無罪,聞之者足以戒。 - “詩序” epoll相關的函式包含在標頭檔案<sys/epoll.h> epoll是Linux核心為處理大批量控制代碼而作了改進的poll,是Linux下多路複用IO介面select/poll的增強版本,它能顯著減少程式在大量併發連線中只有少量活躍

嵌入式Linux網路程式設計,I/O多路複用,epoll()示例,epoll()客戶端,epoll()伺服器,單鏈表

文章目錄 1,I/O多路複用 epoll()示例 1.1,epoll()---net.h 1.2,epoll()---client.c 1.3,epoll()---sever.c 1.4,epoll()---linklist.h

Linux----網路程式設計(IO複用之epoll系統呼叫函式)

伺服器端epoll.c #include <stdio.h> #include <string.h> #include <stdlib.h> #include <assert.h> #include <unistd.h&

Linux網路程式設計 無連線和麵向連線協議的區別

網路程式設計中最基本的概念就是面向連線(connection-oriented)和無連線(connectionless)協議。儘管本質上來說,兩者之間的區別並不難理解,但對那些剛剛開始進行網路程式設計的人來說,卻是個很容易混淆的問題。這個問題與上下文有些關聯:很顯然,如果兩臺計算機要進行通訊,就必須

Linux網路程式設計 網路協議入門

我們每天使用網際網路,你是否想過,它是如何實現的? 全世界幾十億臺電腦,連線在一起,兩兩通訊。北京的某一塊網絡卡送出訊號,深圳的另一塊網絡卡居然就收到了,兩者實際上根本不知道對方的物理位置,你不覺得這是很神奇的事情嗎? 為了使各種不同的計算機之間可以互聯,ARPANet指定了一套計算

Linux網路程式設計(一)一個簡單的socket程式

伺服器: /* *tcp_server.c */ #include <stdio.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include

Linux-C網路程式設計epoll函式

上文中說到如果從100的不同的地方取外賣,那麼epoll相當於一部手機,當外賣到達後,送貨員可以通知你,從而達到每去必得,少走很多路。 它是如何實現這些作用的呢? epoll的功能 epoll是select/poll的強化版,同是多路複用的函式,epoll

linux 高併發網路程式設計epoll詳解

前言       I/O多路複用有很多種實現。在linux上,2.4核心前主要是select和poll,自Linux 2.6核心正式引入epoll以來,epoll已經成為了目前實現高效能網路伺服器的必備技術。儘管他們的使用方法不盡相同,但是本質上卻沒有什麼區別。本文將重點探