一、网络字节序
我们已经知道, 内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分.
- 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
- 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址.
- TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据;如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可。
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。
这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。 例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回; 如果主机是大端字节序,这些 函数不做转换,将参数原封不动地返回。
二、IP地址和端口号
IP地址是在IP协议中, 用来标识网络中不同主机的地址; 对于IPv4来说, IP地址是一个4字节, 32位的整数; 我们通常也使用 "点分十进制" 的字符串表示IP地址, 例如 192.168.0.1 ; 用点分割的每一个数字表示一个 字节, 范围是 0 - 255;
端口号是一个2字节16位的整数;端口号用来标识一个进程,告诉操作系统,当前的这个数据要交给哪一个进程来处理;IP地址 + 端口号能够标识网络上的某一台主机的某一个进程;一个端口号只能被一个进程占用。
三、socket
socket编程是有不同种类的,有的是专门用来进行本地通信的(unix socket),有的是专门进行跨网络通信的(inet socket),有的是用来进行网络管理的(raw socket)。为了统一调用接口,设计者设计出了sockaddr结构。
3.1、sockaddr结构
socket编程既可以用来进行网络通信也可以用来进行本地通信。下面是用来进行网络通信和本地通信不同的sockaddr结构体。sockaddr前两个字节表明自身类型。
3.2、UDPsocket常见API
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器 )
int socket(int domain, int type, int protocol);
// 绑定端口号 (TCP/UDP, 服务器 )
int bind(int socket, const struct sockaddr *address, socklen_t address_len);
//接收信息
ssize_t recvfrom(int sockfd,const void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
//发送信息
ssize_t sendto (int sockfd,const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t *addrlen);
3.2.1、 socket
socket()返回一个文件描述符,domain表示套接字的类型,type表示套接字的服务类型,第三个参数填0,参数填好就能确定是udp套接字。创建失败-1被返回,错误码被设置。socket就相当于在进程内部帮我们打开了一个文件。远端机器要想办法找到这个文件,所以就有了bind。
3.2.2、bind
本地打开的文件sockfd要与网络信息sockaddr关联起来。bind第一个参数就是socket的返回值。第二个参数addr有三个要填充的字段。sin_family表示通信类型,网络通信填写AF_INET,sin_addr表示ip地址,sin_port表示端口号。端口号可以由外部传入,未来是要通过网络告知对方的服务进程的。 要经过网络传输就要将端口号从主机序列转为网络序列**。ip地址也要从主机序列转为网络序列,使用inet_addr函数可以一步到位将string类型的ip转化为4字节ip并转化为网络序列。**
注意点:我们不能绑定一个公网IP,或是任意一个确定的IP,因为一台机器上可能存在不同的IP地址,如果绑定了确定的IP地址,就有可能导致远端服务无法访问的情况。服务器在绑定IP地址时,IP地址可以直接填0,表示任意地址绑定(计算机中所有的IP都绑定了) 。
3.2.3、recvfrom
recvfrom第二个参数buf是一个输出型参数,将来从sockfd中读到的数据就会放到buf中,第三个参数len表示希望从网络套接字中读取的数据的长度,返回值为实际读到的数据长度,读取方式flags默认设为0,后两个参数为输出型参数,可以用来通信对方的信息。
3.2.4、sendto
sendto第二个参数表示要发送的字符串数组,第三个参数为字符串数组的长度,flags填0,后两个参数为通信对方的信息。
四、了解本地环回
本地环回:可以实现本地通信,通常用来进行代码测试。客户端不需要显式地绑定端口号,因为有可能会引起端口号冲突,当client第一次向服务端发送信息时,OS会自动为客户端绑定端口号。