什么是socket
1.socket可以看成是用户进程与内核网络协议栈的编程接口。
2.socket不仅可以用于本机的进程间通信,还可以用于网络上不同主机的进程间通信。
当一台主机的应用程序想发给另一台主机的应用程序,这时候的数据实际上是从下到上,再从上到下的一个过程。但是从逻辑上可以看成双方是进行对等通信的(Application和Application定义了一个逻辑链路进行数据通讯)。
底层那部分已经被内核实现了,也就是TCP/IP协议栈已经属于内核的一部分了,应用层是用户要实现的,它属于用户进程的部分,也就是工作在用户空间,用户空间中的程序要想访问内核,使用内核的服务,就需要通过一定的接口来访问它,这个接口就称为套接口,所以socket可以看成是用户进程与内核网络协议栈的编程接口。
从通信的角度上来看,套接口实现了将应用程序的数据传递给对等方的应用程序。
如下图:
应用程序A要将数据传递给应用程序B,它是通过socket来传输的,这是从逻辑上来看的通信过程,屏蔽了底层的一些实际的传输方式。所以可以把套接口看成一种抽象,两个进程之间进行数据传递的抽象,这个抽象使得我们不会关心底层的传输细节,我们只需关心套接口的存在即可
并且套接口是全双工的通信方式,既可以从A主机到达B主机,也可以从B主机到达A主机。所以说socket不仅可以用于本机的进程间通信,还可以用于网络上不同主机的进程间通信。
并且,套接口还可以在异构系统间进行通信,比如在手机上的QQ和在PC上的QQ进行通讯,两者的硬件结构和软件都可以不相同。
IPv4套接口地址结构
套接口既然能够连接两台机器码,那么套接口就必须拥有一定的属性,要有地址的属性来标识一个端点,对等方也同样要有这个属性。可以把套接口看成电话机,肯定必须都拥有电话号码才能进行连接。
struct sockaddr_in{
uint sin_len;//整个sockaddr_in结构体的长度
sa_family_t sin_family;//指定该地址家族,在这里必须设为AF_INET
in_port_t sin_port;//端口
struct in_addr sin_addr;//IPv4的地址
char sin_zero[8];//暂不使用,一般将其设置为0
};
为什么要有地址族的概念,因为socket在设计的时候不仅仅能够用在TCP/IP协议,还能用于其他协议,比如UNIX域协议,所以必须指定地址家族,一旦指定地址家族为AF_INET,说明采用的是IPv4协议。
地址是用一个结构体来表示,IPv4协议地址是32位,实际上结构体中只有一个成员,无符号的32位整数,端口号是16的无符号整数。
struct in_addr{
unit32_t s_addr;//网络字节序
};
通用地址结构
通用地址结构用来指定与套接字关联的地址
struct sockaddr{
unit8_t sin_len;//整个sockaddr结构体的长度
sa_family_t sin_family;//指定该地址家族
char sa_data[14];//又sin_family决定它的形式
}
为什么要有通用地址结构,因为套接口不仅能用于TCP/IP编程,还能用于UNIX域编程,不同的协议地址结构可能不同,这个结构可以用于任何协议的套接口编程。
上面两个地址结构是兼容的。
网络字节序
1.字节序
大端字节序
最高有效位存储于最低内存地址处,最低有效位存储于最高内存地址处。
小端字节序
最高有效位存储于最高内存地址处,最低有效位存储于最低内存地址处。
这个整数在内存中的存储形式有两种。
为什么要有字节序的概念,因为socket能够实现异构系统间通信,不同的硬件平台对同一个整数的存放形式是不一样的,有的机器采用大端字节序,有的机器采用小端字节序存放。那么当一整数传输至对等方,有可能无法解析,字节序不同,导致解析出来的数据相反。所以必须统一字节序,将数据传输的时候统一为一个字节序,统一出来的这个字节序称为网络字节序(规定为大端字节序)。主机的字节序有可能是大端,也有可能是小端。所以先转成网络字节序。
现代PC大多采用小端字节序,因此小端字节序又被称为主机字节序。提出的解决方法是:发送端总是把发送的数据转化成大端字节序数据(小端机转换,大端机不转换)后再发送,于是接收端知道对方传送过来的数据总是采用大端字节序,所以接收端可以根据自身采用的字节序决定是否对接收到的数据进行转换(小端机转换,大端机不转换)。因此大端字节序也称为网络字节序。
大小端字节序的判断方法
#include <stdio.h>
#include <arpa/inet.h>
int main(void)
{
unsigned int x=0x12345678;
unsigned char *p=(unsigned char*)&x;
printf("%0x %0x %0x %0x\n",p[0],p[1],p[2],p[3]);
return 0;
}
下面的结果说明是小端字节序。
说明
一位十六进制代表四位二进制,0x12345678转换成二进制就是0001-0010-0011-0100-0101-0110-0111- 1000 而每八位二进制占一个字节,所以8位十六进制数占4字节
2.主机字节序
不同的主机有不同的字节序,如X86为小端字节序,ARM字节序是可配置的。
3.网络字节序
网络字节序规定大端字节序
字节序转换函数
uint32_t htonl(uint32_t hostlong);//将4个字节的整数,由主机字节序转换成网络字节序
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
说明:在上述的函数中,h代表host;n代表network;s代表short;l代表long
#include <stdio.h>
#include <arpa/inet.h>
int main(void)
{
unsigned int x=0x12345678;
unsigned char *p=(unsigned char*)&x;
printf("%0x %0x %0x %0x\n",p[0],p[1],p[2],p[3]);
unsigned int y=htonl(x);
p=(unsigned char*)&y;
printf("%0x %0x %0x %0x\n",p[0],p[1],p[2],p[3]);
return 0;
}
地址转换函数
我们人为能够认识的地址不是32位的地址,我们比较习惯的地址是点分十进制的形式。比如192.168.0.100,编程的时候更多指定的是32位地址整数,所以需要引入地址转换函数。
int inet_aton(const char *cp,struct in_addr *inp);//和下面一个作用一样,输出到参数
int_addr_t inet_addr(const char *cp);//将点分十进制的IP地址转化成32位的整数
char *inet_ntoa(struct in_addr in);//将一个地址结构转换成点分十进制的IP地址
#include <stdio.h>
#include <arpa/inet.h>
int main(void)
{
unsigned long addr=inet_addr("192.168.0.100");
printf("addr=%u\n",ntohl(addr));
struct in_addr ipaddr;
ipaddr.s_addr=addr;
printf("%s\n",inet_ntoa(ipaddr));
return 0;
}
套接字类型
1.流式套接字(SOCK_STREAM)
提供面向连接的、可靠的数据传输服务,数据无差错,无重复的发送,且按发送顺序接受。对应的就是TCP协议。
2.数据报式套接字(SOCK_DGRAM)
提供无连接服务。不提供无错保证,数据可能丢失或重复,并且接收顺序混乱。对应的就是UDP协议。
3.原始套接字(SOCK_RAW)