当前位置: 首页 > web >正文

Linux下的Socket编程

1 网络编程基础

1.1 IP地址

        网络通信前,通信双方需要知道本机与对方主机的IP地址,方可进行通信。在IP数据报的报头中需要包含有源IP地址目的IP地址这两个地址,源IP地址即为本机地址,目的IP地址即为对方地址。

        其中,在报头中包含的目的IP地址用于本机向对方主机发送请求报文,而源IP地址则是使对方主机能知道该报文的发送方身份。当对方主机将报文处理完毕后,会将请求报文的源IP地址作为响应报文的目的IP地址,从而对不同主机的请求进行正确响应。

1.2 端口号

        网络通信的本质是进程间通信,而通过IP地址只能实现两主机之间的网络通信,但并不能找到两个主机上进行通信的进程。因此,传输层引入端口号用于标识两个主机上通信的进程。

        端口号通常为16位无符号整数,取值范围为 0~65535,每个端口号只能绑定一个进程以用于标识该进程,这个过程是在程序代码中固定。和IP地址一样,在传输层协议报头中需要包含源端口号目的端口号,以便向正确的进程进行请求和响应。

        有了端口号,我们就可以找到主机上的唯一进程,结合IP地址,即可确定主机和进程。通常采用 “ IP地址:端口号 ” 的格式标识特定主机的特定进程,如 “ 192.168.0.1:8265 ”。

         事实上,每一个进程都有自己的 pid ,那么为什么还要引入端口号用于标识进程呢?

        进程 pid通常依赖操作系统的实现,兼容性较差,如果在网络通信中采用 pid 来标识,就必须要求通信双方的操作系统对进程 pid 的管理分配方式一致,目前这显然已经很难实现了。

        而端口号位于协议层面,要求通信双方遵守协议,从而实现不同系统之间的通信兼容。另外使用端口号标识网络通信进程同时使网络通信与操作系统解耦合。

1.3 网络字节序

        目前主流的字节序主要包括大端字节序小端字节序两种。不同厂家生产的机器可能采用不同的字节序。

        而不同字节序的机器在内存中存储数据的方式是不一样的,例如,大端和小端机器在存储32位整数时有如下区别。

数值:0x3584A02C大端字节序
35 84 A0 2C
低地址 -> 高地址小端字节序
2C A0 84 35
低地址 -> 高地址

        由于在网络通信中,数据通常以字节流的形式传递。由于字节序的不同,导致经过网络传输后,接收端解析后得到的与源数据不同,如上例中,使用大端机向小端机直接发送 “ 0x3584A02C ”,解析后的结果将为 “ 0x2CA08435 ”。

        为了避免这种情况发生,TCP/IP协议规定,网络通信字节序统一为大端字节序,而对于小端机器,进行网络通信时,需自行进行字节序转化。关于机器字节序和网络字节序之间的转换,C语言提供了相应的库函数支持。

#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);

        从接口名称可以分辨出各个接口的作用,以 “ htonl ” 为例,h 表示 host ,n 表示 network,l 表示 32 位,s 表示 16 位,所以 “ htonl ” 表示将32位数据从主机字节序转为网络字节序。

2 Socket编程

        Socket(套接字)也是一种进程间通信方式,可用于网络通信,主要基于传输层协议实现,如TCP和UDP协议。

        这里先简单介绍 Socket 编程中常用的接口。

#include <sys/socket.h>
int socket(int domain, int type, int protocol);

        该接口用于创建套接字,其返回值为该套接字的文件描述符,若创建失败则返回 -1。参数 domain 表示协议族,目前网络通信中通常使用 “ AF_INET ”,即 IPv4 协议;type 为套接字类型,网络通信中通常使用 “ SOCK_DGRAM ”,即数据报类型(UDP协议用),或 “ SOCK_STREAM ” 字节流类型(TCP协议用)。protocol 为协议编号,通常为 0 表示默认。

#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);struct sockaddr_in {sa_family_t     sin_family;     /* AF_INET */in_port_t       sin_port;       /* Port number */struct in_addr  sin_addr;       /* IPv4 address */
};

        该接口用于将套接字与主机信息绑定。其参数 sockfd 为套接字文件描述符;addr 为主机信息,在进行网络通信是,通常传入结构 “ sockaddr_in ” 的对象指针,该结构的三个成员变量分别为协议族、端口号、地址,若使用 IPv4 协议进行通信,则将协议族设置为 “ AF_INET ”即可;addrlen 表示 addr 对象的大小。

        当一个套接字描述符与一个主机绑定后,对该描述符的读写操作即可认为是向该主机的接收和发送操作。

#include <sys/socket.h>int listen(int sockfd, int backlog);
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

        以上三个接口主要用于TCP协议建立连接。

        listen 接口用于将套接字设置为监听状态(等待连接),通常用于服务端。参数 sockfd 为被设置的套接字描述符;backlog 为该套接字所允许建立的最大连接数。

        当服务端被设为监听状态后,客户端即可使用 connet 接口对服务端发起三次握手,以建立连接。其参数效果与 bind 类似,用于将给定套接字描述符与给定主机信息(即被连接的主机)建立连接。

        accept 接口的作用是当服务端开始监听后,阻塞进程,直到被监听的套接字获取到新连接。参数 addr 和 addrlen 用于获取新连接的主机信息。成功获取连接后,该接口返回新连接的套接字描述符。

#include <sys/socket.h>// TCP 发送(需先建立连接)
ssize_t send(int sockfd, const void *buf, size_t len, int flags);// UDP 发送(指定目标地址)
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);// TCP 接收
ssize_t recv(int sockfd, void *buf, size_t len, int flags);// UDP 接收(获取发送方地址)
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);

        以上四个接口用于网络通信中数据的发送和接收,其本质上是对文件进行读写。

        以 sendto 为例,其前三个参数与 write 接口类似,最后两个参数用于指定目标主机和进程,flags 通常设置为 0 表示默认,或设置为 “ MSG_DONTWAIT ” 表示非阻塞。

http://www.xdnf.cn/news/8245.html

相关文章:

  • 多端协同开发能力大比拼: AI 编程工具技术架构对比
  • 华为2025年校招笔试真题手撕教程(一)
  • vue3项目动态路由的相关配置踩坑记录
  • LeetCode Hot100 (双指针)
  • 什么是出入库管理系统?2025年五大出入库管理软件推荐
  • 部署TOMEXAM
  • (tarjan 缩点)洛谷 P3119 USACO15JAN Grass Cownoisseur G 题解
  • 文章被检测出是AI写的怎么办?
  • 手写Tomcat(二)—Tomcat简化模型架构
  • 2023河南CCPC省赛vp部分补题
  • 电子墨水电子阅读器行业2025数据分析报告
  • 如果教材这样讲--碳膜电阻、金属氧化膜电阻、金属膜电阻、保险丝电阻、绕线电阻的区别和用途
  • 618服饰大促新打法:在抖音解锁增长三连击
  • 深入理解 PlaNet(Deep Planning Network):基于python从零实现
  • 50、js 中var { ipcRenderer } = require(‘electron‘);是什么意思?
  • Android System UI 深度解析:从架构演进到车载 / TV 场景的全维度定制
  • go中len和sizeof区别
  • 初学c语言18(自定义类型:结构体)
  • 【DAY26】函数专题1:函数定义与参数
  • 供应链管理:联合国/我国 41个产业/工业大类包括什么/代表公司
  • 【25软考网工】第七章(4)DHCP、DNS
  • 深入理解 BFC:网页布局的关键机制
  • 其他有关Oracle BUFFER CACHE的优化思路
  • Java Spring Boot 集成 Redis 存储 Session:对象修改与 Redis 值更新
  • 2025年排名前五的费控管理软件
  • 封装红黑树实现mymap和myset
  • 密码分析学:从理论框架到实战攻防的全维度解析
  • 编程日志5.17
  • AttributeError: module ‘cv2.dnn‘ has no attribute ‘DictValue‘错误解决方法
  • Redis 的速度为什么这么快