虚拟专用网络:virtual private network,VPN
安全shell:secure shell,SSH
1. 计算机网络基础
1.1 模型
(1)OSI模型
(2)TCP/IP模型
网络接口层:运行网络接口层协议,进行IP数据报接收、发送
网络IP层:提供无连接的网络层服务,进行路由选择、流量控制、拥塞控制
传输层:TCP、UDP协议,建立端到端的连接
应用层:数据处理,进程间通信
1.2 有连接、无连接服务
面向连接:如TCP,transmission control protocol SCTP,stream control transmission protocol
建立连接、数据传输、释放连接
每一个数据分组中都带有该分组数据的源IP和目的IP地址
面向连接的服务是可靠的
协议复杂
无连接的: 如UDP,user datagram protocol
不需要建立、释放连接
可靠性差, 尽最大努力交付
协议简单、通信效率高
(3)其他常见协议
2. UDP 用户数据报协议
往UDP套接字写入消息,消息封装成UDP数据报,再封装成IP数据报,然后发送。
不保证最终能到达、不保证达到的先后顺序、不保证每个数据报只达到一次
到达后校验、检测有错误,或中途丢失,源端也不重传
每个UDP数据报有一个长度(TCP是字节流协议,没有记录边界)
2.1 用户数据报格式
首部字段、数据字段
(1)首部字段
长度:首部长度和数据部分长度
校验和:保证数据安全
伪首部:12字节,在UDP用户数据报前,用于计算校验和
源IP:4字节 目的IP:4字节
0:1字节
协议:1字节,UDP协议号为17
数据报长度:2字节
3. TCP
面向连接
可靠的全双工信道
(1)TCP报文段首部
序号:本报文段发送的数据中第一个字节的序号(例,报文段A序号为1000,报文段长度100字节,则下一个报文段的序号为1100)
确认号:期望收到对方的下一个报文段的 数据的第一个字节的序号(例,已经正确收到了序号为2000,长度100的报文段,则2000~2099都已收到,期望下一个是2100)
4字节,能标记4GB的数据
数据偏移: 4bit,而数据偏移的单位是4字节,所以能表示最大64字节偏移量
保留:值为0
6个bit位:URG:紧急bit,为1表示报文段中有紧急数据,应尽快传送
ACK:确认bit,建立连接时使用
PSH:推送bit,为1时发送端会立即将该报文段发送出去,接收端在接收到推送bit为1的报文段时,会尽快送接收缓存中取出来交给应用进程
RST:复位bit,为1表明TCP连接中出现严重差错,必须释放连接重新建立连接
SYN:同步bit,建立连接时用来同步序号
FIN:终止bit,用来释放连接。为1时,表示此报文段发送的数据已经发送完毕,并要求释放连接
窗口:控制对方发送的数据量,单位字节。用于流量控制、拥塞控制
校验和:检验首部和数据部分。计算时要在TCP报文段前加12字节的伪首部(UDP协议号17,TCP协议号6)
选项:见(5)
填充:0
(2)窗口
发送窗口:数据通信过程中发送端一次可以发送数据的最大字节数
通信过程中,接收端可根据情况动态调整发送端窗口的大小
通知窗口:接收端根据其接收TCP报文段的能力而设定的窗口大小
拥塞窗口:发送端根据网络拥塞情况而设定的窗口的大小
一般发送端发送窗口大小取通知窗口和拥塞窗口中的较小者
(3)TCP的三次握手
假设一个客户端A和服务器B
服务器B被动打开,为接收用户进程请求做准备(socke、bind、listen)
客户端发起主动打开(connect),客户端等的TCP发送连接请求报文段,报文段中首部 SYN 为1,序号为J(后面传送数据时第一个字节的序号就是J+1)
服务器若同意建立连接,发送一个确认报文段,ACK为1,确认号为J+1,SYN为1,自己选择一个序号K,即序号设为K
客户端收到服务器的确认报文段,向服务器发送报文段,序号为K+1。连接建立
(4)TCP四次挥手
客户端主动关闭(close),发出连接释放请求报文段。 FIN为1,ACK为0,序号设为收到数据最后字节序号加1, SEQ设为M
服务器收到FIN执行被动关闭,发出确认报文。ACK=1,确认号ack设为M+1(表示已收到客户端的链接释放请求报文,服务器停止接收从客户端发来的数据)
服务器发送连接释放报文,FIN为1,SEQ为N,ACK为1,ack为M+1
客户端收到服务器的链接释放报文,发送确认报文。ACK为1,ack为N+1
(5)TCP选项
--------------------------------------套接字-----------------------------------
1. 套接字结构
IPv4的:
1 //IPv4套接字结构 2 struct in_addr { 3 in_addr_t s_addr; //32位 IPv4地址,网络字节序 4 }; 5 6 struct sockaddr_in { 7 sa_family_t sin_family; //16位地址族 AF_INET 8 in_port_t sin_port; //16位 TCP或UDP的端口号,网络字节序 9 struct in_addr sin_addr; //32位 10 char sin_zero[8];//填充'0' 11 };
IPv6的:
1 struct in6_addr { 2 uint8_t s6_addr[16]; //IPv6地址 3 }; 4 struct sockaddr_in6 { 5 sa_family_t sin6_family; //AF_INET6 6 in_port_t sin6_port; 7 uint32_t sin6_flowinfo; 8 struct in6_addr sin6_addr; 9 uint32_t sin6_scope_id; 10 };
sockaddr_in或sockaddr_in都被转换成通用的结构
通用的结构:同struct sockaddr_in,也是16字节
struct sockaddr { sa_family_t sa_family; // char sa_data[14]; // };
新的套接字地址结构:<netinet/in.h>
1 struct sockaddr_storage { 2 uint8_t ss_len; //这个结构体的大小 3 sa_family_t ss_family; //地址族 4 .... 5 6 };
2. 字节序
两个进程通信时,先将数据转换成网络字节序,接受到数据后,再转换成本地主机字节序
hton表示主机字节序转网络字节序
ntoh表示网络字节序转本地主机字节序
3. 地址形式转换
(1)
inet_aton:"192.168.0.1"等类似形式字符串转换为网络字节序的IPv4地址存到struct in_addr里
有效返回1;无效返回0
inet_ntoa:网络字节序IPv4地址(struct in_addr里面的)转换成字符串形式
inet_addr:字符串形式IP地址直接转换成网络字节序的地址并返回该数值
(2)适合IPv4和IPv6的
inet_pton:af是AF_INET或AF_INET6
可用来转换IPv4、IPv6地址
使用:
1 struct sockaddr_in addr; 2 inet_ntop(AF_INET,&addr.sin_addr,str,sizeof(str)); 3 4 struct sockaddr_in6 addr6; 5 inet_ntop(AF_INET6,&addr6.sin6_addr,str,sizeof(str));
4. 获取主机信息
/etc/hosts /etc/services
(1)主机信息结构体
1 struct hostent { 2 char *h_name; //主机名 3 char **h_aliases; //主机别名 4 int h_addrtype; //地址类型 5 int h_length; //地址长度,单位字节 IPv4是4字节,IPv6是16字节 6 char **h_addr_list; //地址 7 ... 8 };
(2)
得到一个struct hostent指针,指向一块静态内存,每次调用这个函数该缓冲区都会被覆盖
endhostent会关闭/etc/hosts文件
5. 查询服务与对应的端口号
1 struct servent { 2 char *s_name; //服务名 3 char **s_aliases; //服务别名 4 int s_port; //端口号 5 char *s_proto; //协议名字 6 ... 7 };
6. 域名与IP
1 struct adddrinfo { 2 int ai_flags; 3 int ai_family; 4 int ai_socktype; 5 int ai_protocol; 6 socklen_t ai_addrlen; 7 struct sockaddr *ai_addr; 8 char *ai_canonname; 9 struct addrinfo *ai_next; 10 };
----------------------------------------------------------------套接字编程-----------------------------------------------------------------------------
TCP示意图:
1. 创建、销毁套接字描述符
domain:
AF_INET:IPv4协议
AF_INET6:IPv6协议
AF_UNIX:非网络环境的进程通信,Unix域协议
type:
SOCK_STREAM:有序,面向连接字节流。默认协议TCP
SOCK_DGRAM:长度固定,无连接报文。 默认协议UDP
SOCK_SEQPACKET:长度固定,面向连接的报文。默认协议SCTP
SOCK_RAM:原始套接字。绕过协议直接访问网络层。
2. 套接字绑定地址信息
服务器套接字绑定IP地址、端口号等
addr:实际使用struct sockaddr_in或struct sockaddr_in6(强制转换为struct sockaddr)
IP地址问题:如果只想本机客户端访问本机的服务器,地址选择 "127.0.0.1" 先用aton、pton转换,再转成网络字节序htonl
如果只想局域网内访问,可使用内网地址如 "192.168.1.12"
如果想被公网访问,则使用 "0.0.0.0"或宏 INADDR_ANY、IN6ADDR_ANY
端口:若参数取0,则由内核为套接字选择临时端口号 可通过getsockname获取
获取本机套接字上关联的地址信息:
套接字已和对端连接,通过getpeername获取对方的地址信息:
3. 开始监听
TCP服务器调用,作用:
①socket创建一个套接字时,它默认是一个主动的套接字(一个将发起connect的客户套接字),调用listen后转换成被动套接字,指示内核应接收指向该套接字的连接请求。套接字从CLOSED状态
变为LISTEN状态。
②listen函数的第二个参数规定了内核应为该套接字排队的最大连接数
内核为任一给定的监听套接字维护两个队列:
①未完成连接队列:每个这样的SYN分节对应一项,已由客户发出并到达服务器,服务器正在等待完成TCP三路握手过程,这些套接字处于SYN_RCVD状态
②已完成连接队列:每个已完成三路握手的客户对应一项,这些套接字处于ESTABLISHED状态
4. 开始接受请求
开始监听之和,调用accept开始一个接受请求,已完成队列的对头将返回给进程。如果已完成队列为空,进程睡眠(当套接字设为默认的阻塞方式时)
成功返回描述符;失败-1
addr:返回已连接的对端进程(客户端的)协议地址
accept成功时,返回要给内核创建的新描述符,与相应的客户TCP连接。监听套接字在服务器的整个运行期间有效,而已连接套接字在完成对应客户的服务后关闭。
5. 客户端发起连接
addr:里面有服务器的IP和端口号
对于TCP套接字,客户端调用connect将发起TCP的三路握手过程,连接建立或出错才返回。
6. 数据发送和接收
(1)数据发送
sendto常用语无连接通信,如UDP
sendmsg:可用于多重缓冲区发送
1 struct msghdr { 2 void *msg_name; //可选地址 3 socklen_t msg_namelen; //地址大小 4 struct iovec *msg_iov; //I/O缓冲区数组 5 size_t msg_iovlen; //数组元素个数 6 void *msg_control; //辅助数据 7 size_t msg_controllen; // 8 int msg_flags; 9 };
(2)接收数据
recvfrom常用于无连接通信,如UDP,可用来获取发送方地址端口等信息
7. 简单示例
服务器:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <unistd.h> 5 #include <sys/types.h> 6 #include <sys/socket.h> 7 #include <netinet/in.h> 8 9 int main() 10 { 11 //1.创建socket 12 //IPv4、TCP 13 int listenfd = socket(AF_INET,SOCK_STREAM,0); 14 if(listenfd == -1) { 15 printf("socket error\n"); 16 exit(1); 17 } 18 19 //2.初始化服务器地址、端口 20 struct sockaddr_in bindaddr; 21 bindaddr.sin_family = AF_INET; 22 bindaddr.sin_addr.s_addr = htonl(INADDR_ANY); //IPv4地址 23 /* 24 char ipaddr[16] = "192.168.0.110"; 25 bindaddr.sin_addr.s_addr = inet_addr(ipaddr); 26 */ 27 bindaddr.sin_port = htons(4000); //指定端口号 28 if(bind(listenfd,(struct sockaddr *)&bindaddr,sizeof(bindaddr)) == -1) { 29 printf("bind error\n"); 30 close(listenfd); 31 exit(1); 32 } 33 //3.启动监听 34 if(listen(listenfd,2)==-1) { 35 printf("listen error\n"); 36 exit(1); 37 } 38 printf("listening...\n"); 39 //4.接收连接请求 40 while(1) { 41 int clientfd; 42 struct sockaddr_in clientaddr; 43 socklen_t addrlen = sizeof(struct sockaddr_in); 44 clientfd = accept(listenfd,(struct sockaddr *)&clientaddr,&addrlen); 45 if(clientfd == -1) { 46 continue; 47 } 48 char recvBuf[40] = {0}; 49 int ret = recv(clientfd,recvBuf,20,0); 50 if(ret>0) { 51 //打印收到的信息 52 printf("Get:%s\n",recvBuf); 53 //给客户端回信息,表示我已收到 54 strcat(recvBuf,".From server"); 55 ret = send(clientfd,recvBuf,strlen(recvBuf),0); 56 } 57 /* 58 pid_t pid; 59 if((pid=fork())==0) { 60 //子进程处理连接 61 close(listenfd); 62 char recvBuf[40] = {0}; 63 接收发送数据 64 close(clientfd); 65 exit(0); 66 } 67 */ 68 close(clientfd); 69 70 } 71 close(listenfd); 72 return 0; 73 }
客户端:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <unistd.h> 5 #include <sys/types.h> 6 #include <sys/socket.h> 7 #include <netinet/in.h> 8 #include <arpa/inet.h> 9 10 int main() 11 { 12 //1.创建socket 13 //IPv4、TCP 14 int connfd = socket(AF_INET,SOCK_STREAM,0); 15 if(connfd == -1) { 16 printf("socket error\n"); 17 exit(1); 18 } 19 //2.向服务器发起连接 20 struct sockaddr_in serveraddr; 21 serveraddr.sin_family = AF_INET; 22 serveraddr.sin_addr.s_addr = inet_addr("192.168.0.110"); 23 serveraddr.sin_port = htons(4000); 24 if(connect(connfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr))==-1) { 25 printf("connect error\n"); 26 close(connfd); 27 exit(1); 28 } 29 //3.向服务器发送数据 30 char sendBuf[40] = "Hello,this is client"; 31 int ret = send(connfd,sendBuf,strlen(sendBuf),0); 32 if (ret != strlen(sendBuf)) { 33 printf("send error\n"); 34 close(connfd); 35 exit(1); 36 } 37 //4.从服务器接收数据,接收完后就退出 38 memset(sendBuf,0,sizeof(sendBuf)); 39 ret = recv(connfd,sendBuf,40,0); 40 if(ret > 0) { 41 printf("Recv data from server:%s\n",sendBuf); 42 } 43 close(connfd); 44 return 0; 45 }
----------------------------------------套接字编程-----------------------------------------------------
UDP示意图:
服务器没有listen、accept,客户端少了bind和connect
1. 如果给客户端UDP套接字调用connect
对于调用了connect的已连接UDP套接字:
①不能给输出操作指定目的IP和端口,则只能用send、write或将sendto的相关参数设为NULL。写到已连接UDP套接字上的任何内容都将自动发送到connect指定的IP端口上去
②可用read、recv、recvmsg获取信息,没必要用recvfrom(假设客户套接字A和服务器套接字B建立了UDP连接,则从客户套接字C发送到B的数据报不会被B接收,现在的B只能和已建立连接的
A交换数据报)(是只能与A所在的IP地址交换数据报)
③已连接UDP套接字引发的异步错误会返回给它们所在的进程,未连接UDP套接字不会接收任何异步错误
2. 简单无连接UDP示例
服务器:
1 //UDP服务器 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <string.h> 5 #include <unistd.h> 6 #include <sys/types.h> 7 #include <sys/socket.h> 8 #include <netinet/in.h> 9 #include <arpa/inet.h> 10 11 int main() 12 { 13 //1.创建套接字 14 int listenfd = socket(AF_INET,SOCK_DGRAM,0); 15 if(listenfd == -1) { 16 printf("socket error\n"); 17 exit(1); 18 } 19 //2.初始化服务器地址和端口 20 struct sockaddr_in bindaddr; 21 bindaddr.sin_family = AF_INET; 22 bindaddr.sin_addr.s_addr = htonl(INADDR_ANY); //IPv4地址 23 bindaddr.sin_port = htons(4000); 24 if(bind(listenfd,(struct sockaddr *)&bindaddr,sizeof(bindaddr)) == -1) { 25 printf("bind error\n"); 26 close(listenfd); 27 exit(1); 28 } 29 //3.处理请求 30 struct sockaddr_in clientaddr; 31 socklen_t addrlen = sizeof(struct sockaddr_in); 32 char recvBuf[40] = {0}; 33 while(1) { 34 memset(recvBuf,0,sizeof(recvBuf)); 35 int n = recvfrom(listenfd,recvBuf,40,0,(struct sockaddr *)&clientaddr,&addrlen); 36 if(n>0) { 37 char clientip[16] = {0}; 38 inet_ntop(AF_INET,&clientaddr.sin_addr,clientip,16); 39 printf("Get data from:%s:%d\n",clientip,ntohs(clientaddr.sin_port)); 40 printf("Content is:%s\n",recvBuf); 41 strcat(recvBuf,".From server."); 42 n = sendto(listenfd,recvBuf,strlen(recvBuf),0,(struct sockaddr *)&clientaddr,addrlen); 43 } 44 } 45 close(listenfd); 46 return 0; 47 }
客户端:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <unistd.h> 5 #include <sys/types.h> 6 #include <sys/socket.h> 7 #include <netinet/in.h> 8 #include <arpa/inet.h> 9 10 int main() 11 { 12 //1.创建socket 13 //IPv4、UDP 14 int connfd = socket(AF_INET,SOCK_DGRAM,0); 15 if(connfd == -1) { 16 printf("socket error\n"); 17 exit(1); 18 } 19 //2.向服务器发送数据 20 char sendBuf[40] = "Hello,this is client"; 21 struct sockaddr_in serveraddr; 22 serveraddr.sin_family = AF_INET; 23 serveraddr.sin_addr.s_addr = inet_addr("192.168.0.110"); 24 serveraddr.sin_port = htons(4000); 25 int n = sendto(connfd,sendBuf,strlen(sendBuf),0,(struct sockaddr *)&serveraddr,sizeof(serveraddr)); 26 if (n != strlen(sendBuf)) { 27 printf("send error\n"); 28 close(connfd); 29 exit(1); 30 } 31 //从服务器接收数据 32 struct sockaddr_in serveraddr2; 33 socklen_t addrlen = sizeof(struct sockaddr_in); 34 memset(sendBuf,0,sizeof(sendBuf)); 35 n = recvfrom(connfd,sendBuf,40,0,(struct sockaddr *)&serveraddr2,&addrlen); 36 if(n > 0) { 37 printf("Recv data from server:%s\n",sendBuf); 38 } 39 close(connfd); 40 return 0; 41 }