今天与同学争执一个话题:由于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;
};   

其中,structsock 包含有一个 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和端口,因此可以鉴别)。

关于问题三的再次深探究:

那么新建立的连接使用的端口号是否和listen所用端口号相同呢?以前我一直以为服务器会随机分配一个新的端口号来使用,后来发现错了。

因为1、现在使用多路IO复用epoll等,配置好点的服务器可以支持数十万个并发连接,端口号为16位,最多才2^16-1,且加上一些常用的端口号不能使用,可用的端口号都没那么多。2、现在服务器大多使用防火墙,防火墙只对特定端口开放。如果accept随机分配端口号,会不能通过防火墙。

TCP/IP协议中,IP协议是端到端的协议,它只是负责把把数据发送到端,交付给上层而已。运输层TCP、UDP加上了端口号,目的是区分不同的应用。其中TCP还实现了流量控制、可靠传输等,而UDP只是应该是没有对IP层数据进行处理了。

在以往的知识中,我知道一个应用程序只能使用一个端口号,如果accept返回的句柄还是使用listen的端口号,那么怎么实现通信呢?如果建立多个连接,应用程序怎么区收到的信息来自哪个客户端呢?

现在才理解到accept返回的句柄建立的连接包括四部分:源IP、源端口号、目的IP、目的端口号。这样在一个应用程序中,就算和多个客户端建立连接,在收到数据后,应用程序通过目的IP和目的端口号也能区分是哪一条连接。

通过一个echo服务器来验证一下,client和server都在同一台机器上:

服务器监听8000端口,在未建立连接时,可以看到在监听8000

在通过一个客户端建立连接后,可以看到建立了一条连接,服务器端的端口号是8000,监听的还是8000。

在连接一个客户端,可以看到建立了两条连接,服务器端都是使用8000,监听的还是8000。

socket的accept函数解析以及服务器和多个客户端的端口问题相关推荐

  1. 网络编程socket之accept函数

    网络编程socket之accept函数 摘要:对于服务器编程中最重要的一步等待并接受客户的连接,那么这一步在编程中如何完成,accept函数就是完成这一步的.它从内核中取出已经建立的客户连接,然后把这 ...

  2. socket编程accept函数返回值的理解

    accept函数返回值成功时返回非负值,失败时返回-1 accept函数接受一个客户端请求后会返回一个新的SOCKFD值,当有不同的客户端同时有不同请求时,会返回不同的SOCKFD的值.这个不同的值和 ...

  3. linux socket编程 accept函数详解

    int accept(int sockfd,struct sockaddr * addr,socklen_t * addrlen); sockfd的参数为listen()函数返回的监听套接字: add ...

  4. C语言网络编程:accept函数详解

    文章目录 前言 函数描述 代码实例 如何得到客户端的IP 和 端口号 前言 当使用tcp服务器使用socket创建通信文件描述符,bind绑定了文件描述符,服务器ip和端口号,listen将服务器端的 ...

  5. TCP/IP编程之accept函数详解

    accept函数由TCP服务器调用,用于从已完成连接队列返回下一个已完成连接.如果已完成连接队列为空,那么进程被投入睡眠(假定套接字默为默认的阻塞方式) 函数原型: ACCEPT(2) Linux P ...

  6. accept函数_基础套接字函数入门1

    想不想自己写一个简单的QQ?想不想自己写一个聊天室?想不想知道2000年的人是如何上网的?本节讲解一些基础的网络函数,带你看看,编写一个完整的TCP客户端.服务端需要掌握哪些函数?之后,我们要开发自己 ...

  7. socket中使用多线程创建并发服务器

    启动服务器: bin/echo_tcp_server_th 8888 启动客户端: bin/echo_tcp_client 127.0.0.1 8888 使用ifconfig命令可以看到电脑的换回地址 ...

  8. socket使用多进程实现并发的服务器

    服务器测试效果: andrew@andrew-Thurley:~/work/network$ bin/echo_tcp_server 8888 client: 127.0.0.1(55610) con ...

  9. 网络编程之accept函数和accept函数在三次握手中的位置

    accept函数实际上是在三次握手之后,具体原因请看文章结尾具体解释. 基本TCP客户端/服务器程序的套接字函数 accept函数由TCP服务器调用,用于从已完成连接队列队头返回下一个已完成连接(从这 ...

最新文章

  1. 岛国科普第2弹-再造人类生命的神奇细胞Human.Life.Our.Amazing.Cell
  2. python中args1是什么意思_理解Python中的*,*args
  3. 咖啡馆的故事:FTP, RMI , XML-RPC, SOAP, REST一网打尽
  4. 设计模式(二)模板方法模式
  5. Spring3 整合 Hibernate4实现数据库操作(1)
  6. 4核a5中断linux,Cortex A5 MPcore寄存器TPIDRPRW复位值不为零,造成Linux Kernel不能启动的问题...
  7. ASUS蓝屏和重装系统之后驱动问题总结
  8. 计算机网络之对称密钥加密与非对称密钥加密
  9. 深入探究Retinex
  10. python爬取堆糖网每日精选图片
  11. C#学习纪要(8):7月17日
  12. Redis单机最大并发量
  13. 几道特别难搞的数据库面试题
  14. ONVIF学习笔记11:搜索设备不匹配问题排查
  15. 使用gpu服务器搭建人脸识别系统,基于GPU的大规模人脸识别系统的设计与实现
  16. 权限和归属——基本权限和特殊权限
  17. SPDK简介(其与Ceph rbd的关系)
  18. 统计自然语言处理基础-第三章 语言学基础(笔记)
  19. altera FPGA介紹
  20. java内省和反射机制_Java内省和反射机制三步曲之 - 内省

热门文章

  1. php redis编程,php + redis 实现关注功能
  2. 单模光电转换器怎么接_我对汽车电子电磁兼容测试实验室使用的光电收发器之使用管理方式的一些看法...
  3. java md5运算_java实现计算MD5
  4. mybatis连接oracle_Mybatis 系列 0:初恋Mybatis
  5. python入门教程收藏_特别详细的Python入门教程,建议收藏
  6. python scrapy框架爬虫_Scrapy爬虫框架教程(一)-- Scrapy入门
  7. 写毕业论文,要我狗命!
  8. 2023届IC实习小结
  9. 360手机浏览器_360手机浏览器9.0新功能测评
  10. 2017上半年计算机教学计划,2016—2017学年度第二学期信息技术教学计划