第 13 章 多種 I/O 函數(shù)
13.1 send & recv 函數(shù)
Linux 中的 send & recv:
?????????send 函數(shù)定義:
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t nbytes, int flags);
/*
成功時(shí)返回發(fā)送的字節(jié)數(shù),失敗時(shí)返回 -1
sockfd: 表示與數(shù)據(jù)傳輸對(duì)象的連接的套接字和文件描述符
buf: 保存待傳輸數(shù)據(jù)的緩沖地址值
nbytes: 待傳輸字節(jié)數(shù)
flags: 傳輸數(shù)據(jù)時(shí)指定的可選項(xiàng)信息
*/
????????recv 函數(shù)的定義:
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t nbytes, int flags);
/*
成功時(shí)返回接收的字節(jié)數(shù)(收到 EOF 返回 0),失敗時(shí)返回 -1
sockfd: 表示數(shù)據(jù)接受對(duì)象的連接的套接字文件描述符
buf: 保存接受數(shù)據(jù)的緩沖地址值
nbytes: 可接收的最大字節(jié)數(shù)
flags: 接收數(shù)據(jù)時(shí)指定的可選項(xiàng)參數(shù)
*/
????????send 和 recv 函數(shù)的最后一個(gè)參數(shù)是收發(fā)數(shù)據(jù)的可選項(xiàng),該選項(xiàng)可以用位或(bit OR)運(yùn)算符(| 運(yùn)算符)同時(shí)傳遞多個(gè)信息。send & recv 函數(shù)的可選項(xiàng)意義:
MSG_OOB:發(fā)送緊急消息?:
????????MSG_OOB 可選項(xiàng)用于創(chuàng)建特殊發(fā)送方法和通道以發(fā)送緊急消息。下面為 MSG_OOB 的示例代碼:
recv:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#define BUF_SIZE 30
void error_handling(char *message);
void urg_handler(int signo);
int acpt_sock;
int recv_sock;
int main(int argc, char *argv[])
{
struct sockaddr_in recv_adr, serv_adr;
int str_len, state;
socklen_t serv_adr_sz;
struct sigaction act;
char buf[BUF_SIZE];
if (argc != 2)
{
printf("Usage : %s <port>\n", argv[0]);
exit(1);
}
act.sa_handler = urg_handler;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
acpt_sock = socket(PF_INET, SOCK_STREAM, 0);
memset(&recv_adr, 0, sizeof(recv_adr));
recv_adr.sin_family = AF_INET;
recv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
recv_adr.sin_port = htons(atoi(argv[1]));
if (bind(acpt_sock, (struct sockaddr *)&recv_adr, sizeof(recv_adr)) == -1)
error_handling("bind() error");
listen(acpt_sock, 5);
serv_adr_sz = sizeof(serv_adr);
recv_sock = accept(acpt_sock, (struct sockaddr *)&serv_adr, &serv_adr_sz);
//文件描述符 recv_sock 指向的套接字引發(fā)的 SIGURG 信號(hào)處理進(jìn)程變?yōu)?getpid 函數(shù)返回值用作 ID 進(jìn)程.
fcntl(recv_sock, F_SETOWN, getpid());
state = sigaction(SIGURG, &act, 0); //SIGURG 是一個(gè)信號(hào),當(dāng)接收到 MSG_OOB 緊急消息時(shí),系統(tǒng)產(chǎn)生SIGURG信號(hào)
while ((str_len = recv(recv_sock, buf, sizeof(buf), 0)) != 0)
{
if (str_len == -1)
continue;
buf[str_len] = 0;
puts(buf);
}
close(recv_sock);
close(acpt_sock);
return 0;
}
void urg_handler(int signo)
{
int str_len;
char buf[BUF_SIZE];
str_len = recv(recv_sock, buf, sizeof(buf) - 1, MSG_OOB);
buf[str_len] = 0;
printf("Urgent message: %s \n", buf);
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
send:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define BUF_SIZE 30
void error_handling(char *message);
int main(int argc, char *argv[])
{
int sock;
struct sockaddr_in recv_adr;
if (argc != 3)
{
printf("Usage : %s <IP> <port>\n", argv[0]);
exit(1);
}
sock = socket(PF_INET, SOCK_STREAM, 0);
memset(&recv_adr, 0, sizeof(recv_adr));
recv_adr.sin_family = AF_INET;
recv_adr.sin_addr.s_addr = inet_addr(argv[1]);
recv_adr.sin_port = htons(atoi(argv[2]));
if (connect(sock, (struct sockaddr *)&recv_adr, sizeof(recv_adr)) == -1)
error_handling("connect() error");
write(sock, "123", strlen("123"));
send(sock, "4", strlen("4"), MSG_OOB);
write(sock, "567", strlen("567"));
send(sock, "890", strlen("890"), MSG_OOB);
close(sock);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
???????
?????????輸出結(jié)果,可能出乎意料:
通過(guò) MSG_OOB 可選項(xiàng)傳遞數(shù)據(jù)時(shí)只返回 1 個(gè)字節(jié),而且也不快
????????的確,通過(guò) MSG_OOB 并不會(huì)加快傳輸速度,而通過(guò)信號(hào)處理函數(shù) urg_handler 也只能讀取一個(gè)字節(jié)。剩余數(shù)據(jù)只能通過(guò)未設(shè)置 MSG_OOB 可選項(xiàng)的普通輸入函數(shù)讀取。因?yàn)?TCP 不存在真正意義上的「外帶數(shù)據(jù)」。實(shí)際上,MSG_OOB 中的 OOB 指的是 Out-of-band ,而「外帶數(shù)據(jù)」的含義是:?
????????通過(guò)完全不同的通信路徑傳輸?shù)臄?shù)據(jù)。
????????即真正意義上的 Out-of-band 需要通過(guò)單獨(dú)的通信路徑高速傳輸數(shù)據(jù),但是 TCP 不另外提供,只利用 TCP 的緊急模式(Urgent mode)進(jìn)行傳輸。
緊急模式工作原理:
????????MSG_OOB 的真正意義在于督促數(shù)據(jù)接收對(duì)象盡快處理數(shù)據(jù)。這是緊急模式的全部?jī)?nèi)容,而 TCP 「保持傳輸順序」的傳輸特性依然成立。下面是 MSG_OOB 可選項(xiàng)狀態(tài)下的數(shù)據(jù)傳輸過(guò)程,如圖:
????????上面是: send(sock, "890", strlen("890"), MSG_OOB);
????????圖上是調(diào)用這個(gè)函數(shù)的緩沖狀態(tài)。如果緩沖最左端的位置視作偏移量 0 。字符 0 保存于偏移量 2 的位置。另外,字符 0 右側(cè)偏移量為 3 的位置存有緊急指針(Urgent Pointer)。緊急指針指向緊急消息的下一個(gè)位置(偏移量加一),同時(shí)向?qū)Ψ街鳈C(jī)傳遞以下信息:
????????緊急指針指向的偏移量為 3 之前的部分就是緊急消息。
????????也就是說(shuō),實(shí)際上只用了一個(gè)字節(jié)表示緊急消息。這一點(diǎn)可以通過(guò)圖中用于傳輸數(shù)據(jù)的 TCP 數(shù)據(jù)包(段)的結(jié)構(gòu)看得更清楚,如圖:
????????TCP 數(shù)據(jù)包實(shí)際包含更多信息。TCP 頭部包含如下兩種信息:
- URG=1:載有緊急消息的數(shù)據(jù)包
- URG指針:緊急指針位于偏移量為 3 的位置。
????????指定 MSG_OOB 選項(xiàng)的數(shù)據(jù)包本身就是緊急數(shù)據(jù)包,并通過(guò)緊急指針表示緊急消息所在的位置。緊急消息的意義在于督促消息處理,而非緊急傳輸形式受限的信息。
檢查輸入緩沖:
????????同時(shí)設(shè)置 MSG_PEEK 選項(xiàng)和 MSG_DONTWAIT 選項(xiàng),以驗(yàn)證輸入緩沖是否存在接收的數(shù)據(jù)。設(shè)置 MSG_PEEK 選項(xiàng)并調(diào)用 recv 函數(shù)時(shí),即使讀取了輸入緩沖的數(shù)據(jù)也不會(huì)刪除。因此,該選項(xiàng)通常與 MSG_DONTWAIT 合作,用于以非阻塞方式驗(yàn)證待讀數(shù)據(jù)存在與否。下面的示例是二者的含義:
peek_recv:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define BUF_SIZE 30
void error_handling(char *message);
int main(int argc, char *argv[])
{
int acpt_sock, recv_sock;
struct sockaddr_in acpt_adr, recv_adr;
int str_len, state;
socklen_t recv_adr_sz;
char buf[BUF_SIZE];
if (argc != 2)
{
printf("Usage : %s <port>\n", argv[0]);
exit(1);
}
acpt_sock = socket(PF_INET, SOCK_STREAM, 0);
memset(&acpt_adr, 0, sizeof(acpt_adr));
acpt_adr.sin_family = AF_INET;
acpt_adr.sin_addr.s_addr = htonl(INADDR_ANY);
acpt_adr.sin_port = htons(atoi(argv[1]));
if (bind(acpt_sock, (struct sockaddr *)&acpt_adr, sizeof(acpt_adr)) == -1)
error_handling("bind() error");
listen(acpt_sock, 5);
recv_adr_sz = sizeof(recv_adr);
recv_sock = accept(acpt_sock, (struct sockaddr *)&recv_adr, &recv_adr_sz);
while (1)
{
//保證就算不存在待讀取數(shù)據(jù)也不會(huì)阻塞
str_len = recv(recv_sock, buf, sizeof(buf) - 1, MSG_PEEK | MSG_DONTWAIT);
if (str_len > 0)
break;
}
buf[str_len] = 0;
printf("Buffering %d bytes : %s \n", str_len, buf);
//再次調(diào)用 recv 函數(shù),這一次沒(méi)有設(shè)置任何可選項(xiàng),所以可以直接從緩沖區(qū)讀出
str_len = recv(recv_sock, buf, sizeof(buf) - 1, 0);
buf[str_len] = 0;
printf("Read again: %s \n", buf);
close(acpt_sock);
close(recv_sock);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
?peek_send:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
void error_handling(char *message);
int main(int argc, char *argv[])
{
int sock;
struct sockaddr_in send_adr;
if (argc != 3)
{
printf("Usage : %s <IP> <port>\n", argv[0]);
exit(1);
}
sock = socket(PF_INET, SOCK_STREAM, 0);
memset(&send_adr, 0, sizeof(send_adr));
send_adr.sin_family = AF_INET;
send_adr.sin_addr.s_addr = inet_addr(argv[1]);
send_adr.sin_port = htons(atoi(argv[2]));
if (connect(sock, (struct sockaddr *)&send_adr, sizeof(send_adr)) == -1)
error_handling("connect() error");
write(sock, "123", strlen("123"));
close(sock);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
運(yùn)行結(jié)果:
????????當(dāng)使用MSG_PEEK
標(biāo)志時(shí),接收函數(shù)會(huì)將數(shù)據(jù)復(fù)制到指定的緩沖區(qū)中,但是這些數(shù)據(jù)仍然保留在套接字的接收緩沖區(qū)中。這意味著,即使再次調(diào)用接收函數(shù),仍然可以讀取相同的數(shù)據(jù)。這對(duì)于某些特定的應(yīng)用場(chǎng)景可能是有用的。????????
????????可以通過(guò)結(jié)果驗(yàn)證,僅發(fā)送了一次的數(shù)據(jù)被讀取了 2 次,因?yàn)榈谝淮握{(diào)用 recv 函數(shù)時(shí)設(shè)置了 MSG_PEEK 可選項(xiàng)。?
13.2 readv & writev 函數(shù)
?使用 readv & writev 函數(shù):
????????readv & writev 函數(shù)的功能可概括如下:
????????對(duì)數(shù)據(jù)進(jìn)行整合傳輸及發(fā)送的函數(shù)。
????????也就是說(shuō),通過(guò) writev 函數(shù)可以將分散保存在多個(gè)緩沖中的數(shù)據(jù)一并發(fā)送,通過(guò) readv 函數(shù)可以由多個(gè)緩沖分別接收。因此,適用這 2 個(gè)函數(shù)可以減少 I/O 函數(shù)的調(diào)用次數(shù)。下面先介紹 writev 函數(shù):
#include <sys/uio.h>
ssize_t writev(int filedes, const struct iovec *iov, int iovcnt);
/*
成功時(shí)返回發(fā)送的字節(jié)數(shù),失敗時(shí)返回 -1
filedes: 表示數(shù)據(jù)傳輸對(duì)象的套接字文件描述符。但該函數(shù)并不僅限于套接字,因此,可以像 read 一樣向向其傳遞文件或標(biāo)準(zhǔn)輸出描述符.
iov: iovec 結(jié)構(gòu)體數(shù)組的地址值,結(jié)構(gòu)體 iovec 中包含待發(fā)送數(shù)據(jù)的位置和大小信息
iovcnt: 向第二個(gè)參數(shù)傳遞數(shù)組長(zhǎng)度
*/
????????上述第二個(gè)參數(shù)中出現(xiàn)的數(shù)組 iovec 結(jié)構(gòu)體的聲明如下:
struct iovec
{
void *iov_base; //緩沖地址
size_t iov_len; //緩沖大小
};
????????下圖是該函數(shù)的使用方法:
?
????????writev 的第一個(gè)參數(shù),是文件描述符,因此向控制臺(tái)輸出數(shù)據(jù),ptr 是存有待發(fā)送數(shù)據(jù)信息的 iovec 數(shù)組指針。第三個(gè)參數(shù)為 2,因此,從 ptr 指向的地址開(kāi)始,共瀏覽 2 個(gè) iovec 結(jié)構(gòu)體變量,發(fā)送這些指針指向的緩沖數(shù)據(jù)。
????????下面是 writev 函數(shù)的使用示例:
#include <stdio.h>
#include <sys/uio.h>
int main(int argc, char *argv[])
{
struct iovec vec[2];
char buf1[] = "ABCDEFG";
char buf2[] = "1234567";
int str_len;
vec[0].iov_base = buf1;
vec[0].iov_len = 3;
vec[1].iov_base = buf2;
vec[1].iov_len = 4;
str_len = writev(1, vec, 2);
puts("");
printf("Write bytes: %d \n", str_len);
return 0;
}
?運(yùn)行結(jié)果:
????????下面介紹 readv 函數(shù),功能和 writev 函數(shù)正好相反.函數(shù)為:?
#include <sys/uio.h>
ssize_t readv(int filedes, const struct iovc *iov, int iovcnt);
/*
成功時(shí)返回接收的字節(jié)數(shù),失敗時(shí)返回 -1
filedes: 表示數(shù)據(jù)傳輸對(duì)象的套接字文件描述符。但該函數(shù)并不僅限于套接字,因此,可以像 write 一樣向向其傳遞文件或標(biāo)準(zhǔn)輸出描述符.
iov: iovec 結(jié)構(gòu)體數(shù)組的地址值,結(jié)構(gòu)體 iovec 中包含待數(shù)據(jù)保存的位置和大小信息
iovcnt: 第二個(gè)參數(shù)中數(shù)組的長(zhǎng)度
*/
????????下面是示例代碼:
#include <stdio.h>
#include <sys/uio.h>
#define BUF_SIZE 100
int main(int argc, char *argv[])
{
struct iovec vec[2];
char buf1[BUF_SIZE] = {
0,
};
char buf2[BUF_SIZE] = {
0,
};
int str_len;
vec[0].iov_base = buf1;
vec[0].iov_len = 5;
vec[1].iov_base = buf2;
vec[1].iov_len = BUF_SIZE;
str_len = readv(0, vec, 2);
printf("Read bytes: %d \n", str_len);
printf("First message: %s \n", buf1);
printf("Second message: %s \n", buf2);
return 0;
}
運(yùn)行結(jié)果:
?
????????從圖上可以看出,首先截取了長(zhǎng)度為 5 的數(shù)據(jù)輸出,然后再輸出剩下的。?
合理使用 readv & writev 函數(shù):
????????實(shí)際上,能使用該函數(shù)的所有情況都適用。例如,需要傳輸?shù)臄?shù)據(jù)分別位于不同緩沖(數(shù)組)時(shí),需要多次調(diào)用 write 函數(shù)。此時(shí)可通過(guò) 1 次 writev 函數(shù)調(diào)用替代操作,當(dāng)然會(huì)提高效率。同樣,需要將輸入緩沖中的數(shù)據(jù)讀入不同位置時(shí),可以不必多次調(diào)用 read 函數(shù),而是利用 1 次 readv 函數(shù)就能大大提高效率。
????????其意義在于減少數(shù)據(jù)包個(gè)數(shù)。假設(shè)為了提高效率在服務(wù)器端明確禁用了 Nagle 算法。其實(shí) writev 函數(shù)在不采用 Nagle 算法時(shí)更有價(jià)值,如圖:
?????????上述示例中待發(fā)送的數(shù)據(jù)分別存在3個(gè)不同的地方,此時(shí)如果使用write函數(shù)則需要3次函數(shù)調(diào)用。.但若為提高速度而關(guān)閉了Nagle算法,則極有可能通過(guò)3個(gè)數(shù)據(jù)包傳遞數(shù)據(jù)。反之,若使用writev函數(shù)將所有數(shù)據(jù)一次性寫(xiě)入輸出緩沖,則很有可能僅通過(guò)1個(gè)數(shù)據(jù)包傳輸數(shù)據(jù)。所以writev函數(shù)和readv函數(shù)非常有用。
習(xí)題:
1、利用 readv & writev 函數(shù)收發(fā)數(shù)據(jù)有何優(yōu)點(diǎn)?分別從函數(shù)調(diào)用次數(shù)和 I/O 緩沖的角度給出說(shuō)明。
????????需要傳輸?shù)臄?shù)據(jù)分別位于不同緩沖(數(shù)組)時(shí),需要多次調(diào)用 write 函數(shù)。此時(shí)可通過(guò) 1 次 writev 函數(shù)調(diào)用替代操作,當(dāng)然會(huì)提高效率。同樣,需要將輸入緩沖中的數(shù)據(jù)讀入不同位置時(shí),可以不必多次調(diào)用 read 函數(shù),而是利用 1 次 readv 函數(shù)就能大大提高效率。????????
2、通過(guò) recv 函數(shù)驗(yàn)證輸入緩沖中是否存在數(shù)據(jù)時(shí)(確認(rèn)后立即返回時(shí)),如何設(shè)置 recv 函數(shù)最后一個(gè)參數(shù)中的可選項(xiàng)?分別說(shuō)明各可選項(xiàng)的含義。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-615994.html
????????使用 MSG_PEEK 來(lái)驗(yàn)證輸入緩沖中是否存在待接收的數(shù)據(jù)。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-615994.html
到了這里,關(guān)于《TCP IP網(wǎng)絡(luò)編程》第十三章的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!