• TCP之函数封装


    本文所有函数皆是为实现 TCP之简单回传(二)   系列所封装的函数;

    所有函数皆用C语言实现。函数以及注释如下:

    头文件:

    //.h
    #ifndef SYSUTIL_H
    #define SYSUTIL_H
    
    #include <stdint.h>
    #include <sys/types.h>
    void nano_sleep(double val); //实现定时作用
    ssize_t readn(int fd, void *buf, size_t count);//读取真实数据
    ssize_t writen(int fd, const void *buf, size_t count);//写所读来的数据
    ssize_t readline(int fd, void *usrbuf, size_t maxlen);//读数据(解决粘包问题)
    ssize_t readline_slow(int fd, void *usrbuf, size_t maxlen);//读数据--->效率低下
    void send_int32(int sockfd, int32_t val);//发送一个int
    int32_t recv_int32(int sockfd); //接收一个int 为后来的readn读入精准的数据做准备
    
    #endif

    具体实现:

    /.c
    #include "sysutil.h"
    #include <stdint.h>
    #include <stddef.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <netinet/in.h>
    #include <errno.h>
    #define ERR_EXIT(m) 
        do { 
            perror(m);
            exit(EXIT_FAILURE);
        }while(0)
    
    //睡眠时间
    void nano_sleep(double val)
    {   
        struct timespec tv;
        memset(&tv, 0, sizeof(tv));
        tv.tv_sec = val;//取整
        tv.tv_nsec = (val- tv.tv_sec)*1000*1000*1000;
    
        int ret;
        do
        {
            ret = nanosleep(&tv, &tv);
        }while(ret == -1 && errno ==EINTR);//若是被中断信号打断,则继续执行sleep    
    }
    //告诉server,本次发送数据的真实长度val
    void send_int32(int sockfd, int32_t val)
    {
        int32_t tmp = htonl(val);//转化为网络字节序
        if(writen(sockfd, &tmp, sizeof(int32_t)) != sizeof(int32_t))//发送给server
            ERR_EXIT("write");
    }
    //接收一个int型数据,以确保精确接收
    int32_t recv_int32(int sockfd)
    {
        int32_t tmp;
        if(readn(sockfd, &tmp, sizeof(int32_t))!= sizeof(int32_t))
            ERR_EXIT("read");
        return ntohl(tmp);//网络序转化为主机序。
    }
    
    //server读取数据
    ssize_t readn(int fd, void *buf, size_t count)
    {
        size_t nleft = count;  //剩余字符数
        ssize_t nread;//用于返回值
        char *pbuf = (char*)buf;
        
        while(nleft > 0)
        {
            nread = read(fd, pbuf, nleft);//发送数据
            if( nread == -1)
            {
                if(errno == EINTR)//被中断信号打断
                    continue;
                return -1 ; //err
            }else if( nread == 0)
            {
                break; //读完
            }
            nleft = nleft - nread;//剩余字符数
            pbuf = pbuf + nread;//下次的偏移位置 
        }
        return (count-nleft) ;//attentin 两个条件退出循环
    }
    
    //client向server写数据
    ssize_t writen(int fd, const void* buf, size_t count)
    {
        size_t nleft = count ;//剩余字节流
        ssize_t nwrite;//return 
        const char *pbuf =(const char*)buf;
        
        while(nleft > 0)
        {
            nwrite = write( fd, pbuf, nleft);//写数据
            if(nwrite <= 0)//err
            {
                if(nwrite == -1 && errno == EINTR)
                    continue;
                return -1;
            }
            
            nleft = nleft - nwrite;//剩余字节流
            pbuf = pbuf + nwrite;//偏移位置
        }
    
        return count;
    }
    
    //预览内核缓冲区数据
    ssize_t recv_peek(int fd, void *usrbuf, size_t maxlen)
    {
        ssize_t nread;
        do
        {
            nread = recv(fd, usrbuf, maxlen, MSG_PEEK);        
        } 
        while(nread == -1 && errno == EINTR);
        return nread;
    }
    
    ssize_t readline(int fd, void *usrbuf, size_t maxlen)
    {
        char *bufp = (char *)usrbuf;
        size_t nleft = maxlen - 1;
        ssize_t count = 0;
    
        ssize_t  nread;
        while(nleft > 0)
        {
            nread = recv_peek(fd, bufp, nleft);//预览内核缓冲区数据
            if( nread <= 0)  //由客户端处理
                return nread;
            //遍历bufp,以确定是否存在
     
            int i;
            for ( i = 0; i < nread; i++) 
            {
            //存在'
    '    
                if(bufp[i] == '
    ')
                {
                    size_t nsize = i +1; 
                    if( readn(fd, bufp, nsize) != nsize)//说明
    前有i个字符
                        ERR_EXIT("readn");
                    bufp +=nsize; //重置偏移量
                    count +=nsize;//统计读取个数
                    *bufp = 0;
                    return count;
                }
            }
            //不存在'
    '
            if( readn(fd, bufp, nread) != nread)
                ERR_EXIT("readn");
            bufp += nread;
            count += nread;
            nleft -=nread;
        }
        *bufp = 0;
        return count;
    }
    
    //按字符读取--->由于每读取一个字符就产生一次系统调用,故效率较低
    ssize_t readline_slow(int fd, void *usrbuf, size_t maxlen)
    {
        char *bufp = (char*)usrbuf;//偏移位置
        ssize_t nread;
        size_t nleft = maxlen -1 ;//剩余字节数
        char ch;//保存每次读取的字符
        while( nleft > 0)//只要还有没读的,就一直循环
        {
            if(-1 == (nread=read(fd, &ch, 1)))//把从fd中读取的字符存进ch中
            {
                if(errno == EINTR)//被中断信号打断
                    continue;
                return -1;//err
            }else if(0 == nread )
                break; //EOF
            
            *bufp = ch;//将读取的字符存进buf
            bufp++;//向前移动
            
            nleft --;//剩余字节数--
            if(ch == '
    ')//如果该字符为
    。本次读取完成
                break;
        }
        *bufp ='';//
        return (maxlen- nleft -1);//最大长度 -剩余的字符 - ''
    }

    readn的返回值:

    1.小于0,出错
    
    2.等于0,对方关闭
    
    3.大于0,但是小于count,对方关闭
    
    4.count,代表读满count个字节

    对于readn函数中的read函数返回值为0 的问题,在这里我们给解释一下:

    该readn函数用于TCP连接之后读取buffer中的数据问题,因此会涉及到监听函数select、poll、epool及其返回值。为了叙述方便,假设服务器为S,客户端为C;
    1、当客户端C关闭写端时,就会向服务器S发送(write)一个长度为0的数据;
    2、服务器的 监听函数 监听到客户端C有消息推送过来,这时就会调用read函数,通过read函数的返回值,就得知客户端C的写端已关闭,因此为EOF;
    EOF总结:
    1、客户端C写端关闭;
    2、服务器监听到客户端C有消息发送过来;
    3、通过read函数的返回值得知,nread=0.
  • 相关阅读:
    C# Socket 实现WebSocket服务器端
    Linux Vi 的使用
    Microsoft Sql Server 2016安装在CentOS7下
    通过反射获取所有继承了某一接口的类
    Windows下常用的100个CMD指令以及常见的操作
    CentOS系统安装遇到的一些问题
    SQL Server 2016最值得关注的10大新特性
    用注册表禁止windows添加新用户
    ASP.NET MVC学习之模型验证详解
    (转)RBAC权限管理
  • 原文地址:https://www.cnblogs.com/xfxu/p/4027460.html
Copyright © 2020-2023  润新知