嵌入式Linux網路程式設計,I/O多路複用,epoll()示例,epoll()客戶端,epoll()伺服器,單鏈表
阿新 • • 發佈:2018-12-14
文章目錄
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]