国外网站模板欣赏,用照片做的ppt模板下载网站,近期的新闻热点,网站服务器租一个月Linux网络编程——socket 通信基础 1. socket 介绍2. 字节序2.1 简介2.2 字节序举例2.3 字节序转换函数 3. socket 地址3.1 通用 socket 地址3.2 专用 socket 地址 4. IP地址转换#xff08;字符串ip - 整数#xff0c;主机、网络字节序的转换 #xff09;5. TCP 通信流… Linux网络编程——socket 通信基础 1. socket 介绍2. 字节序2.1 简介2.2 字节序举例2.3 字节序转换函数 3. socket 地址3.1 通用 socket 地址3.2 专用 socket 地址 4. IP地址转换字符串ip - 整数主机、网络字节序的转换 5. TCP 通信流程6. 套接字函数 1. socket 介绍 所谓 socket套接字就是对网络中不同主机上的应用进程之间进行 双向通信的 端点的抽象。一个套接字就是网络上进程通信的一端提供了 应用层进程 利用网络协议交换数据的机制。从所处的地位来讲套接字 上联 应用进程下联 网络协议栈是 应用程序 通过 网络协议 进行通信的接口是 应用程序 与 网络协议根 进行交互的接口。 socket 可以看成是两个网络应用程序进行通信时各自通信连接中的端点这是一个 逻辑上的概念。它是网络环境中 进程间通信 的 API也是可以被命名和寻址的通信端点使用中的每一个套接字都有其类型和一个与之相连进程。通信时其中一个网络应用程序将要传输的一段信息写入它所在主机的 socket 中该 socket 通过与 网络接口卡NIC相连的传输介质将这段信息送到另外一台主机的 socket 中使对方能够接收到这段信息。socket 是由 IP 地址 和 端口 结合的提供向应用层进程传送数据包的机制。 socket 本身有“ 插座 ”的意思在 Linux 环境下用于表示 进程间网络通信 的 特殊文件类型。本质为 内核 借助 缓冲区 形成的 伪文件。既然是文件那么理所当然的我们可以使用 文件描述符 引用套接字。与管道类似的Linux 系统将其封装成文件的目的是为了 统一接口使得 读写套接字 和 读写文件 的操作一致。区别是 管道 主要应用于 本地进程间通信而 套接字 多应用于 网络进程间数据的传递。 使用 文件描述符 fd 引用 socket
套接字通信 分两部分
服务器端被动 接受连接一般不会主动发起连接客户端主动 向服务器发起连接 socket 是一套 通信的接口Linux 和 Windows 都有但是有一些细微的差别。 2. 字节序
2.1 简介 现代 CPU 的累加器 一次都能 装载至少4 字节这里考虑 32 位机即一个整数。那么这 4 字节 在 内存 中排列的顺序 将影响它被累加器装载成的整数的值这就是字节序问题。在各种计算机体系结构中对于字节、字等的存储机制有所不同因而引发了计算机通信领域中一个很重要的问题即通信双方交流的信息单元比特、字节、字、双字 等等应该以什么样的顺序进行传送。如果不达成一致的规则通信双方将无法进行正确的编码/译码从而导致通信失败。 字节序顾名思义 字节的顺序就是大于一个字节类型的数据在内存中的存放顺序 (一个字节的数据当然就无需谈顺序的问题了)。 字节序 分为 小端字节序Big-Endian 和 大端字节序Little-Endian。
小端字节序则 是指 整数的 低位字节 则存储在内存的 低地址 处而 高位字节 存储在内存的 高地址 处。大端字节序 是指一个 整数的 最高位字节 23 ~ 31 bit存储在内存的 低地址 处低位字节0 ~ 7 bit存储在 内存的 高地址 处
2.2 字节序举例
小端字节序 0x 01 02 03 04 (十六进制四字节。ff 255) 内存的方向 ----- 内存的低位 ----- 内存的高位 04 03 02 01
0x 11 22 33 44 12 34 56 78 八个字节
大端字节序
0x 01 02 03 04内存的方向 -----内存的低位 ----- 内存的高位 01 02 03 04
0x 12 34 56 78 11 22 33 44
通过代码检测当前主机的字节序
#include stdio.hint main() {union {short value; // 2字节char bytes[sizeof(short)]; // char[2]} test;test.value 0x0102;if((test.bytes[0] 1) (test.bytes[1] 2)) {printf(大端字节序\n);} else if((test.bytes[0] 2) (test.bytes[1] 1)) {printf(小端字节序\n);} else {printf(未知\n);}return 0;
}2.3 字节序转换函数 当格式化的数据在两台使用不同字节序的主机之间直接传递时接收端必然错误的解释之。 解决问题的方法是发送端 总是 把要发送的数据 转换成 大端字节序数据 后再发送而接收端知道对方传送过来的数据总是采用大端字节序所以接收端可以根据自身采用的字节序决定是否对接收到的数据进行转换小端机转换大端机不转换。 网络字节顺序 是 TCP/IP 中规定好的 一种数据表示格式它与具体的 CPU 类型、操作系统等无关从而可以保证数据在不同主机之间传输时能够被正确解释网络字节顺序 采用 大端排序 方式。 BSD Socket 提供了封装好的 转换接口方便程序员使用。
包括从 主机字节序 到 网络字节序 的 转换函数htons、htonl从 网络字节序 到 主机字节序 的转换函数ntohs、ntohl。
h - host 主机主机字节序
to - 转换成什么
n - network 网络字节序
s - short : unsigned short 无符号短整型两个字节
l - long : unsigned int 无符号长整型四个字节#include arpa/inet.h
/*网络通信时需要将主机字节序转换成网络字节序大端另外一段获取到数据以后根据情况将网络字节序转换成主机字节序。
*/// 32位转IP
uint32_t htonl(uint32_t hostlong); // 主机字节序 - 网络字节序
uint32_t ntohl(uint32_t netlong); // 网络字节序 - 主机字节序// 16位转换端口
uint16_t htons(uint16_t hostshort); // 主机字节序 - 网络字节序
uint16_t ntohs(uint16_t netshort); // 网络字节序 - 主机字节序#include stdio.h
#include arpa/inet.hint main() {// htonl 转换IPchar buf[4] {192, 168, 1, 100};int num *(int *)buf;int sum htonl(num);unsigned char *p (char *)sum;printf(%d %d %d %d\n, *p, *(p1), *(p2), *(p3));printf(\n);// htons 转换端口unsigned short a 0x0102;printf(a : %x\n, a);unsigned short b htons(a);printf(b : %x\n, b);printf(\n);// ntohl 转换IPunsigned char buf1[4] {1, 1, 168, 192};int num1 *(int *)buf1;int sum1 ntohl(num1);unsigned char *p1 (unsigned char *)sum1;printf(%d %d %d %d\n, *p1, *(p11), *(p12), *(p13));// ntohs 转换端口return 0;
}3. socket 地址 socket 地址 其实是一个 结构体封装 端口号 和 IP 等信息。 后面的 socket 相关的 api 中需要使用到这个 socket地址。
客户端 - 服务器IP, Port
3.1 通用 socket 地址 socket 网络编程接口 中表示 socket 地址 的是 结构体 sockaddr其定义如下
#include bits/socket.hstruct sockaddr {sa_family_t sa_family; // 地址族类型char sa_data[14]; // 14字节
};typedef unsigned short int sa_family_t; // 2字节sa_family 成员是 地址族类型sa_family_t的 变量。地址族类型 通常与 协议族类型 对应。常见的 协议族protocol family也称 domain和对应的 地址族address family 如下所示 宏 PF_* 和 AF_* 都定义在 bits/socket.h 头文件中且 后者 与 前者 有 完全相同的值所以二者通常混用。 sa_data 成员用于存放 socket 地址值。但是不同的协议族的地址值具有不同的含义和长度如下所示 由上表可知14 字节的 sa_data 根本无法容纳多数协议族的地址值。因此Linux 定义了下面这个 新的通用的 socket 地址结构体这个结构体不仅提供了足够大的空间用于存放地址值而且是 内存对齐 的。
#include bits/socket.h
struct sockaddr_storage
{sa_family_t sa_family; // 地址族类型unsigned long int __ss_align; // 内存对齐char __ss_padding[ 128 - sizeof(__ss_align) ];
};typedef unsigned short int sa_family_t;3.2 专用 socket 地址 很 多网络编程函数 诞生 早于 IPv4 协议那时候都使用的是 struct sockaddr 结构体为了向前兼容现在 sockaddr 退化成了void *的作用传递一个地址 给函数至于这个函数是 sockaddr_in 还是 sockaddr_in6由地址族确定然后函数内部再 强制类型转化 为所需的地址类型。 UNIX 本地域协议族 使用如下 专用的 socket 地址结构体
#include sys/un.hstruct sockaddr_un
{sa_family_t sin_family;char sun_path[108];
};TCP/IP 协议族 有 sockaddr_in 和 sockaddr_in6 两个专用的 socket 地址结构体它们分别用于 IPv4 和 IPv6
#include netinet/in.hstruct sockaddr_in
{sa_family_t sin_family; /* __SOCKADDR_COMMON(sin_) 地址族类型*/in_port_t sin_port; /* Port number. */struct in_addr sin_addr; /* Internet address. *//* Pad to size of struct sockaddr. 填充*/unsigned char sin_zero[sizeof (struct sockaddr) - __SOCKADDR_COMMON_SIZE - sizeof (in_port_t) - sizeof (struct in_addr)];
};struct in_addr
{in_addr_t s_addr;
};struct sockaddr_in6
{sa_family_t sin6_family;in_port_t sin6_port; /* Transport layer port # */uint32_t sin6_flowinfo; /* IPv6 flow information */struct in6_addr sin6_addr; /* IPv6 address */uint32_t sin6_scope_id; /* IPv6 scope-id */
};typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef uint16_t in_port_t;
typedef uint32_t in_addr_t;#define __SOCKADDR_COMMON_SIZE (sizeof (unsigned short int))所有 专用 socket 地址以及 sockaddr_storage类型的变量 在实际使用时都需要转化 为 通用 socket 地址 类型 sockaddr强制转化 即可因为所有 socket 编程接口 使用的地址参数类型都是 sockaddr。
4. IP地址转换字符串ip - 整数主机、网络字节序的转换 通常人们习惯用 可读性好的字符串 来表示 IP 地址比如用 点分十进制字符串 表示 IPv4 地址以及用 十六进制字符串 表示 IPv6 地址。但编程中我们需要先把它们 转化为 整数二进制数方能使用。而记录日志时则相反我们要把整数表示的 IP 地址 转化为 可读的字符串。下面 3 个函数可用于用 点分十进制字符串 表示的 IPv4 地址 和 用 网络字节序整数 表示的 IPv4 地址 之间的转换
#include arpa/inet.h// 下面的这些函数比较久使用起来比较麻烦不推荐使用
in_addr_t inet_addr(const char *cp); // 点分十进制字符串 - 整数
int inet_aton(const char *cp, struct in_addr *inp); // 点分十进制字符串 - 整数, 并保存到结构体指针 inp 中
char *inet_ntoa(struct in_addr in); // 整数 - 点分十进制字符串下面这对更新的函数也能完成前面 3 个函数同样的功能并且它们同时适用 IPv4 地址 和 IPv6 地址⭐️
#include arpa/inet.h// p:点分十进制的IP字符串n:表示network网络字节序的整数
int inet_pton(int af, const char *src, void *dst);af:地址族 AF_INET AF_INET6src:需要转换的点分十进制的IP字符串dst:转换后的结果保存在这个里面// 将网络字节序的整数转换成点分十进制的IP地址字符串
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);af:地址族 AF_INET AF_INET6src: 要转换的ip的整数的地址dst: 转换成IP地址字符串保存的地方size第三个参数的大小数组的大小返回值返回转换后的数据的地址字符串和 dst 是一样的#include stdio.h
#include arpa/inet.hint main() {// 创建一个ip字符串,点分十进制的IP地址字符串char buf[] 192.168.1.4; // 后面默认还有一个字符串结束符unsigned int num 0;// 将点分十进制的IP字符串转换成网络字节序的整数inet_pton(AF_INET, buf, num);unsigned char * p (unsigned char *)num;printf(%d %d %d %d\n, *p, *(p1), *(p2), *(p3)); // 大端排序// 将网络字节序的IP整数转换成点分十进制的IP字符串char ip[16] ;const char * str inet_ntop(AF_INET, num, ip, 16);printf(str : %s\n, str);printf(ip : %s\n, str);printf(%d\n, ip str);return 0;
}在这里插入图片描述
5. TCP 通信流程
TCP 和 UDP - 传输层的协议
UDP: 用户数据报协议面向无连接可以单播多播广播 面向 数据报不可靠TCP: 传输控制协议面向连接的可靠的基于 字节流仅支持单播传输 UDPTCP是否创建连接无连接面向连接是否可靠不可靠可靠的连接的对象个数一对一、一对多、多对一、多对多支持一对一传输的方式面向数据报面向字节流首部开销8个字节最少20个字节适用场景实时应用视频会议直播可靠性高的应用文件传输 TCP 通信的流程 ⭐️⭐️⭐️ ⭐️服务器端 被动接受连接的角色⭐️
创建 一个用于监听的套接字 监听监听有客户端的连接套接字 这个套接字其实就是一个 文件描述符 将这个 监听 文件描述符 和 本地的IP 和 端口 绑定IP 和 端口 就是 服务器的地址信息 客户端 连接 服务器 的时候使用的就是这个 IP 和 端口 设置 监听监听的 fd 开始工作阻塞等待当有客户端发起连接解除阻塞接受客户端的连接会得到一个 和客户端通信的 套接字fd通信 接收数据发送数据 通信结束断开连接
⭐️客户端⭐️
创建一个用于通信的套接字fd连接服务器需要指定连接的服务器的 IP 和 端口连接成功了客户端可以直接和服务器 通信 接收数据发送数据 通信结束断开连接
6. 套接字函数
#include sys/types.h
#include sys/socket.h
#include arpa/inet.h // 包含了这个头文件上面两个就可以省略int socket(int domain, int type, int protocol);- 功能创建一个套接字- 参数- domain: 协议族AF_INET : ipv4AF_INET6 : ipv6AF_UNIX, AF_LOCAL : 本地套接字通信进程间通信- type: 通信过程中使用的协议类型SOCK_STREAM : 流式协议SOCK_DGRAM : 报式协议- protocol : 具体的一个协议。一般写0(默认)- SOCK_STREAM : 流式协议默认使用 TCP- SOCK_DGRAM : 报式协议默认使用 UDP- 返回值- 成功返回文件描述符操作的就是内核缓冲区。- 失败-1int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); // socket命名- 功能绑定将fd 和本地的IP 端口进行绑定- 参数- sockfd : 通过socket函数得到的文件描述符- addr : 需要绑定的socket地址这个地址封装了ip和端口号的信息- addrlen : 第二个参数结构体占的内存大小int listen(int sockfd, int backlog); /proc/sys/net/core/somaxconn- 功能监听这个socket上的连接- 参数- sockfd : 通过socket()函数得到的文件描述符- backlog : 未连接的队列 和 已经连接的队列 和的最大值以使用 cat /proc/sys/net/core/somaxconn 查看4096一般不用设置那么大如128int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);- 功能接收客户端连接默认是一个阻塞的函数阻塞等待客户端连接- 参数- sockfd : 用于监听的文件描述符- addr : 传出参数记录了连接成功后客户端的地址信息ipport- addrlen : 指定第二个参数的对应的内存大小- 返回值- 成功 用于通信的文件描述符- -1 失败int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);- 功能 客户端连接服务器- 参数- sockfd : 用于通信的文件描述符- addr : 客户端要连接的服务器的地址信息- addrlen : 第二个参数的内存大小- 返回值成功 0 失败 -1ssize_t write(int fd, const void *buf, size_t count); // 写数据
ssize_t read(int fd, void *buf, size_t count); // 读数据TCP 通信实现
1服务器端 创建 server.c 文件
// TCP 通信的服务器端#include stdio.h
#include arpa/inet.h
#include unistd.h
#include string.h
#include stdlib.hint main() {// 1.创建socket(用于监听的套接字)int lfd socket(AF_INET, SOCK_STREAM, 0);if(lfd -1) {perror(socket);exit(-1);}// 2.绑定struct sockaddr_in saddr;saddr.sin_family AF_INET;// inet_pton(AF_INET, 192.168.193.128, saddr.sin_addr.s_addr);saddr.sin_addr.s_addr INADDR_ANY; // 0.0.0.0saddr.sin_port htons(9999);int ret bind(lfd, (struct sockaddr *)saddr, sizeof(saddr));if(ret -1) {perror(bind);exit(-1);}// 3.监听ret listen(lfd, 8);if(ret -1) {perror(listen);exit(-1);}// 4.接收客户端连接struct sockaddr_in clientaddr;int len sizeof(clientaddr);int cfd accept(lfd, (struct sockaddr *)clientaddr, len);if(cfd -1) {perror(accept);exit(-1);}// 输出客户端的信息char clientIP[16];inet_ntop(AF_INET, clientaddr.sin_addr.s_addr, clientIP, sizeof(clientIP));unsigned short clientPort ntohs(clientaddr.sin_port);printf(client ip is %s, port is %d\n, clientIP, clientPort);// 5.通信char recvBuf[1024] {0};while(1) {// 获取客户端的数据int num read(cfd, recvBuf, sizeof(recvBuf));if(num -1) {perror(read);exit(-1);} else if(num 0) {printf(recv client data : %s\n, recvBuf);} else if(num 0) {// 表示客户端断开连接printf(clinet closed...);break;}char * data hello,i am server;// 给客户端发送数据write(cfd, data, strlen(data));}// 关闭文件描述符close(cfd);close(lfd);return 0;
}注 仅供学习参考如有不足欢迎指正