网络通信的基本概念
-
TCP和UDP的区别
- TCP : 传输控制协议,面向连接的服务(类似打电话),安全、可靠(数据完整)(三次握手<发送前>、响应+重传<发送中>、四次挥手<发送后>)相对UDP慢
- 应用范围 :
- 对安全性、完整性有严格要求的场景(ftp、)
三次握手
- 第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。(客户端的发送能力、服务端的接收能力是正常的。)
- 第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(seq=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;(服务端的接收、发送能力,客户端的接收、发送能力是正常的。不过此时服务器并不能确认客户端的接收能力是否正常。)
- 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。(客户端的接收、发送能力正常,服务器自己的发送、接收能力也正常。因此,需要三次握手才能确认双方的接收与发送能力是否正常。)
- 完成三次握手,客户端与服务器开始传送数据。
- 三次握手的好处
- 确认双方的接受能力、发送能力是否正常。
- 指定自己的初始化序列号,为后面的可靠传送做准备。
- 如果是 HTTPS 协议的话,三次握手这个过程,还会进行数字证书的验证以及加密密钥的生成。
四次挥手
- 由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭,确保关闭前发送完未发送的数据。
- TCP客户端发送一个FIN,用来关闭客户到服务器的数据传送。
- 服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号。
- 服务器关闭客户端的连接,发送一个FIN给客户端。
- 客户端发回ACK报文确认,并将确认序号设置为收到序号加1。
-
UDP : 用户数据报文协议,面向无连接的服务(类似发短信),不保证安全可靠,大多数情况下可靠
- 应用范围 :
- 流媒体、视频、音频
- 应用范围 :
-
消息流 A发到B
- A的:应用层->表示层->会话层->网络层->传输层->数据链路层->物理层->
- B的:物理层->数据链路层->传输层->网络层->会话层->表示层->应用层
-
消息包
- 当socket收到一个要发送的数据时,发送进行拆分成Bit流,然后再组成(放丢失)数据包(可能会丢包)。
套接字
-
socket是一种接口机制,可以让程序无论是用什么端口、协议,都可以从socket进出数据。他负责进程与协议之间的链接。
-
编程模型
- 点对点(p2p) : 一对一通信。
- 客户机/服务器(C/S) : 一对多通信
-
函数
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int socket(int domain, int type, int protocol);
- 创建socket描述符,发数据类似写文件,收就是读
- domain
- AF_UNIX/AF_LOCAL
- AF_INET
- AF_INET6
- type
- SOCK_STREAM 数据流协议 TCP
- SOCK_DGRAM 数据报协议 UDP
- protocol
- 特殊通信协议一般来说给0即可
- 返回值
- socket描述符 类似文件描述符
-
通信地址
//函数接口定义的 struct sockaddr { unsigned short sa_family; /* address family, AF_xxx */ char sa_data[14]; /* 14 bytes of protocol address */ }; //实际用的 struct sockaddr_un{ __SOCKADDR_COMMON(sun_);// 地址类型 参看上面的domain char sun_path(108);// socket文件的路径 }; struct sockaddr_in { __SOCKADDR_COMMON(sin_);// 地址类型 参看上面的domain in_port_t sin_port;// 端口号,大端字节序 struct in_addr sin_addr;// IP地址,大端4字节整数 }; struct in_addr { } IN_ADDR;
-
绑定
- socket描述符与物理通信载体(网卡或socket文件)绑定在一起。
- 函数
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
- sockfd
- addr
- addrlen
-
连接
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- sockfd
- addr
- addrlen:通信地址结构的字节数
- 返回值:
- 本地通信:成功0,失败-1;
- 不同通信不同
-
数据的接受与发送
#include <sys/types.h> #include <sys/socket.h> //接收 ssize_t recv(int sockfd, void *buf, size_t len, int flags); //发送 ssize_t send(int sockfd, const void *buf, size_t len, int flags);
- recv/send与read/write功能一样,多了flags是否阻塞功能(0阻塞,1不阻塞)
-
关闭套接字
- close:如果是网络通信,端口号必不会立即回收,大概会占有3分钟左右
-
字节序转换
#include <arpa/inet.h> uint32_t htonl(uint32_t hostlong); //把32位本机字节序转换为对应的32位网络字节序 uint16_t htons(uint16_t hostshort); //把16位本机字节序转换为对应的16位网络字节序 uint32_t ntohl(uint32_t netlong); //把32位网络字节序转换为对应的32位本机字节序 uint16_t ntohs(uint16_t netshort); //把16位网络字节序转换为对应的16位本机字节序
-
IP地址转换
#include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> //把点分十进制IP地址(字符串)转换位32位无符号整数,使用结构指针获取 int inet_aton(const char *cp, struct in_addr *inp); //把点分十进制IP地址(字符串)转换位32位无符号整数,返回值直接返回 in_addr_t inet_addr(const char *cp); //把32位无符号的整数表示的IP地址,转换为点分十进制IP地址(字符串) char *inet_ntoa(struct in_addr in);
-
本地通信
- 编程模型
进程A 进程B 创建套接字(AF_LOCAL) 创建套接字(AF_LOCAL) 准备地址(sockaddr_un) 准备地址(sockaddr_un) 绑定(自己的socket与地址) 连接(connect,连接进程A地址) 接受数据 发送数据 关闭套接字 关闭套接字 #include <stdio.h> #include <unistd.h> #include <sys/socket.h> #include <sys/types.h> #include <sys/un.h> #include <string.h> #define SOCK_FILE "/tmp/sock" int main() { //创建 int sockfd = socket(AF_LOCAL,SOCK_DGRAM,0); if(0 > sockfd) { perror("socket"); return -1; } //准备地址 struct sockaddr_un addr = {}; addr.sun_family = AF_LOCAL; strcpy(addr.sun_path,SOCK_FILE); //绑定 if(bind(sockfd,(struct sochaddr*)&addr,sizeof(addr))) { perror("bind"); return -1; } //接收数据 char buf{1024}; while(1) { int ret = read(sockfd,buf,sizeof(buf)); if(ret <0) { perror("read"); return -1; } printf("read:%s\n",read); if(0 == strcmp("quit",buf)) { printf("通信完成\n"); break; } } //关闭 close(sockfd); //删除 unlink(SOCK_FILE); }
#include <stdio.h> #include <unistd.h> #include <sys/socket.h> #include <sys/types.h> #include <sys/un.h> #include <string.h> #define SOCK_FILE "/tmp/sock" int main() { //创建 int sockfd = socket(AF_LOCAL,SOCK_DGRAM,0); if(0 > sockfd) { perror("socket"); return -1; } //准备地址 struct sockaddr_un addr = {}; addr.sun_family = AF_LOCAL; strcpy(addr.sun_path,SOCK_FILE); //连接 if(connect(sockfd,(struct sochaddr*)&addr,sizeof(addr))) { perror("connect"); return -1; } char buf{1024}; while(1) { printf(">"); gets(buf); int ret = write(sockfd,buf,strlen(buf)-1); if(ret <0) { perror("read"); return -1; } if(0 == strcmp("quit",buf)) { printf("通信完成\n"); break; } } //关闭 close(sockfd); }
-
基于TCP协议的C/S模型
-
函数
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int listen(int sockfd, int backlog);
- 功能:设置等待连接的最大数量
- sockfd:被监听的socket描述符
- backlog:等待连接的最大数量(排队的数量)
- 返回值:成功0,失败-1
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
- 功能:等待连接sockfd连接
- addr:获取连接的地址
- addrlen:设置链接地址机构体的长度
- 返回值:专门用于通信的描述符
-
编程模型
Server Client 创建socket套接字 创建socket套接字 准备地址(sockaddr_in,本机地址) 准备地址(服务器地址) 绑定(bind) ······ 监听(listen) ······ 等待连接(accept、fork(用于服务)) 连接(connect) 接收请求(recv/read) 发送请求(write/send) 响应请求(write/send) 接受响应(recv/read) 关闭(close) 关闭(close) -
例子
//server #include <stdio.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> int main() { printf("服务器创建socketn\n"); int sockfd = socket(AF_INET,SOCK_STREAM,0); if(sockfd < 0) { perror("socket"); return -1; } printf("准备地址\n"); struct sockaddr_in addr = {}; addr.sin_family = AF_INET; addr.sin_port = htons(7777); addr.sin_addr.s_addr = inet_addr("本机ip地址"); socklen_t len = sizeof(addr); printf("绑定scoket与地址\n"); if(bind(sockfd,(struct sockaddr*)&addr,len)) { perror("bind"); return; } printf("设置监听\n"); if(listen(sockfd,5)) { perror("listen"); return -1; } printf("等待客户端连接\n"); while(1) { struct sockaddr_in addrcli = {}; int clifd = accept(sockfd,(struct sockaddr*)&addrcli,&len); if(clifd < 0) { perror("accept"); continue; } if(0 == fork()) { char buf[1024] = {}; while(1) { printf("read:"); read(clifd,buf,sizeof(buf)); printf("%s\n",buf); if(0 == strcmp("quit",buf)) { close(clifd); return 0; } printf(">"); gets(buf); write(clifd,buf,strlen(buf)+1); } } } }
//clinet #include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> int main() { printf("服务器创建socketn\n"); int sockfd = socket(AF_INET,SOCK_STREAM,0); if(sockfd < 0) { perror("socket"); return -1; } printf("准备地址\n"); struct sockaddr_in addr = {}; addr.sin_family = AF_INET; addr.sin_port = htons(7777); addr.sin_addr.s_addr = inet_addr("本机ip地址"); socklen_t len = sizeof(addr); printf("连接服务器地址\n"); if(connect(sockfd,(struct sockaddr*)&addr,len)) { perror("connect"); return; } while(1) { char buf[1024] = {}; printf(">"); gets(buf); write(sockfd,buf,strlen(buf)+1); if(0 == strcmp("quit",buf)) { break; } printf("read:"); read(sockfd,buf,sizeof(buf)); printf("%s\n",buf); } close(sockfd); }