1.IP地址, 端口号, 网络字节序等网络编程中基本概念
1.1 IP地址:(IP协议有两个版本,IPv4和IPv6,本博客凡提到IP协议,默认指IPv4。)
(1)IP地址是在IP协议中,用来标识网络中不同主机的地址;
(2)对于IPv4,IP地址是一个4字节,uint32_t(无符号32位整数,数量不到43亿)的整数;
(3)在网络通信中的每条数据中都应该包含有目的ip地址和源ip地址。
1.2 端口号:
(1)端口号标识了一台主机上进行通信的不同的应用程序,端口号+IP地址可以组成一个套接字,用来标识一个进程;
(2)端口号是一个2字节,uint16_t(0~65535)的整数;
(1)0~1023:知名端口号,是留着备用的,一般都是用于协议,例如HTTP、FTP、SSH
(2)1024~65535:是操作系统动态分配的端口号,客户端程序的端口号,就是由操作糸统从这个范围来分配的,在TCP与UDP的套接字通信中,客户端的端口号就是在此范围中
(3)每条网络中的数据都应该包含源端口和目的端口;
(4)一个端口号只能被一个进程占用;一个进程可以使用多个端口号
- 端口
“端口”,可认为是设备与外界通讯交流的出口。端口分为虚拟端口和物理端口,其中虚拟端口指计算机内部或交换机、路由器内的端口,不可见。例如计算机中的80端口、21端口等。物理端口又称接口,是可见端口,计算机背板的RJ45网口。电话使用RJ11插口属于物理端口的范畴。—“科普中国”
- “端口号(port number)”和“进程ID(processID、PID)”
端口号,就好像门牌号一样,客户端可以通过ip地址找到对应的服务器端,但服务器端有很多端口,每个应用程序对应一个端口号,通过类似门牌号的端口号,客户端才能真正访问到该服务器。 为了对端口进行区分,将每个端口进行了编号,就是端口号。—“科普中国”
进程ID,是大多数操作系统的内核用于唯一标识进程的一个数值。(简言之,就是进程的绰号。)这一数值可以作为许多函数调用的参数,以使调整进程优先级、kill(命令)进程之类的进程控制行为成为可能。—“科普中国”
知名端口号,是知名应用服务程序的编号。客户端只需保证该端口号在本机上是惟一的就可以了,客户端口号因存在时间很短暂又称临时端口号。 pid不是端口号,是操作系统对运行中程序的编号,是系统分配给一个进程的唯一标识符。pid就是各进程的身份标识符,程序一运行系统就会自动分配给进程一个独一无二的pid。进程终止后,pid被系统收回,可能会被继续分配给新运行的程序。
- 源端口号和目的端口号
传输层协议(TCP和UDP)的数据段中有两个端口号,分别叫源端口号和目的端口号。就是描述“数据由哪个应用程序出发,由哪个应用程序接收”。 - linux中查看知名端口号:
cat /etc/services - netstat(是一个用来查看网络状态的重要工具)
语法:netstat[选项]
-n 拒绝显示别名,能显示数字的全部转化为数字
-l 仅列出有在Listen(监听)的服务状态
-p 显示建立相关的链接的程序名
-t(tcp)仅显示tcp相关选项
-u(udp)仅显示udp相关选项
-a(all)显示所有选项,默认不显示LISTEN相关
1.3 TCP协议:
TCP(Transmission Control Protocol传输控制协议):传输层协议、面向连接、可靠传输、面向字节流。
1.4 UDP协议:
UDP(User Datagram Protocol用户数据报协议):传输层协议、无连接、非可靠传输、面向数据报。
1.5 网络字节序:
内存中的多字节数据相对于内存地址有大端和小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大小端之分,同样的,网络数据流也有大小端之分。定义网络字节序的原因是让不同cpu架构的计算机进行网络通信时,字节序不会混淆。
- 发送主机通常将发生缓冲区中的数据按内存地址从低到高的顺序发出;
- 接收主机把从网络上接收到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
- 因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址;
- TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节;
- 不管这台主机是大端机还是小端机,都会按照这个TCP/IP规定的网络字节序来发送/接收数据;
- 如果当前发送主机是小端机,就需要先将数据转为大端字节序,否则忽略直接发送即可。
为使网络程序具有可移植性,使同样的代码在大端和小端计算机上编译后都能正常运行,可调用以下库函数做网络字节序和主机字节序的转换。
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
- h表示host,n表示network,l表示32位长整数,s表示16位短整数;
- 例如htonl表示将32位长整数从主机字节序转换为网络字节序,例如将ip地址转换后准备发送;
- 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
- 如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。
2 socket套接字及socket API基本用法
2.1 套接字概念:
套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口的组合。
2.2 socket常见API
- 创建套接字——在内核中创建struct socket结构体,使进程与网卡之间建立联系
int socket(int domain,int type,int protocol);
//domain:地址域 IPv4:AF_INET
//type:套接字类型 SOCK_STREAM流式套接字,SOCK_DGRAM数据报套接字
//proto:传输层协议类型 0-默认,TCP:IPPROTO_TCP或6,UDP:IPPRPTO_UDP或17
//返回值:套接字操作句柄--文件描述符 创建失败返回-1
- 为套接字绑定地址信息——发送数据的时候能够表述数据是从哪个地址和端口发送的,对端回复数据也就有了依据
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
//sockfd:创建套接字成功后返回的套接字操作句柄
//addr:地址信息
//addlen:地址长度
- 发送数据
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
- 接收数据
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
- 关闭套接字
int close(int fd);
2.3 sockaddr结构
socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6。但是,各种网络协议的地址格式并不相同。
sockaddr结构
struct sockaddr{
sa_family sa_family;//指定当前地址信息是哪一个地址域,ipv4 or ipv6
char sa_data[14];//补全位
};
sockaddr_in结构(虽然socket api的接口是sockaddr,但是在基于IPv4编程时,使用的数据结构是sockaddr_in,这个结构里主要有三部分信息:地址类型、端口号、ip地址)
struct sockaddr_in{//ipv4的地址信息结构
_SOCKADDR_COMMOM(sin_);
in_port_t sin_port;//port number
struct in_addr sin_addr;//internet address
};
in_addr结构
struct in_addr{
in_addr_t s_addr;
};