摘要
本文主要是通过实验模拟一下 scoket 缓冲区的发送和接收过程,以证明 write,read 操作的是 socket 缓冲区而不是等对端处理完才返回,本质上这也是利用缓存提高性能的一种思路。
基本概念
发送缓冲区
当 TCP 三次握手成功,TCP 连接成功建立后,操作系统内核会为每一个连接发送缓冲区。当我们调用 write 时数据会从应用程序缓冲区拷贝到内核 socket 缓冲。
接收缓冲区
当服务端调用 read 函数进行读取数据时候其实读取的是 socket 缓冲里面的数据
实验
服务端
int main(int argc, char **argv)
{
int listenfd, connfd;
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
// IPV4
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(12345);
/* bind 到本地地址,端口为 12345 */
bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
/* listen 的 backlog 为 1024 */
listen(listenfd, 1024);
/* 循环处理用户请求 */
for ( ; ; ) {
clilen = sizeof(cliaddr);
connfd = accept(listenfd, (SA *) &cliaddr, &clilen);
read_data(connfd); /* 读取数据 */
close(connfd); /* 关闭连接套接字,注意不是监听套接字 */
}
}
void read_data(int sockfd)
{
ssize_t n;
char buf[1024];
int time = 0;
for ( ; ; ) {
fprintf(stdout, "block in read\n");
// 等于0说明对端发送了 EOF 包,数据读取结束
if ( (n = Readn(sockfd, buf, 1024)) == 0)
return; /* connection closed by other end */
time ++;
fprintf(stdout, "1K read for %d \n", time);
// sleep 模拟程序处理时间
usleep(1000);
}
}
客户端
int main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr;
if (argc != 2)
err_quit("usage: tcpclient <IPaddress>");
// 创建了 socket 套接字,调用 connect 向对应服务器端发起连接请求。
sockfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
// 建立链接,发起三次握手
connect(sockfd, (SA *) &servaddr, sizeof(servaddr));
// 发送数据
send_data(stdin, sockfd);
exit(0);
}
# define MESSAGE_SIZE 10240000
void send_data(FILE *fp, int sockfd)
{
char * query;
query = malloc(MESSAGE_SIZE+1);
for(int i=0; i< MESSAGE_SIZE; i++){
query[i] = 'a';
}
query[MESSAGE_SIZE] = '\0';
const char *cp;
cp = query;
remaining = strlen(query);
while (remaining) {
n_written = send(sockfd, cp, remaining, 0);
fprintf(stdout, "send into buffer %ld \n", n_written);
if (n_written <= 0) {
perror("send");
return;
}
remaining -= n_written;
cp += n_written;
}
return;
}
执行以上代码
实验一:
启动服务端,调用客户端进行数据发送,我们可以观察到客户端发送完成,服务端立马输出了所有数据
实验二:
把服务端 sleep 时间调大,然后重复实验一。我们会发现客户端已经结束了,服务端还在不断的输出数据,这进一步证明了客户端发送到 socket 缓冲之后就结束了,服务端还在不断的从 socket 缓冲读取数据。
结束语
本文的代码是极客时间网络编程作者的代码,地址:yolanda,可以去这个仓库找到对应的源码并进行实验。计算机的很多的概念我们一听就会,一做就废。软件工程本质上是一门实践科学,我们要多动手,多做实验,才能对事物本身有更深的理解,祝大家变的更强。
Reference
- https://dlonng.com/posts/tcp_udp
- https://time.geekbang.org/column/intro/100032701
- https://github.com/froghui/yolanda
- https://zhuanlan.zhihu.com/p/372798949