網路程式設計常用函式的封裝
阿新 • • 發佈:2022-05-08
1.socket通訊函式的封裝
#include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <sys/socket.h> #include <arpa/inet.h> #include <strings.h> void perr_exit(const char *s) { perror(s); exit(-1); } int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr) { int n; again: if ((n = accept(fd, sa, salenptr)) < 0) { if ((errno == ECONNABORTED) || (errno == EINTR))//如果是被訊號中斷和軟體層次中斷,不能退出 goto again; else perr_exit("accept error"); } return n; } int Bind(int fd, const struct sockaddr *sa, socklen_t salen) { int n; if ((n = bind(fd, sa, salen)) < 0) perr_exit("bind error"); return n; } int Connect(int fd, const struct sockaddr *sa, socklen_t salen) { int n; if ((n = connect(fd, sa, salen)) < 0) perr_exit("connect error"); return n; } int Listen(int fd, int backlog) { int n; if ((n = listen(fd, backlog)) < 0) perr_exit("listen error"); return n; } int Socket(int family, int type, int protocol) { int n; if ((n = socket(family, type, protocol)) < 0) perr_exit("socket error"); return n; }
問題:為什麼訊號中斷時,就不能終止讀取?
由於沒有東西可以讀時,就會阻塞在那裡,但是卻由於訊號過來了,本程序要中斷阻塞狀態去處理別的事件。這時候此阻塞狀態就會被打斷。
所以我們應該讓程序處理完中斷訊號後,繼續處於待讀資料的阻塞狀態。
2.黏包
黏包:接收到兩個資料包,但是不清楚從哪開始是一個數據包,從哪開始是另一個數據包。
黏包解決方法:
- 約定好,一次傳送固定的位元組數
- 資料的結尾加一個標記
- 頭部加上資料的大小
要實現上述功能,就有必要實現讀取固定位元組大小的函式,如下所示:
#include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <sys/socket.h> #include <arpa/inet.h> #include <strings.h> // 從fd中讀取nbytes個位元組,並用ptr指向 ssize_t Read(int fd, void *ptr, size_t nbytes) { ssize_t n; again: if ( (n = read(fd, ptr, nbytes)) == -1) { if (errno == EINTR)//如果是被訊號中斷,不應該退出 goto again; else return -1; } return n; } // 向fd中寫入nbytes個位元組,並用ptr指向帶寫入資料 ssize_t Write(int fd, const void *ptr, size_t nbytes) { ssize_t n; again: if ( (n = write(fd, ptr, nbytes)) == -1) { if (errno == EINTR) goto again; else return -1; } return n; } int Close(int fd) { int n; if ((n = close(fd)) == -1) perr_exit("close error"); return n; } /*應該讀取固定的位元組數資料*/ ssize_t Readn(int fd, void *vptr, size_t n) { size_t nleft; //usigned int 剩餘未讀取的位元組數 ssize_t nread; //int 實際讀到的位元組數 char *ptr; ptr = vptr; // 當前讀取的位置 nleft = n; while (nleft > 0) { if ((nread = read(fd, ptr, nleft)) < 0) { if (errno == EINTR) nread = 0; else return -1; } else if (nread == 0) break; nleft -= nread; ptr += nread; } return n - nleft; } /*:固定的位元組數資料*/ ssize_t Writen(int fd, const void *vptr, size_t n) { size_t nleft; ssize_t nwritten; const char *ptr; ptr = vptr; nleft = n; while (nleft > 0) { if ( (nwritten = write(fd, ptr, nleft)) <= 0) { if (nwritten < 0 && errno == EINTR) nwritten = 0; else return -1; } nleft -= nwritten; ptr += nwritten; } return n; } static ssize_t my_read(int fd, char *ptr) { static int read_cnt; // 靜態的變數初始化為零 static char *read_ptr; static char read_buf[100]; // 一次行讀一百個位元組,但每次只返回一個位元組 if (read_cnt <= 0) { again: if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) { if (errno == EINTR) goto again; return -1; } else if (read_cnt == 0) // 當read_cnt為零時,說明fd中無資料,只有一個頭部——這說明客戶端傳送了關閉報文 return 0; read_ptr = read_buf; } read_cnt--; *ptr = *read_ptr++; return 1; } // 讀取一行,即遇到\n就結束 ssize_t Readline(int fd, void *vptr, size_t maxlen) // 讀到的資料儲存在vptr中 { ssize_t n, rc; char c, *ptr; ptr = vptr; for (n = 1; n < maxlen; n++) { if ( (rc = my_read(fd, &c)) == 1) { *ptr++ = c; if (c == '\n') break; } else if (rc == 0) { *ptr = 0; return n - 1; } else return -1; } *ptr = 0; // 最後加上個0 return n; }
本部落格完整程式碼見:wrap.c