文档章节

socket的accept函数解析

l
 lxtiantian
发布于 2016/12/07 15:40
字数 1499
阅读 12
收藏 1

 今天与同学争执一个话题:由于socket的accept函数在有客户端连接的时候产生了新的socket用于服务该客户端,那么,这个新的socket到底有没有占用一个新的端口?

    讨论完后,才发现,自己虽然熟悉socket的编程套路,但是却并不是那么清楚socket的原理,今天就趁这个机会,把有关socket编程的几个疑问给搞清楚吧。

    先给出一个典型的TCP/IP通信示意图。

                            

问题一:socket结构体对象究竟是怎样定义的?

   我们知道,在使用socket编程之前,需要调用socket函数创建一个socket对象,该函数返回该socket对象的描述符。

函数原型:int socket(int domain, int type, int protocol); 

   那么,这个socket对象究竟是怎么定义的呢?它记录了哪些信息呢?只记录了本机IP及端口、还是目的IP及端口、或者都记录了?

   关于这个问题,大家可以在内核源码里面找,也可以参考这篇文章《struct 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结构体不仅仅记录了本地的IP和端口号,还记录了目的IP和端口。

问题二:connect函数究竟做了些什么操作?

   在TCP客户端,首先调用一个socket()函数,得到一个socket描述符socketfd,然后通过connect函数对服务器进行连接,连接成功后,就可以利用这个socketfd描述符使用send/recv函数收发数据了。

    关于connect函数和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函数仅仅传入sockfd就可以知道服务器的ip和端口号? 

   其实,由“问题一”中的答案我们已经很清楚了,sockfd 描述符所描述的socket对象不仅包含了本地IP和端口,同时也包含了服务器的IP和端口,这样,才能使得send函数只需要传入sockfd 即可知道该把数据发向什么地方。而代码中,目的IP和端口只是在connect函数中出现过,因此,肯定是connect函数在成功建立连接后,将目的IP和端口写入了sockfd 描述符所描述的socket对象中。

问题三: accept函数产生的socket有没有占用新的端口?

    首先,回顾一下accept函数,原型如下:

int accept(int sockfd, struct sockaddr* addr, socklen_t* len)  

    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和端口,因此可以鉴别)。

本文转载自:http://blog.sina.com.cn/s/blog_73428e9a0101ih38.html

共有 人打赏支持
l

lxtiantian

粉丝 0
博文 4
码字总数 2368
作品 0
天津
Iperf 源代码分析(四)

Socket 类 Socket的定义和实现分别在文件Socket.hpp和 Socket.cpp中。它的主要功能是封装了socket文件描述符、此socket对应的端口号,以及socket接口中的listen, accept, connect和close等函...

KiteRunner
2014/05/25
0
0
Tomcat源码分析(二)--连接处理

目标:在这篇文章希望搞明白http请求到tomcat后是怎么由连接器转交到容器的? 在上一节里已经启动了一个HttpConnector线程,并且也启动了固定数量的HttpProcessor线程。HttpConnector用来等...

epiclight
2015/10/09
80
0
Traffic Server Net 子系统分析

Traffic Server设计的思想是将一个大系统划分为若干个小的子系统,每个子系统负责专门的任务或应用。比如,Event子系统负责提供任务调度服务,Net子 系统负责提供网络服务。每个子系统抽象为...

红薯
2012/10/25
602
0
WebSocket的C++服务器端实现

由于需要在项目中增加Websocket协议,与客户端进行通信,不想使用开源的库,比如WebSocketPP,就自己根据WebSocket协议实现一套函数,完全使用C++实现。 代码已经实现,放在个人github上面,...

xumaojun
04/25
0
0
nginx源码分析—处理继承的sockets

作者:阿波 链接:http://blog.csdn.net/livelylittlefish/article/details/7277607 Content 0.序 1.侦听结构 2. ngxaddinherited_sockets()分析 3. ngxsetinherited_sockets()分析 4.小结 ......

晨曦之光
2012/03/09
379
0

没有更多内容

加载失败,请刷新页面

加载更多

使用JavaScript ES6的新特性计算Fibonacci(非波拉契数列)

程序员面试系列 Java面试系列-webapp文件夹和WebContent文件夹的区别? 程序员面试系列:Spring MVC能响应HTTP请求的原因? Java程序员面试系列-什么是Java Marker Interface(标记接口) 使...

JerryWang_SAP
5分钟前
0
0
docker安装redis、mongodb、mysql等

一、启动docker服务,设置镜像: systemctl start dockervi /etc/docker/daemon.json{ "registry-mirrors": ["https://registry.docker-cn.com"]} 二、下拉镜像: 在镜像中心h...

狼王黄师傅
28分钟前
0
0
deepin系统使用deepin-wine安装exe程序

deepin自带原生deepin-wine使用命令如下: deepin-wine QQBrowser.exedeepin-wine QQMusicSetup.exe 默认安装的快捷方式位置: /root/.wine/drive_c/'Program Files'/Tencent/QQBrowser/......

临江仙卜算子
53分钟前
4
0
快速get到学习Linux操作系统的点

快速get到学习Linux操作系统的点 Linux是一套免费使用和自由传播的类Unix操作系统,是一个基于POSIX和UNIX的多用户、多任务、支持多线程和多CPU的操作系统。Linux能够运行主要的UNIX工具软件...

linuxCool
59分钟前
3
0
聊聊:Linux分区的那些方案

安装linux的整体步骤其实比较简单,唯一可能值得说明的地方,大概就是linux的分区了。 下面来给大家推荐一些分区方案。 1 分两个区 实际上,很多时候我们只需要分两个区:`/`和交换分区,日常...

Linux就该这么学
今天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部