网上有一些人关注UDP下的epoll并发框架,但是能够搜到内容一般就是服务器端将采用bind将服务器地址同文件描述符绑定,之后将文件描述符注册到epoll中,就是说服务器同所有的客户端的通信仅使用这一个文件描述符,个人感觉似乎并没有发挥出epoll的性能。
于是今天仿照kcpev中的udp中epoll的使用方式写了一个udp的echoserver。每当有新的客户端端发出请求的时候,客户端都新创建一个文件描述符,这样的使用方式,就和TCP中epoll一样了。
就是说,新的连接出现时,采用connect函数将客户端的地址同fd进行绑定,或者说新创建的fd通过bind函数以及connect函数实现了一个类似于TCP中的accept的功能。需要注意的是,在udp中的connect函数并没有发起syn数据包,这在UDP中当然是不会出现的。
刚开始的时候,我觉得新创建的fd没有必要采用bind函数将服务器的IP:PORT同新创建的fd进行绑定。但是发现客户端程序发送的消息的时候,仍是采用之前的绑定的fd。我个人猜测,一个fd中包含的属性应该有远端与目的端的地址(IP:PORT),对于新的连接,消息就只能走“监听”的fd。根据【1】中的资料,在UDP协议栈,内核中采用udp4_lib_lookup_skb对socket进行查找。
另本人实现了一个简单的协议。消息的第一个字节表示消息类型,0表示客户端请求连接,1表示服务器允许连接,2表示消息,3表示断开连接。
server端代码如下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define ECHO_LEN 1025
#define MAXBUF 1024
#define MAXEPOLLSIZE 100
#define NI_MAXHOST 1025
#define NI_MAXSERV 32
static unsigned int myport=1234;
/*
setnonblocking – 设置句柄为非阻塞方式
*/
int setnonblocking(int sockfd)
{
if (fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)|O_NONBLOCK) == -1)
{
return -1;
}
return 0;
}
static void add_event(int epollfd,int fd,int state)
{
struct epoll_event ev;
ev.events = state;
ev.data.fd = fd;
epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&ev);
}
static void delete_event(int epollfd,int fd,int state)
{
struct epoll_event ev;
ev.events = state;
ev.data.fd = fd;
epoll_ctl(epollfd,EPOLL_CTL_DEL,fd,&ev);
}
static void do_write(int epollfd,int fd,char *buf)
{
int nwrite;
nwrite = write(fd,buf,strlen(buf));
if (nwrite == -1)
{
perror("write error:");
close(fd);
delete_event(epollfd,fd,EPOLLOUT);
}
}
int udp_socket_connect(int epollfd,struct sockaddr_in *servaddr)
{
struct sockaddr_in my_addr, their_addr;
int fd=socket(PF_INET, SOCK_DGRAM, 0);
/*设置socket属性,端口可以重用*/
int opt=SO_REUSEADDR;
setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
setnonblocking(fd);
bzero(&my_addr, sizeof(my_addr));
my_addr.sin_family = PF_INET;
my_addr.sin_port = htons(myport);
my_addr.sin_addr.s_addr = INADDR_ANY;
if (bind(fd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr)) == -1)
{
perror("bind");
exit(1);
}
else
{
printf("IP and port bind success \n");
}
if(fd==-1)
return -1;
connect(fd,(struct sockaddr*)servaddr,sizeof(struct sockaddr_in));
add_event(epollfd,fd,EPOLLIN);
return fd;
}
void accept_client(int epollfd,int fd)
{
struct sockaddr_storage client_addr;
socklen_t addr_size = sizeof(client_addr);
char buf[1024];
int ret = recvfrom(fd, buf,1024, 0, (struct sockaddr *)&client_addr, &addr_size);
//check(ret > 0, "recvfrom error");
buf[ret] = '\0';
char type=buf[0];
char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
ret = getnameinfo((struct sockaddr *)&client_addr, addr_size, hbuf, sizeof(hbuf), \
sbuf, sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV);
//check(ret == 0, "getnameinfo");
printf("recvfrom client [%s:%s] : %d\n", hbuf, sbuf, buf[0]);
if(type!=0)
{
return;
}
int new_sock=udp_socket_connect(epollfd,(struct sockaddr_in*)&client_addr);
buf[0]=1;
do_write(epollfd,new_sock,buf);
}
void msg_process(int epollfd,int fd)
{
int nread=0;
char buf[MAXBUF];
char type;
nread = read(fd,buf,MAXBUF);
//check(nread > 0, "recvfrom error");
buf[nread] = '\0';
type=buf[0];
if(type==2)
{
printf("recv msg %s \n",buf+1);
do_write(epollfd,fd,buf);
}
else{
delete_event(epollfd,fd,EPOLLOUT);
}
}
int main(int argc, char **argv)
{
int listener,kdpfd, nfds, n, curfds;
socklen_t len;
struct sockaddr_in my_addr, their_addr;
struct epoll_event ev;
struct epoll_event events[MAXEPOLLSIZE];
/* 开启 socket 监听 */
if ((listener = socket(PF_INET, SOCK_DGRAM, 0)) == -1)
{
perror("socket create failed !");
exit(1);
}
else
{
printf("socket create success /n");
}
/*设置socket属性,端口可以重用*/
int opt=SO_REUSEADDR;
setsockopt(listener,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
setnonblocking(listener);
bzero(&my_addr, sizeof(my_addr));
my_addr.sin_family = PF_INET;
my_addr.sin_port = htons(myport);
my_addr.sin_addr.s_addr = INADDR_ANY;
if (bind(listener, (struct sockaddr *) &my_addr, sizeof(struct sockaddr)) == -1)
{
perror("bind");
exit(1);
}
else
{
printf("IP and port bind success \n");
}
/* 创建 epoll 句柄,把监听 socket 加入到 epoll 集合里 */
kdpfd = epoll_create(MAXEPOLLSIZE);
len = sizeof(struct sockaddr_in);
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = listener;
if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, listener, &ev) < 0)
{
fprintf(stderr, "epoll set insertion error: fd=%d/n", listener);
return -1;
}
else
{
printf("listen socket added in epoll success /n");
}
while (1)
{
/* 等待有事件发生 */
nfds = epoll_wait(kdpfd, events, 10000, -1);
if (nfds == -1)
{
perror("epoll_wait");
break;
}
/* 处理所有事件 */
for (n = 0; n < nfds; ++n)
{
if (events[n].data.fd == listener)
{
accept_client(kdpfd,listener);
}
else
{
msg_process(kdpfd,events[n].data.fd);
}
}
}
close(listener);
return 0;
}
客户端代码如下:
/*
* =====================================================================================
*
* Filename: client.c
*
* Description: client to link the server
*
* Version: 1.0
* Created: 2013/01/11 10时13分18秒
* Revision: none
* Compiler: gcc
*
* Author: YOUR NAME (),
* Company:
*
* =====================================================================================
*/
#include
#include
#include
#include
#include
#include
#include
#define MAXBUF 100
int main(int argc, const char *argv[])
{
char *server = "127.0.0.1";
char *serverport = "1234";
char *echostring = "helloworld";
struct addrinfo client,*servinfo;
memset(&client,0,sizeof(client));
client.ai_family = AF_INET;
client.ai_socktype = SOCK_DGRAM;
client.ai_protocol= IPPROTO_UDP;
if(getaddrinfo(server,serverport,&client,&servinfo)<0)
{
printf("error in getaddrinfo");
exit(-1);
}
int sockfd = socket(servinfo->ai_family,servinfo->ai_socktype,servinfo->ai_protocol);
if(sockfd <0)
{
printf("error in socket create");
exit(-1);
}
char bufmsg[2];
bufmsg[0]=0;
bufmsg[1]='\0';
ssize_t numBytes = sendto(sockfd,bufmsg,strlen(bufmsg),0,servinfo->ai_addr,servinfo->ai_addrlen);
if(numBytes<0)
{
printf("error in send the data");
}
struct sockaddr_storage fromaddr;
socklen_t fromaddrlen = sizeof(fromaddr);
char buf[MAXBUF+1];
numBytes = recvfrom(sockfd,buf,MAXBUF+1,0,(struct sockaddr *)&fromaddr,&fromaddrlen);
if(buf[0]==1)
{
printf("connected to the server\n");
}
char echomsg[1024];
echomsg[0]=2;
char *ptr=echomsg+1;
strcpy(ptr,echostring);
numBytes=0;
numBytes = sendto(sockfd,echomsg,strlen(echomsg),0,servinfo->ai_addr,servinfo->ai_addrlen);
if(numBytes<0)
{
printf("error in send the data");
}
numBytes=0;
numBytes = recvfrom(sockfd,buf,MAXBUF+1,0,(struct sockaddr *)&fromaddr,&fromaddrlen);
if(buf[0]==2)
{
char *str=buf+1;
printf("recv msg from server %s\n",str);
}
numBytes=0;
buf[0]=3;
numBytes = sendto(sockfd,buf,2,0,servinfo->ai_addr,servinfo->ai_addrlen);
if(numBytes<0)
{
printf("error in send the data");
}
freeaddrinfo(servinfo);
printf("Received:%s \n",buf);
close(sockfd);
return 0;
}
参考【1】http://www.procedurego.com/article/23607.html
【2】linux网络协议栈-UDP
【3】UDP的epoll并发框架-UDPListener解决OpenVPN的并发问题