【Linux】网络编程套接字(1)
1、 什么是网络编程套接字
套接字(socket)是一种通信机制,凭借这种机制,客户/服务器系统的开发工作既可以在本地单机上进行,也可以跨网络进行。套接字的创建和使用与管道是有区别的,因为套接字明确的将客户和服务器区分开来。套接字机制可以实现将多个客户连接到一个服务器。
(1)IP地址和端口号认识
IP地址:IP地址是IP协议中的,用来标识网络中不同主机的地址,并且IP有两个版本:IPv4和IPv6;一般没有明确说明那即是默认IPv4
- IP地址是一个4字节,32位的整数
- 我们一般通常使用点分十进制的字符串来表示IP地址,例如192.168.28.129;用点分割没一个数字表示一个字节,每一个字节的范围0~255
端口号(port):端口是传输层协议内面的内容;用来标识一个进程,告诉操作系统当前这个数据要交给哪一个进程来处理
- 端口号是一个2字节,16位的整数
- IP地址+端口号能够标识网络上的某一台主机的某一个进程
- 一个端口号只能被一个进程占用,但是一个进程可以占用多个段端口号
(2)源IP地址和目的IP地址与源端口和目的端口认识
举一个栗子来说明:QQ聊天
源IP地址和源端口:从哪台机器上面发来的信息,并且信息是主机上谁发的
目的IP地址和目的端口:要接受该信息的主机和并且信息交给主机上谁处理
(3)网络字节序认识
我们知道在计算机内存中多字节数据相对于内存地址就会有大小端之分,操盘文件中对于多字节数据相对于文件中的偏移量地址也有大小端之分,网络数据流同样有大小端之分。那么是怎样定义的?
先来看看一些概念
主机字字节序
- 主机字节序:就是CPU从内存的对数据进行存取的顺序
- 主机字节序分类:大端字节序 和 小端字节序
int a = 0x01020304;
大端:低地址存高位 uchar*b = (uchar*)&a; b[0] = 01,b[2] = 02.....
小端:低地址存地位 uchar*b = (uchar*)&a; b[0] = 04,b[2] = 03.....
- 大小端取决于:CPU架构 ---- X86架构(小端)、MIPS架构(大端)、ARM…
- 主机字节序对网络通信的影响:不同主机字节序进行通信,可能因为大小端造成数据二义性
可能会造成影响的数据类型(多字节类型):short、int、long、float、double、自定义类型等;
网络字节序:
- 发送端通常将发送缓冲区的是数据按照内存地址从低到高的顺序发出
- 接收端把从网络上接受到的字节一次保存在接受缓冲区中同样按照内存地址从低到高的顺序保存
- 因此网络数据流地址规定:先发出的数据是低地址,后发出的数据是高地址
- 网络数据流规定数据流采用大端字节序,即低地址高字节
- 不管当前主机是大端还是小端,都要按照网络字节序来交换数据
- 打钱主机如果是小端,那么就要转换为大端,否则就可以忽略,直接发送
转换函数:
那么问题来了,既然主机有大小端之分,而且网络通信一个规定必须使用大端通信,上面说到了如果主机是小段机,那么就要转换成大端的,这过程中不然有一个转换的步骤,就有大佬封装了一些库函数用来网络字节序和主机字节序之间的转换;
#include<arpa/inet.h>
uint16_t htons(uint16_t hostshort);
uint16_t ntohs(uint16_t netshort);
uint32_t htonl(uint32_t hostlong);
uint32_t ntohl(uint32_t netlong);
1、h表示host,n表示network,s表示16为短整数,l表示32位长整数
2、如果主机字节序是大端;这些函数不干什么将参数原封不动的返回,如果是小端;将参数做出相应的改变然后返回
socket常见的API(应用程序接口)
1、创建套接字(TCP/UDP,客户端+服务器)
int socket(int dmain,int type,int protocol)
参数:
1、domain(地址域):AF_INET(IPV4)/AF_INET6(IPV6)
2、type(套接字类型):SOCK_STREAM(流式传输)/SOCK_DGRAM(无连接,不可靠。有最大长度限制的数据传输)
3、protocol(传输层协议):IPPROTO_TCP(传输控制协议)/IPPROTO_UDP(用户数据报协议)
返回值:
成功:套接字文件描述符-----套接字的操作句柄
失败:-1
2、为套接字绑定地址信息(TCP/UDP,服务器)
int bind(int socket,struct sockaddr* addr,socklen_t len);
参数:
1、socket:创建套接字所返回的文件描述符
2、addr:地址结构(这只是一个泛型,其实用的是struct sockaddr_in))
struct sockaddr_in
{
uint16_t sin_family;//地址类型
uint16_t sin_port;//端口号
strut in_addr sin_addr;//存放IP地址信息的结构体
};
struct in_addr
{
int_addr_t s.addr;//uint32_t 类型的IP地址信息
};
3、len地址结构信息长度
返回值:=0--绑定成功,-1--绑定失败
3、开始监听(TCP,服务器)
int listen(int socket,int backlog);
参数:
1、socket:创建套接字所返回的文件描述符
2、backlog:同一时间最大并发连接数
返回值:
成功:0
失败:-1
4、接受请求(TCP,服务器)
int accept(int socket,struct sockaddr* address,socklen_t* address_len);
参数:
1、socket:创建套接字所返回的文件描述符
2、address(输出参数):地址结构(这只是一个泛型,其实用的是struct sockaddr_in))
struct sockaddr_in
{
uint16_t sin_family;//地址类型
uint16_t sin_port;//端口号
strut in_addr sin_addr;//存放IP地址信息的结构体
};
struct in_addr
{
int_addr_t s.addr;//uint32_t 类型的IP地址信息
};
作为一个传出值:返回时传送出客户端的地址和端口号
3、address_len(输入输出参数):传入时表示addr的长度一边避免缓冲区的问题,传出时是客户端地址结构的实际长度
返回值:
失败:-1
成功:0
5、建立连接(TCP,客户端)
int connect(int sockfd,const struct sockaddr* addr,socklen_t addrlen);
返回值:
1、socket:创建套接字所返回的文件描述符
2、addr:对端地址结构(这只是一个泛型,其实用的是struct sockaddr_in))
struct sockaddr_in
{
uint16_t sin_family;//地址类型
uint16_t sin_port;//端口号
strut in_addr sin_addr;//存放IP地址信息的结构体
};
struct in_addr
{
int_addr_t s.addr;//uint32_t 类型的IP地址信息
};
3、addrlen地址结构信息长度
返回值:
成功 0
失败 -1
sockaddr结构解析:
从上面的一些常见接口就可以看出来,不同的网路协议地址格式都不相同,所以函数参数中使用的协议地址结构其实是一种泛型结构,具体的协议需要用户自己去初始化结构;常见的网络协议入IPv4、IPv6的机构就不相同。
- IPv4和IPv6的地址机构都定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型,16位端口号和32位ip地址
- IPv4、IPv6地址类型定义为常数AF_INET、AF_INET6,只要取得某种sockaddr结构体的首地址,根本不需要知道具体是哪一种类型sockaddr结构体,根据地址类型字段确定结构体中的内容
- scoket API中可以都用struct sockaddr*类型表示,在使用的时候强制转换。这样可以保证通用,就可以做到接受各种地址结构
struct sockaddr结构体:
struct in_addr结构体:
struct sockaddr_in结构体:
注意: 虽然Socket API的接口是时sockaddr,但是这只是一个通用的套接字地址 而struct sockaddr_in则是internet环境下套接字的地址形式,二者长度一样,都是16个字节。二者是并列结构,指向sockaddr_in结构的指针也可以指向sockaddr。在真正使用的时候IPv4编程时,使用的是sockaddr_in这个结构体;该结构体包含:地址类型、端口号、IP地址;其中IP地址还是一个独立的结构体in_addr,结构体存在这个结构体内面可以很清楚的看出来成员算是一个32位的整数
地址转换函数:
从地址结构信息可以看出来:IP地址是一个32位整数,但是在日常中为了方便记忆和理解我们将IP地址用点分十进制的字符串来表示IP地址,但是在网络通信中需要的网络字节序那就需要将我们输入的点分十进制IP字符串转换为32位整数的网络字节序IP地址,这是我们就需要地址转换函数;
(1)字符串转in_addr的函数
#include<arpa/inet.h>
in_addr_t inet_addr(const char* strptr);----将一个点分十进制IP地址转换为网络字节序IP地址(常用)
int inet_aton(const char* strptr,struct in_addr *addptr);
int inet_pton(int family,const char* strptr,void *addrptr);
(2)in_addr转换字符串函数
char* inet_ntoa(struct in_addr inaddr);----将一个网络字节序IP地址转换为点分十进制IP地址(常用)
const char *inet_ntop(int family,const void *addrptr,char* strptr);----建议多线程下使用,避免线程安全问题
注意:
1、inet_ntoa()并不是一个线程安全函数,在多线程情况下可能会出现县城安全问题;
2、可以看到inet_ntoa()函数直接返回char*,那么就可以看出来在函数内部申请了一块静态存储区来存储转换后的字符串,从man手册的看出来,这个空间不需要用户来管,并且多次调用时会在该静态存储区进行覆盖写。
写在结尾:此处知识对网络编程中的常用Socket API接口和基础知识进行介绍,具体的网络套接字编程在下一篇博客中进行详解,以上是个人的一点见解,如有错误请不吝赐教;;;