终于解决了之前的一个Bug

Posted randyniu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了终于解决了之前的一个Bug相关的知识,希望对你有一定的参考价值。

没心情说话,直接贴源码

//commsocket.h
#ifndef __COMMSOCKET__H
#define __COMMSOCKET__H

#ifdef __cplusplus
extern C
{
#endif

#define ERR_EXIT(m) do {         perror(m);         exit(EXIT_FAILURE); } while(0)

typedef struct _Handle
{
    int sockfd;
    int conntime;
    int sendtime;
    int recvtime;
    
}Handle;
        
typedef struct _packet
{
    int len;
    char buf[1024];
}packet;

//////////////////////////////////////////////////
// base functions

ssize_t readn(int fd, void *buf, size_t count);

ssize_t writen(int fd, const void *buf, size_t count);

ssize_t recv_peek(int sockfd, void *buf, size_t len);

ssize_t readline(int sockfd, void *buf, size_t maxline);

int activate_nonblock(int fd);

int deactivate_nonblock(int fd);

/////////////////////////////////////////////////////
//超时处理函数

int connect_timeout(int fd, struct sockaddr_in *addr,  int wait_seconds);

int accept_timeout(int fd, struct sockaddr_in *addr, int wait_seconds);

int read_timeout(int fd,  int wait_seconds);

int write_timeout(int fd,  int wait_seconds);

///////////////////////////////////////////////////////

int cliet_init(Handle **handle, int conntime, int sendtime, int recvtime);

int cliet_getconn(Handle *handle, char *ip, int port, int *connfd);

int client_send(Handle *handle, packet* sendbuf);

int client_recv(Handle *handle, packet* recvbuf);

////////////////////////////////////////////////////
//server functions

int server_init(int port, int *listenfd);

int server_accept(int listenfd, int *connfd, struct sockaddr_in *addr, int timeout);

int server_recv(int connfd, packet* recvbuf, int timeout);

int server_send(int connfd, packet* sendbuf, int timeout);

#ifdef __cpluspluse
}
#endif

#endif
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include "commsocket.h"

ssize_t readn(int fd, void *buf, size_t count)
{
    size_t nleft = count;
    ssize_t nread;
    char *bufp = (char*)buf;

    while (nleft > 0)
    {
        if ((nread = read(fd, bufp, nleft)) < 0)
        {
            if (errno == EINTR)
                continue;
            return -1;
        }
        else if (nread == 0)
            return count - nleft;

        bufp += nread;
        nleft -= nread;
    }
    
    return count;
}

ssize_t writen(int fd, const void *buf, size_t count)
{
    size_t nleft = count;
    ssize_t nwritten = 0;
    char *bufp = (char*)buf;

    while (nleft > 0)
    {
        if ((nwritten = write(fd, bufp, nleft)) < 0)
        {
            if (errno == EINTR)
                continue;
            return -1;
        }
        else if (nwritten == 0)
            continue;

        bufp += nwritten;
        nleft -= nwritten;
    }

    return count;
}

ssize_t recv_peek(int sockfd, void *buf, size_t len)
{
    while (1)
    {
        int ret = recv(sockfd, buf, len, MSG_PEEK);
        if (ret == -1 && errno == EINTR)
            continue;
        return ret;
    }
}

ssize_t readline(int sockfd, void *buf, size_t maxline)
{
    int ret;
    int nread;
    char *bufp = buf;
    int nleft = maxline;
    while (1)
    {
        ret = recv_peek(sockfd, bufp, nleft);
        if (ret < 0)
            return ret;
        else if (ret == 0)
            return ret;

        nread = ret;
        int i;
        for (i=0; i<nread; i++)
        {
            if (bufp[i] == \n)
            {
                ret = readn(sockfd, bufp, i+1);
                if (ret != i+1)
                    exit(EXIT_FAILURE);
                return ret;
            }
        }

        if (nread > nleft)
            exit(EXIT_FAILURE);

        nleft -= nread;
        ret = readn(sockfd, bufp, nread);
        if (ret != nread)
            exit(EXIT_FAILURE);

        bufp += nread;
    }

    return -1;
}


/**
 * activate_noblock - 设置I/O为非阻塞模式
 * @fd: 文件描符符
 */
int activate_nonblock(int fd)
{
    int ret = 0;
    int flags = fcntl(fd, F_GETFL);
    if (flags == -1)
    {
        ret = flags;
        printf("activate_nonblock() Err:%d", ret);
        return ret;
    }
        
    flags |= O_NONBLOCK;
    ret = fcntl(fd, F_SETFL, flags);
    if (ret == -1)
    {
        printf("activate_nonblock() Err : %d", ret);
        return ret;
    }
    return ret;
}

/**
 * deactivate_nonblock - 设置I/O为阻塞模式
 * @fd: 文件描符符
 */
int deactivate_nonblock(int fd)
{
    int ret = 0;
    int flags = fcntl(fd, F_GETFL);
    if (flags == -1)
    {
        ret = flags;
        printf("deactivate_nonblock() Err:%d", ret);
        return ret;
    }

    flags &= ~O_NONBLOCK;
    ret = fcntl(fd, F_SETFL, flags);
    if (ret == -1)
    {
        printf("deactivate_nonblock() Err:%d", ret);
        return ret;
    }
    return ret;
}


/**
 * read_timeout - 读超时检测函数,不含读操作
 * @fd: 文件描述符
 * @wait_seconds: 等待超时秒数,如果为0表示不检测超时
 * 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT
 */
int read_timeout(int fd, int wait_seconds)
{
    int ret = 0;
    if (wait_seconds > 0)
    {
        fd_set read_fdset;
        FD_ZERO(&read_fdset);
        FD_SET(fd, &read_fdset);

        struct timeval timeout;
        timeout.tv_sec = wait_seconds;
        timeout.tv_usec = 0;
        
        //select返回值三态
        //1 若timeout时间到(超时),没有检测到读事件 ret返回=0
        //2 若ret返回<0 &&  errno == EINTR 说明select的过程中被别的信号中断(可中断睡眠原理)
        //2-1 若返回-1,select出错
        //3 若ret返回值>0 表示有read事件发生,返回事件发生的个数
        do
        {
            ret = select(fd + 1, &read_fdset, NULL, NULL, &timeout);
            
        } while (ret < 0 && errno == EINTR); 
    
        if (ret == 0)
        {
            ret = -1;
            errno = ETIMEDOUT;
        }
        else if (ret == 1)
            ret = 0;
    }

    return ret;
}

/**
 * write_timeout - 写超时检测函数,不含写操作
 * @fd: 文件描述符
 * @wait_seconds: 等待超时秒数,如果为0表示不检测超时
 * 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT
 */
int write_timeout(int fd, int wait_seconds)
{
    int ret = 0;
    
    if (wait_seconds > 0)
    {
        fd_set write_fdset;
        FD_ZERO(&write_fdset);
        FD_SET(fd, &write_fdset);

        struct timeval timeout;
        timeout.tv_sec = wait_seconds;
        timeout.tv_usec = 0;
        
        do
        {
            ret = select(fd + 1, NULL, &write_fdset, NULL, &timeout);
            
        } while (ret < 0 && errno == EINTR);

        if (ret == 0)
        {
            ret = -1;
            errno = ETIMEDOUT;
        }
        else if (ret == 1)
            ret = 0;
    }

    return ret;
}


/**
 * accept_timeout - 带超时的accept
 * @fd: 套接字
 * @addr: 输出参数,返回对方地址
 * @wait_seconds: 等待超时秒数,如果为0表示正常模式
 * 成功(未超时)返回已连接套接字,超时返回-1并且errno = ETIMEDOUT,错误返回-1
 */
int accept_timeout(int fd, struct sockaddr_in *addr, int wait_seconds)
{
    int ret;
    
    if(wait_seconds > 0)
    {
        fd_set accept_fdset;
        FD_ZERO(&accept_fdset);
        FD_SET(fd, &accept_fdset);
        
        struct timeval timeout;
        timeout.tv_sec = wait_seconds;
        timeout.tv_usec = 0;
        
        do
        {
            ret = select(fd + 1, &accept_fdset, NULL, NULL, &timeout);
            
        } while (ret < 0 && errno == EINTR);        
        if (ret == -1)
            return -1;
        else if (ret == 0)
        {
            errno = ETIMEDOUT;
            return -1;
        }
    }
    //一但检测出 有select事件发生,表示对等方完成了三次握手,客户端有新连接建立
    //此时再调用accept将不会堵塞
    socklen_t addrlen = sizeof(struct sockaddr_in);
    
    if (addr != NULL)
        ret = accept(fd, (struct sockaddr*)addr, &addrlen); //返回已连接套接字
    else
        ret = accept(fd, NULL, NULL);
    
    if (ret < 0)
    {
        printf("accept() Err:%d \n", errno);
        return ret;
    }
    
    return ret;
}

/**
 * connect_timeout - connect连接超时
 * @fd: 套接字
 * @addr: 要连接的对方地址
 * @wait_seconds: 等待超时秒数,如果为0表示正常模式
 * 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT
 */
int connect_timeout(int fd, struct sockaddr_in *addr, int wait_seconds)
{
    int ret;
    socklen_t addrlen = sizeof(struct sockaddr_in);

    if (wait_seconds > 0)
    {
        ret = activate_nonblock(fd);
        if(ret == -1)
            return -1;
    }
        
    ret = connect(fd, (struct sockaddr*)addr, addrlen);
    
    if (ret < 0 && errno == EINPROGRESS)
    {
        fd_set connect_fdset;
        FD_ZERO(&connect_fdset);
        FD_SET(fd, &connect_fdset);
        
        struct timeval timeout;
        timeout.tv_sec = wait_seconds;
        timeout.tv_usec = 0;
        
        do
        {
            // 一但连接建立,则套接字就可写  所以connect_fdset放在了写集合中
            ret = select(fd + 1, NULL, &connect_fdset, NULL, &timeout);
            
        } while (ret < 0 && errno == EINTR);
        
        if (ret == 0)
        {
            ret = -1;
            errno = ETIMEDOUT;
            return ret;
        }
        else if (ret < 0)
            return -1;
        else if (ret == 1)
        {
            /* ret返回为1(表示套接字可写),可能有两种情况,一种是连接建立成功,一种是套接字产生错误,*/
            /* 此时错误信息不会保存至errno变量中,因此,需要调用getsockopt来获取。 */
            int err;
            socklen_t socklen = sizeof(err);
            int sockoptret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &socklen);
            
            if (sockoptret == -1)
            {
                return -1;
            }
            
            if (err == 0)
            {
                ret = 0;
            }
            else
            {
                errno = err;
                ret = -1;
            }
        }
    }
    if (wait_seconds > 0)
    {
        int tmp;
        tmp = deactivate_nonblock(fd);
        if(tmp == -1)
            return -1;
    }
    return ret;
}

//客户端环境初始化 设置结构体中的信息
/**
 * cliet_init - 创建客户端环境初始化
 * @handle: 句柄
 * @conntime: 连接最长时长
 * @sendtime: 发送最长时长
 * @recvtime: 接受最长时长
 * 成功返回 0,失败返回-1,
 */
int cliet_init(Handle **handle, int conntime, int sendtime, int recvtime)
{
    int ret = 0;
    if (handle == NULL ||conntime < 0 || sendtime < 0 || recvtime < 0)
    {
        printf("cliet_init() Parem Err! \n");
        return -1;
    }
    
    Handle *tmp = (Handle *)malloc(sizeof(Handle));
    if (tmp == NULL)
    {
        printf("cliet_init() malloc Err!\n");
        return -1;
    }
    tmp->conntime = conntime;
    tmp->sendtime = sendtime;
    tmp->recvtime = recvtime;
    *handle = tmp; 
    return ret;
}

/**
 * cliet_getconn - 创建客户端连接
 * @handle: 句柄
 * @ip: ip地址
 * @port: 端口
 * @conn: 回传出去的连接
 * 成功返回 0,失败返回-1, 超时返回-1并且errno = ETIMEDOUT
 */
int cliet_getconn(Handle *handle, char *ip, int port, int *connfd)
{
    int ret = 0;
    
    if (handle == NULL || ip==NULL || connfd==NULL || port<0 || port>65537)
    {
        printf("cliet_getconn() Param Err! \n");
        return -1;
    }
    
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    
    if (sockfd < 0)
    {
        ret = errno;
        printf("socket() Err!\n");
        return ret;
    }
    
    struct sockaddr_in servaddr;
    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(port);
    servaddr.sin_addr.s_addr = inet_addr(ip);
    
    Handle  *tmp = (Handle* )handle;

    ret = connect_timeout(sockfd, (struct sockaddr_in*) (&servaddr), (unsigned int)tmp->conntime);
    
    if (ret < 0)
        return ret;
    
    handle->sockfd = sockfd;
    *connfd = sockfd;
   
       return ret;
}

//客户端发送报文
/**
 * client_send - 发送报文数据
 * @handle: 句柄
 * @sendbuf: 发送数据packet
 * 成功返回成功发送数据的长度,失败返回-1 或者 < 发送数据的长度
 */
int client_send(Handle *handle, packet* sendbuf)
{
    int ret = write_timeout(handle->sockfd, handle->sendtime);
    if (ret < 0)
    {
        //失败返回-1,超时返回-1并且errno = ETIMEDOUT
        if (ret == -1 && errno == ETIMEDOUT)
        {
            printf("client_send() write_timeout()  Timeout Err!\n ");
            return ret;
        }
        else
            return ret;
    }

    int n = strlen(sendbuf->buf);
    sendbuf->len = htonl(n);
    writen(handle->sockfd, sendbuf, 4 + n);
}


int client_recv(Handle *handle, packet* recvbuf)
{
    int ret =  read_timeout(handle->sockfd, handle->recvtime);
    if (ret < 0)
    {
        //失败返回-1,超时返回-1并且errno = ETIMEDOUT
        if (ret == -1 && errno == ETIMEDOUT)
        {
            printf("client_rev() read_timeout() Timeout Err!\n ");
            return ret;
        }
        else
            return ret;
    }
    readn(handle->sockfd, &recvbuf->len, 4);
    //将转换为本地字节存储
    int n = ntohl(recvbuf->len);
    return readn(handle->sockfd, &recvbuf->buf, n);
}

//服务器端初始化
/**
 * server_init - 服务器初始化环境
 * @port:服务器端口
 * @listenfd:传出参数 返回监听套接字
 * 成功返回0, 失败返回相应的错误码
 */
int server_init(int port, int *listenfd)
{
    int ret = 0;
    int listen_fd;
    
    listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    
    if (listen_fd < 0)
    {
        ret = errno ;
        printf("socket() Err: %d \n", ret);
        return ret;
    }
    
    //设置地址复用
    int on = 1;
    ret = setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) );
    
    if (ret < 0)
    {
        ret = errno ;
        printf("setsockopt() Err:%d \n", ret);
        return ret;
    }
    
    struct sockaddr_in servaddr;
    
    bzero(&servaddr, sizeof(servaddr));
    
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(port);
    servaddr.sin_addr.s_addr = inet_addr("172.20.53.162");
    //servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    //inet_aton("127.0.0.1", &servaddr.sin_addr);
    
    ret = bind(listen_fd, (struct sockaddr*)&servaddr, sizeof(servaddr));
    
    if (ret < 0)
    {
        ret = errno ;
        printf("bind() Err:%d \n", ret);
        return ret;
    }
        
    ret = listen(listen_fd, SOMAXCONN);
    
    if (ret < 0)
    {
        ret = errno ;
        printf("listen() Err:%d \n", ret);
        return ret;
    }
    *listenfd = listen_fd;
    return 0;
}

//服务器accept函数
/**
 * server_accept - 服务器接受
 * @listenfd: 监听套接字
 * @connfd: 输出参数,返回连接套接字
 * @timeout: 等待超时秒数,如果为0表示正常模式
 * 成功返回0,失败返回对应的错误码
 */
int server_accept(int listenfd, int *connfd, struct sockaddr_in *addr, int timeout)
{
    int    ret = 0;
    
    ret = accept_timeout(listenfd, addr, (unsigned int)timeout);
    
    if (ret < 0)
    {
        if (ret == -1 && errno == ETIMEDOUT)
        {
            printf("accept_timeout() Err!\n");
            return ret;
        }
        ret = errno;
        return ret;
    }
    
    *connfd = ret;
    
    return 0;
}

//服务器端端接受报文
/**
 * server_send - 服务器发送报文
 * @connfd: 连接套接字
 * @recvbuf: 接受数据packet
 * @timeout: 等待超时秒数,如果为0表示正常模式
 * 成功返回读取数据的长度,失败-1, 超时返回-1错误码为ETIMEOUT
 */
int server_recv(int connfd, packet* recvbuf, int timeout)
{
    int ret =  read_timeout(connfd, timeout);
    if (ret < 0)
    {
        if (ret == -1 && errno == ETIMEDOUT)//失败返回-1,超时返回-1并且errno = ETIMEDOUT
        {
            printf("server_recv() read_timeout()  Timeout Err!\n ");
            return ret;
        }
        else
            return ret;
    }
    //读取包的头4字节
    ret = readn(connfd, recvbuf, 4); //读包头 4个字节
    if (ret == -1)
    {
        printf("readn error\n");
        return -1;
    }//如果读取的个数小于4,则客服端已经关闭
    else if (ret < 4)
    {
        printf("client close\n");
        return -1;
    }
    int n = ntohl(recvbuf->len);
    return readn(connfd, &(recvbuf->buf), n); //根据长度读数据
}

//服务器端发送报文
/**
 * server_send - 服务器发送报文
 * @connfd: 连接套接字
 * @sendbuf: 发送数据packet
 * @timeout: 等待超时秒数,如果为0表示正常模式
 * 成功返回0,失败返回对应的错误码
 */
int server_send(int connfd, packet* sendbuf, int timeout)
{
    int ret = write_timeout(connfd, timeout);
    if (ret < 0)
    {
        if (ret == -1 && errno == ETIMEDOUT)//失败返回-1,超时返回-1并且errno = ETIMEDOUT
        {
            printf("server_send_() write_timeout()  Timeout Err!\n ");
            return ret;
        }
        else
            return ret;
    }
    int n = ntohl(sendbuf->len);
    //将接受到的数据再直接发出去。也就是所谓的echo服务器模型
    return writen(connfd, sendbuf, 4 + n);  //注意写数据的时候,多加4个字节
}
// echoclient.c
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include "commsocket.h"

int echo_cli(int sockfd, Handle *handle)
{
    packet sendbuf;
    packet recvbuf;
    bzero(&sendbuf, sizeof(sendbuf));
    bzero(&recvbuf, sizeof(recvbuf));
    
    while(fgets(sendbuf.buf, sizeof(sendbuf.buf), stdin) != NULL)
    {
        int ret = client_send(handle, &sendbuf);        
        client_recv(handle, &recvbuf);
        fputs(recvbuf.buf, stdout);
        bzero(&sendbuf, sizeof(sendbuf));
        bzero(&recvbuf, sizeof(recvbuf));
    }
    close(sockfd);
}

int main(void)
{
    // 作为传出参数
    int sockfd;
    //保存信息的句柄
    Handle *handle = NULL;

    //设置相应的超时信息。
    int conntime = 15;
    int sendtime = 5;
    int recvtime = 5;

    int ret;
    ret = cliet_init(&handle, conntime, sendtime, recvtime);
        
    ret = cliet_getconn(handle, "172.20.53.162", 8001, &sockfd);
    
    if(ret!=0)
    {
        printf("cliet_getconn() Err!\n");
        return ret;
    }
    
    struct sockaddr_in localaddr;
    socklen_t addrlen = sizeof(localaddr);
    if (getsockname(sockfd, (struct sockaddr*)&localaddr, &addrlen) < 0)
        ERR_EXIT("getsockname");
    printf("ip=%s port=%d\n", inet_ntoa(localaddr.sin_addr), ntohs(localaddr.sin_port));

    echo_cli(sockfd, handle);
    
    close(sockfd);
    return 0;
}
// echoserver.c
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include "commsocket.h"

int echo_srv(int connfd)
{
    //定义了自定封包结构体
    packet recvbuf;
    bzero(&recvbuf, sizeof(recvbuf));
    int ret;
    while (1)
    {
        server_recv(connfd, &recvbuf, 20);
        fputs(recvbuf.buf, stdout);
        server_send(connfd, &recvbuf, 20);
        bzero(&recvbuf, sizeof(recvbuf));
    }
}

void handle_sigchld(int sig) 
{    
    int pid = 0;
    
    while ( ( pid = waitpid(-1, NULL, WNOHANG)) >0 )
    {
        printf("子进程退出,父进程要收尸: %d \n", pid);
    }
}

int main(void)
{
    //注册信号处理函数 
    signal(SIGCHLD, handle_sigchld);
    
    int port = 8001;
    //设置监听的事件,过了监听的时间就自动退出了。
    int listenfd = 0;
    
    //服务器环境初始化
    int ret = server_init(port, &listenfd);
    
    if (ret != 0)
    {
        printf("server_init() Err : %d \n", ret);
        return ret;
    }
    
    //接下来尝试进行连接
    while (1)
    {
        struct sockaddr_in peeraddr;
        
        int connfd;
        int timeout = 0;
        
        //服务器accept函数,可以接受等待时间
        ret = server_accept(listenfd, &connfd, &peeraddr, timeout);
        if (ret != 0)
        {
            printf("server_accept() Err!\n");
            return ret;
        }
            
        printf("ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));

        pid_t pid = fork();
        
        if (pid == -1)
            ERR_EXIT("fork");
        
        if (pid == 0)
        {
            close(listenfd);
            echo_srv(connfd);        
            close(connfd);
            exit(EXIT_SUCCESS);
        }
        else
        {
            close(connfd);
        }
    }
    return 0;
}

哈,就是要给Echo服务器模型,不过使用了select对其进行了优化,酱紫。

 

以上是关于终于解决了之前的一个Bug的主要内容,如果未能解决你的问题,请参考以下文章

PhpStorm 2016.3 For Mac 重大里程碑更新 -- 终于解决了不能输入中文标点符号的重大bug

小程序连续点击bug解决

vscode代码片段建议bug

终于把之前的坑填上了:远看像乱序执行,其实是内存屏障的BUG,如何用汇编搞定?

iOS 14 新版发布,这个 Bug 终于修复

2016最后一贴,终于调通一个测试示例,并发现一个BUG???