在开始之前希望大家都知道以下几点:
首先,一个端口肯定只能绑定一个socket,当然这个socket可能会产生很多“socket连接”;
其次,只要服务器性能好一个端口就可以绑定无数个“socket连接”;
此时我问你 描述两个应用进程之间的端到端的通信需要什么?显然需要五元组:本地IP、本地端口、远程IP、远程端口和协议。而接下来要讲的套接字,它的本质就是涵盖着五元组信息的类/对象。其操作也大多围绕这五元组信息展开的。
一、socket()函数——“亚当夏娃”套接字的诞生
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;
};
其中,struct sock 包含有一个 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在新建的时候只是一个待填充的空的结构,后面的connect()和bind()函数实际上可以看做给内核里边的那个socket对象设置具体IP和端口信息的过程。(注:此处没有包括listen、accept,不过貌似也可以包括。)
注:这个描述符是我们用来监听用的称为——监听socket。
二、bind()和connect()函数——给套接字赋予“本地地址”(对客户端是“远程地址”)
三、listen()函数——开始监听的开关
listen函数的作用可以理解成开启监听的开关,listen被执行之后开关被打开,就开始监听了。轮询的时候判断到有连接请求过来的时候调用accept接受即可。具体地讲使用listen系统调用后会创建一个监听队列以存放待处理的客户连接。
四、accept函数——建立套接字连接
accept()接受一个客户端的连接请求(该客户端的所有信息均来自“监听socket”),并返回一个新的套接字。所谓“新的”就是说这个套接字与最开始socket()调用时候返回的用于监听和接受客户端的连接请求的套接字不是同一个套接字。与本次接受的客户端的通信是都是通过这个新的套接字上发送和接收数据来完成的。再次调用accept()可以接受下一个客户端的连接请求,并再次返回一个新的套接字(与socket()返回的套接字、之前accept()返回的套接字都不同的新的套接字)。
假设一共有三个客户端连接到服务器端。那么在服务器端就一共有4个套接字:第1个是socket()返回的、经过bind或connect填充“本地IP:端口”的用于监听的套接字;其余3个是分别调用3次accept()返回的包含“本地/远程”双重信息的不同的套接字(他们之间远程信息不同)。
如果已经有客户端连接到服务器端,不再需要监听和接受更多的客户端连接的时候,可以关闭由socket()返回的套接字,而不会影响与客户端之间的通信。当某个客户端断开连接、或者是与某个客户端的通信完成之后,服务器端需要关闭用于与该客户端通信的套接字。
由以上可知对服务端而言bind操作的只是那个socket()返回、用于监听的socket。其他几个“socket连接”,他们在内核中应该也是有对应的对象的(猜测)。不过他们并不用于监听,而是用于具体读写。这种“一对多”的关系可以这样理解:一个仅有“本地IP:端口”的父亲(监听socket)将自己监听到的信息用于派生,派生出了一个又一个同时具备“本地IP:端口”和“远程IP:端口”的新的socket(即socket连接)。注:(1)上述的“派生”的过程由accept函数完成的(2)这里监听socket和socket连接的结构应该是一样的,只不过“派生”出的socket把“远程IP:端口号”的信息也分别填充了。(3)“远程IP:端口”的信息是来自监听socket的。
至此为什么只有这个新的套接字才能用于同这次接受的客户端之间的通信也就一目了然了,因为这个套接字才真正包含了“本地信息”和“远程信息”,只有同时包含这两重信息才可以用于数据的收发。
五、accept返回的fd和listen的fd是什么关系?
(1)首先他们肯定是不等的。一个socket是由一个五元组来唯一标识的,即(协议,server_ip, server_port, client_ip,client_port)。只要该五元组中任何一个值不同,则其代表的socket就不同。
(2)那这样算不算 一个监听socket和若干socket连接共享端口呢?
答:准确的说两者的概念有一点区别。“一个端口不能被多个socket共享”,应该理解成“彼此的socket不能共享一个端口”。显然“监听socket”和由其产生的“socket连接”并不是彼此的socket。也就是说他们确实共享端口了,但是此共享非彼共享。或者你可以理解成“本地IP:端口”是被他们所共有的,即都是用“监听socket”的那份。
参考:
https:///mythicsr/article/details/443133
https:///kingshown_wz/article/details/52103327
http://blog.51cto.com/ticktick/779866
https:///cws1214/article/details/9671543