前面一小节,我们已经写出了TcpServer的构造函数。这个函数的实际作用,就是创建了listen socket(监听嵌套字)。这一节,我们来具体分析这个创建的过程。

       socket和sockaddr的创建是可以相互独立的

       在函数中,我们首先通过socket()系统调用创建了listenSock,然后通过为结构体赋值的方法具体定义了服务器端的sockaddr。(memset()函数的作用是把某个内存段的空间设定为某值,这里是清零。)其他的概念已经在前一小节讲完了。这里需要补充的是说明宏定义INADDR_ANY。这里的意思是使用本机所有可用的IP地址。当然,如果你机器绑定了多个IP地址,你也可以指定使用哪一个。

       数据流简易模型(SOCK_STREAM)

       我们的例子以电话做的比喻,实际上,socket stream模型不完全类似电话,它至少有以下这些特点:

       1、一种持续性的连接。这点跟电话是类似的,也可以想象成流动着液体的水管。一旦断开,这种流动就会中断。

       2、数据包的发送实际上是非连续的。这个世界上有什么事物是真正的线性连续的?呵呵,扯远了,这貌似一个哲学问题。我们仅仅需要知道的是,一个数据包不可能是无限大的,所以,总是一个小数据包一个小数据包这样的发送的。这一点,又有点像邮包的传递。这些数据包到达与否,到达的先后次序本身是无法保证的,即是说,是IP协议无法保证的。但是stream形式的TCP协议,在IP之上,做了一定到达和到达顺序的保证。

       3、传送管道实际上是非封闭的。要不干嘛叫“网络”-_-!!!。我们之所以能保证数据包的“定点”传送,完全是依靠每个数据包都自带了目的地址信息。

       由此可见,虽然socket和sockaddr可以分别创建,并无依赖关系。但是在实际使用的时候,一个socket至少会绑定一个本机的sockaddr,没有自己的“地址信息”,就不能接受到网络上的数据包(至少在TCP协议里面是这样的)。

       socket与本机sockaddr的绑定

       有时候绑定是系统的任务,特别是当你不需要知道自己的IP地址和所使用的端口号的时候。但是,我们现在是建立服务器,你必须告诉客户端你的连接信息:IP和Port。所以,我们需要指明IP和Port,然后进行绑定。

C++代码
  1. int bind(int socket, struct sockaddr* localAddress, unsigned int addressLength);  

       作为C++的程序员,也许你会觉得这个函数很不友好,它似乎更应该写成:

C++代码
  1. int bind_cpp_style(int socket, const sockaddr& localAddress);  

       我们需要通过函数原型指明两点:

       1、我们仅仅使用sockaddr结构的数据,但并不会对原有的数据进行修改;

       2、我们使用的是完整的结构体,而不仅仅是这个结构体的指针。(很显然光用指针是无法说明结构体大小的)

       幸运的是,在Linux的实现中,这个函数已经被写为:

C++代码
  1. #include <sys/socket.h>  
  2.   
  3. /* Give the socket FD the local address ADDR (which is LEN bytes long).  */  
  4. extern int bind (int __fd, __CONST_SOCKADDR_ARG __addr, socklen_t __len)  
  5.      __THROW;  

       看到亲切的const,我们就知道这个指针带入是没有“副作用”的。

       监听:listen()

       stream流模型形式上是一种“持续性”的连接,这就是要求信息的流动是“可来可去”的。也就是说,stream流的socket除了绑定本机的sockaddr,还应该拥有对方sockaddr的信息。在listen()中,这“对方的sockaddr”就可以不是某一个特定的sockaddr。实际上,listen socket的目的是准备被动的接受来自“所有”sockaddr的请求。所以,listen()反而就不能指定某个特定的sockaddr。

C++代码
  1. int listen(int socket, int queueLimit);  

       其中第二个参数是等待队列的限制,一般设置在5-20。Linux中实现为:

C++代码
  1. #include <sys/socket.h>  
  2.   
  3. /* Prepare to accept connections on socket FD. 
  4.    N connection requests will be queued before further requests are refused. 
  5.    Returns 0 on success, -1 for errors.  */  
  6. extern int listen (int __fd, int __n) __THROW;  

       完成了这一步,回到我们的例子,就像是让你小弟在电话机前做好了接电话的准备工作。需要再次强调的是,这些行为仅仅是改变了socket的状态,实际上我想强调的是,为什么这些函数不会造成block(阻塞)的原因。(block的概念以后再解释)

除非特别注明,鸡啄米文章均为原创
转载请标明本文地址:http://www.jizhuomi.com/software/407.html
2015年6月19日
作者:鸡啄米 分类:软件开发 浏览: 评论:2