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,以确定是否存在\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 的问题,在这里我们给解释一下:
该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.