今天与同学争执一个话题:由于socket的accept函数在有客户端连接的时候产生了新的socket用于服务该客户端,那么,这个新的socket到底有没有占用一个新的端口?
int socket(int domain, int type, int protocol);
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;
};
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; //本地端口(主机字节序)。
…………
}
int connect( int sockfd, const struct sockaddr* server_addr, socklen_t addrlen)
int send( int sockfd, const void *msg,int len,int flags);
int accept(int sockfd, struct sockaddr* addr, socklen_t* len)
因为1、现在使用多路IO复用epoll等,配置好点的服务器可以支持数十万个并发连接,端口号为16位,最多才2^16-1,且加上一些常用的端口号不能使用,可用的端口号都没那么多。2、现在服务器大多使用防火墙,防火墙只对特定端口开放。如果accept随机分配端口号,会不能通过防火墙。
TCP/IP协议中,IP协议是端到端的协议,它只是负责把把数据发送到端,交付给上层而已。运输层TCP、UDP加上了端口号,目的是区分不同的应用。其中TCP还实现了流量控制、可靠传输等,而UDP只是应该是没有对IP层数据进行处理了。
在以往的知识中,我知道一个应用程序只能使用一个端口号,如果accept返回的句柄还是使用listen的端口号,那么怎么实现通信呢?如果建立多个连接,应用程序怎么区收到的信息来自哪个客户端呢?
现在才理解到accept返回的句柄建立的连接包括四部分:源IP、源端口号、目的IP、目的端口号。这样在一个应用程序中,就算和多个客户端建立连接,在收到数据后,应用程序通过目的IP和目的端口号也能区分是哪一条连接。
通过一个echo服务器来验证一下,client和server都在同一台机器上:
服务器监听8000端口,在未建立连接时,可以看到在监听8000
在通过一个客户端建立连接后,可以看到建立了一条连接,服务器端的端口号是8000,监听的还是8000。
在连接一个客户端,可以看到建立了两条连接,服务器端都是使用8000,监听的还是8000。