关于accept得到的socket本地端口

本文详细探讨了socket结构体的定义、connect函数的作用、以及accept函数产生的socket是否占用新端口的问题,深入剖析了socket编程的核心原理与通信机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

服务器端accept得到的socket本地端口就是listen端口
客户端如果不指定会随机一个本地端口
因此服务器端没有连接数量的限制(硬件无限),客户端最多不能超过65533个连接



转自http://ticktick.blog.51cto.com/823160/779866


今天与同学争执一个话题:由于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函数,原型如下:
/* 参数:sockfd 监听套接字,即服务器端创建的用于listen的socket描述符。  
 * 参数:addr  这是一个结果参数,它用来接受一个返回值,这返回值指定客户端的地址  
 * 参数:len 描述 addr 的长度  
 */ 
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和端口,因此可以鉴别)。

<think>嗯,用户想了解如何使用Socket实现本地端口监听,并且需要示例代码。首先,我需要回忆一下Socket编程的基础知识。Socket通常用于网络通信,监听端口需要创建一个套接字,绑定到特定的地址和端口,然后开始监听连接请求。 用户提到的问题可能涉及到TCP和UDP两种协议,可能需要分别说明。不过通常监听端口更多是指TCP,因为UDP是无连接的。所以示例代码可能以TCP为例比较合适。 首先,步骤应该是创建套接字,设置地址和端口,绑定,然后监听。对于Python来说,使用socket模块。要注意的是,在绑定之前可能需要设置SO_REUSEADDR选项,这样在端口被占用后可以快速重用,避免等待时间。例如,server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)。这个选项在示例代码中应该包含,因为用户可能遇到地址已占用的问题。 然后,绑定地址和端口,比如('127.0.0.1', 8080),然后调用listen()方法开始监听。之后进入循环,接受客户端的连接,使用accept()方法,返回新的套接字和客户端地址。之后进行数据的接收和发送。 需要注意的是,用户可能对多线程或异步处理多个连接感兴趣,但示例可能先展示一个简单的单线程处理。不过,如果用户需要处理并发,可能需要使用多线程或者异步IO,但问题中用户只是要求监听本地端口,可能基础的示例就足够。 另外,用户提到的引用内容是关于端口复用后门和重定向的,虽然这可能和SO_REUSEADDR有关联,但示例代码中的端口复用选项是为了避免地址占用问题,而不是用于后门。所以在解释时需要说明这个选项的正当用途,比如服务器重启时快速重新绑定端口。 检查代码是否正确,比如Python中socket的创建是否正确,AF_INET和SOCK_STREAM是否正确使用。绑定后是否调用了listen,accept的位置是否正确。数据处理部分,比如recv和send的方法是否正确使用,是否需要循环接收数据直到结束。 可能还需要在代码中添加异常处理,比如捕获端口被占用的情况,但用户可能只需要基本示例,所以可以暂时省略,或者简单提到。 最后,用户可能还会关心如何测试这个服务端代码,比如使用telnet或者编写客户端代码进行连接测试。可以考虑在相关问题里提出,或者在示例后添加简单的测试步骤。 总结一下,回答的结构应该是:步骤说明,示例代码(Python),解释关键点,注意事项,以及相关问题。确保代码中的注释清晰,关键步骤如设置SO_REUSEADDR被突出显示,以说明其作用。同时,引用中的端口复用可能与代码中的选项相关,需要正确引用,但用户的问题主要在于监听端口,所以引用可能作为补充说明。 现在,需要将上述思考整理成中文回答,结构清晰,步骤明确,代码正确,并生成相关问题。同时,确保使用正确的LaTeX格式,但用户的问题不涉及数学公式,可能代码部分更重要。最后,检查是否所有系统级指令都满足,比如行内公式是否正确,独立公式是否单独成段,引用标识是否正确添加。</think>### 使用Socket实现本地端口监听的方法 #### 1. 核心步骤 - **创建Socket对象**:选择协议类型(如TCP或UDP),TCP常用`SOCK_STREAM`,UDP用`SOCK_DGRAM`。 - **设置端口复用**:通过`SO_REUSEADDR`选项避免端口被占用后无法立即绑定[^1]。 - **绑定地址和端口**:将Socket绑定到本地地址(如`127.0.0.1`)和指定端口。 - **开始监听(仅TCP)**:调用`listen()`方法等待客户端连接。 - **接受连接并通信**:使用`accept()`接收客户端请求,通过新Socket收发数据。 #### 2. Python示例代码(TCP协议) ```python import socket # 创建TCP Socket server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 设置端口复用 # 绑定本地端口 server.bind(('127.0.0.1', 8080)) server.listen(5) # 允许最多5个等待连接 print("正在监听 127.0.0.1:8080...") while True: client_sock, addr = server.accept() print(f"来自 {addr} 的连接") data = client_sock.recv(1024) print(f"收到数据: {data.decode()}") client_sock.send(b"Message received") client_sock.close() ``` #### 3. 关键点说明 - **端口复用**:`setsockopt`中的`SO_REUSEADDR`允许程序重启后快速重新绑定端口,避免因`TIME_WAIT`状态导致延迟[^1]。 - **地址绑定**:`127.0.0.1`表示仅允许本机访问,若需外部访问需改为`0.0.0.0`。 - **TCP vs UDP**:UDP无需`listen()`和`accept()`,直接使用`recvfrom()`和`sendto()`。 #### 4. 测试方法 ```bash # 使用telnet测试TCP服务 telnet 127.0.0.1 8080 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值