1. 使用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;
shrot type;
};
其中,struct sock包含一个sock_common结构体,sock_common结构体又包含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和port以及目的ip和port.
2.bind为套接字描述符绑定ip和port,目的就是将主机暴露在网络中(服务端都使用bind,为客户端提供ip和端口),而在客户端可以不需要使用。
3.客户端使用connect连接服务端成功时,内核就会给socket结构体完善目的信息(目的ip和port)。
4.服务端listen函数将socket套接字由主动变为被动(使成为监听套接字),只用来连接客户端和设置允许同时最大连接数。因此监听套接字里的socket结构体里没有目的ip和端口。
5.服务端accept函数通过监听套接字等待客户端连接,和返回与客户端连接的连接套接字。内核里根据连接套接字找到的socket结构体有目的ip和端口。
问题:服务端有监听套接字和连接套接字(如果多个客户端相连,就会有多个连接套接字),当客户端发消息时,如何区分是发到哪个套接字?
显然,服务端所有套接字本地ip和port都是一个。我们都知道一个端口只能绑定一个套接字,这里是为什么呢?
我要说明一下,监听套接字是我们绑定的ip和port。而通过accept产生的连接套接字它的ip和port不是绑定的而是复制监听套接字里的。
当客户端(通过connect)发送连接请求时,服务端就会使用监听套接字,进行accept处理。建立一个连接套接字。监听套接字和连接套接字是互不影响,当close掉监听套接字后,连接套接字仍能使用,反之,一样。
当多个客户端连接服务端后(此时服务端有多个连接套接字),都给服务端发送数据,那么服务端怎么区分由哪个连接套接字接收数据的呢?
系统内核会为每个套接字注册一个socket结构体,当产生一个连接套接字时,内核会添加一个带有本地ip,port和目的ip,port的socket结构体。当协议将数据传到服务端时,会根据服务端内核注册的socket结构体找到与客户端相连接的套接字,并将数据放到该套接字的接受缓冲区中。
附:ip地址,网络中每个ip地址都是唯一的,可以找到主机。
port(端口号):每个端口号只能被一个进程使用,每个进程能监听多个端口号。因此,端口号可以用来唯一标识一个进程。在通讯里,用来告知主机哪个进程来处理信息。