网络编程(六):端口那些事儿

TCP和UDP协议都存在一个叫做端口的东西,但端口却不是IP协议的一部分。

端口被设计出来主要是为了给协议栈和应用对应:

  • 协议栈用端口号将数据分配给不同的应用层程序
  • 应用层程序用端口号去区分不同的连接,参见之前提到过的“四元组”

TCP和UDP协议都使用了端口号(Port number)的概念来标识发送方和接收方的应用层。 对每个TCP连接的一端都有一个相关的16位的无符号端口号分配给它们。 即使是UDP这种没有连接的协议,依旧有一个16位的无符号端口号。 可能的、被正式承认的端口号有 2^16 -1 = 65535 个。

三类端口

端口被分为三类:著名端口、监听端口和动态端口。

  • 著名端口是由因特网赋号管理局(IANA)来分配的,并且通常被用于系统进程。 IANA对于端口号的分配见这里 Service Name and Transport Protocol Port Number Registry 。 系统的/etc/services也有相应端口和服务名的对应,主要是用来给netstat、nmap 等系统命令做端口名反解用。

    著名的应用程序作为服务器程序来运行,并侦听经常使用这些端口的连接。 这些端口的一个显著特征就是限定在0~1023,并且在Linux、UNIX平台均需要 Root权限才能监听这些端口。

    在UNIX刚刚兴起的年代,服务器资源是十分稀缺的, 通常一台服务器上会有很多的用户,同时这台服务器往往还兼任一个学院、公司的邮件、 网站等服务。为了保证这些服务的端口不被普通用户占用, 当时UNIX的设计者就把使用这些端口的权限限制在系统管理员(Root)手里。

    常见的`著名端口`有:FTP:21、SSH:22、SMTP:25、HTTP:80、HTTPS:443等。
  • 监听端口通常被用来运行各种用户自己写的服务,服务监听在这些端口下不需要特别的权限。
    • BSD使用的监听端口范围是1024到4999。
    • IANA建议49152至65535作为“监听端口”。
    • 许多Linux内核使用32768至61000范围。 配置文件 /proc/sys/net/ipv4/ip_local_port_range 有当前系统设定。
  • 动态端口通常被用来在主动发起连接时随机分配使用,在任何特定的TCP连接外不具有任何意义。 这是由于TCP等协议是通过四元组来区分不同的网络连接。 当本机主动发起TCP连接的时候如果目的IP、目的端口、本地IP都是一样的, 只能通过占用不同的本地端口来区分不同的连接。

    0~65535除去上述著名端口、监听端口两种端口号,剩下的端口都是备用的动态端口。 所以在某些特殊用途的需要主动发起大量连接的服务器上(例如:爬虫、代理), 需要调整 /proc/sys/net/ipv4/ip_local_port_range 的数值,来保留更多的 动态端口以供使用。

0号端口

端口号里有一个极为特殊的端口,各种文档书籍中都鲜有记载,就是0号端口。

在IANA官方的标准里0号端口是保留端口。

也就是说无论是TCP还是UDP网络通信,0号端口都是不能使用的。

然而,标准归标准,在UNIX/Linux网络编程中0号端口被赋予了特殊的涵义:

如果在bind绑定的时候指定端口0,意味着由系统随机选择一个可用端口来绑定。

用Python实现一个获取可用监听端口的示例:

def findFreePort():  """    函数返回值是当前可用来监听的一个随机端口。  """  import socket  s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  s.bind((‘localhost‘, 0))  # 用getsockname来获取我们实际绑定的端口号  addr, port = s.getsockname()  # 释放端口  s.close()  return port

网络地址转换NAT

既然说到了端口,不得不提一下NAT。

NAT是"Network Address Translation"的缩写,直译就是网络地址转换。 1990年代中期,为了应对IPv4地址短缺,NAT技术流行起来。

WikiPedia的解释为:

在一个典型的配置中,一个本地网络使用一个专有网络的指定子网 (比如192.168.x.x或10.x.x.x)和连在这个网络上的一个路由器。 这个路由器占有这个网络地址空间的一个专有地址(比如192.168.0.1), 同时它还通过一个或多个因特网服务提供商提供的公有的IP地址(叫做“过载”NAT) 连接到因特网上。当信息由本地网络向因特网传递时,源地址被立即从专有地址转换为公用地址。 由路由器跟踪每个连接上的基本数据,主要是目的地址和端口。当有回复返回路由器时, 它通过输出阶段记录的连接跟踪数据来决定该转发给内部网的哪个主机; 如果有多个公用地址可用,当数据包返回时,TCP或UDP客户机的端口号可以用来分解数据包。 对于因特网上的一个系统,路由器本身充当通信的源和目的地址。

这个技术能够被广泛使用还要感谢当时端口号的记录字段是2Bytes而不是1Byte。

NAT技术的广泛应用也给很多应用带来了极大的麻烦: 处于NAT网络环境内的服务器很难被外部的网络程序主动连接,受这一点伤害最大的莫过于: 点对点视频、语音、文件传输类的程序。

当然我们聪明的工程师经过长时间的努力,发明了“NAT打洞”技术,一定程度上解决了此类问题。

如果没有他们的努力,我们现在各种QQ视频、微信实时语音、网络电话都是需要用户连接到 服务商的服务器上进行数据传输。这样对服务商的网络消耗将是十分巨大的, 服务质量也是很难以提高的,具体的技术实现,我们以后再表。

多进程端口监听

我们都有一个计算机网络的常识:不同的进程不能使用同一端口。

如果一个端口正在被使用,无论是TIME_WAIT、CLOSE_WAIT、还是ESTABLISHED状态。 这个端口都不能被复用,这里面自然也是包括不能被用来LISTEN(监听)。

但这件事也不是绝对的,之前跟大家讲进程的创建过程提到过一件事: 当进程调用fork(2)系统调用的时候,会发生一系列资源的复制,其中就包括句柄。 所以,在调用fork(2)之前,打开任何文件,监听端口产生的句柄也将会被复制。

通过这种方式,我们就可以达成"多进程端口监听"。

但,这又有什么用呢?

我们大名鼎鼎的Nginx就是通过这种手法让多个进程同时监听在HTTP的服务端口上的, 这么做的好处就在于,当外部请求到达,Linux内核会保证多个进程只会有一个accept(2) 成功,这种情况下此端口的服务可用性就和单个进程存在与否无关。 Nginx正是利用这一点达成“不停服务reload、restart”的。

SO_REUSEADDR

要说SO_REUSEADDR,我们需要先需要说一段历史: 记得大学的时候面试我们学校的“星辰工作室”,有一个问题就是

为什么有时候重启Apache会失败,报“Address already in use”?

当时答得不太好,不太明白这个问题的关键点在哪里,后来逐渐明白了。

TCP的原理会导致这样的一个结果:

主动close socket的一方会进入TIME_WAIT,这个状况持续的时间取决于三件事:

  • TCP关闭连接的五次挥手包什么时候到达
  • SO_LINGER的设置
  • /proc/sys/net/ipv4/tcp_tw_recycle 和 /proc/sys/net/ipv4/tcp_tw_reuse 的设置

总之默认情况下,处于TIME_WAIT状态的端口是不能用来LISTEN的。 这就导致,Apache重启时产生80端口TIME_WAIT,进而导致Apache再次尝试LISTEN失败。

在很多开源代码里我们会看到如下代码:

int reuseaddr = 1;setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(int));

有了上面这段神奇的代码,就不会出现上面的惨剧。但SO_REUSEADDR的作用不仅限于上述

Linux 的 SO_REUSEADDR 设置为 1 有四种效果:

  1. 当端口处在TIME_WAIT时候,可以复用监听。
  2. 可以允许多个进程监听同一端口,但是必须不同IP。

    这里说的比较隐晦,如果进程A监听0.0.0.0:80,B进程可以成功监听127.0.0.1:80, 顺序反过来也是可以的。

  3. 允许单个进程绑定相同的端口到多个socket上,但每个socket绑定的IP地址不同。
  4. 使用UDP时候,可以允许多个实例或者单进程同时监听同个端口同个IP。

著作权归作者所有

商业转载请联系作者获得授权非商业转载请注明出处。
作者大家可以看我的知乎专栏
链接http://zhuanlan.zhihu.com/auxten/20315482
这是一个系列的文章,后期会陆陆续续搬到Linuxtone着急的同学可以看

==================================

》》想要了解更多精彩内容欢迎关注》》

联系Reboot-有更多技术分享、交流请加群:238757010

网络编程(六):端口那些事儿

时间: 2024-08-11 03:36:58

网络编程(六):端口那些事儿的相关文章

iOS网络编程(六) NSURLSession详解

昨夜浏览Demo的时候,看到别人请求网络数据用的是NSURLSession,当时就在想这里什么,怎么没有用过,引起了我的好奇心,遂去百度-谷歌-官方文档一一查看,有了一定的了解,原来NSURLSession是iOS7中新的网络接口,它与咱们熟悉的NSURLConnection是并列的. 查找资料,写了一个小Demo,大家可以看看,有什么不足的地方,可以留言帮我指出来. // // HMTRootViewController.m // // // Created by HMT on 14-6-7.

Linux网络编程:端口复用

在<绑定( bind )端口需要注意的问题>提到:一个网络应用程序只能绑定一个端口( 一个套接字只能绑定一个端口 ). 实际上,默认的情况下,如果一个网络应用程序的一个套接字 绑定了一个端口( 占用了 8000 ),这时候,别的套接字就无法使用这个端口( 8000 ), 验证例子如下: #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #i

Unix网络编程(六)高级I/O技术之复用技术 select

I/O复用技术 本文将讨论网络编程中的高级I/O复用技术,将从下面几个方面进行展开: a. 什么是复用技术呢? b. 什么情况下需要使用复用技术呢? c. I/O的复用技术的工作原理是什么? d. select, poll and epoll的实现机制,以及他们之间的区别. 下面我们以一个背景问题来开始: 包括在以前的文章中我们讨论的案例都是阻塞式的I/O包括(fgetc/getc, fgets/gets),即当输入条件未满足时进程会阻塞直到满足之后进行读取,但是这样导致的一个 问题是如果此时进

Linux 网络编程六(多进程服务器僵尸进程解决方案)

僵尸进程解决方案 1.忽略SIGCHLD信号,这样不会出现僵尸进程 2.安装信号,父进程接收到SIGCHLD信号后,wait()子进程 注意:这里的客户端有两个进程,一个接收信息,一个发送信息,当客户端退出时,会将sockfd套接字关闭两次 //头文件 int server_socket(); int client_socket(); //服务器端 #include "pub.h" int main(int arg,char *args[]) { server_socket(); re

网络编程(六)

前情回顾:waitpid 创建二级子进程 注意事项:先根据功能计划模块,确定技术点做好整体架构的设计模块的编写 ----> 模块的测试发现bug或者进行优化积累调试经验 multiprocessing Process(name,target,args,kwargs) ---> 进程对象pp.pid p.name p.start() p.join() p.is_alive()p.daemon 文件拷贝 cookie :size = os.path.getsize('file')功能:获取一个文件

网络编程懒人入门(六):史上最通俗的集线器、交换机、路由器功能原理入门

1.前言 即时通讯网整理了大量的网络编程类基础文章和资料,包括<TCP/IP协议 卷1>.<[通俗易懂]深入理解TCP协议>系列.<网络编程懒人入门>系列.<不为人知的网络编程>系列.<P2P技术详解>系列.<高性能网络编程>系列.甚至还有图文并貌+实战代码的<NIO框架入门>等,目的是帮助即时通讯类应用的开发者,至少要掌握网络编程最基本的原理,所谓知其然更要知其所以然.尤其现在移动网络大行其道的时代,在网络环境如此复杂的

[转帖]脑残式网络编程入门(一):跟着动画来学TCP三次握手和四次挥手

脑残式网络编程入门(一):跟着动画来学TCP三次握手和四次挥手 http://www.52im.net/thread-1729-1-1.html 1.引言 网络编程中TCP协议的三次握手和四次挥手的问题,在面试中是最为常见的知识点之一.很多读者都知道"三次"和"四次",但是如果问深入一点,他们往往都无法作出准确回答. 本篇文章尝试使用动画图片的方式,来对这个知识点进行"脑残式"讲解(哈哈),期望读者们可以更加简单.直观地理解TCP网络通信交互的本

网络编程懒人入门(九):通俗讲解,有了IP地址,为何还要用MAC地址?

1.前言 标题虽然是为了解释有了 IP 地址,为什么还要用 MAC 地址,但是本文的重点在于理解为什么要有 IP 这样的东西.本文对读者的定位是知道 MAC 地址是什么,IP 地址是什么. (本文同步发布于:http://www.52im.net/thread-2067-1-1.html) 2.关于作者 翟志军,个人博客地址:https://showme.codes/,Github:https://github.com/zacker330.感谢作者的原创分享. 作者的另一篇<即时通讯安全篇(七)

9. 网络编程:

网络编程: 端口: 物理端口: 逻辑端口:用于标识进程的逻辑地址,不同进程的标识:有效端口:0~65535,其中0~1024系统使用或保留端口. java 中ip对象:InetAddress. import java.net.*; class  IPDemo{ public static void main(String[] args) throws UnknownHostException{ //通过名称(ip字符串or主机名)来获取一个ip对象. InetAddress ip = InetA

网络编程总结

网络编程:端口 逻辑端口:用于标识进程的逻辑地址,不同进程的标识:有效端口:0~65535,其中0~1024系统使用或保留端口. java 中ip对象:InetAddress. import java.net.*; class IPDemo{ public static void main(String[] args) throws UnknownHostException{ //通过名称(ip字符串or主机名)来获取一个ip对象. InetAddress ip = InetAddress.ge