TCP之函數封裝

本文全部函數皆是爲實現 TCP之簡單回傳(二)   系列所封裝的函數;html

全部函數皆用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

 

具體實現:socket

/.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,以肯定是否存在\n 
        int i;
        for ( i = 0; i < nread; i++) 
        {
        //存在'\n'    
            if(bufp[i] == '\n')
            {
                size_t nsize = i +1; 
                if( readn(fd, bufp, nsize) != nsize)//說明\n前有i個字符
                    ERR_EXIT("readn");
                bufp +=nsize; //重置偏移量
                count +=nsize;//統計讀取個數
                *bufp = 0;
                return count;
            }
        }
        //不存在'\n'
        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 == '\n')//若是該字符爲\n。本次讀取完成
            break;
    }
    *bufp ='\0';//
    return (maxlen- nleft -1);//最大長度 -剩餘的字符 - '\0'
}

readn的返回值:函數

1.小於0,出錯

2.等於0,對方關閉

3.大於0,可是小於count,對方關閉

4.count,表明讀滿count個字節

對於readn函數中的read函數返回值爲0 的問題,在這裏咱們給解釋一下:post

該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.
相關文章
相關標籤/搜索