上一节《linux下I/O复用与epoll实际使用(一)》主要讲解了epoll的原理,这一节结合socket的编程,详解select与epoll编程示例。
一、socket编程
在TCP/IP协议中“IP地址+TCP或者UDP端口号”唯一标识网络通讯中的一个进程,"IP+端口号"就称为socket。在TCP协议中,建立连接的两个进程各自有一个socket来标识,name两个socket组成的socjet pair就唯一标识一个连接。
预备知识:
网络字节序:内存中多字节数据相对于内存地址有大端小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分。网络数据流同样有大端小端之分,所以发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出,接收主机把从网络上接收到的字节按内存从低到高的顺序保存,因此网络数据流的地址应该规定:**先发出的数据是低地址,后发出的数据是高地址。**TCP/IP协议规定网络数据流应该采用大端字节序,即低地址高字节。所以发送主机和接收主机是小段字节序的在发送和接收之前需要做字节序的转换。
为了使网络程序具有可移植性可以调用以下函数进行网络字节数的转换。
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
socket地址数据类型及相关函数
sockaddr数据结构
tcpsocket 实现
实现模型:
1.服务器端 socket -> bind -> listen -> accept(阻塞,三次握手)-> send。
2.客户端 socket -> connect(阻塞,三次握手)-> rcv。
上一节我们已经比较过select与epoll的优缺点。
现在直接上select和epoll的示例代码:
代码在(一)的基础上修改的
server端
#include<stdio.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<string.h>
#include<errno.h>
#include<signal.h>
#include<fcntl.h>
#include<stdbool.h>
#include<sys/epoll.h>
#define _SELECT
int gSetNonblocking(int fd)
{
int old_option=fcntl(fd,F_GETFL);
int new_option=old_option|O_NONBLOCK;
fcntl(fd,F_SETFL,new_option);
return new_option;
}
/*往 epoll 描述符添加套接字*/
void gAddfd(int epollfd,int fd,bool oneshoot)
{
struct epoll_event event;
event.data.fd=fd;
event.events=EPOLLIN;
int ret;
if(oneshoot)
{
event.events=event.events|EPOLLONESHOT;
}
ret=epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&event);
gSetNonblocking(fd);
}
void gDelfd(int epollfd,int fd,bool oneshoot)
{
struct epoll_event event;
event.data.fd=fd;
event.events=EPOLLIN;
int ret;
if(oneshoot)
{
event.events=event.events|EPOLLONESHOT;
}
ret=epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&event);
if(ret!=0)
{
if(errno==EEXIST)
{
epoll_ctl(epollfd,EPOLL_CTL_DEL,fd,&event);
}
}
}
void gCtrlfd(int epollfd,int fd,bool oneshoot)
{
struct epoll_event event;
event.data.fd=fd;
event.events=EPOLLIN;
int ret;
if(oneshoot)
{
event.events=event.events|EPOLLONESHOT;
}
ret=epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&event);
if(ret!=0)
{
if(errno==EEXIST)
{
epoll_ctl(epollfd,EPOLL_CTL_MOD,fd,&event);
}
}
}
int main(int argc,char *argv[])
{
int sock=socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
write(STDERR_FILENO,"socket error",14);
struct sockaddr_in addr;
memset(&addr,0,sizeof(addr));
addr.sin_family=AF_INET;
addr.sin_port=htons(atoi(argv[1]));
addr.sin_addr.s_addr=INADDR_ANY;
bind(sock,(struct sockaddr *)&addr,sizeof(addr));
listen(sock,32767);
signal(SIGPIPE,SIG_IGN);
// signal(SIGINT,sig);
#ifdef _SELECT
int maxfd=0,i=0;
int array[4096];
int ret;
fd_set rfds;
fd_set wfds;
array[0]=sock;
int array_size=sizeof(array)/sizeof(array[0]);
for(i=1;i<array_size;i++)
{
array[i]=-1;
}
#else
int epollfd;
epollfd=epoll_create(20);
gAddfd(epollfd,sock,false);
int connfd[20]={-1};
int number;
int i,j;
int sockfd;
int count=0;
struct epoll_event event[512];
#endif
while(1)
{
#ifdef _SELECT
FD_ZERO(&rfds);
FD_ZERO(&wfds);
for(i=0;i<array_size;i++)
{
if(array[i]>0)
{
FD_SET(array[i],&rfds);
FD_SET(array[i],&wfds);
if(array[i]>maxfd)
{
maxfd=array[i];
}
}
}
ret=select(maxfd+1,&rfds,&wfds,NULL,NULL);
if(ret>0)
{
int j;
for(j=0;j<array_size;j++)
{
if(j==0 && FD_ISSET(array[j],&rfds))
{
struct sockaddr_in client;
socklen_t len=sizeof(client);
int new_sock=accept(sock,(struct sockaddr *)&client,&len);
if(new_sock<0)
{
perror("accept");
continue;
}
else
{
printf("get a new client %s\n",inet_ntoa(client.sin_addr));
fflush(stdout);
int k=1;
for(;k<array_size;++k)
{
if(array[k]<0)
{
array[k]=new_sock;
if(new_sock > maxfd)
maxfd=new_sock;
break;
}
}
if(k==array_size)
{
close(new_sock);
}
}
}else if(j!=0 && FD_ISSET(array[j],&rfds))
{
char buf[1024];
ssize_t s=read(array[j],buf,sizeof(buf)-1);
if(s>0)
{
buf[s]='\0';
printf("clientsay#%s",buf);
if(FD_ISSET(array[j],&wfds))
{
char *msg="HTTP/1.0 200 OK <\r\n\r\n<html><h1>welecom to select world!!!</h1></html>\r\n";
write(array[j],msg,strlen(msg));
}
}
else if(0==s)
{
printf("client quit\n");
close(array[j]);
array[j]=-1;
}
else
{
perror("read");
close(array[j]);
array[j]=-1;
}
}
}
}
#else
number=epoll_wait(epollfd,event,512,-1);
if(number < 0 && errno !=EINTR)
{
printf("epoll failure\n");
break;
}
for(i=0;i<number;i++)
{
sockfd=event[i].data.fd;
printf("sockfd=%d,sock=%d,connfd=%d",sockfd,sock,connfd[count]);
if(sockfd==sock && (event[i].events & EPOLLIN))
{
struct sockaddr_in cliaddr;
socklen_t clilen=sizeof(struct sockaddr_in);
if(connfd[count]==-1)
{
connfd[count]=accept(sock,(struct sockaddr *)&cliaddr,&clilen);
}else
{
for(j=0;j<20;j++)
{
if(connfd[j]==-1)
{
count=j;
}
}
connfd[count]=accept(sock,(struct sockaddr *)&cliaddr,&clilen);
}
if(connfd[count]<0)
{
printf("errno is -> %d:%s\n",errno,strerror(errno));
continue;
}
/*设置连接套接字EPOLLONESHOT*/
gAddfd(epollfd,connfd[count],false);
count++;
if(count>19)
{
for(j=0;j<count;j++)
{
if(connfd[j]==-1)
{
count=j;
}
}
/*reserve*/
}
printf("client connect\n");
}else
{
for(j=0;j<=count;j++)
{
if(sockfd==connfd[j] && (event[i].events & EPOLLIN))
{
printf("Start sleep(10) ...\n");
sleep(10);
char text[512];
int ret=recv(connfd[j],text,512,0);
while(recv>0)
{
if(ret>0)
{
text[ret]='\0';
printf("Recv(%d):%s\n",ret,text);
}
else if(ret==0)
{
printf("Client close socket\n");
gDelfd(epollfd,connfd[j],false);
close(connfd[j]);
connfd[j]=-1;
break;
}
else if(errno == EWOULDBLOCK)
{
printf("wouldblock\n");
break;
}
else if(errno == EPIPE)
{
printf("Broken pipe\n");
break;
}
ret=recv(connfd[j],text,512,0);
}
}
}
}
}
#endif
}
}
client端
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdio.h>
#include <sys/un.h>
#include <string.h>
#include <arpa/inet.h>
#include <errno.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/epoll.h>
#include <fcntl.h>
#define path "tempfile.socket"
int sock;
void sig(int sig)
{
char buf[512];
memset(buf, 0, sizeof(buf));
sprintf(buf, "close client %d",sock);
write(sock, buf, strlen(buf));
// sleep(3);
// exit(-1);
close(sock);
}
int main(int argc, char *argv[])
{
if (argc != 2)
{
printf("Usage:./exec [port]\n");
exit(-1);
}
signal(SIGINT, sig);
sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(argv[1]));
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
connect(sock, (struct sockaddr *)&addr, sizeof(addr));
int old = fcntl(sock, F_GETFL);
int newoption = old | O_NONBLOCK;
fcntl(sock, F_SETFL, newoption);
char buf[512];
memset(buf, 0, sizeof(buf));
int epollfd = epoll_create(5);
struct epoll_event event[128];
struct epoll_event e1,e2;
e1.data.fd = sock;
e1.events = EPOLLIN | EPOLLET;
epoll_ctl(epollfd, EPOLL_CTL_ADD, sock, &e1);
e2.data.fd = STDIN_FILENO;
e2.events = EPOLLIN | EPOLLET;
epoll_ctl(epollfd, EPOLL_CTL_ADD, STDIN_FILENO, &e2);
int nR;
int ret;
int i;
while (1)
{
int number = epoll_wait(epollfd, event, 128, -1);
if (number <= 0)
{
printf("epoll_wait error\n");
return 0;
}
for(i = 0; i < number; ++i)
{
if (event[i].data.fd == sock && (event[i].events & EPOLLIN))
{
memset(buf, 0, sizeof(buf));
nR = read(sock, buf, 512);
if (nR == 0)
{
close(sock);
break;
}
write(STDOUT_FILENO, buf, nR);
}
else if (event[i].data.fd == STDIN_FILENO && (event[i].events & EPOLLIN))
{
printf("please input string:");
fflush(stdout);
memset(buf, 0, sizeof(buf));
nR = read(STDIN_FILENO, buf, 512);
if (nR <= 0)
{
printf("errno[%d]:%s\n", errno, strerror(errno));
}
ret = write(sock, buf, nR);
if (ret == 0 && errno == EINTR)
{
printf("write sock error\n");
exit(-1);
}
else if (ret < 0)
{
printf("write sock ret < 0\n");
exit(-1);
}
printf("Send [%d]byte\n", ret);
}
}
}
close(sock);
return 0;
}
执行结果:
server
client