网络基础:网络编程套接字

IP地址

我们再用Xshell的时候,在虚拟机终端下,输入ifconfig可以查看虚拟机IP地址。IP地址究竟是什么?
这里写图片描述

IP协议有两个版本,IPv与IPv6,在一般情况下默认的都是IPv4。IP地址是在IP协议中,用来标识网络中不同主机的地址。对于IPv4来说,IP地址是一个四字节32位的整数。我平常看的IP地址都是用点分十进制的字符串表示的。而用点分割的每一段数字表示的范围在0~255之间。

通常在传输层协议报头信息中有两个IP地址,而两个IP地址分别是源IP地址与目的IP地址。在网络中传输时,两主机需要通信,倘若只有目的IP地址的话,数据到达目的主机,目的主机如何回复源主机呢?所以就需要两个IP地址。而当数据到达了目的主机,数据从下自上依次交付,到最后的应用层应该交付给哪个应用呢?一个主机有很多网络进程,每个网络进程也只能处理自己的网络数据。这时候就需要一个东西来标识目的主机的目的网络进程了。端口号就产生了。

端口号(port)

在介绍端口号之前,我们要知道端口号其实是在传输层协议的报头信息里面所包含的。

端口号的实质是2字节的16位整数,类似于进程的进程ID。而每个端口号标记着唯一一个网络进程,它的作用是在传输层协议向应用层交付的时候,告诉操作系统该数据应该交付给应用层的哪一个网络进程。而不同的主机相同的进程,它们的端口号不一定是相同的。

端口号的划分

由于端口号是2个字节的整数,那么也就是说端口号的范围应该在0~65535之间,在这个范围中,0~1023是知名端口号,所谓知名端口号就是我们常常使用的某些服务器所绑定的端口号。比如,HTTP、FTP、SSH等这些广为使用的应用层协议。它们的端口号都是固定的,其中HTTP服务器使用的80端口号,FTP服务器使用的是21端口号,SSH服务器使用的是22端口号。在Linux终端下,我们可以用vim /etc/services来查看这些知名端口号。

这里写图片描述

在1024~65525这个范围内,是操作系统动态分配的端口号,我们使用的一些客户端程序的端口号就在这个里面。所以,我们在写程序需要用到端口号时,一定要避免使用这些知名端口号。
在一台主机上,一个端口号对应一个唯一的网络进程,也就是说一个端口号只能够绑定一个进程,但是一个进程不一定对应一个端口号。

同样,在传输层协议当中,其报头信息当中一定也有两个端口号,一个是源端口号,一个是目的端口号,这就是为了两主机之间更好的通信。原因与上文中两个IP地址相同。


到这里,我们知道了一个IP地址对应网络中唯一的一个主机,一个端口号对应着一个主机上唯一的一个网络进程。而IP地址加端口号就可以对应在网络中唯一的一个进程。那么这个时候,我们就是可以通过IP地址加端口号的形式来查找到网络当中唯一的一个网络进程。而IP地址加端口号的形式就叫做套接字!!!

网络字节序

我们知道所有数据在存储的时候都有大端小端之分。而不同的机器其存储方式也是不同的。那么如果两个主机在进行网络通信的过程中,发送与接收数据,由于不清楚对方的主机是大端存储还是小端存储,那么发送的数据的含义可能会被误解。所以网络数据流也是有大端小端之分的。

发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出,而接受主机把从网络中接收到的字节依次保存到接受缓冲区当中,也是按照内存地址从低到高的顺序保存。因此网络数据流规定,先发出的数据是低地址,后发出的是高地址。并且TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节。不论主机是大端还是小端,都会按照TCP/IP规定的网络字节序来发送或接受数据。如果当前主机是小端,就需要先将数据转成大端;否则就忽略。

所以在进行网络传输数据的时候,就需要将主机字节序与网络字节序进行转换。

#include <arpa/inet.h>

uint32_t htonl(uint32_t hostalong);
uint16_t htons(uint16_t hostalong);
uint32_t ntohl(uint32_t hostalong);
uint16_t ntohs(uint16_t hostalong);

为了保证代码的可移植性,一般都需要调用这些函数去进行转换。如果主机是小端的,那么调用过后将参数转成相应的大小端返回。如果是大端,那么就不做任何事情。其中h代表主机n代表网络,hton代表主机到网络。l表示32为长整数,s表示16位短整数。

socket编程接口

//创建socket文件描述符(TCP/UDP,客户端+服务器)
int socket(int domain, int type, int protocol);

//绑定端口号(TCP/UDP,服务器)
int bind(int domain, const struct sockaddr* address, socklen_t address_len);

//开始监听socket(TCP,服务器)
int listen(int socket, int backlog);

//接受请求(TCP,服务器)
int accept(int socket, struct sockaddr* address, socklen_t* address_len);

//建立连接(TCP,客户端)
int connect(int sockfd, const struct sockaddr* addr, socklen_t addrlen);

我们发现,在这些调用接口里,很多都涉及到了一个名叫struct sockaddr的结构体,这个结构体是什么呢?

sockaddr结构

socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4,IPv6。其中IPv4地址用sockaddr_in来表示,包括16为地址类型,16位端口号和32为IP地址。IPv4地址类型分别为常数AF_INET与AF_INET6。socket API可以都用struct sockaddr*类型表示,在使用的时候需要强制转化成sockaddr_in。
这里写图片描述
sockaddr_in结构体内主要有三部分信息:地址类型、端口号、IP地址。
其中存放IP地址的也是一个名叫struct in_addr sin_addr的 结构体。

地址转换函数

我们平常所看到的IP地址都是以点分十进制所表示的,而操作系统中对IP地址的看待都是二进制序列。并且sockaddr_in里的IP地址也是二进制序列,所以我们在使用的时候就需要将点分十进制的与二进制序列进行转化。

//点分十进制转in_addr函数
#include <arpa/inet.h>
int inet_aton(const char* strptr, struct in_addr* addrptr);
in_addr_t inet_addr(const char* strptr);
int inet_pton(int family, const char* strptr, void* addrptr);
//in_addr转字符串的函数
char* inet_ntoa(struct in_addr inaddr);
const char* inet_ntop(int family, const void* addrptr, char* strptr, size_t len);

注意!!!
关于in_addr转字符串的函数inet_ntoa函数,这个函数的返回值是char*。显然是这个函数自己在内部申请了一片空间存放字符串IP地址。那么申请的这片空间需不需要释放呢?
这里写图片描述
我们可以看到,这个函数把返回值的结果放到了静态存储区,这个时候并不需要我们释放。那么如果重复调用这个函数会有什么结果?
事实上,我们多次调用inet_ntoa的时候,每次调用都会将返回的内容覆盖到上次的结果上,也就是说,调用完inet_ntoa后,如果不及时的取出里面的内容,下次调用就会被覆盖,导致上次调用结果丢失。所以inet_ntoa是不可重入函数。


欢迎大家共同讨论,如有错误及时联系作者指出,并改正。谢谢大家!

猜你喜欢

转载自blog.csdn.net/liuchenxia8/article/details/80269879