第一节我们已经讲述了TCP的基本客户服务器模型,现在有这样一个问题:当客户端connect成功了,但是服务器在accept函数前阻塞了,会发生什么现象?
将前面服务端代码修改一下,在accept函数之间使进程睡眠。客户端代码不用变动。
/******** 头文件 *********/
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <strings.h>
#include <stdlib.h>
#include <stdio.h>
/******** 宏定义 ********/
#define BUFFSIZE 100
int main()
{
int listenFd, connectFd;
socklen_t len;
int n;
struct sockaddr_in srcAddr, cliAddr;
char buf[BUFFSIZE];
//create socket
if((listenFd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket error");
exit(1);
}
bzero(&srcAddr, sizeof(srcAddr));
srcAddr.sin_family = AF_INET;
srcAddr.sin_port = htons(8888);
srcAddr.sin_addr.s_addr = htonl(INADDR_ANY);
//绑定套接字
if(bind(listenFd, (struct sockaddr*)&srcAddr, sizeof(srcAddr)) == -1)
{
perror("bind error");
exit(1);
}
//监听套接字
if(listen(listenFd, 5) == -1)
{
perror("listen error");
exit(1);
}
len = sizeof(cliAddr);
printf("listening ...\n");
/* 在这里使accept阻塞 */
printf("sleeping...\n");
sleep(30);
printf("sleep finished\n");
while(1)
{
//从已完成队列中抓取一个套接字进行服务
if((connectFd = accept(listenFd, (struct sockaddr*)&cliAddr, &len)) == -1)
{
perror("accept error");
exit(1);
}
printf("accepted successful\n");
//从套接字中读取从客户端发来的数据
while((n = read(connectFd, buf, BUFFSIZE)) > 0)
{
if(buf[n] != '\0')
{
buf[n] = '\0';
}
printf("receive a message: %s", buf);
//将读取的数据写进套接字,发给客户端
if(write(connectFd, buf, n) < 0)
{
perror("write error");
exit(1);
}
}
}
//进程退出后,所有打开的文件描述符都会被关闭,因此打开的套接字文件也被关闭
return 0;
}
先运行服务端程序,然后运行客户端程序,在睡眠结束之前给服务端发送几条消息。结果发现,睡眠期间服务器没有进行回射,但是睡眠完成后,服务器accept后,将之前发送的几条消息回射给客户端。
为什么会出现这个现象?
在《TCP/IP协议 卷一》的18.11.4节中有这样一句话:“当客户进程的主动打开成功但是服务器的应用层还不知道这个新的连接时,它可能会认为服务器进程已经准备好接受数据了(如果发生这种情况,服务器的TCP仅将接受的数据放入缓冲队列)”。也就是说,对于已经完成三次握手的套接字连接,虽然应用层还没有accept,但是客户端发来的数据会被操作系统缓存,待到该套接字被accept后,将缓存的数据发给该程序进行处理。而且数据是按照客户端发送的批次,一次次发送的。