摘要:对于服务器编程中最重要的一步等待并接受客户的连接,那么这一步在编程中如何完成,accept函数就是完成这一步的。它从内核中取出已经建立的客户连接,然后把这个已经建立的连接返回给用户程序,此时用户程序就可以与自己的客户进行点到点的通信了。
accept函数等待并接受客户请求:
#include
int accept(int sockfd, struct sockaddr* addr, socklen_t* len)
返回:非负描述字——成功, -1——失败
accept默认会阻塞进程,直到有一个客户连接建立后返回,它返回的是一个新可用的套接字,这个套接字是连接套接字。此时我们需要区分两种套接
字,一种套接字正如accept的参数sockfd,它是监听套接字,在调用listen函数之后,一个套接字会从主动连接的套接字变身为一个监听套接
字;而accept返回是一个连接套接字,它代表着一个网络已经存在的点点连接。自然要问的是:为什么要有两种套接字?原因很简单,如果使用一个描述字的
话,那么它的功能太多,使得使用很不直观,同时在内核确实产生了一个这样的新的描述字。
参数sockfd
参数sockfd就是上面解释中的监听套接字,这个套接字用来监听一个端口,当有一个客户与服务器连接时,它使用这个一个端口号,而此时这个端口号正与这个套接字关联。当然客户不知道套接字这些细节,它只知道一个地址和一个端口号。
参数addr
这是一个结果参数,它用来接受一个返回值,这返回值指定客户端的地址,当然这个地址是通过某个地址结构来描述的,用户应该知道这一个什么样的地址结构。如果对客户的地址不感兴趣,那么可以把这个值设置为NULL。
参数len
如同大家所认为的,它也是结果的参数,用来接受上述addr的结构的大小的,它指明addr结构所占有的字节个数。同样的,它也可以被设置为NULL。
如果accept成功返回,则服务器与客户已经正确建立连接了,此时服务器通过accept返回的套接字来完成与客户的通信。
这周同学们在做网络编程的时候,碰到一个监听套接字的问题,在这里大概描述一下:
比如我的程序开了一个监听端口,与客户端建立连接之后,生成了一个新套接字。这时我执行了只关闭监听端口的语句,结果却
发现监听端口和已建立的连接仍然存在。我都已经关闭了监听套接字,为什么客户端还可以继续往监听端口发信息?这到底是因为什么呢?新套接字和监听套接字有
什么关系呢?
比如,你开了80监听端口,有一个客户连接你accept了,这时关闭80端口。但此时客户端发信息的时候必然是发向
80断口,但是80已经关了啊,但是通信依然正常进行。其实我刚接触套接字的时候也是认为所有从客户端发来的数据都需要经过监听套接字转一下才能收到。所
有的初学者都容易犯这个误解。
经过一段时间的使用,我现在是明白了,监听套接字就是个牵线指路的,你实质上是跟它指的那个人说话。因为你要找的那个人不可能随时等你来,而监听套接字就是专职等你来问,它回答你要找的人在哪,并唤醒你要找的人,于是通话就建立起来了,就像现实生活中的接线员一样。
也就是说,在连接建立后,客户端用发出连接的那个SOCKET向服务器发数据,是发给服务器新创建的SOCKET,而不是服务器的监听SOCKET。服务器的监听SOCKET永远只是用来接受连接请求。
这就好比你去吃饭,饭馆门口有迎宾小姐(监听SOCKET)看到你来后和你打招呼,然后(ACCEPT)找来一个新的服务员(NEW SOCKET)来接待你,然后守在门口继续监听下一个。监听的小姐走了,接待你的服务员当然不受影响。
说到这里有必要说一下accept()函数。以下是《Linux网络编程》一书,第六章 Berkeley套接字对accept()函数的描述:
函数 accept()有一些难懂。当调用它的时候,大致过程是下面这样的:
● 有人从很远很远的地方尝试调用 connect()来连接你的机器上的某个端口(当然是你已经在 listen()的)。
● 他的连接将被 listen 加入等待队列等待 accept()函数的调用(加入等待队列的最多数目由调用 listen()函数的第二个参数 backlog 来决定)。
● 你调用 accept()函数,告诉他你准备连接。
● accept()函数将回返回一个新的套接字描述符,这个描述符就代表了这个连接!
好,这时候你有了两个套接字描述符,返回给你的那个就是和远程计算机的连接,而第一个套接字描述符仍然在你的机器上原来的那个端口上 listen()。
这时候你所得到的那个新的套接字描述符就可以进行 send()操作和recv()操作了。
通过上面的解释,相信您一定已经对监听套接字有了进一步的了解了吧!