为了抓包方便一些,我在ubuntu虚拟机运行服务端程序,而在windows运行客户端程序,关于客户端与服务端程序如下。
##1.程序
客户端:
vs_client.cpp
#include "stdafx.h"
#include
#include
#pragma comment(lib,"ws2_32.lib")
using namespace std;
int main(){
WSADATA wsaData;
if(WSAStartup(MAKEWORD(2,2),&wsaData)){
cout<<"WinSock不能被初始化";
WSACleanup();
return 0;
}
SOCKET sockCli;
sockCli=socket(AF_INET,SOCK_STREAM,0);
SOCKADDR_IN addrSer;
addrSer.sin_family=AF_INET;
addrSer.sin_port=htons(8899);
addrSer.sin_addr.S_un.S_addr=inet_addr("192.168.6.139");
int res=connect(sockCli,(SOCKADDR *)&addrSer,sizeof(SOCKADDR));
if(res){
cout<<"客户端连接服务器失败"<<endl;
return -1;
}else{
cout<<"客户端连接服务器成功"<<endl;
}
//3.向服务端发送消息
char send_buf[256] = "hello server===>>>";
char recv_buf[512];
send(sockCli,send_buf,sizeof(send_buf),0);
//4.接收服务端发来的消息
int len = recv(sockCli,recv_buf,sizeof(recv_buf)-1,0);
recv_buf[len] = '\0';
printf("收到服务端的返回:%s\n",recv_buf);
Sleep(100);
closesocket(sockCli);
WSACleanup();
while(1)
{
Sleep(100);
}
return 0;
}
服务端:
sever.c
#include
#include
#include
#include
#include
#include
int main()
{
//1.创建一个socket文件,也就是打开一个网络通讯端口
int serv_sock = socket(AF_INET, SOCK_STREAM,0);
//2.绑定服务器ip和端口到这个socket
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));//先清空一下初始的值,写上地址和端口号
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr("192.168.6.139");//本机ip
serv_addr.sin_port = htons(8899);//随意选了一个端口8899
bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
//3.将socket设置为监听状态
listen(serv_sock,128);//设置最大连接数为128
//4.准备接收客户端的请求连接,这里的步骤可以重复进行,接收多个客户端的请求
while(1){
//接收客户端的请求连接后,返回一个新的socket(clnt_sock)用于和对应的客户端进行通信
struct sockaddr_in clnt_addr;
socklen_t clnt_addr_size = sizeof(clnt_addr);
int clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
//5.读取客户端发送来的数据
char recv_buf[256];
char send_buf[512]="hello client";
int len = read(clnt_sock,recv_buf,sizeof(recv_buf)-1);
recv_buf[len] = '\0';//字符串以“\0”结尾
//6.打印出客户端发来的消息
printf("recv client:%s\n",recv_buf);
write(clnt_sock,send_buf,sizeof(send_buf));
sleep(3);
//8.关闭客户端连接
close(clnt_sock);
sleep(2);
break;
}
//9.关闭服务端监听的socket
close(serv_sock);
return 0;
}
在Ubuntu上,我们可以通过ifconfig命令,查看到系统的IP地址。
$ ifconfig
ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.6.139 netmask 255.255.255.0 broadcast 192.168.6.255
inet6 fe80::8bc1:6df8:a7f2:95c5 prefixlen 64 scopeid 0x20<link>
ether 00:0c:29:4a:43:e6 txqueuelen 1000 (Ethernet)
RX packets 11886 bytes 3135825 (3.1 MB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 13969 bytes 11893886 (11.8 MB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
##2.抓包
关于windows平台的抓包,这里不描述,本次测试抓的是服务端的包。
首先在线安装抓包软件:我们用是是tcpdump,此软件可以在网上找到开源代码,假如用于嵌入式环境抓包,大家可以自己在网上下载源码后交叉编译
sudo apt-get install tcpdump
运行下面命令持续抓包,每一个包抓60秒。 可以自己调节-G后面参数设置每一个包的抓包时长:
sudo tcpdump -i ens33 -s0 -G 60 -w %Y_%m%d_%H%M_%S.pcap
得到抓包图如下
##3.根据抓包分析Tcp
这里通过分析Ack和Seq号,让大家了解Tcp为什么是可靠传输
Seq(Sequence Number):
32bits,表示这个tcp包的序列号。tcp协议拼凑接收到的数据包时,根据seq来确定顺序,并且能够确定是否有数据包丢失。
Ack(Acknowledgment Number):
32bits,表示这个包的确认号。首先意味着已经收到对方了多少字节数据,其次告诉对方接下来的包的seq要从ack确定的数值继续接力
len:表示tcp携带的数据长度,不包括tcp头部信息的长度
下面根据上面抓包来说明:关于包的序号,我在抓包图中已标明
(1)三次握手
(客户端)1号包:我能和你建立连接
seq=0,表客户端第一条数据
没有ack,因为前面没有收到数据,所以不用确认已收到
Len=0。
(服务端)2号包:我收到了请求
seq=0,表此连接中,服务端第一条数据
ack=1 表示收到了客户端的seq=0的连接请求,告诉客户端接下来请从seq=1开始传输数据
Len=0,没有负载数据。
(客户端)3号包:连接建立。
seq=1,响应2号包
ack=1,收到服务器seq=0同意连接,告诉服务端从seq=1传输数据
Len=0
(2)数据传输
(客户端) 4号包:传256字节给服务端
seq=1,上次没有传输数据,seq号不变,也就是3号包的seq=1,len=0
ack=1,告诉服务端你要是发送数据,从seq=1开始
len=256,表示我这次传输的数据字节数
(服务端)5号包:响应。
seq=1,4号包的ack所要求的
ack=257,ack=(4号包的seq)+(4号包的len) = 1+256=257;下次客户端seq=257开始
len=0
(服务端)6号包:发512字节数据给客户端
5、6号均为服务端发送的包,在这期间没有接收到包,所以,5、6号包的seq、ack是一样的。
seq=1
ack=257
len=512,数据的长度
(客户端)7号包:响应
seq=257,5号包让从这个序列发
ack=513,ack=(6号包的seq)+(6号包的len)=1+512=513
len=0
(3)总结一下
3次握手的过程:
(1).起始包的seq都等于0
(2).三次握手中的ack=对方上一个的seq+1
(3).seq等于对方上次的ack号
数据发送过程:
(1).发送方的包包括seq和len,接收方通过ack=发送方的seq+发送方的len。
(2).三次握手时,客户端、服务端握手时,len=0,对方ack=seq+1。
而数据传输过程时,len=0,对方ack=seq+0