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.
原文地址:https://www.cnblogs.com/xfxu/p/4027460.html