Udp 並發問題分析與總結


一、 tcp並發與udp並發的區別
       無論是epoll還是select,在觀察有無數據就緒時,都是針對多個文件描述符。如果只有一個文件描述符,那么進程只要觀察那一個文件描述符即可。在網絡編程中,一個Socket對應一個文件描述符。Tcp協議的server在監聽端口前初始化一個socket,每有一個新的連接,就新建一個socket。因此當tcp服務器面對高並發請求時,實際上有多個socket,也就是有多個文件描述符。Udp協議的Server沒有真正意義上的“連接”的概念,在監聽端口和響應請求時都只有一個socket,也就只有一個文件描述符。
二、udp並發的常規思路
       大部分udp服務器是順序迭代的,服務器等待客戶端請求,然后讀取請求,處理請求,發回響應。但是,當處理客戶端請求需要很長時間,就需要考慮某種形式的並發。一個“長處理”可以理解為處理請求的時間明顯大於發送請求的時間。
        並發的常見思路是使用多線程。服務器在讀取一個新請求之后,可以交由一個線程處理,該線程在處理之后直接將響應內容發給客戶端。另一方面,udp服務器和多個客戶端交互,但是卻沒有多個socket。典型的解決方案是,服務器為每個客戶端創建一個新的socket,並綁定一個新的端口。客戶端以后就通過這個新的socket與服務器通信,獲得響應。總結來說,udp並發服務器,針對多個客戶端,可以創建多個socket;針對多個請求,可以使用多線程(線程池)進行處理。
三、編程模型
1.多個socket
  1.  
    for ( ; ; )
  2.  
    {
  3.  
    /* 等待新的客戶端連接 */
  4.  
    recvform( &from_addr)
  5.  
    /* 創建一個新的進程,由該進程去處理 (線程也可以)*/
  6.  
    if (fork() == 0)
  7.  
    break; //子進程跳出循環
  8.  
    }
  9.  
    //child now here
  10.  
    peer = socket(AF_INET, SOCK_DGRAM, 0);
  11.  
    //綁定一個隨機端口
  12.  
    myaddr.sin_port = htons( 0);
  13.  
    bind(peer,(struct sockaddr *)&myaddr, sizeof(myaddr));
  14.  
    /*
  15.  
    把這個套接字跟客戶端的地址連接起來
  16.  
    這也就意味之后之后套接字使用 send recv這些函數時
  17.  
    都是直接跟指定的客戶端進行通信的
  18.  
    */
  19.  
    connect(peer, (struct sockaddr *)&from, sizeof from)
  20.  
    /*處理請求*/

2.使用epoll進行處理
/*
1、UDP srv創建UDP socket(listen_fd), 設置socket為REUSEASSR,REUSEPORT和非阻塞
   同時bind本地地址local_addr
*/
listen_fd = socket(PF_INET, SOCK_DGRAM, 0);
fcntl(listen_fd, F_SETFL, fcntl(listen_fd, F_GETFD, 0)|O_NONBLOCK)
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
bind(listen_fd, (struct sockaddr *)&local_addr, sizeof(struct sockaddr));
/*
2、創建epoll fd, 並將listen_fd添加到epoll中,兵監聽其可讀事件
*/
epoll_fd = epoll_create(100);
ep_event.events = EPOLLIN |EPOLLET;
ep_event.data.fd = listen_fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &ep_event);
while (1) 
{
    in_fds = epoll_wait(epoll_fd, in_events, 1000, 5000);
/*
3、epoll_wait返回時,如果返回的是listen_fd, 調用recvfrom接受client
第一個UDP包,並根據recvfrom返回client地址,創建一個新的socket(new_fd)
設置new_fd為REUSEADDR,REUSEPORT和非阻塞,同時bind本地地址local_addr
然后connect上recvfrom返回的client地址
*/
for (i = 0; i < in_fds; i++)
{
if(in_events[i].data.fd = listen_fd)
{
recvfrom(listen_fd, buf, sizeof(buf), 0, (struct sockaddr *)&client_addr, &client_len);
new_fd = socket(PF_INET, SOCK_DGRAM, 0);
fcntl(new_fd, F_SETFL, fcntl(new_fd, F_GETFD, 0)|O_NONBLOCK);
setsockopt(new_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
setsockopt(new_fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
bind(new_fd, (struct sockaddr *)&local_addr, sizeof(struct sockaddr));
connect(new_fd, (struct sockaddr *)&client_addr, sizeof(struct sockaddr));
/*
4、將新創建的new_fd加入到epoll中並監聽其可讀事件
*/
client_ev.events = EPOLLIN;
client_ev.data.fd = new_fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, new_fd, &client_addr);
}
else if (in_events[i].events & EPOLLIN)
{
/*
5、當epoll_wait返回時,如果返回的是new_fd, 那么調用recvfrom來接收特定client的UDP包
*/
recvfrom(new_fd, recvbuf, sizeof(recvbuf), 0, (struct sockaddr *)&client_addr, &client_len);
data->fd = new_fd;
data-> ptr= process(recvbuf); /*data中包括socket信息*/
ev.data.ptr = data;
ev.events = EPOLLOUT | EPOLLET;
epoll_ctl(epoll_fd,EPOLL_CTL_MOD,new_fd,&ev);
}
else if (in_events[i].events & EPOLLOUT)
{
sockfd = data->fd;
send( sockfd, data->ptr, strlen((char*)data->ptr), 0 );
ev.data.ptr = data;
ev.events = EPOLLIN | EPOLLET;
epoll_ctl(epoll_fd,EPOLL_CTL_MOD,sockfd,&ev);
}
else
{
 
}
}
}  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM