与朋友争执一个话题:由于的函数在有客户端连接的时候产生了新的用于服务该客户端,那么,这个新的到底有没有占用一个新的端口?
讨论完后,才发现,自己虽然熟悉的编程套路,但是却并不是那么清楚的原理,今天就趁这个机会,把有关编程的几个疑问给搞清楚吧。
先给出一个典型的TCP/IP通信示意图。
推荐视频:
Linux内核源码分析之TCP/IP协议栈源码
徒手实现网络协议栈,请准备好环境,一起来写代码
学习地址:C/C++Linux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂
问题一:结构体对象究竟是怎样定义的?
我们知道,在使用编程之前,需要调用函数创建一个对象,该函数返回该对象的描述符。
函数原型:int socket(int domain, int type, int protocol);
那么,这个对象究竟是怎么定义的呢?它记录了哪些信息呢?只记录了本机IP及端口、还是目的IP及端口、或者都记录了?
关于这个问题,大家可以在内核源码里面找,我们可以看到 结构体的定义如下:
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;
};
其中, 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; //本地端口(主机字节序)。
…………
}
由此,我们清楚了,结构体不仅仅记录了本地的IP和端口号,还记录了目的IP和端口。
问题二:函数究竟做了些什么操作?
在TCP客户端,首先调用一个()函数,得到一个描述符,然后通过函数对服务器进行连接,连接成功后,就可以利用这个描述符使用send/recv函数收发数据了。
关于函数和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函数仅仅传入就可以知道服务器的ip和端口号?
其实,由“问题一”中的答案我们已经很清楚了, 描述符所描述的对象不仅包含了本地IP和端口,同时也包含了服务器的IP和端口,这样,才能使得send函数只需要传入 即可知道该把数据发向什么地方。而代码中,目的IP和端口只是在函数中出现过,因此,肯定是函数在成功建立连接后,将目的IP和端口写入了 描述符所描述的对象中。
【文章福利】需要C/C++ Linux服务器架构师学习资料加群(资料包括C/C++,Linux,技术,内核,Nginx,,MySQL,Redis,,,ZK,流媒体,CDN,P2P,K8S,,TCP/IP,协程,DPDK,等)
问题三: 函数产生的有没有占用新的端口?
首先,回顾一下函数,原型如下:
/* 参数:sockfd 监听套接字,即服务器端创建的用于listen的socket描述符。
* 参数:addr 这是一个结果参数,它用来接受一个返回值,这返回值指定客户端的地址
* 参数:len 描述 addr 的长度
*/
int accept(int sockfd, struct sockaddr* addr, socklen_t* len)
函数主要用于服务器端,一般位于函数之后,默认会阻塞进程,直到有一个客户请求连接,建立好连接后,它返回的一个新的套接字 ,此后,服务器端即可使用这个新的套接字与该客户端进行通信,而 则继续用于监听其他客户端的连接请求。
至此,我的困惑产生了,这个新的套接字 与监听套接字 是什么关系?它所代表的对象包含了哪些信息? 是否占用了新的端口与客户端通信?
先简单分析一番,由于网站的服务器也是一种TCP服务器,使用的是80端口,并不会因客户端的连接而产生新的端口给客户端服务,该客户端依然是向服务器端的80端口发送数据,其他客户端依然向80端口申请连接。因此,可以判断, 并没有占用新的端口与客户端通信,依然使用的是与监听套接字一样的端口号。
那这么说,难道一个端口可以被两个对象绑定?当客户端发送数据过来的时候,究竟是与哪一个对象通信呢?
我是这么理解的。
首先,一个端口肯定只能绑定一个。我认为,服务器端的端口在bind的时候已经绑定到了监听套接字所描述的对象上,函数新创建的对象其实并没有进行端口的占有,而是复制了的本地IP和端口号,并且记录了连接过来的客户端的IP和端口号。
那么,当客户端发送数据过来的时候,究竟是与哪一个对象通信呢?
客户端发送过来的数据可以分为2种,一种是连接请求,一种是已经建立好连接后的数据传输。
由于TCP/IP协议栈是维护着一个接收和发送缓冲区的。在接收到来自客户端的数据包后,服务器端的TCP/IP协议栈应该会做如下处理:如果收到的是请求连接的数据包,则传给监听着连接请求端口的套接字,进行处理;如果是已经建立过连接后的客户端数据包,则将数据放入接收缓冲区。这样,当服务器端需要读取指定客户端的数据时,则可以利用 套接字通过recv或者read函数到缓冲区里面去取指定的数据(因为代表的对象记录了客户端IP和端口,因此可以鉴别)。
———END———
限 时 特 惠: 本站每日持续更新海量各大内部创业教程,永久会员只需109元,全站资源免费下载 点击查看详情
站 长 微 信: nanadh666