本篇目标:
1.认识端口号
2.了解传输层的典型代表
3.网络字节序的掌握
4.socket编程接⼝的初步理解
一.Socket编程预备
1.认识端口号
1.1.引入概念
首先我们先思考一个问题:数据传输到主机是目的吗?
答案:不是的,因为数据是给人用的,比如我们用微信聊天是两个人之间在聊天;下载是人在下
载;看网页是人从网页中获取数据。
但是人是怎么看到聊天信息的呢?怎么执行下载任务呢?怎么浏览网页信息呢?
我们是通过启动的qq,迅 雷,浏览器,而启动的qq,迅雷,浏览器都是进程。
结论:
进程是人在系统中的代表,只要把数据给进程,就相当于人就拿到了数据,数据传输到主机
不是目的,而是手段,而到达主机内部,在交给主机内的进程,才是目的。
如图:

解释:数据经过网络传输到目标主机,完成解包后,操作系统还需要判断数据应该交给哪个应用进程。网络层通过 IP 地址标识目标主机,传输层则通过端口号标识目标主机中的网络通信端点。内核根据协议、IP 地址和端口号找到对应的 Socket,再将数据交给持有该 Socket 的进程。
1.2.端口号
作用:
端口号( port )是传输层协议的内容,用来标识⼀个进程,告诉操作系统,当前的这个数据要交给哪一个进程来
处理;
大小:
一个 2 字节 16比特位的整数;
特点:
⼀个端口号只能被⼀个进程占用。
那么此时可能有人会问:既然 PID 已经能够唯一标识进程,为什么还要引入端口号,而不是直接
用 PID 来替代呢?
答案:
首先不是所有的进程都要进行网络通信,然后理论上用pid是可行的,但是没有这个必要,因为pid是系统的概念,
而端口号是网络的概念,如果将pid运用到了网络,那万一有一天pid变化了,或者突然换了一个新的呢?那是不是
网络也要跟着改变?这就像办事大厅中的窗口号和工作人员的工号:用户需要寻找的是“3号业务窗口”,而不是某
位工作人员的工号。即使工作人员发生变化,只要3号窗口仍然提供相同的服务,用户的访问方式就不需要改变。
所有用端口号而不是pid是为了与系统解耦。
通过之前的学习我们已经知道了ip是用来标识全网内唯一的一个主机,而端口号是用来标识主机上的一个网络进程,所以ip+端口号是用来标识全网内唯一的一个进程。
我们把 ip+port 叫做套接字 socket。
结论:
网络通信的本质:全网中两个进程之间进行进程间通信,两个互联网进程代表⼈来进行通信,{srcIp(源ip),
srcPort(源port),dstIp(目的ip),dstPort(目的port)} 这样的4元组就能标识互联网中唯二的两个进程。
端⼝号范围划分:
0 – 1023 :知名端口号,HTTP,FTP, SSH 等这些广为使用的应用层协议,他们的端口号都是固
定的
024 – 65535 :操作系统动态分配的端口号,例如客户端程序的端口号,就是由操作系统从这个范
围分配的.
2.传输层的典型代表
如果我们了解了系统,也了解了网络协议栈,我们就会清楚,传输层是属于内核的,那么我们要通
过网络协议栈进行通信,必定调用的是传输层提供的系统调用,来进行的网络通信。
如图:

传输层比较有代表的两个协议就是UDP协议和TCP协议,目前我们先对这两个协议有个直观的认识,后面再深入理解。
2.1.认识TCP协议
特点:
<1>通信之前,双方需要先建立连接;
例如打电话时,电话接通后,双方通常会先说“喂,听得到吗?”,确认彼此都能够听见对方的声
音,然后才开始正式交谈。
<2>.可靠传输。
TCP 能够保证数据:
不丢失;
不重复;
按顺序到达;
出错后能够重传。
<3>.面向字节流。
TCP 将应用程序交付的数据视为一串连续的字节,不保留应用程序每次发送数据时的边界。
例如发送方调用三次:
send("hello")
send("world")
send("TCP")
接收方可能一次收到:
helloworldTCP
也可能分多次收到。因此,一次 send() 不一定对应一次 recv(),应用层需要自行设计消息边界。
这个字节流目前我们稍微认识一下即可。
2.2.认识UDP协议
特点:
<1>.UDP通信之前不需要建立连接,发送方知道对方的 IP 地址和端口号后,就可以直接发送数据。
例如:寄信人只需要填写收件地址并将信寄出,不需要先与收件人建立联系。
<2>.不可靠传输。
UDP只负责尽力发送数据,但不保证:
数据一定能够到达;
数据按照发送顺序到达;
数据不会重复;
数据丢失后能够自动重传.
<3>.面向数据报
UDP会保留应用程序每次发送数据时的边界。
例如发送方调用三次:
sendto("hello")
sendto("world")
sendto("UDP")
UDP会形成三个独立的数据报:
数据报1:hello
数据报2:world
数据报3:UDP
此时还有个问题:既然 TCP 比 UDP 更可靠,那为什么还需要 UDP 呢?直接用 TCP 不就行了
吗?
答案:
我们不应当将UDP的“不可靠”理解为贬义,而应该把它看作一个描述协议特点的中性词。TCP虽然能够提供可靠传
输,但这也意味着它需要通过确认应答、超时重传等机制完成更多工作,因此协议实现更加复杂,占用的系统资源
也相对更多。UDP不提供这些可靠性保证,恰恰使它拥有了简单、轻量和灵活的特点。
3.网络字节序
我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分,网络数据流同样有大端小端之分,那么如何定义网络数据流的地址呢?
• 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
• 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保
存;
• 因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址;
• TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节;
• 不管这台主机是大端机还是小端机,都会按照这个TCP/IP规定的网络字节序来发送/接收数据;
• 如果当前发送主机是小端,就需要先将数据转成大端;否则就忽略,直接发送即可。
如图:

为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调
用以下库函数做网络字节序和主机字节序的转换。
如图:

解释:
• 这些函数名很好记, h 表示 host , n 表示 network , l 表示 htonl 表示将 32 位长整数, s 表⽰ 16 位短整数,htonl表示将32 位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
• 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
• 如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。
4.socket编程接口
// 创建 socket 文件描述符,适用于:TCP/UDP、客户端/服务器
int socket(int domain, int type, int protocol);
// 绑定 IP 地址和端口号,适用于:TCP/UDP、服务器
int bind(int sockfd,const struct sockaddr* addr,socklen_t addrlen);
// 开始监听 socket,适用于:TCP、服务器
int listen(int sockfd, int backlog);
// 接收客户端的连接请求,适用于:TCP、服务器
int accept(int sockfd,struct sockaddr* addr,socklen_t* addrlen);
// 向服务器发起连接,适用于:TCP、客户端
int connect(int sockfd,const struct sockaddr* addr,socklen_t addrlen);
我们要知道,网络通信的本质是进程间的通信。之前学习的 system V 用于本地进程间通信,而后
面将要学习的 POSIX 标准既可以用于网络通信,也可以用于进程间通信。
socket会有很多的种类,以便于满足不同的应用场景,既有网络socket,也有本地socket,所以
socket的接口会有会有不同的通信接口规范,但是socket的设计者只想提供一种通信接口规范,如
图:

解释:
如果我们想进行IPv4网络通信,就要使用 struct sockaddr_in结构,并将其中的地址类型 sin_family设置为 AF_INET;如果想进行本地Socket通信,也就是Unix域套接字通信,就要使用 struct sockaddr_un结构,并将其中的地址类型 sun_family设置为 AF_UNIX。
当我们调用 bind()、connect()等接口时,系统会根据地址结构开头的地址类型,判断当前进行的是IPv4网络通信还是本地通信。其原理可以简单理解为:如果通用地址结构中的 sa_family等于 AF_INET,系统就按照 sockaddr_in结构解析;如果等于 AF_UNIX,系统就按照 sockaddr_un结构解析。
但是,这些接口接收的参数统一都是 struct sockaddr*类型,而我们实际使用的是 struct sockaddr_in*或 struct sockaddr_un*。解决方法很简单:调用接口时,将实际地址结构的指针强制转换成 struct sockaddr*即可。这个强制转换不会改变原来的数据,只是让接口能够通过统一的指针类型接收不同的Socket地址结构。
总结:
本篇介绍了端口号、TCP与UDP、网络字节序以及Socket编程接口。端口号用于标识主机上的网络通信端点;TCP和UDP提供不同的传输方式;网络字节序保证不同主机能够正确解析数据;Socket则为网络通信和本地通信提供了一套统一的编程接口。


