【udp并发实现】

udp通信过程

一、客户端

​1. 初始化本地sockaddr_in,初始化对端sockaddr_in
struct sockaddr_in
{
​ addr:
​ port:
}

  1. 创建本地的 套接字
fd = socket(PF_INET, SOCK_DGRAM, 0)
 // 注意这里 的二个参数是SOCK_DGRAM  如果是TCP套接字的话  第二个参数是SOCK_STREAM 

这里可以设置端口和地址复用(多个套接字绑定到同一个地址和端口

if(setsockopt(socketFd, SOL_SOCKET, SO_REUSEADDR, &reuse,sizeof(reuse))){
        exit(1);
}
if(setsockopt(socketFd, SOL_SOCKET, SO_REUSEPORT, &reuse, sizeof(reuse))){
    exit(1);
}

在服务器重启时,快速重新绑定到同一个端口,而无需等待 TCP 的 TIME_WAIT 状态。

服务器端允许多个进程或线程绑定到同一个 UDP 端口,从而实现负载均衡。

绑定 本地地址和端口:

bind(socketFd, (struct sockaddr *) &self_Addr, sizeof(struct sockaddr))

连接 :绑定创建本地套接字 和 对端地址和端口 ,可以不绑定但是第一次发送时会自己选择一个port

connect(socketFd, (struct sockaddr *) &peer_Addr, sizeof(struct sockaddr)) 

connect之后可以调用send()直接发送,

不然的话调用sendto发送 sendto参数中



#include <unistd.h>
#include <string.h>

#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>


#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <sys/time.h>
#include <sys/resource.h>

void createClient(int id,int myPort,int peerPort){
  
    int reuse = 1;
      
    int socketFd;
    struct sockaddr_in peer_Addr;
    peer_Addr.sin_family = PF_INET;
    peer_Addr.sin_port = htons(peerPort);
    peer_Addr.sin_addr.s_addr = inet_addr("192.168.1.136");

    struct sockaddr_in self_Addr;
    self_Addr.sin_family = PF_INET;
    self_Addr.sin_port = htons(myPort);
    self_Addr.sin_addr.s_addr = inet_addr("0.0.0.0"); 
    
    if ((socketFd = socket(PF_INET, SOCK_DGRAM| SOCK_CLOEXEC, 0)) == -1) {
        perror("child socket");
        exit(1);
    } 

    int opt=fcntl(socketFd,F_GETFL);
    fcntl(socketFd,F_SETFL,opt|O_NONBLOCK);

    if(setsockopt(socketFd, SOL_SOCKET, SO_REUSEADDR, &reuse,sizeof(reuse))){
            exit(1);
    }
    if(setsockopt(socketFd, SOL_SOCKET, SO_REUSEPORT, &reuse, sizeof(reuse))){
        exit(1);
    }
    
    if (bind(socketFd, (struct sockaddr *) &self_Addr, sizeof(struct sockaddr))){
        perror("chid bind");
        exit(1);
    } else {

    }
   
    if (connect(socketFd, (struct sockaddr *) &peer_Addr, sizeof(struct sockaddr)) == -1) {
        perror("chid connect");
        exit(1);
    }
   
    
    usleep(1); // --> key

    char buffer[1024] = {0};
    memset(buffer, 0, 1024);
    sprintf(buffer, "hello %d", id);
    sendto(socketFd, buffer, strlen(buffer), 0, (struct sockaddr *) &peer_Addr, sizeof(struct sockaddr_in));

}

void serial(int clinetNum){
    for(int i=1;i<=clinetNum;i++){
        createClient(i,2025+i,1234);
    }
}
int main(int argc, char * argv[])
{

	serial(1000);
	
    printf("serial success\n");
    return 0;
}

二、服务端

1、创建本地套接字listenfd

2、bind本地addr

3、创建epoll对象,将 listenfd 加入该epoll对象中

4、通过epoll_wait() 检测事件,如果第一次发消息,该检测到事件的fd一定是listenfd
5、接收处理,然后服务端获取客户端的连接的ip 和port ,

重新建立一个新连接,设置端口复用,通过connect将新的socket和客户端地址端口绑定(这样下次该客户端发送消息时,内核会将事件与该fd绑定在一起)

6、然后将该socket加入epoll实例中,通过epoll_wait()来检测读写事件

当该客户端再次发送消息时,会检测到是刚刚新建的fd触发的事件,根据该fd进行解析

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <pthread.h>
#include <assert.h>

#define SO_REUSEPORT    15

#define MAXBUF 10240
#define MAXEPOLLSIZE 100

int flag = 0;
int count = 0;

int read_data(int sd)
{
    char recvbuf[MAXBUF + 1];
    int  ret;
    struct sockaddr_in client_addr;
    socklen_t cli_len=sizeof(client_addr);

    bzero(recvbuf, MAXBUF + 1);
  
    ret = recvfrom(sd, recvbuf, MAXBUF, 0, (struct sockaddr *)&client_addr, &cli_len);
    if (ret > 0) {
        printf("read[%d]: %s  from  %d\n", ret, recvbuf, sd);
    } else {
        printf("read err:%s  %d\n", strerror(errno), ret);
      
    }
    //fflush(stdout);
}




int udp_accept(int sd, struct sockaddr_in my_addr)
{
    int new_sd = -1;
    int ret = 0;
    int reuse = 1;
    char buf[16];
    struct sockaddr_in peer_addr;
    socklen_t cli_len = sizeof(peer_addr);

    ret = recvfrom(sd, buf, 16, 0, (struct sockaddr *)&peer_addr, &cli_len);
    if (ret < 0) {
		return -1;
    }
//    printf("ret: %d, buf: %s\n", ret, buf);

    if ((new_sd = socket(PF_INET, SOCK_DGRAM, 0)) == -1) {
        perror("child socket");
        exit(1);
    } else {
        printf("%d, parent:%d  new:%d\n",count++, sd, new_sd); //1023
    }

    ret = setsockopt(new_sd, SOL_SOCKET, SO_REUSEADDR, &reuse,sizeof(reuse));
    if (ret) {
        exit(1);
    }

    ret = setsockopt(new_sd, SOL_SOCKET, SO_REUSEPORT, &reuse, sizeof(reuse));
    if (ret) {
        exit(1);
    }

	//my_addr.sin_port += count;
    ret = bind(new_sd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr));
    if (ret){
        perror("chid bind");
        exit(1);
    } else {
    }

    peer_addr.sin_family = PF_INET;
    //printf("aaa:%s\n", inet_ntoa(peer_addr.sin_addr));
    if (connect(new_sd, (struct sockaddr *) &peer_addr, sizeof(struct sockaddr)) == -1) {
        perror("chid connect");
        exit(1);
    } else {
    }

out:
    return new_sd;
}

int main(int argc, char **argv)
{
    int listener, kdpfd, nfds, n, curfds;
    socklen_t len;
    struct sockaddr_in my_addr, their_addr;
    unsigned int port;
    struct epoll_event ev;
    struct epoll_event events[MAXEPOLLSIZE];
    int opt = 1;;
    int ret = 0;

    port = 1234;
    if ((listener = socket(PF_INET, SOCK_DGRAM, 0)) == -1) {
        perror("socket");
        exit(1);
    } else {
        printf("socket OK\n");
    }

    ret = setsockopt(listener,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
    if (ret) {
        exit(1);
    }

    ret = setsockopt(listener, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
    if (ret) {
        exit(1);
    }

	int flags = fcntl(listener, F_GETFL, 0);
	flags |= O_NONBLOCK;
	fcntl(listener, F_SETFL, flags);
  
    bzero(&my_addr, sizeof(my_addr));
    my_addr.sin_family = PF_INET;
    my_addr.sin_port = htons(port);
    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 bind OK\n");
    }
	
    kdpfd = epoll_create(MAXEPOLLSIZE);

    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=%dn", listener);
        return -1;
    } else {
        printf("ep add OK\n");
    }
 
    while (1) {
      
        nfds = epoll_wait(kdpfd, events, MAXEPOLLSIZE, -1);
        if (nfds == -1) {
            perror("epoll_wait");
            break;
        }
      
        for (n = 0; n < nfds; ++n) {
            if (events[n].data.fd == listener) {
                
                int new_sd;               
                struct epoll_event child_ev;

				while (1) {
	                new_sd = udp_accept(listener, my_addr);
					if (new_sd == -1) break;
					
	                child_ev.events = EPOLLIN;
	                child_ev.data.fd = new_sd;
	                if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, new_sd, &child_ev) < 0) {
	                    fprintf(stderr, "epoll set insertion error: fd=%dn", new_sd);
	                    return -1;
	                }

				}
            } else {
                read_data(events[n].data.fd);
            }
        }
    }
    close(listener);
    return 0;
}


三、整个流程梳理:

1、客户端发送消息

​ 客户端通过sendto向服务端listenfd发送消息

​ 服务端的listenfd通过epoll_wait()检测到可读事件,触发udp_accept函数

2、服务端创建新的socket

​ udp_accept函数的核心逻辑:

​ 1、接收客户端地址:通过recvfrom获取客户端ip和port

​ 2、创建新的socket:生成一个新的udp socket;

​ 3、绑定相同端口: 新的udp socket绑定到相同的服务端地址(ip + port),依赖reuse实现绑定同一个端口

​ 4、将新的socket和客户端地址绑定 connect(newfd, &peer_port);

​ 5、将新的socket添加到epoll实例,后续通过它来接受消息

3、后续通信流程

​ 客户端继续发送消息:数据包的目标端口号仍未1234

​ 内核路由规则:由于新的socket以通过connect绑定到客户端地址,于是内核会将该客户端的后续数据包自动路由到新的socket;

​ 主socket(listenfd)保持监听: 主socket继续接收其他客户端的首次消息,重复上述流程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值