ServerSocket接口客户端请求端口问题

今天与同窗争执一个话题:因为socket的accept函数在有客户端链接的时候产生了新的socket用于服务该客户端,那么,这个新的socket到底有没有占用一个新的端口?编程

讨论完后,才发现,本身虽然熟悉socket的编程套路,可是却并非那么清楚socket的原理,今天就趁这个机会,把有关socket编程的几个疑问给搞清楚吧。服务器

   先给出一个典型的TCP/IP通讯示意图。并发

问题一:socket结构体对象到底是怎样定义的?dom

   咱们知道,在使用socket编程以前,须要调用socket函数建立一个socket对象,该函数返回该socket对象的描述符socket

int socket(int domain, int type, int protocol);

那么,这个socket对象到底是怎么定义的呢?它记录了哪些信息呢?只记录了本机IP及端口、仍是目的IP及端口、或者都记录了?async

关于这个问题,你们能够在内核源码里面找,也能够参考这篇文章《struct socket 结构详解》,咱们能够看到socket 结构体的定义以下:函数

struct socket    
{     
    socket_state              state;    
    unsigned long             flags;    
    const struct proto_ops    *ops;    
    struct fasync_struct      *fasync_list;    
    struct file               *file;    
    struct sock               *sk;    
    wait_queue_head_t         wait;    
    short                     type;    
};

其中,structsock 包含有一个 sock_common 结构体,而sock_common结构体又包含有struct inet_sock结构体,而struct inet_sock 结构体的部分定义以下:网站

struct inet_sock    
{     
    struct sock     sk;    
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)    
    struct ipv6_pinfo   *pinet6;    
#endif    
    __u32           daddr;          //IPv4的目的地址。    
    __u32           rcv_saddr;      //IPv4的本地接收地址。    
    __u16           dport;          //目的端口。    
    __u16           num;            //本地端口(主机字节序)。   
      
…………       
}

由此,咱们清楚了,socket结构体不只仅记录了本地的IP和端口号,还记录了目的IP和端口。spa

问题二:connect函数究竟作了些什么操做?code

在TCP客户端,首先调用一个socket()函数,获得一个socket描述符socketfd,而后经过connect函数对服务器进行链接,链接成功后,就能够利用这个socketfd描述符使用send/recv函数收发数据了。

关于connect函数和send函数的原型以下:

int connect( int sockfd, const struct sockaddr* server_addr, socklen_t addrlen)   
   
int send( int sockfd, const void *msg,int len,int flags);

那么,如今的困惑是,为何send函数仅仅传入sockfd就能够知道服务器的ip和端口号?

   其实,由“问题一”中的答案咱们已经很清楚了,sockfd描述符所描述的socket对象不只包含了本地IP和端口,同时也包含了服务器的IP和端口,这样,才能使得send函数只须要传入sockfd便可知道该把数据发向什么地方。而代码中,目的IP和端口只是在connect函数中出现过,所以,确定是connect函数在成功创建链接后,将目的IP和端口写入了sockfd描述符所描述的socket对象中。

问题三:accept函数产生的socket有没有占用新的端口?

   首先,回顾一下accept函数,原型以下:

int accept(int sockfd, struct sockaddr* addr, socklen_t* len)

accept函数主要用于服务器端,通常位于listen函数以后,默认会阻塞进程,直到有一个客户请求链接,创建好链接后,它返回的一个新的套接字socketfd_new ,此后,服务器端便可使用这个新的套接字socketfd_new与该客户端进行通讯,而sockfd则继续用于监听其余客户端的链接请求。

至此,个人困惑产生了,这个新的套接字socketfd_new 与监听套接字sockfd 是什么关系?它所表明的socket对象包含了哪些信息?socketfd_new是否占用了新的端口与客户端通讯?

先简单分析一番,因为网站的服务器也是一种TCP服务器,使用的是80端口,并不会因客户端的链接而产生新的端口给客户端服务,该客户端依然是向服务器端的80端口发送数据,其余客户端依然向80端口申请链接。所以,能够判断,socketfd_new并无占用新的端口与客户端通讯,依然使用的是与监听套接字socketfd_new同样的端口号。

那这么说,难道一个端口能够被两个socket对象绑定?当客户端发送数据过来的时候,到底是与哪个socket对象通讯呢?

我是这么理解的(欢迎拍砖)。

   首先,一个端口确定只能绑定一个socket。我认为,服务器端的端口在bind的时候已经绑定到了监听套接字socetfd所描述的对象上,accept函数新建立的socket对象其实并无进行端口的占有,而是复制了socetfd的本地IP和端口号,而且记录了链接过来的客户端的IP和端口号。

那么,当客户端发送数据过来的时候,到底是与哪个socket对象通讯呢?

客户端发送过来的数据能够分为2种,一种是链接请求,一种是已经创建好链接后的数据传输。

   因为TCP/IP协议栈是维护着一个接收和发送缓冲区的。在接收到来自客户端的数据包后,服务器端的TCP/IP协议栈应该会作以下处理:若是收到的是请求链接的数据包,则传给监听着链接请求端口的socetfd套接字,进行accept处理;若是是已经创建过链接后的客户端数据包,则将数据放入接收缓冲区。这样,当服务器端须要读取指定客户端的数据时,则能够利用socketfd_new套接字经过recv或者read函数到缓冲区里面去取指定的数据(由于socketfd_new表明的socket对象记录了客户端IP和端口,所以能够鉴别)。

关于问题三的再次深探究:

那么新创建的链接使用的端口号是否和listen所用端口号相同呢?之前我一直觉得服务器会随机分配一个新的端口号来使用,后来发现错了。

由于一、如今使用多路IO复用epoll等,配置好点的服务器能够支持数十万个并发链接,端口号为16位,最多才2^16-1,且加上一些经常使用的端口号不能使用,可用的端口号都没那么多。二、如今服务器大多使用防火墙,防火墙只对特定端口开放。若是accept随机分配端口号,会不能经过防火墙。

TCP/IP协议中,IP协议是端到端的协议,它只是负责把把数据发送到端,交付给上层而已。运输层TCP、UDP加上了端口号,目的是区分不一样的应用。其中TCP还实现了流量控制、可靠传输等,而UDP只是应该是没有对IP层数据进行处理了。

在以往的知识中,我知道一个应用程序只能使用一个端口号,若是accept返回的句柄仍是使用listen的端口号,那么怎么实现通讯呢?若是创建多个链接,应用程序怎么区收到的信息来自哪一个客户端呢?

如今才理解到accept返回的句柄创建的链接包括四部分:源IP、源端口号、目的IP、目的端口号。这样在一个应用程序中,就算和多个客户端创建链接,在收到数据后,应用程序经过目的IP和目的端口号也能区分是哪一条链接。

经过一个echo服务器来验证一下,client和server都在同一台机器上:

服务器监听8000端口,在未创建链接时,能够看到在监听8000

在经过一个客户端创建链接后,能够看到创建了一条链接,服务器端的端口号是8000,监听的仍是8000。

在链接一个客户端,能够看到创建了两条链接,服务器端都是使用8000,监听的仍是8000。

相关文章
相关标签/搜索