相关文章推荐
曾经爱过的南瓜  ·  Unable to open socket ...·  1 月前    · 
强悍的书签  ·  離線安裝Zulu SDK - iT ...·  1 月前    · 
耍酷的猴子  ·  ConcurrentQueue<t> 类 ...·  2 年前    · 
个性的拐杖  ·  arrch64 ...·  2 年前    · 
道上混的稀饭  ·  Mysqldump ...·  2 年前    · 
首发于 C++和我

Linux——UDP epoll实现服务端和客户端

1.基于UDP epoll的服务端和客户端的实现

注:本篇文章中,客户端(Client)和服务端(Server)均为UDP协议下的实现。

什么是epoll? epoll是Linux内核提供的一种高效的I/O多路复用机制,可以监控多个文件描述符的状态,读写数据时非常高效,比select和poll性能更好,在高并发时表现尤其突出。

2. 实现流程

服务端

- 创建socket

- 绑定socket到指定端口

- 创建epoll文件描述符

- 将socket添加到epoll监控列表

- 创建一个数组,用于存储接收到的数据

- 不断循环,等待事件发生

- 如果有事件发生,判断事件类型并相应处理

- 如果是新连接,则将新连接socket添加到epoll监控列表

- 如果是已连接socket可读,则接收数据并存储到数组中

- 如果是已连接socket可写,则将存储的数据发送出去

客户端

- 创建socket

- 绑定socket到指定端口

- 创建epoll文件描述符

- 将socket添加到epoll监控列表

- 不断循环,等待事件发生

- 如果有事件发生,判断事件类型并相应处理

- 如果是socket可写,则将数据发送出去

- 如果是socket可读,则接收数据

3. 代码实现

服务端:
//g++ epoll_server_udp.cpp -o epoll_server_udp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <fcntl.h>
#define MAX_EVENTS 10
#define MAX_BUFFER_SIZE 1024
int set_nonblocking(int sockfd) {
    int flags = fcntl(sockfd, F_GETFL, 0);
    if (flags == -1) {
        perror("fcntl(F_GETFL) failed");
        return -1;
    flags |= O_NONBLOCK;
    if (fcntl(sockfd, F_SETFL, flags) == -1) {
        perror("fcntl(F_SETFL) failed");
        return -1;
    return 0;
int main(int argc, char *argv[]) {
    if (argc != 2) {
        printf("usage: %s port", argv[0]);
        return -1;
    int port = atoi(argv[1]);
    if (port <= 0 || port > 65535) {
        printf("invalid port number");
        return -1;
    int server_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (server_sockfd == -1) {
        perror("socket() failed");
        return -1;
    sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    if (bind(server_sockfd, (sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror("bind() failed");
        return -1;
    int epollfd = epoll_create1(0);




    

    if (epollfd == -1) {
        perror("epoll_create1() failed");
        return -1;
    epoll_event event, events[MAX_EVENTS];
    memset(&event, 0, sizeof(event));
    event.events = EPOLLIN;
    event.data.fd = server_sockfd;
    if (epoll_ctl(epollfd, EPOLL_CTL_ADD, server_sockfd, &event) == -1) {
        perror("epoll_ctl(EPOLL_CTL_ADD) failed");
        return -1;
    char buffer[MAX_BUFFER_SIZE];
    memset(buffer, 0, sizeof(buffer));
    sockaddr_in client_addr;
    socklen_t client_addr_len;
    while (true) {
        int nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
        if (nfds == -1) {
            if (errno == EINTR) {
                continue;
            perror("epoll_wait() failed");
            break;
        for (int i = 0; i < nfds; i++) {
            int sockfd = events[i].data.fd;
            if ((sockfd == server_sockfd) && (events[i].events & EPOLLIN)) {
                client_addr_len = sizeof(client_addr);
                ssize_t len = recvfrom(sockfd, buffer, MAX_BUFFER_SIZE, 0, (sockaddr *)&client_addr, &client_addr_len);
                if (len == -1) {
                    perror("recvfrom() failed");
                    continue;
                printf("Received %zd bytes from %s:%d", len, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
                printf("Content: %s", buffer);
                memset(buffer, 0, sizeof(buffer));
    close(epollfd);
    close(server_sockfd);
    return 0;
//g++ epoll_client_udp.cpp -o epoll_client_udp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <fcntl.h>
#define MAX_EVENTS 10
#define MAX_BUFFER_SIZE 1024
int set_nonblocking(int sockfd) {
    int flags = fcntl(sockfd, F_GETFL, 0);
    if (flags == -1) {
        perror("fcntl(F_GETFL) failed");
        return -1;
    flags |= O_NONBLOCK;
    if (fcntl(sockfd, F_SETFL, flags) == -1) {
        perror("fcntl(F_SETFL) failed");
        return -1;
    return 0;
int main(int argc, char *argv[]) {
    if (argc != 3) {
        printf("usage: %s ip port", argv[0]);
        return -1;
    char *ip = argv[1];
    int port = atoi(argv[2]);
    if (port <= 0 || port > 65535) {
        printf("invalid port number");
        return -1;
    int client_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (client_sockfd == -1) {
        perror("socket() failed");
        return -1;
    if (set_nonblocking(client_sockfd) == -1) {
        return -1;
    sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port);
    inet_pton(AF_INET, ip, &server_addr.sin_addr);
    int epollfd = epoll_create1(0);
    if (epollfd == -1) {
        perror("epoll_create1() failed");
        return -1;
    epoll_event event, events[MAX_EVENTS];
    memset(&event, 0, sizeof(event));
    event.events = EPOLLOUT;
    event.data.fd = client_sockfd;
    if (epoll_ctl(epollfd, EPOLL_CTL_ADD, client_sockfd, &event) == -1) {
        perror("epoll_ctl(EPOLL_CTL_ADD) failed");
        return -1;
    char buffer[MAX_BUFFER_SIZE];
    memset(buffer, 0, sizeof(buffer));
    strncpy(buffer, "Hello", sizeof(buffer) - 1);
    while (true) {
        int nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
        if (nfds == -1) {
            if (errno == EINTR) {
                continue;
            perror("epoll_wait() failed"




    
);
            break;
        for (int i = 0; i < nfds; i++) {
            int sockfd = events[i].data.fd;
            if ((events[i].events & EPOLLOUT) && (sockfd == client_sockfd)) {
                ssize_t len = sendto(sockfd, buffer, strlen(buffer), 0, (sockaddr *)&server_addr, sizeof(server_addr));
                if (len == -1) {
                    perror("sendto() failed");
                    continue;
                printf("Sent %zd bytes to %s:%d", len, ip, port);
                memset(buffer, 0, sizeof(buffer));
            if ((events[i].events & EPOLLIN) && (sockfd == client_sockfd)) {
                ssize_t len = recv(sockfd, buffer, MAX_BUFFER_SIZE, 0);
                if (len == -1) {
                    perror("recv() failed");
                    continue;
                printf("Received %zd bytes from %s:%d", len, ip, port);
                printf("Content: %s", buffer);
                close(epollfd);
                close(client_sockfd);
                return 0;
    close(epollfd);
    close(client_sockfd);
    return 0;

4. 测试结果

在两台机器上分别编译运行服务端和客户端程序,通过UDP协议进行通信。其中,192.168.1.102为服务端IP地址,192.168.1.101为客户端IP地址。

服务端:

$ ./epoll_server_udp 1234 
Received 5 bytes from 192.168.1.101:39136 
Content: Hello  

客户端:

$ ./epoll_client_udp 192.168.1.102 1234 
Sent 5 bytes to 192.168.1.102:1234 
Received 14 bytes from 192.168.1.102:1234 
Content: Received Hello

5. 知识点:

1.socket套接字

创建套接字的函数是socket(),函数原型为:

#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);

其中 “int domain”参数 表示套接字要使用的协议簇,协议簇的在“linux/socket.h”里有详细定义,常用的协议簇:

  • AF_UNIX(本机通信)
  • AF_INET(TCP/IP – IPv4)
  • AF_INET6(TCP/IP – IPv6)

其中 “type”参数 指的是套接字类型,常用的类型有:

  • SOCK_STREAM(TCP流)
  • SOCK_DGRAM(UDP数据报)
  • SOCK_RAW(原始套接字)

最后一个“ protocol”一般设置为“0 ”,也就是当确定套接字使用的协议簇和类型时,这个参数的值就为0,但是有时候创建原始套接字时,并不知道要使用的协议簇和类型,也就是domain参数未知情况下,这时protocol这个参数就起作用了,它可以确定协议的种类。

socket是一个函数,那么它也有返回值,当套接字创建成功时,返回套接字,失败返回“-1”,错误代码则写入“errno”中。

创建套接字:

#include <sys/types.h>
#include <sys/socket.h>
#include <linux/socket.h>
int sock_fd_tcp;
int sock_fd_udp;
sock_fd_tcp = socket(AF_INET, SOCK_STREAM, 0);
sock_fd_udp = socket(AF_INET, SOCK_DGRAM, 0);
if(sock_fd_tcp < 0){
perror("TCP SOCKET ERROR! \n ");
exit(-1);
}

if(sock_fd_udp < 0){
perror("UDP SOCKET ERROR! \n ");
exit(-1);
}

什么是Socket?举一个例子:Lewis跟Nico两人聊QQ,QQ是一个独立的应用程序,那么它对应了两个Socket,一个在Lewis的电脑上,一个在Nico的电脑上。当Lewis对Nico说:”周末我们去开卡丁车吧!“,这句话就是一段数据,这段数据会先储存在Lewis电脑Socket上,我们在” 分层网络模型 “一文中提到过,TCP存在于传输层,同时,我们在” 端口、IP协议 “一文中又提到了TCP传输过程(三次握手建立连接,三次握手关闭连接),当Lewis的QQ和Nico的QQ连接成功后,Lewis的Socket将这段话的数据发送到Nico的电脑中,但是Nico暂时还没看到,因为数据会先存放在Nico电脑的Socket当中,然后Socket会把数据呈现给Nico看。

到了这里不禁要问,数据传送过程中为什么要多出Socket这样东西?

答:因为不同的应用程序对应不同的Socket,而Socket保证了QQ的数据不会到处乱跑,不会一冲动跑到MSN上去了。因为QQ和MSN两个应用程序的Socket内容是完全不同的。那么Socket里面到底是什么?

答:Socket套接字地址!套接字地址是一个数据结构,我们仅基于TCP传输协议作为例子。套接字地址这个数据结构里面包含了:地址类型、端口号、IP地址、填充字节这4种数据。而它的数据结构原型为:

#include <netinet/in.h>
struct sockaddr_in{
unsigned short sin_family;
unsigned short int sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
};

其中:

  • sin_family表示地址类型,对于基于TCP/IP传输协议的通信,该值只能是AF_INET;
  • sin_prot表示端口号,例如:21 或者 80 或者 27015,总之在0 ~ 65535之间;
  • sin_addr表示32位的IP地址,例如:192.168.1.5 或 202.96.134.133;
  • sin_zero表示填充字节,一般情况下该值为0;

Socket数据的赋值实例:

struct sockaddr_in Lewis;
Lewis.sin_family = AF_INET;
Lewis.sin_port = htons(80);
Lewis.sin_addr.s_addr = inet_addr("202.96.134.133");
memset(Lewis.sin_zero,0,sizeof(Lewis.sin_zero));

分析:我们设置了一个名叫Lewis的套接字地址,它基于TCP/IP协议,因此sin_family的值为AF_INET,这个是雷打不动的,只要使用TCP/IP协议簇,该值就是AF_INET;htons是端口函数,以后介绍,这就表示设置了端口号为80;

sin_addr是一个数据结构,原型是:

struct in_addr{
unsigned long s_addr;
};

因此,Lewis这个套接字地址的IP赋值格式是Lewis.sin_addr.s_addr,inet_addr函数也是日后再说,这里表示设置IP地址为202.96.134.133;而memset函数在这里起到给sin_zero数组清零的作用,它的原型是:

memset(void *s, int c, size_t n);

2. INADDR_ANY

3.epoll

1)epoll函数

Epoll 作为一种 IO 复用机制多应用与高并发领域,网上有很多如何使用 epoll 的基础教程,但对于 epoll 中很重要的结构体 epoll_event 讲的都模棱两可,这篇文章将做深入解析

在解析之前,先回顾一下 epoll 的使用方法。

  • 首先调用 int epoll_create(int size); 创建一个 epoll
  • 调用 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 为 epoll 注册事件(如果是新建的 epoll 一般 op 选项是 EPOLL_CTL_ADD 添加事件)

··· EPOLL_CTL_ADD(注册新的 fd 到epfd)

··· EPOLL_CTL_DEL(从 epfd 中删除一个 fd)

··· EPOLL_CTL_MOD(修改已经注册的 fd 监听事件)

  • 调用 int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); 等待事件的到来,得到的结果存储在 event 中
  • 完全处理完毕后,再次调用 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 删除已经注册的事件(op 选项是 EPOLL_CTL_DEL

值得注意的是 epoll_wait 函数 只能获取是否有注册事件发生 ,至于这个事件到底是什么、从哪个 socket 来、发送的时间、包的大小等等信息,统统不知道。这就好比一个人在黑黢黢的山洞里,只能听到声响,至于这个声音是谁发出的根本不知道。因此我们就需要 struct epoll_event 来帮助我们读取信息。

2)struct epoll_event 结构分析

typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;
struct epoll_event {
__uint32_t events; /* Epoll events /
epoll_data_t data; / User data variable */

epoll_event 结构体的定义如上所示,分为 events 和 data 两个部分。

events 是 epoll 注册的事件,比如 EPOLLIN EPOLLOUT 等等,这个参数在 epoll_ctl 注册事件时,可以明确告知注册事件的类型。

第二个参数 data 是一个联合体,很多人搞不清除 data 拿来干嘛,网上给的解释一般是传递参数,至于怎么传?有什么用?都不清不楚。下面一个小节将用实例的方式分析。


3)struct epoll_event 使用实例

下面将从两个实际案例中,分析 epoll_event 的作用。

3.1 示例 1:服务器侦听客户端连接

//创建socket
nSocketListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//绑定地址
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_addr.s_addr = htonl(INADDR_ANY);//0.0.0.0所有地址都合法
local.sin_port = htons(TCP_PORT);
bind(nSocketListen, (struct sockaddr*) & local, sizeof(local))
//创建epoll
nListenEpoll = epoll_create(MAX_LISTEN_EVENTS);
//注册事件
struct epoll_event Ev;
memset(&Ev, 0, sizeof(epoll_event));
Ev.events= EPOLLIN | EPOLLET 
Ev.data.fd = nSocketListen;
epoll_ctl(nListenEpoll, EPOLL_CTL_ADD, nSocketListen, &Ev);
int nFdNumber = epoll_wait(nListenEpoll, lpListenEvents, MAX_LISTEN_EVENTS, -1);
//处理侦听结果
for (int i = 0; i < nFdNumber; i++)
    if (lpListenEvents[i].data.fd != nSocketListen) continue;

上述代码是网上很常见的 demo 片段,作用是建立一个服务器,侦听所有客户端的连接。具体过程是先建立了一个 socket,地址设为设为 0.0.0.0(所有人都可以连接),然后将这个 socket 的句柄 nSocketListen 附加在注册事件 Ev.data.fd 上。在 wait 等到结果后做一个判断,看看接收到和预设的是否一致

if (lpListenEvents[i].data.fd != nSocketListen) 
    continue;

这里联合体 data 中的 fd 起到了传递 socket 句柄的作用,这样我们就知道:等到的事件是不是我们想要的 socket 产生的。但是 这个网上很常见的 demo 其实并没有体现出 fd 传参的作用!

整个程序仅仅设置并注册了一个 socket 来连接所有 IP 地址 htonl(INADDR_ANY); ,因此 wait 收到的消息必然来自于这个唯一的 socket,所以这句判断根本是多此一举。

正确的使用方法是:我们可以建立三个 socket 管理不同的字段

Socket 句柄 管理的 IP 地址范围

101 100-120

102 121-191

103 192-255

将这三个 socket 都注册进 epoll 里面,当 wait 到来时,我们就可以根据 Ev.data.fd 传进来的 socket 句柄来进行处理。比如上午 8 点到 10 点这个时间段,服务器只允许 100-120 范围的 IP 连接进来,就可以做一个判断 if (lpListenEvents[i].data.fd == 101) ,如果是再接受连接。

这个例子中,fd 传递了 socket 的句柄,帮助我们管理不同的网络连接。


3.2 示例 2:线程间通信

//线程A代码
struct epoll_event Ev;
memset(&Ev, 0, sizeof(Ev));
Ev.events= EPOLLOUT | EPOLLET | EPOLLERR | EPOLLHUP
Ev.data.ptr = lpCatList;
epoll_ctl(iClientEpoll, EPOLL_CTL_ADD, lpCatList->nClientSocket, &Ev);
//线程B代码
int nFdNumber = epoll_wait(iClientEpoll, lpEvent, MAX_CLIENT_EVENTS, -1);
IOPACKHEAD_LIST* RelpCatList = (IOPACKHEAD_LIST*)lpEvent[i].data.ptr;

上述 demo 展示了 epoll 在两个线程间协同工作。线程 A 功能相当于接线员,跟 3.1 节展示的服务器功能相同:监听客户的连接,accept 客户的请求,建立客户与服务器间的 socket 连接通道(此处的建立的 socket 句柄为 nClientSocket)。然后将这些客户连接注册到 iClientEpoll 中

这些通道建立后,客户一般不会时刻收发数据,也就是说客户可能 不定时 的使用为他们建立的 socket 连接通道,线程 B 的 iClientEpoll 就是用来监听有没有 已经建立连接的客户 需要收发数据的。

如果仅仅像 3.1 节所展示的那样用 Ev.data.fd 传一个客户 socket 的句柄,这样线程 B 能得到的信息太少了。所以我们需要使用结构体 lpCatList 来传参。

lpCatList 相当于一个令牌,他是一个指针,指向的地址存储了客户的信息(Socket 句柄,IP 地址,MAC 地址,请求时间等等),A 线程在接收客户连接后,将他们写到这个令牌中,一并注册到 iClientEpoll。B 线程就可以利用 Ev.data.ptr 包含的重要的地址信息。

这样 ptr 就相当于一个小纸条,A 线程通过 iClientEpoll 将这个小纸条交到 B 线程手中,B 线程就能了解 A 线程的信息,实现了线程间的通信。

下面我们打印一下线程 A 的 lpCatlist

(gdb) p lpCatList
$18 = (IOPACKHEAD_LIST *) 0x7ffff0001120

再打印一下线程 B 的 ptr,可以发现他们指向同一个地址 0x7ffff0001120,说明参数成功传递

(gdb) p lpEvent[0]
$14 = {events = 4, data = {ptr = 0x7ffff0001120, fd = -268431072, u32 = 4026536224, 
    u64 = 140737219924256}}

4.其他例子

/* 实现功能:通过epoll, 处理多个socket
 * 监听一个端口,监听到有链接时,添加到epoll_event
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <poll.h>
#include <sys/epoll.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <unistd.h> 
#define MYPORT 12345
//最多处理的connect
#define MAX_EVENTS 500
//当前的连接数
int currentClient = 0; 
//数据接受 buf
#define REVLEN 10
char recvBuf[REVLEN];
//epoll描述符
int epollfd;
//事件数组
struct epoll_event eventList[MAX_EVENTS];
void AcceptConn(int srvfd);
void RecvData(int fd);
int main()
    int i, ret, sinSize;
    int recvLen = 0;
    fd_set readfds, writefds;
    int sockListen, sockSvr, sockMax;
    int timeout;
    struct sockaddr_in server_addr;
    struct sockaddr_in client_addr;
    //socket
    if((sockListen=socket(AF_INET, SOCK_STREAM, 0)) < 0)
        printf("socket error\n");
        return -1;
    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family  =  AF_INET;
    server_addr.sin_port = htons(MYPORT);
    server_addr.sin_addr.s_addr  =  htonl(INADDR_ANY); 
    //bind
    if(bind(sockListen, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0)
        printf("bind error\n");
        return -1;
    //listen
    if(listen(sockListen, 5) < 0)
        printf("listen error\n");
        return -1;
    // epoll 初始化
    epollfd = epoll_create(MAX_EVENTS);
    struct epoll_event event;
    event.events = EPOLLIN|EPOLLET;
    event.data.fd = sockListen;
    //add Event
    if(epoll_ctl(epollfd, EPOLL_CTL_ADD, sockListen, &event) < 0)
        printf("epoll add fail : fd = %d\n", sockListen);
        return -1;
    //epoll
    while(1)
        timeout=3000;                
        //epoll_wait
        int ret = epoll_wait(epollfd, eventList, MAX_EVENTS, timeout);
        if(ret < 0)
            printf("epoll error\n");
            break;
        else if(ret == 0)
            printf("timeout ...\n");
            continue;
        //直接获取了事件数量,给出了活动的流,这里是和poll区别的关键
        int i = 0;
        for(i=0; i<ret; i++)
            //错误退出
            if ((eventList[i].events & EPOLLERR) ||
                (eventList[i].events & EPOLLHUP) ||
                !(eventList[i].events & EPOLLIN))
              printf ( "epoll error\n");
              close (eventList[i].data.fd);
              return -1;
            if (eventList[i].data.fd == sockListen)
                AcceptConn(sockListen);
            }else{
                RecvData(eventList[i].data.fd);
    close(epollfd);
    close(sockListen);
    return 0;
/**************************************************
函数名:AcceptConn
功能:接受客户端的链接
参数:srvfd:监听SOCKET
***************************************************/
void AcceptConn(int srvfd)
    struct sockaddr_in sin;
    socklen_t len = sizeof(struct sockaddr_in);
    bzero(&sin, len);
    int confd = accept(srvfd, (struct sockaddr*)&sin, &len);
    if (confd < 0)
       printf("bad accept\n");
       return;
    }else
        printf("Accept Connection: %d", confd);
    //将新建立的连接添加到EPOLL的监听中
    struct epoll_event event;
    event.data.fd = confd;
    event.events =  EPOLLIN|EPOLLET;
    epoll_ctl(epollfd, EPOLL_CTL_ADD, confd, &event);
//读取数据
void RecvData(int fd)
    int ret;
    int recvLen = 0;
    memset(recvBuf, 0, REVLEN);
    printf("RecvData function\n");
    if(recvLen != REVLEN)
        while(1)
            //recv数据
            ret = recv(fd, (char *)recvBuf+recvLen, REVLEN-recvLen, 0);
            if(ret == 0)
                recvLen = 0;
                break;
            else if(ret < 0)
                recvLen = 0;
                break;
            //数据接受正常
            recvLen = recvLen+ret;
            if(recvLen<REVLEN)
                continue;
                //数据接受完毕
                printf("buf = %s\n",  recvBuf);
                recvLen = 0;
                break;