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

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

文章目錄

1,I/O多路複用 epoll()示例

1.1,epoll()—net.h

#ifndef __NET_H__
#define __NET_H__

#include <stdio.h>
#include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <strings.h> #include <arpa/inet.h> #include <unistd.h> #include <stdlib.h> #include <errno.h> #include <sys/time.h> #include <sys/select.h> #include <sys/epoll.h> #define
SERV_IP_ADDR "192.168.31.100"
#define SERV_PORT 5002 #define BACKLOG 5 #define QUIT_STR "quite" #define SERV_RESP_STR "Server:" #endif

1.2,epoll()—client.c

/* ./client serv_ip serv_port */
#include "net.h"
#include "poll.h"

#define FDSIZE 25//epoll處理的檔案描述符大小
#define EPOLLEVENTS 2 //epoll處理的時間個數

void
usage(char *s) { printf("Usage: %s <serv_ip> <serv_port>\n",s); printf("\tserv_ip: server ip address\n"); printf("\tserv_port: server port(>5000)\n "); } void client_do_epoll(int fd); int handle_epoll_enents(int epfd,struct epoll_event *ep_events,int nfds,int fd); int main(int argc, const char *argv[]) { int fd;//fd用於建立socket fd short port; struct sockaddr_in sin; if(argc != 3) { usage((char *)argv[0]); exit(1); } if((port = atoi(argv[2])) < 5000) { usage((char *)argv[0]); exit(1); } /* 1 建立socket fd */ if((fd = socket(AF_INET,SOCK_STREAM,0)) < 0) { perror("socket"); exit(-1); } /* 2 連線伺服器 */ /* 2.1 填充struct sockaddr_in結構體變數*/ bzero(&sin,sizeof(sin)); sin.sin_family = AF_INET; sin.sin_port = htons(port);//轉為網路位元組序埠號 if(inet_pton(AF_INET,argv[1],(void *)&sin.sin_addr.s_addr) < 0) { perror("inet_pton"); goto _error1; } /* 2.2 連線伺服器*/ if(connect(fd,(struct sockaddr *)&sin,sizeof(sin)) < 0) { perror("connect"); goto _error1; } printf("client staring ... OK!\n"); client_do_epoll(fd); _error1: close(fd); return 0; } void client_do_epoll(int fd) { int epfd;//epfd用於生成epoll專用的檔案描述符 int nfds;//用於接受epoll_wait()返回的發生事件數 struct epoll_event ep_ev,ep_events[EPOLLEVENTS];//申明epoll_event結構體變數,ep_ev用於註冊事件,ep_events用於回傳要處理的事件 /*生成epoll的專用檔案描述符*/ epfd = epoll_create(FDSIZE); /*新增連線描述符,及要處理的事件*/ ep_ev.data.fd = fd; ep_ev.events = EPOLLIN|EPOLLET; epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ep_ev); //新增標準輸入描述符,及要處理的事件 ep_ev.data.fd = STDIN_FILENO; ep_ev.events = EPOLLIN|EPOLLET; epoll_ctl(epfd,EPOLL_CTL_ADD,STDIN_FILENO,&ep_ev); while(1) { nfds = epoll_wait(epfd,ep_events,EPOLLEVENTS,-1); if(nfds < 0) { perror("epoll_wait"); goto _error; } else if(nfds == 0) { printf("time out\n"); continue; } if(handle_epoll_enents(epfd,ep_events,nfds,fd) < 0) { break ; } } _error: close(epfd); close(fd); } int handle_epoll_enents(int epfd,struct epoll_event *ep_events,int nfds,int fd) { char buf[BUFSIZ]; int ret = -1; int i; for(i=0;i<nfds;++i) { if(ep_events[i].data.fd == fd)//伺服器傳送過來了資料 { /* 讀取套接字資料,處理 */ bzero(buf,BUFSIZ); do { ret = read(fd,buf,BUFSIZ-1); }while(ret <0 && EINTR == errno); if(ret < 0) { perror("read from socket"); continue ; } if(ret == 0)//從套接字中讀到的資料個數小於0,說明伺服器關閉 { return -1 ; } printf("server said: %s",buf); if((strlen(buf) > strlen(SERV_RESP_STR)) && strncasecmp(buf+strlen(SERV_RESP_STR),QUIT_STR,strlen(QUIT_STR)) == 0) { printf("sender client is existing!\n"); break; } } else if(ep_events[i].events & EPOLLIN)//標準輸入裡面是不是有輸入 { /* 讀取鍵盤輸入,傳送到網路套接字fd */ bzero(buf,BUFSIZ); do { //ret = read(STDIN_FILENO,buf,BUFSIZ-1); ret = read(ep_events[i].data.fd,buf,BUFSIZ-1); }while(ret <0 && EINTR == errno); if(ret < 0) { perror("read"); continue ; } if(ret == 0)//沒讀到資料 { continue; } if(write(fd,buf,strlen(buf)) < 0) { perror("write() to socket"); continue ; } if(strncasecmp(buf,QUIT_STR,strlen(QUIT_STR)) == 0)//退出在傳送之後 { printf("client is existing!\n"); return -1; } } } return 0; }

1.3,epoll()—sever.c

#include "net.h"
#include "linklist.h"
#include <sys/ioctl.h>

#define EPFDSIZE 25//epoll處理的檔案描述符大小
#define EPOLLEVENTS 20 //epoll處理的時間個數

/* IO多路複用select()處理函式 */
void sever_do_epoll(int fd);
void sever_do_write(datatype sin_data);
int epoll_ctl_add(int epfd,int fd,linklist fdlist,struct sockaddr_in cin);

int main(int argc, const char *argv[])
{
	int fd;
	struct sockaddr_in sin;//如果是IPV6的程式設計,要使用struct sockddr_in6結構體(詳細情況請參考man 7 ipv6),通常更通用的方法可以通過struct sockaddr_storage來程式設計

	/* 1 建立socket fd */
	if((fd = socket(AF_INET,SOCK_STREAM,0)) < 0)
	{
		perror("socket");
		exit(-1);
	}
	/* 優化 1 允許繫結地址快速重用 */ 
	int b_reuse = 1;
	setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&b_reuse,sizeof(int)); 
	
	/* 2 繫結 */
	/* 2.1 填充struct sockaddr_in 結構體變數*/
	bzero(&sin,sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_port = htons(SERV_PORT);
#if 1
	/* 優化 2 讓伺服器可以繫結在任意的IP上*/
	sin.sin_addr.s_addr = htonl(INADDR_ANY);
#else
	if(inet_pton(AF_INET,SERV_IP_ADDR,(void *)&sin.sin_addr.s_addr) < 0)
	{
		perror("inet_pton");
		goto _error1;
	}
#endif
	/* 2.2 繫結*/
	if(bind(fd,(struct sockaddr *)&sin,sizeof(sin)))
	{
		perror("bind");
		goto _error1;
	}

	/* 3 使用listen()把主動套接字變成被動套接字 */
	if(listen(fd,BACKLOG) < 0)
	{
		perror("listen");
		goto _error1;
	}
	
	sever_do_epoll(fd);

_error1:
	close(fd);
	return 0;
}

void sever_do_epoll(int fd)
{
	linklist fdlist,sin_list;//建立一個列表,用於檔案描述符及客戶端資訊儲存
	fdlist = create_linklist();
	datatype sin_data;//每個物件包括客戶端的socket fd,ipv4地址,埠號
	sin_data.pfds.data.fd = fd;
	sin_data.pfds.events = EPOLLIN|EPOLLET;
	//struct timeval tout = {5,0};

	insert_end_linklist(fdlist,sin_data);//將lsten()處理後的fd加入列表
	//show_linklist(fdlist);
	
	int newfd = -1;
	int ret = -1;
	char buf[BUFSIZ];//BUFSIZ是系統提供的
	struct sockaddr_in cin;
	socklen_t cin_addr_len = sizeof(cin);

	int epfd;//epfd用於生成epoll專用的檔案描述符
	int nfds;//用於接受epoll_wait()返回的發生事件數
	struct epoll_event ep_ev,ep_events[EPOLLEVENTS];//申明epoll_event結構體變數,ep_ev用於註冊事件,ep_events用於回傳要處理的事件

	/*生成epoll的專用檔案描述符*/
	epfd = epoll_create(EPFDSIZE);

	/*新增連線描述符,及要處理的事件*/
	ep_ev.data.fd = fd;
	ep_ev.events = EPOLLIN|EPOLLET;
	epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ep_ev);
	/* 用epoll()函式實現I/O多路複用*/
	int i;
	while(1)
	{
		nfds = epoll_wait(epfd,ep_events,EPOLLEVENTS,-1);
		if(nfds == -1)
		{
			perror("epoll_wait");
			goto _error1;
		}
		else if(nfds == 0)
		{
			printf("time out!\n");
			goto _error1;
		}
		else
		{
			for(i=0;i<nfds;i++)	
			{
				if(ep_events[i].data.fd == fd)//有客戶端傳送了連線請求
				{
					if((newfd = accept(fd,(struct sockaddr *)&cin,&cin_addr_len)) < 0)
					{
						perror("connect");
						goto _error1;
					}
					/* 將分配成功的套接字newfd設定成非阻塞模式*/
					int b_on = 1;
					ioctl(newfd, FIONBIO, &b_on);//將分配成功的套接字newfd設定為非阻塞方式

					/* 新增新連線的客戶端檔案描述到epoll_event事件處理列表中
					 * 將新的客戶端資訊新增到連結串列fdlist中*/
					if(epoll_ctl_add(epfd,newfd,fdlist,cin) < 0)
					{
						goto _error2;
					}
				}
				else
				{
					if(ep_events[i].events & EPOLLIN)//有連線好的客戶端傳送了資料
					{
						sin_data.pfds.data.fd = ep_events[i].data.fd;	
						sin_list = get_list_locate_linklist(fdlist,sin_data);
						sin_data = sin_list->data;
						//printf("reading fd is ->(第 %d 個)(fd:%d)(ip:%s)(port:%d)\n",i,sin_data.pfds.fd,sin_data.ipv4_addr,sin_data.port);
						bzero(buf,BUFSIZ);
						do
						{
							ret = read(sin_data.pfds.data.fd,buf,BUFSIZ-1);
						}while(ret < 0 && errno == EINTR);//阻塞讀寫
						
						if(ret < 0)
						{
							perror("read");
							continue;
						}
						if(ret == 0)//對方已關閉
						{
							printf("client is existing!\n");
							delete_locate_linklist(fdlist,sin_data);//從連結串列中清除該客戶端資訊

							ep_ev.data.fd = ep_events[i].data.fd;
							epoll_ctl(epfd,EPOLL_CTL_DEL,ep_events[i].data.fd,&ep_ev);//將該客戶端檔案描述符從監聽列表中清除
							continue;
						}

						/* 更新讀入資料的客戶端資訊 */
						delete_locate_linklist(fdlist,sin_data);
						strncpy(sin_data.buf,buf,ret);
						insert_end_linklist(fdlist,sin_data);

						printf("client ip(:%s) port(:%d) fd(:%d) receive data: %s",sin_data.ipv4_addr,sin_data.port,sin_data.pfds.data.fd,sin_data.buf);

						/* 將輸入事件改為輸出事件*/
						ep_ev.data.fd = ep_events[i].data.fd;
						ep_ev.events = EPOLLOUT|EPOLLET;
						epoll_ctl(epfd,EPOLL_CTL_MOD,ep_events[i].data.fd,&ep_ev);

						//printf("client ip(:%s) port(:%d) fd(:%d) receive data: %s",sin_data.ipv4_addr,sin_data.port,sin_data.pfds.data.fd,buf);
					
						if(strncasecmp(buf,QUIT_STR,strlen(QUIT_STR)) == 0)
						{
							printf("client (fd:%d)(ip:%s)(potr:%d) is existing!\n",sin_data.pfds.data.fd,sin_data.ipv4_addr,sin_data.port);
							delete_locate_linklist(fdlist,sin_data);//將退出的客戶端的fd從列表中刪除

							ep_ev.data.fd = ep_events[i].data.fd;
							epoll_ctl(epfd,EPOLL_CTL_DEL,ep_events[i].data.fd,&ep_ev);//將該客戶端檔案描述符從監聽列表中清除
							close(sin_data.pfds.data.fd);
							//show_linklist(fdlist);
						}
					}
					if(ep_events[i].events & EPOLLOUT)//向客戶端傳送資料
					{
						sin_data.pfds.data.fd = ep_events[i].data.fd;	
						sin_list = get_list_locate_linklist(fdlist,sin_data);
						sin_data = sin_list->data;
						//printf("reading fd is ->(第 %d 個)(fd:%d)(ip:%s)(port:%d)\n",i,sin_data.pfds.fd,sin_data.ipv4_addr,sin_data.port);

						sever_do_write(sin_data);
						/* 將輸出事件改為輸入事件*/
						ep_ev.data.fd = ep_events[i].data.fd;
						ep_ev.events = EPOLLIN|EPOLLET;
						epoll_ctl(epfd,EPOLL_CTL_MOD,ep_events[i]