本文全部函數皆是爲實現 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.