TCP/IP四层协议,每层都有自己的协议方案,每层协议都要有自己的协议报头。从上到下递交数据时,要添加报头;从下到上交付数据时,要去掉报头。
不同协议层对数据包有不同的称谓,在传输层叫做段,在网络层叫做数据报,在链路层叫做帧。
一般而言:
对于一台主机,它的操作系统内核实现了从传输层到物理层的内容;
对于一台路由器,它至少实现了从网络层到物理层的内容;
对于一台交换机,它至少实现了从数据链路层到物理层的内容;
对于集线器,它只实现了物理层。
局域网中两台主机是可以直接通信的。
IP地址(公网IP):是在IP协议中,用来标识网络中不同主机的地址。
IPv4是32位
的,通常也使用“点分十进制”的字符串来表示IP地址,而点分割的每个数字的范围是0-255。
MAC地址:用来识别数据链路层中的节点,Linux下可以通过ifconfig
指令查看主机的MAC地址。
MAC地址是48位
的,一般用16进制数字加冒号的形式表示。
端口号是传输层协议的内容。端口号是16位
的。
理解端口号和进程ID:
一个进程可以和多个端口号进行绑定,一个端口号最多只能和一个进程进行关联。
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);
如果主机是小端字节序,这些函数将参数做相应的大端转换然后返回;如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。
常见的套接字有域间套接字,原始套接字,网络套接字等。针对不同的应同场景选用不同的套接字,对应的需要不同的套接字接口,但是Linux将这些接口做了统一。
UDP:
创建套接字:
domain
:
type
:通信种类(字节流/数据报)
绑定:
bind
的作用是将参数sockfd和addr绑定在一起,使sockfd这个用于网络通讯的文件描述符监听addr所描述的地址和端口号。
服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接。
客户端没有必要通过手动bind
来固定一个端口号,客户端的端口号可以由内核自动分配,否则如果在同一台机器上启动多个客户端,就可能会出现端口号被占用的情况;服务器也不是必须调用bind
,但如果服务器不调用bind
,内核会自动给服务器分配监听端口,每次启动服务器时端口号都不一样,使客户端连接服务器就会遇到麻烦。
struct sockaddr*
是一个通用指针类型,addr参数实际上可以接受多种协议的sockaddr结构体,而他们的长度各不相同,所以需要第三个参数addrlen来指定结构体的长度。
对addr参数初始化的一个示例:
// 将整个结构体清零
bzero(&servaddr, sizeof(servaddr));
// 设置地址类型为INADDR_ANY, 表示本地的任意IP地址
// 因为服务器可能有多个网卡,每个网卡可能绑定多个IP地址,设置成INADDR_ANY可以在所有IP地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个IP地址
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
ip地址的转化:
点分十进制字符串转in_addr的函数:
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);
inet_pton
和inet_ntop
不仅可以转换IPv4的in_addr,还可以转换IPv6的in6_addr,因此函数接口是void *addrptr
;
在多线程环境下,推荐使用inet_ntop
,这个函数由调用者提供一个缓冲区保存结果,可以规避线程安全问题。
查看启动的UDP链接:
TCP:
listen
声明sockfd处于监听状态,并且最多允许有backlog个客户端处于连接等待状态,如果接收到更多的连接请求就忽略。backlog的设置不会太大(一般是5)。
三次握手完成后,服务器调用accept
接收连接。如果服务器调用accept
时还没有客户端的连接请求,就会阻塞等待直到客户端连接上来。
addr是一个输出型参数,传出客户端就的地址和端口号,如果addr参数为空,表示不关心客户端的地址。
addrlen参数是一个输入输出型参数,传入的是调用者提供的缓冲区addr的长度,避免缓冲区的溢出问题;传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区)。
sudo yum install telnet*
ctrl + ]
换行
通信
ctrl + ]
quit
客户端需要调用connect
连接服务器。connect
和bind
的参数形式一致,区别在于bind
的参数是自己的地址,而connect
的参数是对方的地址。