Linux网络编程【8】(网络超时检测)

589 阅读13分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

1 网络超时检测

1.1 网络信息检索函数

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname,
             void *optval, socklen_t *optlen);
功能:获取一个套接字的选项
参数:
    sockfd:文件描述符
    level:协议层次,每一个协议层次对应都有选项名称
        查看选项名称使用man 7 层次名,例如man 7 socket
        SOL_SOCKET 套接字层次  
        IPPROTO_TCP TCP层次
        IPPROTO_IP  IP层次
    optname:选项的名称,确定选项名称前需要确定层次
        套接字层次:
        SO_BROADCAST 是否允许发送广播
        SO_RCVBUF  获取接收缓冲区大小
        SO_SNDBUF  获取发送缓冲区大小
        SO_REUSEADDR  设置端口复用
        SO_RCVTIMEO  获取接收超时时间
        SO_SNDTIMEO  获取发送超时时间
    optval:获取到的选项的值
    optlen:optval的大小
返回值:
    成功:0
    失败:-1

1.1.1 使用getsockopt获取接收和发送缓冲区大小

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h> 


#define ERRLOG(errmsg) do{\
                            perror(errmsg);\
                            printf("%s - %s - %d\n", __FILE__, __func__, __LINE__);\
                            exit(1);\
                            }while(0)

int main(int argc, char const *argv[])
{
    int sockfd;

    //第一步:创建套接字
    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        ERRLOG("socket error");
    }

    //使用getsockopt函数获取套接字的选项
    int nbuf;
    socklen_t lenbuf = sizeof(nbuf);
    if(getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &nbuf, &lenbuf) == -1)
    {
        ERRLOG("getsockopt error");
    }
    printf("send buf:%dk\n", nbuf / 1024);

    if(getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &nbuf, &lenbuf) == -1)
    {
        ERRLOG("getsockopt error");
    }
    printf("recv buf:%dk\n", nbuf / 1024);

    return 0;
}

1.1.2 使用setsockopt设置端口复用

#include <sys/types.h>          
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname,
               const void *optval, socklen_t optlen);
功能:设置一个套接字的选项
参数:
    sockfd:文件描述符
    level:协议层次
        SOL_SOCKET 套接字层次  
    optname:选项的名称
        SO_REUSEADDR  是否允许端口复用
    optval:要设置的值
        1  允许
        0  不允许
    optlen:optval的大小
返回值:
    成功:0
    失败:-1    
//使用setsockopt函数设置端口复用
int on = 1;
if(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)
{
    ERRLOG("setsockopt error");
}

1.2 网络超时检测相关概念

  1. 阻塞:以读函数为例,缓冲区中如果有数据,则函数正常执行,如果没有数据,会一直等待;
  2. 非阻塞:以读函数为例,缓冲区中如果有数据,则函数正常执行,如果没有数据,则立即错误返回;
  3. 超时检测:他介于阻塞和非阻塞之间,设定一定的时间,在时间范围内如果缓冲区中没有数据,则函数一直阻塞等待,当时间到的时候还没有数据,则函数立即编程非阻塞的形式。

1.3 网络超时检测的方法

  • 方法1:使用setsockopt函数实现网络超时检测
  • 方法2:使用select函数实现网络超时检测
  • 方法3:使用alarm闹钟实现网络超时检测

2 使用setsockopt函数实现网络超时检测

#include <sys/types.h>          
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname,
               const void *optval, socklen_t optlen);
功能:设置一个套接字的选项
参数:
    sockfd:文件描述符
    level:协议层次
        SOL_SOCKET 套接字层次  
    optname:选项的名称
        SO_RCVTIMEO 设置接收超时时间
    optval:要设置的值
        struct timeval {
            long    tv_sec;     秒                          
            long    tv_usec;    微秒
        };
    optlen:optval的大小
返回值:
    成功:0
    失败:-1    

2.1 服务器

//TCP网络编程之服务器

#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h> 
#include <string.h>
#include <errno.h>
#include <unistd.h>

#define N 128
#define ERRLOG(errmsg) do{\
                            perror(errmsg);\
                            printf("%s - %s - %d\n", __FILE__, __func__, __LINE__);\
                            exit(1);\
                            }while(0)

int main(int argc, char const *argv[])
{
    if(argc < 3)
    {
        fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
        exit(1);
    }

    int sockfd, acceptfd;
    struct sockaddr_in serveraddr, clientaddr;
    socklen_t addrlen = sizeof(serveraddr);
    char buf[N] = {0};
    ssize_t bytes;

    //第一步:创建套接字
    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        ERRLOG("socket error");
    }

    //使用setsockopt函数设置端口复用
    int on = 1;
    if(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)
    {
        ERRLOG("setsockopt error");
    }

    //第二步:填充服务器网络信息结构体
    //inet_addr:将点分十进制ip地址转换为网络字节序的无符号4字节整数
    //atoi:将数字型字符串转换为整形数据
    //htons:将主机字节序转化为网络字节序
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    serveraddr.sin_port = htons(atoi(argv[2]));

    //第三步:将套接字与服务器网络信息结构体绑定
    if(bind(sockfd, (struct sockaddr *)&serveraddr, addrlen) == -1)
    {
        ERRLOG("bind error");
    }

    //第四步:将套接字设置为被动监听状态
    if(listen(sockfd, 5) == -1)
    {
        ERRLOG("listen error");
    }

    //使用setsockopt实现网络超时检测
    //场景:5秒内如果没有客户端连接,则打印连接超时,
    //      如果客户端与服务器通信是,5秒内客户端没有说话,
    //      则强制与客户端断开
    struct timeval mytime;
    mytime.tv_sec = 5;
    mytime.tv_usec = 0;
    if(setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &mytime, sizeof(mytime)) == -1)
    {
        ERRLOG("setsockopt error");
    }

NEXT:
    //第五步:阻塞等待客户端的连接
    if((acceptfd = accept(sockfd, (struct sockaddr *)&clientaddr, &addrlen)) == -1)
    {
        //printf("errno = %d\n", errno);
        if(errno == 11)
        {
            printf("连接超时...\n");
            goto NEXT;
        }
        else 
        {
            ERRLOG("accept error");
        }
    }

    //打印客户端的信息
    printf("客户端%s:%d连接了\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
    
    //进行通信

    while(1)
    {
        if((bytes = recv(acceptfd, buf, N, 0)) == -1)
        {
            if(errno == 11)
            {
                printf("客户端存在挂机行为,强制断开...\n");
                close(acceptfd);
                goto NEXT;
            }
            else 
            {
                ERRLOG("recv error");
            }
        }
        else if(bytes == 0)
        {
            printf("客户端%s-%d退出了\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
            goto NEXT;
        }

        if(strcmp(buf, "quit") == 0)
        {
            printf("客户端%s-%d退出了\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
            goto NEXT;
        }

        printf("%s-%d:%s\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port), buf);
    
        strcat(buf, "^_^");

        //如果发送放关闭,当send执行第二次的时候会结束当前进程
        //是因为第二次执行send的时候产生了SIGPIPE信号,这个信号默认对当前进程
        //的处理方式是结束进程
        if(send(acceptfd, buf, N, 0) == -1)
        {
            ERRLOG("send error");
        }
    }

    return 0;
}

2.2 客户端

//TCP网络编程之客户端

#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h> 
#include <string.h>

#define N 128
#define ERRLOG(errmsg) do{\
                            perror(errmsg);\
                            printf("%s - %s - %d\n", __FILE__, __func__, __LINE__);\
                            exit(1);\
                            }while(0)

int main(int argc, char const *argv[])
{
    if(argc < 3)
    {
        fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
        exit(1);
    }

    int sockfd;
    struct sockaddr_in serveraddr;
    socklen_t addrlen = sizeof(serveraddr);

    //第一步:创建套接字
    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        ERRLOG("socket error");
    }

    //第二步:填充服务器网络信息结构体
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    serveraddr.sin_port = htons(atoi(argv[2]));

    //第三步:给服务器发送客户端的连接请求
    if(connect(sockfd, (struct sockaddr *)&serveraddr, addrlen) == -1)
    {
        ERRLOG("connect error");
    }

    //进行通信
    char buf[N] = {0};
    ssize_t bytes;
    while(1)
    {
        fgets(buf, N, stdin);
        buf[strlen(buf) - 1] = '\0';

        if(send(sockfd, buf, N, 0) == -1)
        {
            ERRLOG("send error");
        }

        if(strcmp(buf, "quit") == 0)
        {
            printf("客户端退出了\n");
            exit(0);
        }

        memset(buf, 0, N);

        if((bytes = recv(sockfd, buf, N, 0)) == -1)
        {
            ERRLOG("recv error");
        }
        else if(bytes == 0)
        {
            printf("长时间不说话,那就结束吧\n");
            exit(0);
        }

        printf("服务器:%s\n", buf);
    }

    return 0;
}

3 使用select函数实现网络超时检测

#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
       fd_set *exceptfds, struct timeval *timeout);
功能:允许一个程序操作多个文件描述符,阻塞等待文件描述符
     准备就绪,如果有文件描述符准备就绪,函数立即返回,
     执行相应的IO操作
参数:
    nfds:最大的文件描述符加1
    readfds:保存读操作文件描述符的集合
    writefds:保存写操作文件描述符的集合
    exceptfds:保存其他或者异常的文件描述符的集合
    timeout:超时
        struct timeval {
            long    tv_sec;     秒                          
            long    tv_usec;    微秒
        };
        参数设置为NULL 阻塞
        结构体中的成员设置为0  非阻塞
返回值:
    成功:准备就绪的文件描述符的个数
    失败:-1
    
清空集合set
void FD_ZERO(fd_set *set);

将文件描述符fd添加到集合setvoid FD_SET(int fd, fd_set *set);

将文件描述符fd从集合set中移除
void FD_CLR(int fd, fd_set *set);

判断文件描述符fd是否在集合setint FD_ISSET(int fd, fd_set *set);
返回值:
    存在:1
    不存在:0

3.1 服务器

//TCP网络编程之服务器

#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h> 
#include <string.h>
#include <sys/select.h>
#include <sys/time.h>
#include <unistd.h>

#define N 128
#define ERRLOG(errmsg) do{\
                            perror(errmsg);\
                            printf("%s - %s - %d\n", __FILE__, __func__, __LINE__);\
                            exit(1);\
                            }while(0)

int main(int argc, char const *argv[])
{
    if(argc < 3)
    {
        fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
        exit(1);
    }

    int sockfd;
    struct sockaddr_in serveraddr, clientaddr;
    socklen_t addrlen = sizeof(serveraddr);
    char buf[N] = {0};

    //第一步:创建套接字
    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        ERRLOG("socket error");
    }

    //第二步:填充服务器网络信息结构体
    //inet_addr:将点分十进制ip地址转换为网络字节序的无符号4字节整数
    //atoi:将数字型字符串转换为整形数据
    //htons:将主机字节序转化为网络字节序
    serveraddr.sin_family = AF_INET;
    //注意:ip地址不能随便写,服务器在那个主机中运行,ip地址就是这个主机的
    //如果是自己主机的客户端服务器测试,可以使用127网段的
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    serveraddr.sin_port = htons(atoi(argv[2]));

    //第三步:将套接字与服务器网络信息结构体绑定
    if(bind(sockfd, (struct sockaddr *)&serveraddr, addrlen) == -1)
    {
        ERRLOG("bind error");
    }

    //第四步:将套接字设置为被动监听状态
    if(listen(sockfd, 5) == -1)
    {
        ERRLOG("listen error");
    }

    //使用select实现网络超时检测
    //select函数在超时检测中相对用的比较多,只要是阻塞函数都可以通过
    //select设置超时,所以一般要设置某一个阻塞函数的超时,就在函数基
    //础上使用select即可
    int ret;

    //第一步:创建一个保存要操作的文件描述符集合并清空
    fd_set readfds;
    FD_ZERO(&readfds);

    int maxfd = sockfd;

    struct timeval mytime;

    while(1)
    {
        //select函数返回之后,会将设置的超时时间清零,所以需要每次都设置
        mytime.tv_sec = 5;
        mytime.tv_usec = 0;

        //第二步:将要操作的文件描述符添加到集合中
        FD_SET(0, &readfds);
        FD_SET(sockfd, &readfds);

        //第三步:调用select函数,阻塞等待文件描述符准备就绪
        if((ret = select(maxfd+1, &readfds, NULL, NULL, &mytime)) == -1)
        {
            ERRLOG("select error");
        }
        //select函数超时,函数不会报错,而是立即返回,此时的select函数的
        //返回值为0
        else if(ret == 0)
        {
            printf("超时了...\n");
        }
        else 
        {
            if(FD_ISSET(0, &readfds) == 1)
            {
                fgets(buf, N, stdin);
                buf[strlen(buf) - 1] = '\0';
                printf("buf = %s\n", buf);
            }

            if(FD_ISSET(sockfd, &readfds) == 1)
            {
                if(accept(sockfd, (struct sockaddr *)&clientaddr, &addrlen) == -1)
                {
                    ERRLOG("accept error");
                }
                printf("客户端%s:%d连接了\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
            }
        }
    }

    return 0;
}

3.2 客户端

//TCP网络编程之客户端

#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h> 
#include <string.h>

#define N 128
#define ERRLOG(errmsg) do{\
                            perror(errmsg);\
                            printf("%s - %s - %d\n", __FILE__, __func__, __LINE__);\
                            exit(1);\
                            }while(0)

int main(int argc, char const *argv[])
{
    if(argc < 3)
    {
        fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
        exit(1);
    }

    int sockfd;
    struct sockaddr_in serveraddr;
    socklen_t addrlen = sizeof(serveraddr);

    //第一步:创建套接字
    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        ERRLOG("socket error");
    }

    //第二步:填充服务器网络信息结构体
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    serveraddr.sin_port = htons(atoi(argv[2]));

    //第三步:给服务器发送客户端的连接请求
    if(connect(sockfd, (struct sockaddr *)&serveraddr, addrlen) == -1)
    {
        ERRLOG("connect error");
    }

    return 0;
}

4 使用alarm闹钟实现网络超时检测

4.1 相关概念

信号默认对进程的处理方式:

  1. 忽略,信号产生之后对进程任何影响
  2. 终止进程,信号产生之后使得进程结束
  3. 停止进程,当信号产生之后当前进程变成停止态
  4. 让停止的进程继续运行,当信号产生之后停止的进程会在后台执行

信号人为处理方式:

  1. 忽略
  2. 默认
  3. 捕捉

使用alarm闹钟实现网络超时检测

当使用alarm闹钟函数设置一定的时候后,代码继续向下运行,当时间达到,会产生SIGALRM信号:

  • 如果这个信号按照默认的方式处理,就会让整个进程结束
  • 如果这个信号按照捕捉的方式处理,当信号产生之后,会立即执行信号处理函数,当信号处理函数执行完毕之后,会继续回到之前的代码运行,将这种信号的属性称之为自重启属性
  • 如果要实现超时检测,需要关闭这个自重启属性,如果关闭了自重启属性,当执行完信号处理函数之后,代码不会继续执行之前的代码,而是错误返回,然后继续运行

使用sigaction函数设置信号的属性

#include <signal.h>
int sigaction(int signum, const struct sigaction *act,
              struct sigaction *oldact);
功能:获取或者设置一个信号的行为
参数:
    signum:信号,除了SIGKILL和SIGSTOP
    act:新的行为,通过这个参数设置新的行为
        struct sigaction {
               void     (*sa_handler)(int); 信号处理函数
                   SIG_DFL 默认
                   SIG_IGN 忽略
                   自己定义的信号处理函数  捕捉
               void     (*sa_sigaction)(int, siginfo_t *, void *); 信号处理函数
               sigset_t   sa_mask; 关于阻塞的掩码
               int        sa_flags; 标志位
                   SA_RESTART 自重启属性
               void     (*sa_restorer)(void);  没有用
        };

    oldact:之前的行为,通过这个参数获取之前的行为
返回值:
    成功:0
    失败:-1

4.2.1 服务器

//TCP网络编程之服务器

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

#define N 128
#define ERRLOG(errmsg) do{\
                            perror(errmsg);\
                            printf("%s - %s - %d\n", __FILE__, __func__, __LINE__);\
                            exit(1);\
                            }while(0)

void SignalHandler(int sig)
{
}

int main(int argc, char const *argv[])
{
    if(argc < 3)
    {
        fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
        exit(1);
    }

    int sockfd, acceptfd;
    struct sockaddr_in serveraddr, clientaddr;
    socklen_t addrlen = sizeof(serveraddr);
    char buf[N] = {0};
    ssize_t bytes;

    //第一步:创建套接字
    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        ERRLOG("socket error");
    }

    //使用setsockopt函数设置端口复用
    int on = 1;
    if(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)
    {
        ERRLOG("setsockopt error");
    }

    //第二步:填充服务器网络信息结构体
    //inet_addr:将点分十进制ip地址转换为网络字节序的无符号4字节整数
    //atoi:将数字型字符串转换为整形数据
    //htons:将主机字节序转化为网络字节序
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    serveraddr.sin_port = htons(atoi(argv[2]));

    //第三步:将套接字与服务器网络信息结构体绑定
    if(bind(sockfd, (struct sockaddr *)&serveraddr, addrlen) == -1)
    {
        ERRLOG("bind error");
    }

    //第四步:将套接字设置为被动监听状态
    if(listen(sockfd, 5) == -1)
    {
        ERRLOG("listen error");
    }

    //使用alarm闹钟实现网络超时检测
    //当使用alarm闹钟函数设置一定的时候后,代码继续向下运行,当时间达到,会产生SIGALRM信号,
    //如果这个信号按照默认的方式处理,就会让整个进程结束
    //如果这个信号按照捕捉的方式处理,当信号产生之后,会立即执行信号处理函数,当信号处理函数
    //执行完毕之后,会继续回到之前的代码运行,将这种信号的属性称之为自重启属性
    //如果要实现超时检测,需要关闭这个自重启属性,如果关闭了自重启属性,当执行完信号处理函数
    //之后,代码不会继续执行之前的代码,而是错误返回,然后继续运行
    //使用sigaction函数设置信号的行为
    //信号的自重启属性是信号的一个标志位,所以要执行读改写三步
    //第一步:读,获取之前的标志位
    struct sigaction act;
    if(sigaction(SIGALRM, NULL, &act) == -1)
    {
        ERRLOG("sigaction error");
    }

    //第二步:改,修改属性
    act.sa_handler = SignalHandler;
    act.sa_flags = act.sa_flags & (~SA_RESTART);

    //第三步:设置新的属性
    if(sigaction(SIGALRM, &act, NULL) == -1)
    {
        ERRLOG("sigaction error");
    }

NEXT:
    alarm(5);

    //第五步:阻塞等待客户端的连接
    if((acceptfd = accept(sockfd, (struct sockaddr *)&clientaddr, &addrlen)) == -1)
    {
        //printf("errno = %d\n", errno);
        if(errno == 4)
        {
            printf("连接超时...\n");
            goto NEXT;
        }
        else 
        {
            ERRLOG("accept error");
        }
    }

    //打印客户端的信息
    printf("客户端%s:%d连接了\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
    
    //进行通信

    while(1)
    {
        alarm(5);

        if((bytes = recv(acceptfd, buf, N, 0)) == -1)
        {
            if(errno == 4)
            {
                printf("客户端存在挂机行为,强制断开...\n");
                close(acceptfd);
                goto NEXT;
            }
            else 
            {
                ERRLOG("recv error");
            }
        }
        else if(bytes == 0)
        {
            printf("客户端%s-%d退出了\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
            goto NEXT;
        }

        if(strcmp(buf, "quit") == 0)
        {
            printf("客户端%s-%d退出了\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
            goto NEXT;
        }

        printf("%s-%d:%s\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port), buf);
    
        strcat(buf, "^_^");

        //如果发送放关闭,当send执行第二次的时候会结束当前进程
        //是因为第二次执行send的时候产生了SIGPIPE信号,这个信号默认对当前进程
        //的处理方式是结束进程
        if(send(acceptfd, buf, N, 0) == -1)
        {
            ERRLOG("send error");
        }
    }

    return 0;
}

4.2.2 客户端

//TCP网络编程之客户端

#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h> 
#include <string.h>

#define N 128
#define ERRLOG(errmsg) do{\
                            perror(errmsg);\
                            printf("%s - %s - %d\n", __FILE__, __func__, __LINE__);\
                            exit(1);\
                            }while(0)

int main(int argc, char const *argv[])
{
    if(argc < 3)
    {
        fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
        exit(1);
    }

    int sockfd;
    struct sockaddr_in serveraddr;
    socklen_t addrlen = sizeof(serveraddr);

    //第一步:创建套接字
    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        ERRLOG("socket error");
    }

    //第二步:填充服务器网络信息结构体
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    serveraddr.sin_port = htons(atoi(argv[2]));

    //第三步:给服务器发送客户端的连接请求
    if(connect(sockfd, (struct sockaddr *)&serveraddr, addrlen) == -1)
    {
        ERRLOG("connect error");
    }

    //进行通信
    char buf[N] = {0};
    ssize_t bytes;
    while(1)
    {
        fgets(buf, N, stdin);
        buf[strlen(buf) - 1] = '\0';

        if(send(sockfd, buf, N, 0) == -1)
        {
            ERRLOG("send error");
        }

        if(strcmp(buf, "quit") == 0)
        {
            printf("客户端退出了\n");
            exit(0);
        }

        memset(buf, 0, N);

        if((bytes = recv(sockfd, buf, N, 0)) == -1)
        {
            ERRLOG("recv error");
        }
        else if(bytes == 0)
        {
            printf("长时间不说话,那就结束吧\n");
            exit(0);
        }

        printf("服务器:%s\n", buf);
    }

    return 0;
}