这里的“通讯”加上了引号,是因为实际上所有的socket都有通讯的功能,只是在我们的例子中,之前那个socket只负责listen,而这个socket负责接受信息并echo回去。
我们现看看这个函数:
- bool TcpServer::isAccept()
- {
- unsigned int clntAddrLen = sizeof(clntAddr);
- if ( (communicationSock = accept(listenSock, (sockaddr*)&clntAddr, &clntAddrLen)) < 0 ) {
- return false;
- } else {
- std::cout << "Client(IP: " << inet_ntoa(clntAddr.sin_addr) << ") connected.\n";
- return true;
- }
- }
用accept()创建新的socket
在我们的例子中,communicationSock实际上是用函数accept()创建的。
- int accept(int socket, struct sockaddr* clientAddress, unsigned int* addressLength);
在Linux中的实现为:
- /* Await a connection on socket FD.
- When a connection arrives, open a new socket to communicate with it,
- set *ADDR (which is *ADDR_LEN bytes long) to the address of the connecting
- peer and *ADDR_LEN to the address's actual length, and return the
- new socket's descriptor, or -1 for errors.
- This function is a cancellation point and therefore not marked with
- __THROW. */
- extern int accept (int __fd, __SOCKADDR_ARG __addr,
- socklen_t *__restrict __addr_len);
这个函数实际上起着构造socket作用的仅仅只有第一个参数(另外还有一个不在这个函数内表现出来的因素,后面会讨论到),后面两个指针都有副作用,在socket创建后,会将客户端sockaddr的数据以及结构体的大小传回。
当程序调用accept()的时候,程序有可能就停下来等accept()的结果。这就是我们前一小节说到的block(阻塞)。这如同我们调用std::cin的时候系统会等待输入直到回车一样。accept()是一个有可能引起block的函数。请注意我说的是“有可能”,这是因为accept()的block与否实际上决定与第一个参数socket的属性。这个文件描述符如果是block的,accept()就block,否则就不block。默认情况下,socket的属性是“可读可写”,并且,是阻塞的。所以,我们不修改socket属性的时候,accept()是阻塞的。
accept()的另一面connect()
accept()只是在server端被动的等待,它所响应的,是client端connect()函数:
- int connect(int socket, struct sockaddr* foreignAddress, unsigned int addressLength);
虽然我们这里不打算详细说明这个client端的函数,但是我们可以看出来,这个函数与之前我们介绍的bind()有几分相似,特别在Linux的实现中:
- /* Open a connection on socket FD to peer at ADDR (which LEN bytes long).
- For connectionless socket types, just set the default address to send to
- and the only address from which to accept transmissions.
- Return 0 on success, -1 for errors.
- This function is a cancellation point and therefore not marked with
- __THROW. */
- extern int connect (int __fd, __CONST_SOCKADDR_ARG __addr, socklen_t __len);
connect() 也使用了const的sockaddr,只不过是远程电脑上的而非bind()的本机。
accept()在server端表面上是通过listen socket创建了新的socket,实际上,这种行为是在接受对方客户机程序中connect()函数的请求后发生的。综合起看,被创建的新socket实际上包含了listen socket的信息以及客户端connect()请求中所包含的信息——客户端的sockaddr地址。
新socket与sockaddr的关系
accept()创建的新socket(我们例子中的communicationSock,这里我们简单用newSock来带指)首先包含了listen socket的信息,所以,newSock具有本机sockaddr的信息;其次,因为它响应于client端connect()函数的请求,所以,它还包含了clinet端sockaddr的信息。
我们说过,stream流形式的TCP协议实际上是建立起一个“可来可去”的通道。用于listen的通道,远程机的目标地址是不确定的;但是newSock却是有指定的本机地址和远程机地址,所以,这个socket,才是我们真正用于TCP“通讯”的socket。
inet_ntoa()
- #include <arpa/inet.h>
- /* Convert Internet number in IN to ASCII representation. The return value
- is a pointer to an internal array containing the string. */
- extern char *inet_ntoa (struct in_addr __in) __THROW;
对于这个函数,我们可以作为一种,将IP地址,由in_addr结构转换为可读的ASCII形式的固定用法。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。