socket的accept函数解析
今天与同学争执⼀个话题:由于socket的accept函数在有客户端连接的时候产⽣了新的socket⽤于服务该客户端,那么,这个新的socket到底有没有占⽤⼀个新的端⼝?
讨论完后,才发现,⾃⼰虽然熟悉socket的编程套路,但是却并不是那么清楚socket的原理,今天就趁这个机会,把有关socket编程的⼏个疑问给搞清楚吧。
先给出⼀个典型的TCP/IP通信⽰意图。
问题⼀:socket结构体对象究竟是怎样定义的?
我们知道,在使⽤socket编程之前,需要调⽤socket函数创建⼀个socket对象,该函数返回该socket对象的描述符。
01. 函数原型:int socket(int domain, int type, int protocol);
那么,这个socket对象究竟是怎么定义的呢?它记录了哪些信息呢?只记录了本机IP及端⼝、还是⽬的IP及端⼝、或者都记录了?
关于这个问题,⼤家可以在内核源码⾥⾯找,也可以参考这篇⽂章《struct socket 结构详解》,我们可以看到 socket 结构体的定义如下:
01. struct socket
02. {
03. socket_state state;
泰戈尔经典语录04. unsigned long flags;
05. const struct proto_ops *ops;
06. struct fasync_struct *fasync_list;
07. struct file *file;
08. struct sock *sk;
09. wait_queue_head_t wait;
10. short type;
11. };
其中,struct sock 包含有⼀个 sock_common 结构体,⽽sock_common结构体⼜包含有struct inet_sock 结构体,⽽struct inet_sock 结构体的部分定义如下:
伤感扎心语录01. struct inet_sock
02. {
03. struct sock sk;
04. #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
05. struct ipv6_pinfo *pinet6;
06. #endif
07. __u32 daddr; //IPv4的⽬的地址。缺铁性贫血治疗
08. __u32 rcv_saddr; //IPv4的本地接收地址。
09. __u16 dport; //⽬的端⼝。
公司介绍怎么写
10. __u16 num; //本地端⼝(主机字节序)。
11.
12. …………
13. }
由此,我们清楚了,socket结构体不仅仅记录了本地的IP和端⼝号,还记录了⽬的IP和端⼝。
运动健身视频问题⼆:connect函数究竟做了些什么操作?
在TCP客户端,⾸先调⽤⼀个socket()函数,得到⼀个socket描述符socketfd,然后通过connect函数对服务器进⾏连接,连接成功后,就可以利⽤这个socketfd描述符使⽤nd/recv函数收发数据了。
关于connect函数和nd函数的原型如下:
01. int connect( int sockfd, const struct sockaddr* rver_addr, socklen_t addrlen)
02.
03. int nd( int sockfd, const void *msg,int len,int flags);蜜煎
双离合变速箱的优缺点
都市之神级抓取
那么,现在的困惑是,为什么nd函数仅仅传⼊sockfd就可以知道服务器的ip和端⼝号?
其实,由“问题⼀”中的答案我们已经很清楚了,sockfd 描述符所描述的socket对象不仅包含了本地IP和端⼝,同时也包含了服务器的IP和端⼝,这样,才能使得nd函数只需要传⼊sockfd 即可知道该把数据发向什么地⽅。⽽代码中,⽬的IP和端⼝只是在connect函数中出现过,因此,肯定是connect函数在成功建⽴连接后,将⽬的IP和端⼝写⼊了sockfd 描述符所描述的socket对象中。
问题三: accept函数产⽣的socket有没有占⽤新的端⼝?
⾸先,回顾⼀下accept函数,原型如下:
01.
02. int accept(int sockfd, struct sockaddr* addr, socklen_t* len)
03.
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和端⼝,因此可以鉴别)。
这就是我对socket编程的⼀些疑问的理解,有不正确的地⽅欢迎留⾔或者来信交流。