《Unix網路程式設計》卷1:套接字聯網API(第3版):簡介、傳輸層、套接字程式設計
阿新 • • 發佈:2019-01-07
全書共31章+附錄。
計劃安排:吃透這本書,一天三章+原始碼,並實測程式碼做當天筆記,CSDN見。
時間安排:計劃時間1.5個月 == 6個週末 == 12天。
2017.08.05 第01-03章:TCP/IP簡介、傳輸層、套接字程式設計簡介
2017.08.06 第04-06章:基本TCP程式設計、TCP客戶端/伺服器程式、I/O複用
2017.08.12 第07-09章:套接字選項、基本UDP程式設計、基本SCTP程式設計
2017.08.13 第10-12章:SCTP客戶端/伺服器程式例子、名字與地址互換、IPv4和IPv6互操作性
2017.08.19 第13-15章:守護程序和inetd超級伺服器、高階I/O、Unix域協議
2017.08.20 第16-18章:非阻塞I/O、ioctl操作、路由套接字
2017.08.26 第19-21章:金鑰管理套接字、廣播、多播
2017.08.27 第22-24章:高階UDP程式設計、高階SCTP程式設計、帶外資料
2017.09.02 第25-27章:訊號驅動I/O、執行緒、IP選項
2017.09.03 第28-30章:原始套接字、資料鏈路訪問、客戶端/伺服器程式設計正規化
2017.09.09 第31章-附錄:流。附錄:IPv4/6協議、除錯技術
2017.09.10 整理、總結:思維導圖。
網路應用系統主要構成有兩部分:客戶端(client)和伺服器(server)。
舉例來說:web伺服器程式時一個長時間執行的守護程式,web客戶與伺服器之間使用TCP通訊,TCP轉而使用IP通訊,IP通過乙太網驅動程式的資料鏈路層通訊。
客戶端和伺服器通常是使用者程序,而TCP和IP協議通常是核心中"協議棧"的一部分。
LAN:區域網(內網)
WAN:廣域網(外網)
路由器是廣域網的架構裝置。當下最大的廣域網是因特網internet。
【Tips】呼叫sprintf無法檢查目的緩衝區是否溢位,相反,snprintf要求其第二個引數指定目的緩衝區的大小,因此可以確保該緩衝區不溢位。get√
許多網路入侵是由黑客通過傳送資料,導致伺服器對sprintf的呼叫使其緩衝區溢位而發生的,必須小心使用的函式還有gets/strcat/strcpy,通常應分別改為呼叫fgets/strncat/strncpy,更好的替代函式還有strlcat/strlcpy可以確保結果是正確終止的字串。
OSI模型 open systems interconnection
全稱:計算機通訊開放系統互連模型。
物理層/資料鏈路層:主要是裝置驅動和網路硬體,通常我們不必關心。
網路層:由IPv4和IPv6這兩個協議處理。詳細在附錄A中。
傳輸層:即本書所講的套接字程式設計介面,從應用層(上3層)進入傳輸層的介面。
應用層/會話層/表示層:統稱為應用層,如web客戶端(瀏覽器)、telnet客戶端、web伺服器、FTP伺服器等。
網路細節的兩個基本命令:netstat / ifconfig
netstat
(1)netstat -ni // 提供網路介面資訊,-n輸出數值地址而不是反向解析為名字
$ netstat -ni
Kernel Interface table
Iface MTU Met RX-OK RX-ERR RX-DRP RX-OVR TX-OK TX-ERR TX-DRP TX-OVR Flg
eth0 1500 0 15459 0 0 0 10444 0 0 0 BMRU
lo 16436 0 138 0 0 0 138 0 0 0 LRU
lo 環回介面
eth0 乙太網介面
(2)netstat -nr // 展示路由表資訊,另一種確定介面的方法
$ netstat -nr
核心 IP 路由表
Destination Gateway Genmask Flags MSS Window irtt Iface
0.0.0.0 192.168.31.1 0.0.0.0 UG 0 0 0 eth0
169.254.0.0 0.0.0.0 255.255.0.0 U 0 0 0 eth0
192.168.31.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0
(3)ifconfig eth0 // 獲得eth0乙太網介面的詳細資訊
$ ifconfig eth0
eth0 Link encap:乙太網 硬體地址 00:0c:29:55:a0:99
inet 地址:192.168.31.205 廣播:192.168.31.255 掩碼:255.255.255.0
inet6 地址: fe80::20c:29ff:fe55:a099/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 躍點數:1
接收資料包:15624 錯誤:0 丟棄:0 過載:0 幀數:0
傳送資料包:10571 錯誤:0 丟棄:0 過載:0 載波:0
碰撞:0 傳送佇列長度:1000
接收位元組:1468669 (1.4 MB) 傳送位元組:1070042 (1.0 MB)
中斷:19 基本地址:0x2000
// MULTICAST 標誌通常指明該介面所在主機支援多播。
(4)ping // 測試ip地址是否聯通當前乙太網絡
$ ping -b 192.168.31.255
PING 192.168.31.255 (192.168.31.255) 56(84) bytes of data.
64 bytes from 192.168.31.255: icmp_req=1 ttl=64 time=0.253 ms
64 bytes from 192.168.31.255: icmp_req=2 ttl=64 time=0.022 ms
64 bytes from 192.168.31.255: icmp_req=3 ttl=64 time=0.029 ms
...
64位體系結構的趨勢
原因之一是:在每個程序內部可以由此使用更長的編址長度(即64位指標),從而可以定址更大的記憶體空間(超過2^32位元組)。
TCP:傳輸控制協議,面相連線,全雙工位元組流。流套接字。關心:確認、超時、重傳等細節。
UDP:使用者資料報協議,無連線協議。資料報套接字。不保證最終達到目的地。
ICMP:網際控制訊息協議,處理路由器和主機之間流通的錯誤和控制訊息。
ARP:地址解析協議,把IPv4地址對映成一個硬體地址。
RARP:反地址解析協議,把一個硬體地址對映成一個IPv4地址。
SCTP(內容略)
TIME_WAIT狀態有兩個存在的理由:
(1)可靠地實現TCP全雙工連線的終止;
(2)允許老的重複分節在網路中消逝。
埠號port:
傳輸層協議都使用16位整數的埠號來區分不同的程序。
0~1023(自控保留) | 1024~49151(已登記) |49152~65535(動態私有)
通訊對socket pair:
每個TCP分節中都有16bit的埠號和32bit的IPv4地址。
【套接字地址結構】
IPv4套接字地址結構:sockaddr_in
#include <netinet/in.h>
struct in_addr {
in_addr_t s_addr; /* 32bit IPv4 address. */
};
struct sockaddr_in {
uint8_t sin_len; /* length of structure (16) */
sa_family_t sin_family; /* AF_INET */
in_port_t sin_port; /* 16bit TCP/UDP port number */
struct in_addr sin_addr; /* 32bit IPv4 address */
char sin_zero[8]; /* unused */
};
通用套接字地址結構:sockaddr
#include <sys/socket.h>
struct sockaddr {
uint8_t sa_len; /* length of structure */
sa_family_t sa_family; /* address family: AF_XXXX value */
char sa_data[14]; /* protocol-specific address */
};
IPv6套接字地址結構:sockaddr_in6
#include <netinet/in.h>
struct in6_addr {
uint8_t s6_addr[16]; /* 128bit IPv6 address */
};
#define SIN6_LEN
struct sockaddr_in6 {
uint8_t sin6_len; /* length of structure (28) */
sa_family_t sin6_family; /* AF_INET6 */
in_port_t sin6_port; /* transport layer port */
uint32_t sin6_flowinfo; /* flow information, undefined */
struct in6_addr sin6_addr; /* IPv6 address */
uint32_t sin6_scope_id; /* set of interfaces for a scope */
};
新的通用套接字地址結構:sockaddr_storage
#include <netinet/in.h>
struct sockaddr_strorage {
uint8_t ss_len; /* length of structure */
sa_family_t ss_family; /* address family: AF_XXXX value */
};
位元組序轉換介面函式:大小端位元組序轉換
#include <netinet/in.h>
uint16_t htons (uint16_t host16bitvalue);
uint32_t htonl (uint32_t host32bitvalue); // 此2個函式均返回:網路位元組序的值
uint16_t ntohs (uint16_t net16bitvalue);
uint32_t ntohl (uint32_t net32bitvalue); // 此2個函式均返回:主機位元組序的值
介面特點:h代表host,n代表network,s代表short,l代表long。
位元組操縱介面函式:
#include <string.h>
void bzero (void *dest, size_t nbytes); // 將目標位元組串中指定數目的位元組置0
void bcopy (const void *src, void *dest, size_t nbytes);
int bcmp (const void *ptr1, const void *ptr2, size_t nbutes);
#include <string.h> // ANSI C函式
void *memset (void *dest, int c, size_t len); // 將目標自揭穿指定數目位元組置c
void *memcpy (void *dest, const void *src, size_t nbytes);
int memcmp (const void *ptr1, const void *ptr2, size_t nbytes);
介面特點:b代表位元組,mem代表記憶體。
地址轉換函式:
#include <arpa/inet.h>
int inet_aton (const char *strptr, struct in_addr *addrptr); // 返回值:字串有效為1,否則為0
in_addr_t inet_addr (const char *strptr); // 字串有效則為32位二進位制網路位元組序的IPv4地址,否則為INADDR_NONE
char *inet_ntoa (struct in_addr inaddr); // 指向一個點分十進位制數字符串的指標
/* 隨IPv6出現的新函式×2 */
int inet_pton (int family, const char *strptr, void *addrptr);
const char *inet_ntop (int family, const void *addrptr, char *strptr, size_t len);
【Tips】函式以結構為引數是罕見的,更常見的是以指向結構變數的指標為引數。
2017.08.05
01-03章節完成...
計劃安排:吃透這本書,一天三章+原始碼,並實測程式碼做當天筆記,CSDN見。
時間安排:計劃時間1.5個月 == 6個週末 == 12天。
2017.08.05 第01-03章:TCP/IP簡介、傳輸層、套接字程式設計簡介
2017.08.06 第04-06章:基本TCP程式設計、TCP客戶端/伺服器程式、I/O複用
2017.08.12 第07-09章:套接字選項、基本UDP程式設計、基本SCTP程式設計
2017.08.13 第10-12章:SCTP客戶端/伺服器程式例子、名字與地址互換、IPv4和IPv6互操作性
2017.08.19 第13-15章:守護程序和inetd超級伺服器、高階I/O、Unix域協議
2017.08.20 第16-18章:非阻塞I/O、ioctl操作、路由套接字
2017.08.26 第19-21章:金鑰管理套接字、廣播、多播
2017.08.27 第22-24章:高階UDP程式設計、高階SCTP程式設計、帶外資料
2017.09.02 第25-27章:訊號驅動I/O、執行緒、IP選項
2017.09.03 第28-30章:原始套接字、資料鏈路訪問、客戶端/伺服器程式設計正規化
2017.09.09 第31章-附錄:流。附錄:IPv4/6協議、除錯技術
2017.09.10 整理、總結:思維導圖。
>>第1章丶簡介
網路應用系統主要構成有兩部分:客戶端(client)和伺服器(server)。
舉例來說:web伺服器程式時一個長時間執行的守護程式,web客戶與伺服器之間使用TCP通訊,TCP轉而使用IP通訊,IP通過乙太網驅動程式的資料鏈路層通訊。
客戶端和伺服器通常是使用者程序,而TCP和IP協議通常是核心中"協議棧"的一部分。
LAN:區域網(內網)
WAN:廣域網(外網)
路由器是廣域網的架構裝置。當下最大的廣域網是因特網internet。
/* 一個簡單的時間獲取客戶程式server */ #include <time.h> #include "unp.h" #define MAXLINE 4096 #define LISTENQ 1024 //#define SA struct sockaddr typedef struct sockaddr SA; int main(int argc, char **argv) { int listenfd, connfd; struct sockaddr_in servaddr; char buff[MAXLINE]; time_t ticks; listenfd = socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(1300); /* daytime server */ bind(listenfd, (SA *) &servaddr, sizeof(servaddr)); listen(listenfd, LISTENQ); for ( ; ; ) { connfd = accept(listenfd, (SA *) NULL, NULL); ticks = time(NULL); snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks)); write(connfd, buff, strlen(buff)); close(connfd); } } /* 一個簡單的時間獲取客戶程式client */ #include "unp.h" #define MAXLINE 1024 //#define SA struct sockaddr typedef struct sockaddr SA; int main(int argc, char **argv) { int sockfd, n; char recvline[MAXLINE + 1]; struct sockaddr_in servaddr; if (argc != 2) printf ("usage: a.out <IPaddress>\n"); if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) printf ("socket error\n"); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(1300); /* daytime server */ if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0) printf ("inet_pton error for %s\n", argv[1]); if (connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) < 0) printf ("connect error\n"); while ( (n = read(sockfd, recvline, MAXLINE)) > 0) { recvline[n] = 0; /* null terminate */ if (fputs(recvline, stdout) == EOF) printf ("fputs error\n"); } if (n < 0) printf ("read error\n"); exit(0); } /* unp.h */ #ifndef __UNP_H__ #define __UNP_H__ #include <sys/types.h> /* basic system data types */ #include <sys/socket.h> /* basic socket definitions */ #include <sys/time.h> /* timeval{} for select() */ #include <time.h> /* timespec{} for pselect() */ #include <netinet/in.h> /* sockaddr_in{} and other Internet defns */ #include <arpa/inet.h> /* inet(3) functions */ #include <errno.h> #include <fcntl.h> /* for nonblocking */ #include <netdb.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/stat.h> /* for S_xxx file mode constants */ #include <sys/uio.h> /* for iovec{} and readv/writev */ #include <unistd.h> #include <sys/wait.h> #include <sys/un.h> /* for Unix domain sockets */ #endif //__UNP_H__
/* 上述例子中原書做了比較安全的介面實現,如 Socket() 虛擬碼演示:*/
/* include Socket */
int Socket(int family, int type, int protocol)
{
int n;
if ( (n = socket(family, type, protocol)) < 0)
err_sys("socket error");
return(n);
}
/* err_sys() */
void err_sys(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
err_doit(1, fmt, ap);
va_end(ap);
exit(1);
}
/* err_doit() */
static void err_doit(int errnoflag, const char *fmt, va_list ap)
{
int errno_save;
char buf[MAXLINE];
errno_save = errno; /* value caller might want printed */
vsprintf(buf, fmt, ap);
if (errnoflag)
sprintf(buf+strlen(buf), ": %s", strerror(errno_save));
strcat(buf, "\n");
fflush(stdout); /* in case stdout and stderr are the same */
fputs(buf, stderr);
fflush(stderr); /* SunOS 4.1.* doesn't grok NULL argument */
return;
}
【Tips】呼叫sprintf無法檢查目的緩衝區是否溢位,相反,snprintf要求其第二個引數指定目的緩衝區的大小,因此可以確保該緩衝區不溢位。get√
許多網路入侵是由黑客通過傳送資料,導致伺服器對sprintf的呼叫使其緩衝區溢位而發生的,必須小心使用的函式還有gets/strcat/strcpy,通常應分別改為呼叫fgets/strncat/strncpy,更好的替代函式還有strlcat/strlcpy可以確保結果是正確終止的字串。
OSI模型 open systems interconnection
全稱:計算機通訊開放系統互連模型。
物理層/資料鏈路層:主要是裝置驅動和網路硬體,通常我們不必關心。
網路層:由IPv4和IPv6這兩個協議處理。詳細在附錄A中。
傳輸層:即本書所講的套接字程式設計介面,從應用層(上3層)進入傳輸層的介面。
應用層/會話層/表示層:統稱為應用層,如web客戶端(瀏覽器)、telnet客戶端、web伺服器、FTP伺服器等。
網路細節的兩個基本命令:netstat / ifconfig
netstat
(1)netstat -ni // 提供網路介面資訊,-n輸出數值地址而不是反向解析為名字
$ netstat -ni
Kernel Interface table
Iface MTU Met RX-OK RX-ERR RX-DRP RX-OVR TX-OK TX-ERR TX-DRP TX-OVR Flg
eth0 1500 0 15459 0 0 0 10444 0 0 0 BMRU
lo 16436 0 138 0 0 0 138 0 0 0 LRU
lo 環回介面
eth0 乙太網介面
(2)netstat -nr // 展示路由表資訊,另一種確定介面的方法
$ netstat -nr
核心 IP 路由表
Destination Gateway Genmask Flags MSS Window irtt Iface
0.0.0.0 192.168.31.1 0.0.0.0 UG 0 0 0 eth0
169.254.0.0 0.0.0.0 255.255.0.0 U 0 0 0 eth0
192.168.31.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0
(3)ifconfig eth0 // 獲得eth0乙太網介面的詳細資訊
$ ifconfig eth0
eth0 Link encap:乙太網 硬體地址 00:0c:29:55:a0:99
inet 地址:192.168.31.205 廣播:192.168.31.255 掩碼:255.255.255.0
inet6 地址: fe80::20c:29ff:fe55:a099/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 躍點數:1
接收資料包:15624 錯誤:0 丟棄:0 過載:0 幀數:0
傳送資料包:10571 錯誤:0 丟棄:0 過載:0 載波:0
碰撞:0 傳送佇列長度:1000
接收位元組:1468669 (1.4 MB) 傳送位元組:1070042 (1.0 MB)
中斷:19 基本地址:0x2000
// MULTICAST 標誌通常指明該介面所在主機支援多播。
(4)ping // 測試ip地址是否聯通當前乙太網絡
$ ping -b 192.168.31.255
PING 192.168.31.255 (192.168.31.255) 56(84) bytes of data.
64 bytes from 192.168.31.255: icmp_req=1 ttl=64 time=0.253 ms
64 bytes from 192.168.31.255: icmp_req=2 ttl=64 time=0.022 ms
64 bytes from 192.168.31.255: icmp_req=3 ttl=64 time=0.029 ms
...
64位體系結構的趨勢
原因之一是:在每個程序內部可以由此使用更長的編址長度(即64位指標),從而可以定址更大的記憶體空間(超過2^32位元組)。
>>第2章丶傳輸層TCP-UDP-SCTP
TCP:傳輸控制協議,面相連線,全雙工位元組流。流套接字。關心:確認、超時、重傳等細節。
UDP:使用者資料報協議,無連線協議。資料報套接字。不保證最終達到目的地。
ICMP:網際控制訊息協議,處理路由器和主機之間流通的錯誤和控制訊息。
ARP:地址解析協議,把IPv4地址對映成一個硬體地址。
RARP:反地址解析協議,把一個硬體地址對映成一個IPv4地址。
SCTP(內容略)
TIME_WAIT狀態有兩個存在的理由:
(1)可靠地實現TCP全雙工連線的終止;
(2)允許老的重複分節在網路中消逝。
埠號port:
傳輸層協議都使用16位整數的埠號來區分不同的程序。
0~1023(自控保留) | 1024~49151(已登記) |49152~65535(動態私有)
通訊對socket pair:
每個TCP分節中都有16bit的埠號和32bit的IPv4地址。
>>第3章丶套接字程式設計簡介
【套接字地址結構】
IPv4套接字地址結構:sockaddr_in
#include <netinet/in.h>
struct in_addr {
in_addr_t s_addr; /* 32bit IPv4 address. */
};
struct sockaddr_in {
uint8_t sin_len; /* length of structure (16) */
sa_family_t sin_family; /* AF_INET */
in_port_t sin_port; /* 16bit TCP/UDP port number */
struct in_addr sin_addr; /* 32bit IPv4 address */
char sin_zero[8]; /* unused */
};
通用套接字地址結構:sockaddr
#include <sys/socket.h>
struct sockaddr {
uint8_t sa_len; /* length of structure */
sa_family_t sa_family; /* address family: AF_XXXX value */
char sa_data[14]; /* protocol-specific address */
};
IPv6套接字地址結構:sockaddr_in6
#include <netinet/in.h>
struct in6_addr {
uint8_t s6_addr[16]; /* 128bit IPv6 address */
};
#define SIN6_LEN
struct sockaddr_in6 {
uint8_t sin6_len; /* length of structure (28) */
sa_family_t sin6_family; /* AF_INET6 */
in_port_t sin6_port; /* transport layer port */
uint32_t sin6_flowinfo; /* flow information, undefined */
struct in6_addr sin6_addr; /* IPv6 address */
uint32_t sin6_scope_id; /* set of interfaces for a scope */
};
新的通用套接字地址結構:sockaddr_storage
#include <netinet/in.h>
struct sockaddr_strorage {
uint8_t ss_len; /* length of structure */
sa_family_t ss_family; /* address family: AF_XXXX value */
};
/* 輸出主機位元組序程式 */
#include "unp.h"
int main(int argc, char **argv)
{
union {
short s;
char c[sizeof(short)];
} un;
un.s = 0x0102;
printf("This host byteorder is: ");
if (sizeof(short) == 2) {
if (un.c[0] == 1 && un.c[1] == 2)
printf("big-endian\n");
else if (un.c[0] == 2 && un.c[1] == 1)
printf("little-endian\n");
else
printf("unknown\n");
} else
printf("sizeof(short) = %d\n", sizeof(short));
exit(0);
}
位元組序轉換介面函式:大小端位元組序轉換
#include <netinet/in.h>
uint16_t htons (uint16_t host16bitvalue);
uint32_t htonl (uint32_t host32bitvalue); // 此2個函式均返回:網路位元組序的值
uint16_t ntohs (uint16_t net16bitvalue);
uint32_t ntohl (uint32_t net32bitvalue); // 此2個函式均返回:主機位元組序的值
介面特點:h代表host,n代表network,s代表short,l代表long。
位元組操縱介面函式:
#include <string.h>
void bzero (void *dest, size_t nbytes); // 將目標位元組串中指定數目的位元組置0
void bcopy (const void *src, void *dest, size_t nbytes);
int bcmp (const void *ptr1, const void *ptr2, size_t nbutes);
#include <string.h> // ANSI C函式
void *memset (void *dest, int c, size_t len); // 將目標自揭穿指定數目位元組置c
void *memcpy (void *dest, const void *src, size_t nbytes);
int memcmp (const void *ptr1, const void *ptr2, size_t nbytes);
介面特點:b代表位元組,mem代表記憶體。
地址轉換函式:
#include <arpa/inet.h>
int inet_aton (const char *strptr, struct in_addr *addrptr); // 返回值:字串有效為1,否則為0
in_addr_t inet_addr (const char *strptr); // 字串有效則為32位二進位制網路位元組序的IPv4地址,否則為INADDR_NONE
char *inet_ntoa (struct in_addr inaddr); // 指向一個點分十進位制數字符串的指標
/* 隨IPv6出現的新函式×2 */
int inet_pton (int family, const char *strptr, void *addrptr);
const char *inet_ntop (int family, const void *addrptr, char *strptr, size_t len);
【Tips】函式以結構為引數是罕見的,更常見的是以指向結構變數的指標為引數。
/* 預防萬一,不讓實現返回一個不足的位元組計數值,封裝read和write程式碼: */
ssize_t readn(int fd, void *vptr, size_t n)
{
size_t nleft;
ssize_t nread;
char *ptr;
ptr = vptr;
nleft = n;
while (nleft > 0) {
if ( (nread = read(fd, ptr, nleft)) < 0) {
if (errno == EINTR)
nread = 0; /* and call read() again */
else
return(-1);
} else if (nread == 0)
break; /* EOF */
nleft -= nread;
ptr += nread;
}
return(n - nleft); /* return >= 0 */
}
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; /* and call write() again */
else
return(-1); /* error */
}
nleft -= nwritten;
ptr += nwritten;
}
return(n);
}
2017.08.05
01-03章節完成...