从写一个TCP套接字的write调用成功返回仅仅表示我们可以重新使用原来的应用进程缓冲区,并不代表对端TCP或应用进程已接收到数据。
对端TCP必须确认收到的数据,伴随来自对端的ACK的不断到达,本端TCP至此才能从套接字发送缓冲区中丢弃已确认的数据,TCP必须为已发送的数据保留一个副本,直到它被对端确认为止。
UDP不保存应用进程数据的副本因此无需一个真正的发送缓冲区,write调用成功返回表示所写的数据报或其所有分片已被加入数据链路层的输出队列。
对于read调用(套接字标志为阻塞),如果接收缓冲区中有20字节,请求读100个字节,就会返回20。对于write调用(套接字标志为阻塞),如果请求写100个字节,而发送缓冲区中只有20个字节的空闲位置,那么write会阻塞,直到把100个字节全部交给发送缓冲区才返回,如果write中得套接字标志为非阻塞,则直接返回20,因此我们可以实现自己的readn和writen函数。
每个TCP套接字都有一个发送缓冲区和一个接收缓冲区,每个UDP套接字都有一个接收缓冲区;
先模拟服务端阻塞:
client:
struct sockaddr_in serverAdd; bzero(&serverAdd, sizeof(serverAdd)); serverAdd.sin_family = AF_INET; serverAdd.sin_addr.s_addr = inet_addr(SERV_ADDR); serverAdd.sin_port = htons(SERV_PORT); connfd = socket(AF_INET, SOCK_STREAM, 0); int connResult = connect(connfd, (struct sockaddr *)&serverAdd, sizeof(serverAdd)); if (connResult < 0) { printf("连接失败,errno = %d\n",errno); close(connfd); return ; } else { printf("连接成功\n"); } ssize_t writeLen; char sendMsg[246988] = {0}; int count = 0; while (1) { count++; if (count == 5) { exit(0); } writeLen = write(connfd, sendMsg, sizeof(sendMsg)); if (writeLen < 0) { printf("发送失败\n"); close(connfd); return ; } else { printf("发送成功\n"); } }
server:
int main(int argc, const char * argv[]) { struct sockaddr_in serverAdd; struct sockaddr_in clientAdd; bzero(&serverAdd, sizeof(serverAdd)); serverAdd.sin_family = AF_INET; serverAdd.sin_addr.s_addr = htonl(INADDR_ANY); serverAdd.sin_port = htons(SERV_PORT); socklen_t clientAddrLen; int listenfd = socket(AF_INET, SOCK_STREAM, 0); int yes = 1; setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes)); if (listenfd < 0) { printf("创建socket失败\n"); close(listenfd); return -1; } int bindResult = bind(listenfd, (struct sockaddr *)&serverAdd, sizeof(serverAdd)); if (bindResult < 0) { close(listenfd); printf("绑定端口失败,errno = %d\n",errno); return -1; } else { printf("绑定端口成功\n"); } listen(listenfd, 20); int connfd; unsigned char recvMsg[246988]; clientAddrLen = sizeof(clientAdd); connfd = accept(listenfd,(struct sockaddr *)&clientAdd,&clientAddrLen); if (connfd < 0) { close(listenfd); printf("连接失败\n"); return -1; } else { printf("连接成功\n"); int rcvbuf_len; socklen_t len = sizeof(rcvbuf_len); if( getsockopt( connfd, SOL_SOCKET, SO_RCVBUF, (void *)&rcvbuf_len, &len ) < 0 ){ perror("getsockopt: "); } printf("the recv buf len: %d\n", rcvbuf_len ); } ssize_t totalLen = 0; while (1) { sleep(1); ssize_t readLen = read(connfd, recvMsg, sizeof(recvMsg)); printf("readLen:%ld\n",readLen); if (readLen < 0) { printf("读取失败\n"); return -1; } else if (readLen == 0) { printf("读取完成-len = %ld\n",totalLen); close(listenfd); return 0; } else { totalLen += readLen; } } close(connfd); return 0; }
先运行服务器,再运行客户端,打印信息如下
client:
连接成功
发送成功
发送成功
发送成功
发送成功
这里调用write时会阻塞,是因为内核从该应用进程的缓冲区中复制所有数据到所写套接字的发送缓冲区;而由于服务器还未处理完数据,没有回复确认给客户端,客户端的套接字发送缓冲区暂时要保留数据,要是数据满了,write就会休眠等待,直到有可用的空间把要写的数据全部写完才返回;
server:
绑定端口成功
连接成功
the recv buf len: 131768
readLen:246988
readLen:246988
readLen:246988
readLen:246988
readLen:0
读取完成-len = 987952
这里read方法读取的长度和套接字接收缓冲区的长度无关
再屏蔽掉服务端的休眠代码,重新运行,服务端打印信息如下
绑定端口成功
连接成功
the recv buf len: 131768
readLen:1448
readLen:1448
......
readLen:1448
readLen:2896
readLen:5792
readLen:10136
readLen:1448
......
readLen:1448
readLen:1040
readLen:0
读取完成-len = 987952
这里read每次读取的时候有多少就读取多少,不会等待读到想要的长度;
参考:
《UNIX Network ProgrammingVolume 1, Third Edition: TheSockets Networking API》