计算机网络背景网络发展独立模式: 计算机之间相互独立;网络互联: 多台计算机连接在一起, 完成数据共享局域网 LAN: 计算机数量更多了, 通过交换机和路由器连接在一起广域网 WAN: 将远隔千里的计算机都连在一起;初识协议• 协议 是一种约定.• 打电话约定电话铃响的次数的约定计算机之间的传输媒介是光信号和电信号. 通过 频率 和 强弱 来表示 0 和 1 这样的 信息. 要想传递各种不同的信息, 就需要约定好双方的数据格式只要通信的两台主机, 约定好协议就可以了么?• 定好协议但是你用频率表示 01我用强弱表示 01就好比我用中国话你 用葡萄牙语一样虽然大家可能遵守的一套通信规则但是语言不同即是订好了 基本的协议也是无法正常通信的所以完善的协议需要更多更细致的规定并让参与的人都要遵守。• 计算机生产厂商有很多;• 计算机操作系统,也有很多• 计算机网络硬件设备, 还是有很多;• 如何让这些不同厂商之间生产的计算机能够相互顺畅的通信? 就需要有人站出 来, 约定一个共同的标准, 大家都来遵守, 这就是网络协议协议分层协议本质也是软件在设计上为了更好的进行模块化解耦合也是被设计成为 层状结构的软件分层的好处分层可以实现解耦合让软件维护的 成本更低OSI七层模型• OSIOpen System Interconnection开放系统互连七层网络模型称为开放 式系统互联参考模型是一个逻辑上的定义和规范;• 把网络从逻辑上分为了 7 层. 每一层都有相关、相对应的物理设备比如路由 器交换机;• OSI 七层模型是一种框架性的设计方法其最主要的功能使就是帮助不同类型 的主机实现数据传输;• 它的最大优点是将服务、接口和协议这三个概念明确地区分开来概念清楚 理论也比较完整. 通过七个层次化的结构模型使不同的系统不同的网络之间实现可 靠的通讯;• 但是, 它既复杂又不实用; 所以我们按照 TCP/IP 四层模型来讲在网络角度OSI 定的协议 7 层模型其实非常完善但是在实际操作的过程 中会话层、表示层是不可能接入到操作系统中的所以在工程实践中最终落地是 5 层协议TCP/IP 五层(或四层)模型TCP/IP 是一组协议的代名词它还包括许多协议组成了 TCP/IP 协议簇. TCP/IP 通讯协议采用了 5 层的层级结构每一层都呼叫它的下一层所提供的网络来完 成自己的需求物理层我们考虑的比较少我们只考虑软件相关的内容. 因此很多时候我们直接称为 TCP/IP 四层模型.• 物理层:负责光/电信号的传递方式. 比如现在以太网通用的网线(双绞 线)、早 期以太网采用的的同轴电缆(现在主要用于有线电视)、光纤, 现在的 wifi 无线网使用 电磁波等都属于物理层的概念。物理层的能力决定了最大传输速率、传输距离、抗 干扰性等. 集线器(Hub)工作在物理层.• 数据链路层:负责设备之间的数据帧的传送和识别. 例如网卡设备的驱动、帧同 步(就是说从网线上检测到什么信号算作新帧的开始)、冲突检测(如果检测到冲突就 自动重发)、数据差错校验等工作. 有以太网、令牌环网, 无线 LAN 等标准. 交换机 (Switch)工作在数据链路层.• 网络层:负责地址管理和路由选择. 例如在 IP 协议中, 通过 IP 地址来标识一台 主机, 并通过路由表的方式规划出两台主机之间的数据传输的线路(路由). 路由器 (Router)工作在网路层.• 传输层:负责两台主机之间的数据传输. 如传输控制协议 (TCP), 能够确保数据 可靠的从源主机发送到目标主机.• 应用层:负责应用程序间沟通如简单电子邮件传输SMTP、文件传输协 议FTP、网络远程访问协议Telnet等. 我们的网络编程主要就是针对应用层再识协议为什么要有 TCP/IP 协议• 首先即便是单机你的计算机内部其实都是存在协议的比如其他设备和 内存通信会有内存协议。其他设备和磁盘通信会有磁盘相关的协议比如 SATAIDESCSI 等。只不过我们感知不到罢了。而且这些协议都在本地主机各自 的硬件中通信的成本、问题比较少• 其次网络通信最大的特点就是主机之间变远了。任何通信特征的变化一定会 带来新的问题有问题就得解决问题所以需要新的协议咯什么是 TCP/IP 协议• TCP/IP 协议的本质是一种解决方案 • TCP/IP 协议能分层前提是因为问题们本身能分层简单来说TCP/IP 协议不是一个单一的协议而是一个协议族的统称。它定义了电子设备如何连入互联网以及数据如何在它们之间进行传输的标准。这个名字来源于其中两个最核心的协议传输控制协议 (TCP)和网际协议 (IP)。TCP/IP 协议与操作系统的关系(宏观上怎么实现的)问题主机 B 能识别 data并且准确提取 a10b20c30 吗 回答答案是肯定的因为双方都有同样的结构体类型 struct protocol。也就是说用同样的代码实现协议用同样的自定义数据类型天然就具有”共识“能够识别 对方发来的数据这不就是约定吗关于协议的朴素理解所谓协议就是通信双方都认识的结构化的数据类型 因为协议栈是分层的所以每层都有双方都有协议同层之间互相可以认识对 方的协议网络传输基本流程局域网网络传输流程图局域网(以太网为例)通信原理两台主机在同一个局域网是能够直接通信的 原理类似上课每台主机在局域网上要有唯一的标识来保证主机的唯一性mac 地址认识 MAC 地址• MAC 地址用来识别数据链路层中相连的节点;• 长度为 48 位, 及 6 个字节. 一般用 16 进制数字加上冒号的形式来表示(例如: 08:00:27:03:fb:19)• 在网卡出厂时就确定了, 不能修改. mac 地址通常是唯一的(虚拟机中的 mac 地 址不是真实的 mac 地址, 可能会冲突; 也有些网卡支持用户配置 mac 地址)• 以太网中任何时刻只允许一台机器向网络中发送数据• 如果有多台同时发送会发生数据干扰我们称之为数据碰撞• 所有发送数据的主机要进行碰撞检测和碰撞避免• 没有交换机的情况下一个以太网就是一个碰撞域• 局域网通信的过程中主机对收到的报文确认是否是发给自己的是通过目标 mac 地址判定初步明白了局域网通信原理再来看同一个网段内的两台主机进行发送消息的过程而其中每层都有协议所以当我进行进行上述传输流程的时候要进行封装和解包下面我们明确一下概念• 报头部分就是对应协议层的结构体字段我们一般叫做报头• 除了报头剩下的叫做有效载荷• 故报文 报头 有效载不同层的完整报文的叫法• 不同的协议层对数据包有不同的称谓。在传输层叫做段(segment),在网络层叫做 数据报 (datagram),在链路层叫做帧(frame).• 应用层数据通过协议栈发到网络上时,每层协议都要加上一个数据首部 (header),称为封装(Encapsulation).• 首部信息中包含了一些类似于首部有多长, 载荷(payload)有多长, 上层协议是 什么等信息.• 数据封装成帧后发到传输介质上,到达目的主机后每层协议再剥掉相应的首部, 根据首部中的 上层协议字段 将数据交给对应的上层协议处理.总结在网络传输的过程中数据不是直接发送给对方主机的而是先要自定向下将数据交 付给下层协议最后由底层发送然后由对方主机的底层来进行接受在自底向上进 行向上交付数据包封装和分用数据封装的过程数据分用的过程我们学习任何协议都要先宏观上建立这样的认识1. 要学习的协议是如何做到解包的只有明确了解包封包也就能理解2. 要学习的协议是如何做到将自己的有效载荷交付给上层协议的跨网络传输流程图网络中的地址管理 - 认识 IP 地址IP 协议有两个版本, IPv4 和 IPv6.。提到 IP 协议, 没有特殊说明的, 默认都是指 IPv4• IP 地址是在 IP 协议中, 用来标识网络中不同主机的地址;• 对于 IPv4 来说, IP 地址是一个 4 字节, 32 位的整数;• 我们通常也使用 点分十进制 的字符串表示 IP 地址, 例如 192.168.0.1 ; 用点 分割的每一个数字表示一个字节, 范围是 0 - 255跨网段的主机的数据传输. 数据从一台计算机到另一台计算机传输过程中要经过一个或 多个路由器.首先理解一下 IP 地址的意义 • 为什么要去目标主机先要走路由器 • 目的 IP 的意义1. 目的 IP 的意义唯一的身份标识图中的目的 IP172.168.2.2 代表了“你最终想去哪里”。全球唯一它就像是你在互联网世界里的“门牌号”。不管你在哪个城市、哪栋楼不管你的物理位置在哪这个 IP 地址都能唯一定位到你这台电脑。逻辑地址它是工作在网络层的图中圈出的部分。它不关心具体的路线只负责告诉网络“我是谁我要找谁”。跨网段通信图中用户 A 的 IP 是192.168.2.2用户 B 是172.168.2.2。这两个 IP 属于不同的网段可以理解为两个不同的小区。IP 地址的存在使得系统知道这是一个跨网段的请求从而触发了“找路由器”的机制。2. 为什么要先走路由器要回答这个问题得看图中的那个提示框“发现不是发给本网段主机的报文就推送给路由器”。简单来说因为目标主机“不在同一个房间”你需要一个“管家”帮你递送。局域网的限制你的电脑用户 A发出的数据首先会通过数据链路层寻找目标。数据链路层使用的是MAC 地址物理地址它的寻址范围非常有限通常只能在同一个局域网内比如同一个办公室、校园网直达。路由器的角色网关当你发送数据给172.168.2.2时你的电脑一查发现“咦这个 IP 不在我的本地列表里不是 192.168.2.x 网段”。这时候电脑就知道“我自己直接发不过去必须交给路由器。”因此路由器充当了“跨网段快递员”的角色。你的数据先发给路由器图中左侧线路路由器再根据它的地图路由表把数据转发给下一个路由器直到抵达用户 B 所在的网络。3. 总结流程应用层用户 A 产生数据Data。网络层加上 IP 头源 IP192.168.2.2目的 IP172.168.2.2。系统发现目的 IP 不在本地于是将数据交给默认网关即路由器 192.168.2.1。数据链路层将数据封装成帧此时需要找路由器的 MAC 地址macA而不是找目的主机的 MAC 地址。传输数据到达路由器路由器“拆包”看一眼目的 IP重新“打包”发给下一段路最终到达用户 B。然后结合封装与解包体现路由器解包和重新封装的特点1. 用户 A 端封装Encapsulation当数据从用户 A 的应用层产生并向下传递时每一层都会为其添加特定的头部信息就像寄快递时不断添加包装和面单网络层加 IP 头系统为数据包打上源 IP192.168.2.2和目的 IP172.168.2.2。此时系统发现目的 IP 不在本地网段于是通过 ARP 协议查找到达网关路由器的 MAC 地址macLeft。数据链路层加 MAC 头在数据帧的头部填入源 MACmacA和目的 MACmacLeft。发送网卡将最终的二进制数据帧通过物理线路发送出去。2. 路由器端解包De-encapsulation路由器接收到数据帧后开始进行反向操作以读取地址信息去 MAC 头数据链路层剥离外层的数据帧读取里面的目的 MAC 地址macLeft。确认是发给自己的于是将数据交给网络层。读 IP 头网络层剥离 IP 头读取里面的目的 IP 地址172.168.2.2。核心决策路由选择路由器查询自身的路由表发现目的地在另一个网段决定通过右侧接口转发出去。此时内层携带原始数据和 IP 地址的数据包被提取出来准备进行下一步。3. 路由器端重新封装Re-encapsulation为了将数据包送到下一站路由器必须为其换上“新衣服”换 MAC 头数据链路层根据路由决策将源 MAC 地址替换为路由器右侧接口的 MACmacRight目的 MAC 地址替换为用户 B 的 MACmacB。保留 IP 头关键点在于内层的 IP 头源 IP 和目的 IP在整个过程中完全没有被修改。这保证了数据在复杂的网络中流转时始终能明确最终的通信目标。4. 用户 B 端解包交付用户 B 接收到数据帧后重复常规的解包流程剥离 MAC 头检查目的 MAC 是否为自己。剥离 IP 头检查目的 IP 是否为自己。确认无误后将最内层的“数据data: 你好”向上交付给传输层和应用层进行处理。对比 IP 地址和 Mac 地址的区别• IP 地址在整个路由过程中一直不变(目前我们只能这样说明后面在修正)• Mac 地址一直在变• 目的 IP 是一种长远目标Mac 是下一阶段目标目的 IP 是路径选择的重要依 据mac 地址是局域网转发的重要依据Socket 编程预备1. 理解源 IP 地址和目的 IP 地IP 在网络中用来标识主机的唯一性思考一个问题数据传输到主机不是目的。因为数据是给人用 的。比如聊天是人在聊天下载是人在下载浏览网页是人在浏览 但是人是怎么看到聊天信息的呢怎么执行下载任务呢怎么浏览网页信息呢通过 启动的 qq迅雷浏览器。而启动的 qq迅雷浏览器都是进程。换句话说进程是人在系统中的代表只要把 数据给进程人就相当于就拿到了数据。 所以数据传输到主机不是目的而是手段。到达主机内部在交给主机内的进程 才是目的。但是系统中同时会存在非常多的进程当数据到达目标主机之后怎么转发给目标 进程这就要在网络的背景下在系统中标识主机的唯一性。2. 认识端口号端口号(port)是传输层协议的内容.• 端口号是一个 2 字节 16 位的整数;•端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来 处理;•IP 地址 端口号能够标识网络上的某一台主机的某一个进程;•一个端口号只能被一个进程占用端口号范围划分• 0 - 1023: 知名端口号, HTTP, FTP, SSH 等这些广为使用的应用层协议, 他们的 端口号都是固定的.• 1024 - 65535:操作系统动态分配的端口号.客户端程序的端口号, 就是由操作 系统从这个范围分配的理解 端口号 和 进程 ID我们之前在学习系统编程的时候, 学习了pid 表示唯一一个进程; 此处我们的端口号也 是唯一表示一个进程. 那么这两者之间是怎样的关系?这是问题触及了网络编程和操作系统设计的核心。我们可以用一个经典的比喻来理解端口号是“服务窗口”或“电话号码”而进程ID是“窗口后的办事员”或“接电话的客服”。核心关系分工与解耦端口号Port网络通信的“门户”作用它是一个16位的数字用于标识主机上的一个网络服务。当数据从网络到达你的电脑时操作系统根据目标端口号来决定将数据交给哪个进程中的哪个服务来处理。类比10086例子10086是中国移动的客服热线号码一个著名的端口。这个号码是公开、固定、众所周知的。任何想联系移动客服的人都会拨打这个号码。特点相对稳定知名服务使用固定端口如HTTP: 80, HTTPS: 443。对外公开是网络协议的一部分用于远程寻址。资源标识是网络栈传输层管理的资源。进程IDPID系统内部的“身份证”作用它是操作系统内核分配的一个数字用于在系统内部唯一标识和管理一个正在运行的进程实例。它用于进程调度、内存分配、信号发送等。类比10086例子在10086热线背后可能有成千上万个客服人员进程。每个客服都有一个内部工号PID。工号是内部管理用的对外部客户没有意义。客户不关心是谁接的电话只关心电话能接通到正确的服务部门。特点动态变化进程每次启动时PID通常都会改变。内部私有只在单机操作系统内部有效不具备网络意义。管理标识是操作系统进程管理模块管理的资源。为什么不用PID直接进行网络通信设计哲学为了解耦。如果直接用PID作为网络地址会导致强耦合网络协议将完全依赖特定操作系统的进程管理机制破坏了网络层的独立性。不稳定性PID是动态的。一个服务重启后PID就变了所有远程客户端都需要重新获取新的PID才能连接这不可行。缺乏抽象一个进程可能提供多种网络服务如一个Web服务器同时提供HTTP和HTTPS。一个PID无法区分这些不同的服务“入口”。两者如何协同工作当你的电脑收到一个数据包比如发往你的IP:80时操作系统的网络协议栈和进程管理模块会这样协作网络层根据IP地址确认数据包是发给本机的。传输层查看TCP/UDP头中的目标端口号例如80。系统协作操作系统内核中维护着一张“端口号-进程”映射表。它通过查表找到监听80端口的那个进程的PID比如找到了Nginx服务器的进程。交付数据内核将数据包负载应用层数据放入该进程对应的缓冲区并唤醒该进程进行处理。总结一下关系端口号是网络通信的公共地址用于从网络定位到主机上的服务。进程ID是操作系统内部的私有句柄用于管理系统资源。一个进程一个办事员可以打开多个端口负责多个服务窗口。一个端口在任一时刻只能被一个进程监听一个窗口后只能坐一个办事员否则会发生冲突。操作系统内核充当“调度中心”维护着端口到进程的映射关系完美地将网络世界的寻址端口与系统内部的管理PID解耦开来。理解源端口号和目的端口号传输层协议(TCP 和 UDP)的数据段中有两个端口号, 分别叫做源端口号和目的端口号. 就是在描述 数据是谁发的,要发给谁;理解 socket• 综上IP 地址用来标识互联网中唯一的一台主机port 用来标识该主机上唯一的 一个网络进程• IPPort 就能表示互联网中唯一的一个进程• 所以通信的时候本质是两个互联网进程代表人来进行通信{srcIp srcPortdstIpdstPort}这样的 4 元组就能标识互联网中唯二的两个进程• 所以网络通信的本质也是进程间通信• 我们把ipport 叫做套接字 socke3. 传输层的典型代表传输层是属于内核的那么我们要通过网络协议栈进行通信必定调用的是传输层提供的系统调用来进行的网络通信认识 TCP 协议此处我们先对 TCP(Transmission Control Protocol 传输控制协议)有一个直观的认识; 后面我们再详细讨论 TCP 的一些细节问题.• 传输层协议• 有连接• 可靠传输• 面向字节流认识 UDP 协议此处我们也是对 UDP(User Datagram Protocol 用户数据报协议)有一个直观的认识; 后 面再详细讨论.• 传输层协议• 无连接• 不可靠传输• 面向数据4. 网络字节序内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的 多字节数据相对于文件中的偏移地址也有大端小端之分,网络数据流同样有大端小端之 分. 那么如何定义网络数据流的地址呢?• 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;• 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从 低到高的顺序保存;• 因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高 地址.• TCP/IP 协议规定,网络数据流应采用大端字节序,即低地址高字节.• 不管这台主机是大端机还是小端机, 都会按照这个 TCP/IP 规定的网络字节序来 发送/接收数据;• 如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即 可;为什么要转换如果你用的是小端机比如常见的 x86 电脑它在内存里存0x1234abcd的时候0xcd是在0x0000地址的。但是网络规定必须先发0x12。所以小端机在发送前必须先在 CPU 内部把数据“倒过来”转成大端序然后再发出去。这就是所谓的“主机字节序转网络字节序”。为使网络程序具有可移植性,使同样的 C 代码在大端和小端计算机上编译后都能正常运 行,可以调用以下库函数做网络字节序和主机字节序的转换。• 这些函数名很好记,h 表示 host,n 表示 network,l 表示 32 位长整数,s 表示 16 位 短整数。• 例如 htonl 表示将 32 位的长整数从主机字节序转换为网络字节序,例如将 IP 地 址转换后准备发送。• 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;• 如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。5. socket 编程接口socket 常见 API// 创建 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);1. socket() - 创建套接字int socket(int domain, int type, int protocol);参数名类型详细描述domainint指定通信的协议族Protocol Family即地址族Address Family。决定了socket的地址类型如IPv4、IPv6等。常见取值•AF_INETIPv4协议•AF_INET6IPv6协议•AF_UNIX 或AF_LOCAL本地通信进程间通信•AF_PACKET底层数据包接口用于直接访问网络层typeint指定socket类型决定了通信的语义如面向连接、无连接。常见取值•SOCK_STREAM面向连接的流套接字提供可靠、双向、基于字节流的通信。对应TCP协议。•SOCK_DGRAM无连接的数据报套接字提供不可靠、不保证顺序、固定最大长度的数据报通信。对应UDP协议。•SOCK_RAW原始套接字允许程序直接访问底层协议如IP、ICMP。需要特权。protocolint指定socket使用的具体协议。通常设置为0表示根据domain和type选择默认协议。常见取值•0自动选择。对于AF_INET和SOCK_STREAM默认是TCP对于AF_INET和SOCK_DGRAM默认是UDP。•IPPROTO_TCPTCP协议通常用于SOCK_STREAM。•IPPROTO_UDPUDP协议通常用于SOCK_DGRAM。返回值成功返回一个非负整数即套接字描述符socket descriptor类似于文件描述符。失败返回-1并设置errno。2. bind() - 绑定地址int bind(int socket, const struct sockaddr *address, socklen_t address_len);参数类型详细说明socketintsocket()函数返回的套接字描述符addressconst struct sockaddr*指向地址结构体的指针包含要绑定的IP地址和端口号。这个指针需要被强制转换为struct sockaddr*类型无论实际使用的是什么地址结构address_lensocklen_t地址结构体的长度以字节为单位。通常使用sizeof()运算符获取常见地址设置INADDR_ANY: 绑定到所有网络接口(0.0.0.0)htonl(INADDR_LOOPBACK): 绑定到环回地址(127.0.0.1)inet_addr(192.168.1.100): 绑定到指定IP地址返回值成功返回0。失败返回-1并设置errno。注意服务器程序通常需要调用bind()来绑定一个众所周知的端口而客户端可以不调用bind()由系统自动分配端口。3. listen() - 监听连接int listen(int socket, int backlog);参数名类型详细描述socketint要设置为监听状态的套接字描述符。这个套接字必须已经通过bind()绑定了一个地址。backlogint指定连接队列的最大长度。当有多个客户端连接请求到达时如果服务器正在处理则这些请求会在队列中排队等待。backlog参数决定了队列中最多可以有多少个等待接受的连接。注意在Linux 2.2之后这个参数指的是已完成三次握手但还未被accept取走的连接数即ESTABLISHED状态。未完成连接队列SYN_RCVD状态的最大长度由系统参数控制。返回值成功返回0。失败返回-1并设置errno。注意listen()只用于面向连接的套接字如SOCK_STREAM并且通常由服务器调用。backlog参数深入解释内核为每个监听套接字维护两个队列未完成连接队列(SYN队列)存放收到SYN但未完成三次握手的连接状态为SYN_RCVD已完成连接队列(Accept队列)存放已完成三次握手但未被accept()取走的连接状态为ESTABLISHEDbacklog参数指定的是这个队列的大小backlog取值建议Linux 2.2之前backlog是未完成已完成队列的总大小Linux 2.2之后backlog只是已完成队列的大小常用值5, 10, 128, 511可以通过/proc/sys/net/core/somaxconn调整系统级最大值4. accept() - 接受连接int accept(int socket, struct sockaddr* address, socklen_t* address_len);参数名类型详细描述socketint监听套接字描述符即已经调用了listen()的套接字。addressstruct sockaddr*用来返回客户端的地址信息。如果不需要客户端的地址可以设置为NULL。address_lensocklen_t*这是一个值-结果参数value-result argument。调用时指向一个整数表示address指向的缓冲区的长度返回时这个整数会被设置为客户端地址的实际长度。如果地址的实际长度超过提供的缓冲区长度则地址会被截断。返回值成功返回一个新的套接字描述符这个新套接字代表与客户端的连接。原监听套接字继续用于接受其他连接。失败返回-1并设置errno。注意accept()会阻塞直到有连接请求到达。如果不想阻塞可以将监听套接字设置为非阻塞模式。函数行为详解accept()从已完成连接队列中取出第一个连接如果队列为空accept()会阻塞(默认情况下)直到有连接可用返回一个新的套接字描述符专门用于与该客户端通信原监听套接字继续监听新的连接请求5. connect() - 发起连接int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);参数名类型详细描述sockfdint客户端套接字描述符由socket()创建。addrconst struct sockaddr*指向服务器地址结构体的指针包含了服务器的IP地址和端口号。addrlensocklen_t服务器地址结构体的长度通常为sizeof(struct sockaddr_in)。返回值成功返回0表示连接已建立。失败返回-1并设置errno。常见错误返回值错误码含义可能原因EACCES权限被拒绝绑定到保留端口(0-1023)但无root权限EADDRINUSE地址已被使用端口已被其他进程占用EADDRNOTAVAIL地址不可用指定的IP地址不属于本机ECONNREFUSED连接被拒绝目标服务器未运行或拒绝连接ETIMEDOUT连接超时服务器不响应EINPROGRESS连接进行中套接字是非阻塞的连接需要时间注意对于TCP套接字connect()会触发三次握手过程阻塞直到握手成功或失败。对于UDP套接字connect()不会真正建立连接而是记录目标地址这样后续可以使用send()和recv()而不必每次都指定地址。同时只会接收来自该地址的数据报。函数行为详解对于TCP套接字尝试与指定服务器建立TCP连接触发TCP三次握手过程默认情况下阻塞直到连接成功或失败成功返回0失败返回-1对于UDP套接字不实际建立连接(无握手过程)只是设置默认的目标地址之后可以使用send()/recv()代替sendto()/recvfrom()只接收来自该地址的数据报sockaddr 结构socket API 是一层抽象的网络编程接口,适用于各种底层网络协议,如 IPv4、IPv6,以及 后面要讲的 UNIX Domain Socket. 然而, 各种网络协议的地址格式并不相同• IPv4 和 IPv6 的地址格式定义在 netinet/in.h 中,IPv4 地址用 sockaddr_in 结构 体表示,包括 16 位地址类型, 16 位端口号和 32 位 IP 地址.• IPv4、IPv6 地址类型分别定义为常数 AF_INET、AF_INET6. 这样,只要取得某 种 sockaddr 结构体的首地址,不需要知道具体是哪种类型的 sockaddr 结构体,就可 以根据地址类型字段确定结构体中的内容.• socket API 可以都用 struct sockaddr *类型表示, 在使用的时候需要强制转化成 sockaddr_in; 这样的好处是程序的通用性, 可以接收 IPv4, IPv6, 以及 UNIX Domain Socket 各种类型的 sockaddr 结构体指针做为参数;sockaddr 结构sockaddr_in 结构虽然 socket api 的接口是 sockaddr,但是我们真正在基于 IPv4 编程时, 使用的数据结 构是 sockaddr_in; 这个结构里主要有三部分信息: 地址类型, 端口号, IP 地址in_addr 结构in_addr 用来表示一个 IPv4 的 IP 地址. 其实就是一个 32 位的整数;