六十九、 域套接字模型
69.1 域套接字的概念
- 只能做一台主机内的进程间通信,协议族(地址族)指定为:AF_UNIX AF_LOCAL
- bsp-lcd: s类型文件,就是域套接字
- 如果客户端不手动绑定,则操作系统不会创建一个套接字文件给客户端自动绑定的。
69.2 域套接字的函数使用
69.2.1 socket
c
功能:
在内核空间中创建两个缓冲区(接收缓冲区,发送缓冲区),并返回缓冲区的文件描述(套接字文件描述符);
原型:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
参数:
int domain:地址族,协议族;
Name Purpose Man page
AF_UNIX, AF_LOCAL Local communication unix(7)
int type:套接字类型
SOCK_STREAM:字节流式套接字,流式套接字---》TCP协议
SOCK_DGRAM: 数据报式套接字,报式套接字---》UDP协议
SOCK_RAW:原始套接字,协议需要在第三个参数指定;
int protocol:填0,代表使用默认协议;
IPPROTO_TCP:TCP协议 IPPROTO_UDP:UDP协议;
返回值:
>=0,套接字文件描述符;
=-1,失败,更新errno;
69.2.2 bind
c
功能:
绑定地址信息到指定套接字上;
原型:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
参数:
int sockfd:指定要绑定到哪个套接字上,填对应的文件描述符;
struct sockaddr *addr:通用地址信息结构体,真实的地址信息结构体根据地址族指定。
手动填充上地址信息(例如:IP和端口),给bind函数绑定使用;
AF_UNIX : man 7 unix
struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX */ 必须填AF_UNIX
char sun_path[108]; /* pathname */ 套接字文件路径名
必须事先不存在,由bind函数生成
};
socklen_t addrlen:真实的地址信息结构体的大小,sizeof(struct sockaddr_un);
返回值:
成功,返回0;
失败,返回-1,更新errno;
69.2.3 access
c
功能:判断文件是否存在,或者文件是否有某种权限;
原型:
69.2.4 unlink
c
功能:删除文件(的硬链接)
原型:
69.3 流式域套接字
69.3.1 UNIX TCP服务器
c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <sys/un.h>
#define ERR_MSG(msg) do{\
fprintf(stderr, "__%d__:", __LINE__); \
perror(msg);\
}while(0)
int main(int argc, const char *argv[])
{
//创建流式套接字 socket
int sfd = socket(AF_UNIX, SOCK_STREAM, 0);
if(sfd < 0)
{
ERR_MSG("socket");
return -1;
}
printf("socket create success sfd=%d\n", sfd);
//判断套接字文件是否存在
if(access("./unix", F_OK) == 0)
{
//若存在则删除该套接字文件
unlink("./unix");
}
//填充地址信息结构体给bind函数绑定,
//真实的地址信息结构体根据地址族指定 AF_UNIX:man 7 unix
struct sockaddr_un sun;
sun.sun_family = AF_UNIX; //必须填AF_UNIX;
strcpy(sun.sun_path, "./unix"); //必须事先不存在,由bind函数生成
//绑定服务器的地址信息---> 必须绑定 bind
if(bind(sfd, (struct sockaddr*)&sun, sizeof(sun)) < 0)
{
ERR_MSG("bind");
return -1;
}
printf("bind success\n");
//将套接字设置为被动监听状态 listen
if(listen(sfd, 128) < 0)
{
ERR_MSG("listen");
return -1;
}
printf("listen success\n");
int newfd = -1;
struct sockaddr_un cun; //存储客户端的地址信息
socklen_t addrlen = sizeof(cun); //真实的地址信息结构体的大小
//获取一个已经完成的客户端信息,生成一个新的文件描述符 accept
//newfd = accept(sfd, NULL, NULL);
newfd = accept(sfd, (struct sockaddr*)&cun, &addrlen);
if(newfd < 0)
{
ERR_MSG("newfd");
return -1;
}
printf("[]客户端连接成功 newfd=%d\n", newfd);
char buf[128] = "";
ssize_t res = 0;
while(1)
{
//清空字符串
bzero(buf, sizeof(buf)); //memset
//接收
res = recv(newfd, buf, sizeof(buf), 0);
if(res < 0)
{
ERR_MSG("recv");
return -1;
}
else if(0 == res)
{
printf("[]客户端下线 newfd=%d\n", newfd);
break;
}
printf("[] newfd=%d : %s\n", newfd, buf);
//发送
strcat(buf, "*_*");
if(send(newfd, buf, sizeof(buf), 0) < 0)
{
ERR_MSG("send");
return -1;
}
printf("send success\n");
}
//关闭文件名描述符
close(newfd);
if(close(sfd) < 0)
{
ERR_MSG("close");
return -1;
}
return 0;
}
69.3.2 UNIX TCP客户端
c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <sys/un.h>
#define ERR_MSG(msg) do{\
fprintf(stderr, "__%d__:", __LINE__); \
perror(msg);\
}while(0)
int main(int argc, const char *argv[])
{
//创建流式套接字 socket
int cfd = socket(AF_UNIX, SOCK_STREAM, 0);
if(cfd < 0)
{
ERR_MSG("socket");
return -1;
}
printf("socket create success cfd=%d\n", cfd);
//绑定客户端的地址信息---》非必须绑定
//当不手动绑定的时候,操作系统会自动给客户端绑定本机IP和随机端口。
//填充服务器的地址信息结构体给connect函数连接,
//想连接哪个服务器,就填哪个服务器绑定的地址信息
//真实的地址信息结构体根据地址族指定 AF_UNIX:man 7 unix
struct sockaddr_un sun;
sun.sun_family = AF_UNIX; //必须填AF_UNIX;
strcpy(sun.sun_path, "./unix"); //服务器绑定的套接字文件
//连接指定服务器 connect
if(connect(cfd, (struct sockaddr*)&sun, sizeof(sun)) < 0)
{
ERR_MSG("connect");
return -1;
}
printf("connect success\n");
char buf[128] = "";
ssize_t res = 0;
while(1)
{
//清空字符串
bzero(buf, sizeof(buf)); //memset
printf("请输入>>> ");
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf)-1] = 0;
//发送
if(send(cfd, buf, sizeof(buf), 0) < 0)
{
ERR_MSG("send");
return -1;
}
printf("send success\n");
bzero(buf, sizeof(buf)); //memset
//接收
res = recv(cfd, buf, sizeof(buf), 0);
if(res < 0)
{
ERR_MSG("recv");
return -1;
}
else if(0 == res)
{
printf("服务器下线 cfd=%d\n", cfd);
break;
}
printf("cfd=%d : %s\n", cfd, buf);
}
//关闭文件名描述符
if(close(cfd) < 0)
{
ERR_MSG("close");
return -1;
}
return 0;
}
69.4 报式域套接字
- 报式域套接字的编程与网络报式套接字编程思路是一致的,但是由于客户端不手动绑定的话,操作系统不会自动创建一个套接字文件给客户端绑定
- 此时若服务器想给客户端回复信息,会找不到客户端的地址信息。此时客户端是必须绑定的。
69.4.1 UNIX UDP服务器
c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <sys/un.h>
#define ERR_MSG(msg) do{\
fprintf(stderr, "__%d__:", __LINE__); \
perror(msg);\
}while(0)
int main(int argc, const char *argv[])
{
//创建报式套接字 socket
int sfd = socket(AF_UNIX, SOCK_DGRAM, 0);
if(sfd < 0)
{
ERR_MSG("socket");
return -1;
}
printf("socket create success sfd=%d\n", sfd);
//判断套接字文件是否存在 。 若存在则删除
if(access("./myunix", F_OK) == 0)
{
unlink("./myunix");
}
//填充服务器的地址信息结构体,给bind函数使用
//真实的地址信息结构体根据地址族指定,AF_UNIX:man 7 unix
struct sockaddr_un sun;
sun.sun_family = AF_UNIX; //必须填AF_UNIX;
strcpy(sun.sun_path, "./myunix"); //必须事先不存在,由bind函数生成
//绑定地址信息结构体--->bind
if(bind(sfd, (struct sockaddr*)&sun, sizeof(sun)) < 0)
{
ERR_MSG("bind");
return -1;
}
printf("bind success\n");
char buf[128] = "";
struct sockaddr_un cun; //存储发送方的地址信息;
socklen_t addrlen = sizeof(cun);
while(1)
{
bzero(buf, sizeof(buf));
//接收数据
if(recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&cun, &addrlen) < 0)
{
ERR_MSG("recv");
return -1;
}
printf("[%s] : %s\n", cun.sun_path, buf);
//发送数据----->谁发给我,我发还给谁
strcat(buf, "*_*");
if(sendto(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&cun, sizeof(cun)) < 0)
{
ERR_MSG("sendto");
return -1;
}
printf("sendto success\n");
}
//关闭套接字
close(sfd);
return 0;
}
69.4.2 UNIX UDP客户端
c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <sys/un.h>
#define ERR_MSG(msg) do{\
fprintf(stderr, "__%d__:", __LINE__); \
perror(msg);\
}while(0)
int main(int argc, const char *argv[])
{
printf("请输入套接字文件名>>> ");
char sockname[20] = "";
scanf("%s", sockname);
while(getchar()!=10);
//创建报式套接字 socket
int cfd = socket(AF_UNIX, SOCK_DGRAM, 0);
if(cfd < 0)
{
ERR_MSG("socket");
return -1;
}
printf("socket create success cfd=%d\n", cfd);
/*
//绑定客户端的地址信息---》非必须绑定
//若不绑定则操作系统不会自动给客户端绑定套接字文件
//若udp服务器想要给客户端回复信息,则客户端必须绑定套接字文件
*/
//填充客户端的地址信息结构体,给bind函数使用
struct sockaddr_un cun;
cun.sun_family = AF_UNIX;
strcpy(cun.sun_path, sockname);
//绑定地址信息结构体--->bind
if(bind(cfd, (struct sockaddr*)&cun, sizeof(cun)) < 0)
{
ERR_MSG("bind");
return -1;
}
printf("bind success\n");
//填充服务器的地址信息结构体,给sendto函数使用
//真实的地址信息结构体根据地址族指定,AF_UNIX:man 7 unix
struct sockaddr_un sun;
sun.sun_family = AF_UNIX; //必须填AF_UNIX;
strcpy(sun.sun_path, "./myunix"); //服务器绑定的套接字文件
char buf[128] = "";
struct sockaddr_un rcvaddr; //存储发送方的地址信息;
socklen_t addrlen = sizeof(rcvaddr);
while(1)
{
bzero(buf, sizeof(buf));
//发送数据----->从终端获取数据发送给指定端
printf("请输入>>> ");
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf)-1] = 0;
if(strcmp(buf, "quit") == 0)
{
unlink(sockname); //若客户端下线,则删除套接字文件
break;
}
if(sendto(cfd, buf, sizeof(buf), 0, (struct sockaddr*)&sun, sizeof(sun)) < 0)
{
ERR_MSG("sendto");
return -1;
}
printf("sendto success\n");
//接收数据
bzero(buf, sizeof(buf));
if(recvfrom(cfd, buf, sizeof(buf), 0, (struct sockaddr*)&rcvaddr, &addrlen) < 0)
{
ERR_MSG("recv");
return -1;
}
printf("[%s] : %s\n", rcvaddr.sun_path, buf);
}
//关闭套接字
close(cfd);
return 0;
}
七十、 抓包工具--- ---wireshark
70.1 wireshak安装
- 保存虚拟机中代码,因为wireshark安装可能导致计算机重启
- 安装wireshark,
- 可以修改安装路径到自定义路径
- 安装时候看到Desktop Icon选项,请勾选。其余都默认
- 安装完毕后,打开wireshark,若出现未找到接口字眼,说明安装失败。
70.2 wireshark使用
70.2.1 wireshark 抓包
- 开始界面
- wireshark是捕获机器上的某一块网卡的网络包,当你的机器上有多块网卡的时候,你需要选择一个网卡。
- 双击需要的网卡,开始抓包
- Wireshark 窗口介绍
70.2.2 wireshark与对应的OSI七层模型
- 服务器和客户端的代码不能都运行在ubuntu,因为wireshark抓的是流经真实网卡的数据包。
- 若将服务器客户端都运行在ubuntu,数据直接经过虚拟网卡通信,而不会经过真实网卡。
70.3 包头分析
70.3.1 以太网头
以太网中封装了源mac地址以及目的mac地址,还有ip类型,以太网又称之为mac头
0X0800 只接收发往本机的mac的ipv4类型的数据帧
0X0806 只接收发往本机的ARP类型的数据帧
0x8035 只接受发往本机的RARP类型的数据帧
0X0003 接收发往本机的MAC所有类型:ip,arp,rarp数据帧,接收从本机发出去的数据帧,
混杂模式打开的情况下,会接收到非发往本地的MAC数据帧
70.3.2 IP头
IP头中需要掌握用于分帧的部分:id flags fregment_offset
两个IP地址:源IP与目的IP地址。
TTL:time to live, 指定数据帧可以最多经过几个路由器,每经过一个路由器TTL-1。当数据帧被目标方接收后,TTL清除为0.
当TTL为0时,从网络中删除。
Linux TTL:64 Winodws:TTL 128
70.3.3 UDP头
70.3.4 TCP头
- 端口号
- SYN:握手包,连接时候出现
PSH:数据传输包,在传输数据时候出现
FIN:挥手包,在断开连接的时候出现
ACK:应答包,用于应答 非应答包 - Seq:序列号,占4个字节,用于给数据段进行编号的。所有非应答包的数据段,都有seq。
Ack:应答号,用于应答非应答包(握手包,挥手包,数据包)。告诉对方下一次从这个seq编号发送数据包。 - PSH Ack = Seq+len;
SYN FIN Ack = Seq+1;
70.3.5 三次握手(重点!!!)
- 三次握手的发起方,肯定是客户端
- 第一次握手:客户端发送SYN包(SYN=1, seq=0)给服务器,并进入SYN_SENT状态,等待服务器返回确认包。
- 第二次握手:服务器接收到SYN包,确认客户端的SYN,发送ACK包(ACK=1 , ack=1),同时发送一个SYN包(SYN=1, seq=0),并进入SYN_RCVD状态。
- 第三次握手:客户端接收到服务器的SYN包,以及ACK包,进入establish状态,同时向服务器发送ACK包(ACK=1, ack=1)。此时三次握手包发送完毕,服务器也进入establish状态
70.3.6 四次挥手(重点!!!)
- 四次挥手的发起方可能是服务器,也可能是客户端
- 第一次挥手,主动关闭方发送一个FIN包(FIN=1, seq = u)给被动方,进入FIN_WAIT_1状态;
- 第二次挥手:被动方接收到FIN包,给主动方发送一个ACK包(ACK=1, ack=u+1);并进入CLOKSE_WAIT状态。主动方接受到ACK包后,进入FIN_WAIT_2状态。如果有数据没有发送完毕,则继续发送,直到发送完毕为止;
- 第三次挥手:被动方发送一个FIN包(FIN=1, seq=w),进入LAST_ACK状态.
- 第四次挥手:主动关闭方收到FIN包,回复一个ACK包(ACK=1, ack=w+1)。被动关闭方收到主动关闭方的ACK后关闭连接。
笔试面试题型
- 这几个问题问的都是同一个:三次握手四次挥手
c
1. 三次握手四次挥手流程。
2. 请简述TCP建立连接断开连接的过程。(三次握手,四次挥手)
3. 请简述TCP和UDP通信过程中的区别?(三次握手,四次挥手 ,有无应答)
4. 请简述如何用UDP模型实现TCP式传输?(三次握手,四次挥手 ,有无应答)