本文是基于韦东山“嵌入式Linux应用开发完全手册V5.3_IMX6ULL_Pro开发板”的网络编程的助解套接字应用层与 TCP/IP 协议族通信的中间软件抽象层它是一组接口API。它隐藏了复杂的网络协议细节让开发者只需通过简单的“打开、读写、关闭”操作就能实现进程间的网络数据传输。Socket 就像是“公寓楼的门牌号 房门”想象你要给住在某公寓的一位朋友寄快递IP 地址就是这栋公寓楼的地址。它能让你找到这台电脑。端口号 (Port)就是朋友家的房间号。一台电脑运行着很多程序端口号决定了数据该交给哪个程序。Socket (套接字)就是那个房门。它是数据进入房间程序的唯一入口。结论没有 Socket快递员只能送到大楼门口不知道该敲哪家的门。socket通过bind绑定IP地址以及一个具体的端口号不使用bind的socket会随机分配端口号通常经过bind绑定的socket将作为服务端因为客户端要向服务端发送数据则服务端需要有一个固定的地址以及端口号有了这个固定的端口号当客户端向这个IP及端口号发送连接建立请求时对应的socket服务端才能收到并做出响应。调用了listen之后socket才能开始监听有没有客户端向之前用bind绑定了的具体端口发送了连接请求 Socket 通信全过程医院诊疗模型bind —— 分配诊室确定地址医院给专家划定了一个具体的房间“3 楼 302 诊室”。技术对应这就是绑定了 IP 地址3 楼和 端口号302 室。意义病人客户端有了明确的目标地址知道去哪里发起连接。listen —— 开启自动挂号机建立监听医生刷卡上班按下了诊室墙上**“开始挂号”**的开关。此时医生可能还在诊室内整理病历或喝茶程序在处理其他非通信逻辑。内核操作此时门口的自动挂号机内核正式启动。只要病人客户端发起请求挂号机就自动与其完成“三次握手”并给病人一张号牌让其在候诊大厅全连接队列/Accept Queue坐下等待。重点此时医生程序甚至还没见到病人但连接已经由内核替医生建立好了。accept —— 叫号进屋获取连接医生忙完了按下桌上的“请下一位”按钮。内核操作挂号机收到指令从候诊大厅请出最前面的一位病人带进医生的诊室。结果accept 函数成功返回医生获得了一个新的 Socket这就像建立了一个和该病人面对面交流的专属通道。为什么需要新的 Socket传统模式的弊端想象一下如果医生只有一个“房门”原来的监听 Socket医生正和病人甲在屋里沟通。这时病人乙来了他敲门但房门被甲锁着乙只能在外面等。结果医生同一时间只能接待一个病人。这在现代网络中如上万人同时刷网页是不可接受的低效。Socket 设计者的方案为了解决并发问题Socket 被设计成了两种角色监听 SocketListening Socket就像医院的大门。它永远只负责“领人进屋”永远不关门永远在 80 端口守着。已连接 SocketConnected Socket就像临时的谈话窗口。每当 accept 成功系统就变出一个专门的“小窗口”新的 FD让这个病人和医生沟通。在 Linux 系统中的样子文件描述符 FD在 Linux 里Socket 本质上都是数字监听 Socket编号为 3。它通过 bind 固定在 80 端口。调用 accept 之后第一次 accept 返回 FD 4专门用于陪【病人甲】聊天。第二次 accept 返回 FD 5专门用于陪【病人乙】聊天。原有的 FD 3 毫无压力继续在 80 端口迎接【病人丙】。在服务器端通过 bind 定好了位置、listen 开启了挂号机之后客户端就必须调用 connect 来请求与服务端进行连接它的目标就是图片里提到的 serv_addr。这个地址必须包含服务器固定绑定bind好的 IP 和端口。它的动作触发 TCP 三次握手。客户端向服务器发送“SYN”信号。如果服务器的 listen 队列没满内核就会自动回应。一旦握手成功connect 函数就会返回 0表示与服务器连接成功。服务器端有两个 Socket。一个用于守门监听一个用于聊天accept 产生的新 Socket。客户端通常只有一个 Socket。这个 sockfd 在调用 connect 之前是用来指定“我要去连谁”。在 connect 成功之后它直接“变身”成了通话通道。也就是说客户端不需要分身因为它只和一个服务端说话。用于TCP协议这两个函数用于在成功建立连接后服务端生成的新socket中传输数据的在 C 语言以及 Linux 底层中send 和 recv 不是“属于”某个 Socket 的方法而是全局的系统调用函数。传递给它们的 sockfd本质上就是一个“操作编号”。send和recv的第一个参数sockfd逻辑是一样的这两个函数并不知道要操作哪个socket比如A是服务端的监听socketB是客户端的socket当A和B成功连接之后监听socket就会生成一个新的“交流”socketA1。当服务端要向B发送数据时send的sockfd就要填A1A1指向客户端B把数据塞进B的缓冲区中对应的客户端B要在接收函数recv中填写B否则客户端B不会读取A1发到缓冲区中的数据反之亦然。服务端 → 客户端 的数据流服务端操作调用 send(A1, buf, …)。内核查找 A1 的档案发现它绑定了“目标客户端 B 的 IPPort”。结果数据顺着 A1 对应的管道流向 B。客户端操作调用 recv(B, buf, …)。内核查找 B 的档案发现 B 的接收缓冲区里有来自 A1 的数据。结果数据被提取到客户端程序的 buf 中。send 函数的本质数据拷贝当你调用 send 时数据并没有立刻飞到网线上而是经历以下过程拷贝内核将应用程序 buf 中的数据拷贝到该 Socket 对应的内核发送缓冲区Send Buffer。返回一旦拷贝完成send 就会立刻返回发送的字节数。发送随后内核协议栈TCP/IP会根据拥塞控制、窗口大小等算法自动将内核缓冲区里的数据封包并发送出去。注意如果内核发送缓冲区满了例如网络拥塞send 将会阻塞直到缓冲区腾出空间。recv 函数的本质数据提取recv 的逻辑与 send 相反它是从内核向用户空间提取数据等待内核协议栈收到网络包并解包后会将数据放入该 Socket 对应的内核接收缓冲区Receive Buffer。拷贝当你的程序调用 recv 时内核将接收缓冲区里的数据拷贝到你的应用程序 buf 中。阻塞行为如果内核接收缓冲区里没有数据recv 会一直阻塞停在这一行代码直到对端发来数据。如果对方关闭了连接发送了 FIN 包recv 会立即返回 0。用于UDP协议在 TCP 中因为已经建立了连接recv 知道数据一定来自管道的另一头。但在 UDP 中你的 Socket 就像一个开放的信箱任何人都可以往里投信。recvfrom 的本质不仅把数据拿出来还读取了数据发送方的地址。为什么需要它如果收到了一个 UDP 数据包但不知道是谁发的就没办法给对方回信。recvfrom 多出来的最后两个参数src_addr 和 addrlen就是专门用来自动填充发送方 IP 和端口的。struct sockaddr *src_addr这是一个空盒子。当你调用函数时内核会把发送方的 IP 和端口号“填”进这个盒子里。socklen_t *addrlen这个盒子的大小。逻辑流如下程序调用 recvfrom递给内核一个 buf 和一个空的地址盒子 src_addr。内核从接收缓冲区拿数据放到 buf同时把发件人的信息写到 src_addr 盒子里。结果函数返回后既拿到了数据也知道了是谁发的。UDP 场景服务端只有一个 Socket我们叫它 U。动作客户端 1 发来数据→\rightarrow→服务端用 recvfrom(U, …) 接收src_addr 填入客户端 1 的地址。客户端 2 发来数据→\rightarrow→服务端还是用 recvfrom(U, …) 接收此时 src_addr 被覆盖为客户端 2 的地址。结论它不需要像 TCP 那样为每个客户端生成新的 A1、A2新的socket它就用一个 Socket 处理所有人全靠 src_addr 来分辨现在是谁在说话。函数适用协议是否知道发送者应用场景举例recvTCP默认知道建立连接时已锁定网页浏览、文件传输要求可靠recvfromUDP必须靠函数参数告知视频直播、在线游戏、DNS 查询sendto 是 UDP 通信无连接的专用发送函数。它的核心逻辑是不要求预先建立连接只需提供“数据”和“目的地”。dest_addr 的本质它是你临时写在信封上的收件人 IP 和端口。为什么需要它因为sockfd操作编号是一个公共 Socket内核档案里没有记录它到底要发给谁。所以每次调用 sendto你都必须明确告诉内核“这一次把这包数据发给这个地址”。比 send 多了两个关键的“入口参数”const struct struct sockaddr *dest_addr目的地信息。包含目标的 IP 和 Port。socklen_t addrlen地址结构体的长度。逻辑过程程序准备把要发的数据放进 buf把目标的 IP/Port 填进 dest_addr 结构体。调用函数执行 sendto(sockfd, buf, len, 0, dest_addr, sizeof(dest_addr))。内核操作内核看到 sockfd 没绑定目标于是立即读取你提供的 dest_addr封装成 UDP 报文发出去。完美的闭环recvfrom sendto在嵌入式开发如开发一个简单的网络温湿度传感器中最常见的逻辑是这样的接收请求服务端调用 recvfrom 收到一个包同时拿到了客户端的地址存入 src_addr。处理数据发现客户端是在查询温度。回传数据服务端紧接着调用 sendto而这里的目的地参数直接填入刚才从 recvfrom 拿到的那个 src_addr。协议“路”的状态接收函数发送函数特点TCP固定管道recvsend档案里已有目标无需重复指定地址。UDP公共马路recvfromsendto每次通信都需要指明对方地址。
Linux网络编程入门学习笔记
本文是基于韦东山“嵌入式Linux应用开发完全手册V5.3_IMX6ULL_Pro开发板”的网络编程的助解套接字应用层与 TCP/IP 协议族通信的中间软件抽象层它是一组接口API。它隐藏了复杂的网络协议细节让开发者只需通过简单的“打开、读写、关闭”操作就能实现进程间的网络数据传输。Socket 就像是“公寓楼的门牌号 房门”想象你要给住在某公寓的一位朋友寄快递IP 地址就是这栋公寓楼的地址。它能让你找到这台电脑。端口号 (Port)就是朋友家的房间号。一台电脑运行着很多程序端口号决定了数据该交给哪个程序。Socket (套接字)就是那个房门。它是数据进入房间程序的唯一入口。结论没有 Socket快递员只能送到大楼门口不知道该敲哪家的门。socket通过bind绑定IP地址以及一个具体的端口号不使用bind的socket会随机分配端口号通常经过bind绑定的socket将作为服务端因为客户端要向服务端发送数据则服务端需要有一个固定的地址以及端口号有了这个固定的端口号当客户端向这个IP及端口号发送连接建立请求时对应的socket服务端才能收到并做出响应。调用了listen之后socket才能开始监听有没有客户端向之前用bind绑定了的具体端口发送了连接请求 Socket 通信全过程医院诊疗模型bind —— 分配诊室确定地址医院给专家划定了一个具体的房间“3 楼 302 诊室”。技术对应这就是绑定了 IP 地址3 楼和 端口号302 室。意义病人客户端有了明确的目标地址知道去哪里发起连接。listen —— 开启自动挂号机建立监听医生刷卡上班按下了诊室墙上**“开始挂号”**的开关。此时医生可能还在诊室内整理病历或喝茶程序在处理其他非通信逻辑。内核操作此时门口的自动挂号机内核正式启动。只要病人客户端发起请求挂号机就自动与其完成“三次握手”并给病人一张号牌让其在候诊大厅全连接队列/Accept Queue坐下等待。重点此时医生程序甚至还没见到病人但连接已经由内核替医生建立好了。accept —— 叫号进屋获取连接医生忙完了按下桌上的“请下一位”按钮。内核操作挂号机收到指令从候诊大厅请出最前面的一位病人带进医生的诊室。结果accept 函数成功返回医生获得了一个新的 Socket这就像建立了一个和该病人面对面交流的专属通道。为什么需要新的 Socket传统模式的弊端想象一下如果医生只有一个“房门”原来的监听 Socket医生正和病人甲在屋里沟通。这时病人乙来了他敲门但房门被甲锁着乙只能在外面等。结果医生同一时间只能接待一个病人。这在现代网络中如上万人同时刷网页是不可接受的低效。Socket 设计者的方案为了解决并发问题Socket 被设计成了两种角色监听 SocketListening Socket就像医院的大门。它永远只负责“领人进屋”永远不关门永远在 80 端口守着。已连接 SocketConnected Socket就像临时的谈话窗口。每当 accept 成功系统就变出一个专门的“小窗口”新的 FD让这个病人和医生沟通。在 Linux 系统中的样子文件描述符 FD在 Linux 里Socket 本质上都是数字监听 Socket编号为 3。它通过 bind 固定在 80 端口。调用 accept 之后第一次 accept 返回 FD 4专门用于陪【病人甲】聊天。第二次 accept 返回 FD 5专门用于陪【病人乙】聊天。原有的 FD 3 毫无压力继续在 80 端口迎接【病人丙】。在服务器端通过 bind 定好了位置、listen 开启了挂号机之后客户端就必须调用 connect 来请求与服务端进行连接它的目标就是图片里提到的 serv_addr。这个地址必须包含服务器固定绑定bind好的 IP 和端口。它的动作触发 TCP 三次握手。客户端向服务器发送“SYN”信号。如果服务器的 listen 队列没满内核就会自动回应。一旦握手成功connect 函数就会返回 0表示与服务器连接成功。服务器端有两个 Socket。一个用于守门监听一个用于聊天accept 产生的新 Socket。客户端通常只有一个 Socket。这个 sockfd 在调用 connect 之前是用来指定“我要去连谁”。在 connect 成功之后它直接“变身”成了通话通道。也就是说客户端不需要分身因为它只和一个服务端说话。用于TCP协议这两个函数用于在成功建立连接后服务端生成的新socket中传输数据的在 C 语言以及 Linux 底层中send 和 recv 不是“属于”某个 Socket 的方法而是全局的系统调用函数。传递给它们的 sockfd本质上就是一个“操作编号”。send和recv的第一个参数sockfd逻辑是一样的这两个函数并不知道要操作哪个socket比如A是服务端的监听socketB是客户端的socket当A和B成功连接之后监听socket就会生成一个新的“交流”socketA1。当服务端要向B发送数据时send的sockfd就要填A1A1指向客户端B把数据塞进B的缓冲区中对应的客户端B要在接收函数recv中填写B否则客户端B不会读取A1发到缓冲区中的数据反之亦然。服务端 → 客户端 的数据流服务端操作调用 send(A1, buf, …)。内核查找 A1 的档案发现它绑定了“目标客户端 B 的 IPPort”。结果数据顺着 A1 对应的管道流向 B。客户端操作调用 recv(B, buf, …)。内核查找 B 的档案发现 B 的接收缓冲区里有来自 A1 的数据。结果数据被提取到客户端程序的 buf 中。send 函数的本质数据拷贝当你调用 send 时数据并没有立刻飞到网线上而是经历以下过程拷贝内核将应用程序 buf 中的数据拷贝到该 Socket 对应的内核发送缓冲区Send Buffer。返回一旦拷贝完成send 就会立刻返回发送的字节数。发送随后内核协议栈TCP/IP会根据拥塞控制、窗口大小等算法自动将内核缓冲区里的数据封包并发送出去。注意如果内核发送缓冲区满了例如网络拥塞send 将会阻塞直到缓冲区腾出空间。recv 函数的本质数据提取recv 的逻辑与 send 相反它是从内核向用户空间提取数据等待内核协议栈收到网络包并解包后会将数据放入该 Socket 对应的内核接收缓冲区Receive Buffer。拷贝当你的程序调用 recv 时内核将接收缓冲区里的数据拷贝到你的应用程序 buf 中。阻塞行为如果内核接收缓冲区里没有数据recv 会一直阻塞停在这一行代码直到对端发来数据。如果对方关闭了连接发送了 FIN 包recv 会立即返回 0。用于UDP协议在 TCP 中因为已经建立了连接recv 知道数据一定来自管道的另一头。但在 UDP 中你的 Socket 就像一个开放的信箱任何人都可以往里投信。recvfrom 的本质不仅把数据拿出来还读取了数据发送方的地址。为什么需要它如果收到了一个 UDP 数据包但不知道是谁发的就没办法给对方回信。recvfrom 多出来的最后两个参数src_addr 和 addrlen就是专门用来自动填充发送方 IP 和端口的。struct sockaddr *src_addr这是一个空盒子。当你调用函数时内核会把发送方的 IP 和端口号“填”进这个盒子里。socklen_t *addrlen这个盒子的大小。逻辑流如下程序调用 recvfrom递给内核一个 buf 和一个空的地址盒子 src_addr。内核从接收缓冲区拿数据放到 buf同时把发件人的信息写到 src_addr 盒子里。结果函数返回后既拿到了数据也知道了是谁发的。UDP 场景服务端只有一个 Socket我们叫它 U。动作客户端 1 发来数据→\rightarrow→服务端用 recvfrom(U, …) 接收src_addr 填入客户端 1 的地址。客户端 2 发来数据→\rightarrow→服务端还是用 recvfrom(U, …) 接收此时 src_addr 被覆盖为客户端 2 的地址。结论它不需要像 TCP 那样为每个客户端生成新的 A1、A2新的socket它就用一个 Socket 处理所有人全靠 src_addr 来分辨现在是谁在说话。函数适用协议是否知道发送者应用场景举例recvTCP默认知道建立连接时已锁定网页浏览、文件传输要求可靠recvfromUDP必须靠函数参数告知视频直播、在线游戏、DNS 查询sendto 是 UDP 通信无连接的专用发送函数。它的核心逻辑是不要求预先建立连接只需提供“数据”和“目的地”。dest_addr 的本质它是你临时写在信封上的收件人 IP 和端口。为什么需要它因为sockfd操作编号是一个公共 Socket内核档案里没有记录它到底要发给谁。所以每次调用 sendto你都必须明确告诉内核“这一次把这包数据发给这个地址”。比 send 多了两个关键的“入口参数”const struct struct sockaddr *dest_addr目的地信息。包含目标的 IP 和 Port。socklen_t addrlen地址结构体的长度。逻辑过程程序准备把要发的数据放进 buf把目标的 IP/Port 填进 dest_addr 结构体。调用函数执行 sendto(sockfd, buf, len, 0, dest_addr, sizeof(dest_addr))。内核操作内核看到 sockfd 没绑定目标于是立即读取你提供的 dest_addr封装成 UDP 报文发出去。完美的闭环recvfrom sendto在嵌入式开发如开发一个简单的网络温湿度传感器中最常见的逻辑是这样的接收请求服务端调用 recvfrom 收到一个包同时拿到了客户端的地址存入 src_addr。处理数据发现客户端是在查询温度。回传数据服务端紧接着调用 sendto而这里的目的地参数直接填入刚才从 recvfrom 拿到的那个 src_addr。协议“路”的状态接收函数发送函数特点TCP固定管道recvsend档案里已有目标无需重复指定地址。UDP公共马路recvfromsendto每次通信都需要指明对方地址。