【Linux】Socket编程基础
目录
源IP地址和目的IP地址
IP在网络中用来标识主机的唯一性,Mac地址只在局域网中标识主机的唯一性,Mac地址不可能透过局域网,因为在经过路由器时,就把源Mac地址和目标Mac地址脱掉重新封装了,IP地址是可以跨网络的。
我们需要思考一个问题:数据传输到主机不是目的,因为数据是给人用的。而人是通过QQ、迅雷、网页这样的进程来聊天、下载、浏览信息。也就是说,进程是人在系统中的代表。所以,数据传输到主机不是目的,而是手段,到达主机内部,再交给主机内的进程才是目的!
但是在系统中会同时存在很多进程,当数据达到目标主机之后,怎么转发给目标进程呢?这就要求在网络中标识主机的唯一性和主机中进程的唯一性。
实际上,使用IP地址来标识主机的唯一性。那如何标识进程的唯一性呢?我们会想到进程的pid,但是并没有采用pid标识进程的唯一性,而是使用端口号标识进程唯一性!
端口号
端口号是传输层协议的内容:
- 端口号是一个2字节16位的整数;
- 端口号用来标识一个进程,告诉操作系统,当前这个数据要交给哪一个进程来处理;
数据在传输层封装的时候,会在传输层报头加上目的端口号,在网络层网络报头填上目的IP,在数据链路层的链路报头会填上下一站的Mac地址。IP地址表明目标主机,端口号表明到主机的哪一个应用。这就要求进程在启动时要绑定特定的端口号。
- IP地址+端口号能够标识网络上的某一台主机的某一个进程
- 一个端口号只能被一个进程占用
进程通信是两个主机之间,严格的说,是A主机上的进程和B主机上的进程在通信,所以IPA表示全网唯一的一个主机,PORTA表示该主机上唯一一个进程,所以IPA+PORTA表示互联网上唯一一个进程,IPB+PORTB也表示互联网中的一个进程,所以网络通信的本质就是进程间通信!之前我们学习进程的时候说,进程之间通信的前提是看到同一块资源,这份公共资源就是网络,而IPA+PORTA以及IPB+PORTB就叫做socket,把基于IP+端口的这种网络通信的方式叫做socket通信。
服务端启动的时候,就一定要和一个port关联起来。那这个port不能用进程pid吗,为啥要单独搞一个port?实际上,在技术看来,端口号是几不重要,端口号具有唯一性才重要!进程的pid是一个系统的概念,而端口号是一个网络的概念,如果非要用pid来标识进程,也不是不可以,但这带来了系统和网络之间的强耦合,万一pid发生更改,那网络部分是不是也要调整,耦合度太高会带来很多问题,不划算。所以,最主要的是要解耦,系统是系统,网络是网络!
另外,系统和网络解耦还有别的好处,系统中所有进程都必须有pid,但并不是所有进程都想网络通信,所以,少量需要通信的进程才需要有端口号,这样,凡是有端口号的进程才是需要通信的进程。所以,pid和端口号是有不同应用场景的数字,比如我们每个学生同时有身份证号和学号。
端口号范围划分
- 0-1023:知名端口号,HTTP,FTP,SSH等这些广泛使用的应用层协议,它们的端口号都是固定的。(类似110和警察、120和医院)
- 1024-65535:操作系统动态分配的端口号,客户端程序的端口号就是由OS从这个范围分配的。
源端口号和目的端口号
传输层协议(TCP、UDP)的数据段中有两个端口号,分别叫做源端口号和目的端口号,就是在描述“数据是谁发的,要发给谁”。
理解socket
- IP地址标识互联网中唯一的一台主机,port表示该主机上唯一一个网络进程;
- IP+Port表示互联网中唯一一个进程;
- 通信本质就是两个互联网进程代表人来进行通信,{srcIP,srcPort,dstIP,dstPort}这样一组就能标识互联网中唯二的两个进程;
- 网络通信的本质就是进程间通信;
- IP+PORT叫做套接字socket
网络通信中所有的行为无外乎两种,1)把自己的东西传上去交给服务器,2)从服务器上下载东西。这其实就是IO问题,就到了冯诺依曼体系。
传输层的典型代表
传输层是属于内核的,我们通过网络协议栈进行通信,必定调用的是传输层的系统调用,来进行网络通信。
我们在进行套接字编程时,其实我们在写一个应用层,使用系统调用接口来访问传输层,网络属于OS,OS不相信任何用户,用户要访问网络功能,必须通过系统调用。
TCP协议
TCP,Transmission Control Protocol,传输控制协议,先简要了解一下:
- 传输层协议
- 有连接
- 可靠传输
- 面向字节流
UDP协议
UDP,User Datagram Protocol,用户数据报协议,简单了解一下:
- 传输层协议
- 无连接
- 不可靠传输
- 面向数据报
这些只是TCP/UDP协议的特征,而非优缺点。
网络字节序
我们以前知道,主机分为大端机和小端机,如果主机A是大端,主机B是小端,那把数据通过网络传输过去,不就解释反了吗?!所以,为了解决这样的问题,做了一个规定,凡是在网络中通信,必须大端,即低地址高字节!所以,有了一个规定,从网络中读取的数据肯定是大端。
- 发送主机通常将发送缓冲区的数据按内存地址从低到高的顺序发出;
- 接受主机把从网络上接到的字节依次保存在接收缓冲区中,也是按照内存地址从低到高的顺序保存;
- 因此,网络数据流的地址应该这样规定:先发出去的数据是低地址,后发出去的数据是高地址;
- TCP/IP协议规定,网络数据流应该采用大端字节序,即低地址高字节。这样发送数据看起来比较舒服。(比如发送0x1234abcd,那就先发12,然后34,再ab,再cd)
为了使网络程序具有可移植性,可以调用下面的库函数做网络字节序和主机字节序的转换:
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong); // 主机转网络
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong); //网络转主机
uint16_t ntohs(uint16_t netshort);
- 这些名字很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。
- 例如,htonl表示将32位的长整数从主机字节序转换为网络字节序;
- 如果主机是小端字节序,这些函数将参数做大小端转换然后返回;
- 如果主机是大端字节序,这些函数不做转换,将参数原封不动返回。
socket编程接口
常见接口
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address, socklen_t address_len);
// 开始监听 socket (TCP, 服务器)
int listen(int socket, int backlog);
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address, socklen_t* address_len);
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
我们观察到这几个函数中好几个都有struct sockaddr这个结构,下面我们学一下这个结构体。
struct sockaddr结构体
实际上,socket编程有很多种类,一种是网络套接字,使用IPv4/IPv6实现跨网络通信,另一种是域间套接,实现本主机内通信。使用同一套接口,既可以实现网络通信,又可以实现本主机通信,
如果想使用网络通信(中间那种结构体struct sockaddr_in,16位地址类型是AF_INET),要告诉目标主机端口号、IP地址。后面那个结构体struct sockaddr_un16位地址类型是AF_UNIX,告诉主机想使用接口实现本地通信,就需要有108字节的路径名,未来可以让本主机内两个不同进程看到同一个路径名访问同一个资源,这样就可以类似网络通信一样实现本主机通信。为了让这些接口更通用,又设计了上图第一个结构体struct sockaddr,未来如果传入sockaddr_in,发现其前16位是AF_INET,那就是网络通信,如果传入sockaddr_un,根据前16位,发现是AF_UNIX,就是要本机内通信,所以就把下面的路径拿出来。这种思想相当于是多态。struct sockaddr相当于基类,sockaddr_in和sockaddr_un是两个子类。
关于socket编程的预备知识就介绍到这里了~
原文地址:https://blog.csdn.net/qq_48460693/article/details/143834689
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!