您的位置:首页 > 理论基础 > 计算机网络

基本TCP套接字编程

2015-08-30 18:12 603 查看
这一章是围绕下面这个图展开介绍的:



所有客户和服务器都从调用socket开始,它返回一个套接字描述符。客户随后调用connect,服务器则调用bind、listen和accept。套接字通常使用标准的close函数关闭。

个人认为重要的内容:1)TCP为监听套接字维护的两个队列;2)监听套接字与已连接套接字;3)并发服务器。

TCP为监听套接字维护的两个队列

未完成连接队列

每个这样的SYN分节对应其中一项:已由某个客户发出并到达服务器,而服务器正在等待完成相应的TCP三路握手过程。这些套接字处于SYN_RCVD状态。


已完成连接队列

每个已完成TCP三路握手过程的客户对应其中一项。这些套接字处于ESTABLISHED状态。


下图描绘了监听套接字的这两个队列:



每当在未完成连接队列中创建一项时,来自监听套接字的参数就复制到即将建立的连接中。连接的创建机制是完全自动的,无需服务器进程插手。下图展示了用这两个队列建立连接时所交换的分组:



当一个客户SYN到达时,若这些队列是满的,TCP就忽略该分节,也就是不发送RST(表示复位)。这么做是因为:这种情况是暂时的,客户TCP将重发SYN,期望不就就能在这些队列中找到可用空间。要是服务器TCP立即响应一个RST,客户的connect调用就会立即返回一个错误,强制应用进程处理这种情况,而不是让TCP的正常重传机制来处理。另外,客户无法区别响应SYN的RST究竟意味着“该端口没有服务器在监听”,还是意味着“该端口有服务器在监听,不过它的队列满了”。

监听套接字与已连接套接字

accept函数由TCP服务器调用,用于从已完成连接队列队头返回下一个已完成连接。如果已完成连接队列为空,那么进程被投入睡眠(假定套接字为默认的阻塞方式)。

int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);


如果accept成功,那么其返回值是由内核自动生成的一个全新描述符,代表与所返回客户的TCP连接。accept函数的第一个参数即为监听套接字描述符,其返回值即为已连接套接字描述符。

区分这两个套接字非常重要。一个服务器通常仅仅创建一个监听套接字,它在服务器的生命期内一直存在。内核为每个由服务器进程接受的客户连接创建一个已连接套接字(也就是说对于它的TCP三路握手过程已经完成)。当服务器完成对某个给定客户的服务时,相应的已连接套接字就被关闭。

并发服务器

当服务一个客户请求可能花费较长时间时,我们并不希望整个服务器被单个客户长期占用,而是希望同时服务多个客户,这个时候迭代服务器(即通过for循环迭代)就没法满足要求了,此时就需要并发服务器了。

Unix中编写并发服务器程序最简单的办法就是fork一个子进程来服务每个客户,下面是一个典型的并发服务器程序的轮廓:

pid_t pid;
int listenfd, connfd;
listenfd = Socket( ... );
/* fill in sockaddr_in{} with servers well-known port */
for ( ; ; ) {
connfd = Accept(listenfd, ... );  /* probably blocks */
if ( (pid == Fork()) == 0) {
Close(listenfd);    /* child closes listening socket */
doit(connfd);       /* process the request */
Close(connfd);      /* done with this client */
exit(0);
}
Close(connfd);          /* parent closes connected socket */
}


为什么父进程对connfd调用close没有终止它与客户的连接呢?一言以蔽之:每个文件或套接字都有一个引用计数。不管是父进程先调的Close还是子进程先调的Close,只有当引用计数为0的时候,该套接字才会正真的清理和释放资源。unp书上有几幅图画的特别清楚:



从accept返回后,立即就有4.15所示的状态。连接被内核接受,新的套接字connfd被创建。这是一个已连接套接字,可由此跨连接读写数据。



接着是调用fork返回后的状态。



注意,此时listenfd和connfd这两个描述符都在父进程和子进程之间共享(被复制)。再下一步是由父进程关闭已连接套接字,由子进程关闭监听套接字。



这是这两个套接字所期望的最终状态。子进程处理与客户的连接,父进程则可以在监听套接字上再次调用accept来处理下一个客户连接。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  TCP套接字 tcp