學習網絡編程最主要的是能理解底層編程細節,一開始看《UNIX網絡編程卷1:套接字聯網API》的時候搞不懂什麼seq、ack到底是什麼東西,最近了解了tcpdump的一些用法後感覺兩者結合起來還是比較容易理握手過程的。以下就通過tcpdump工具來監控相關內容,並和書本上的流程進行對比介紹,希望對入門的童靴有些幫助吧
服務端代碼如下:
#include <sys/socket.h> //socket listen bind
#include <arpa/inet.h> // sockaddr head
#include <string.h> //memset and strlen head
#include <sys/socket.h> //socket listen bind
#include <iostream>
#include <time.h>
#include <stdio.h>
#define MAXLINE 4096
#define LISTENQ 1024
using namespace std;
int main(int argc, char ** argv)
{
int listenfd, connfd;
socklen_t len;
struct sockaddr_in servaddr, cliaddr;
char buff[MAXLINE];
time_t ticks;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(10000);
bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
int ret = listen(listenfd,LISTENQ);
cout << "go to listen" <<ret<< endl;
for(; ;)
{
len=sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &len);
cout << "connfd = " << connfd << inet_ntop(AF_INET, &cliaddr.sin_addr, buff, sizeof(buff)) << endl;
sleep(20);
ticks = time(NULL);
snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
write(connfd, buff, strlen(buff));
cout << "write date ok" << endl;
sleep(20);
close(connfd);
}
return 0;
}
客戶端代碼如下:
#include <string.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <iostream>
#include <stdio.h>
#include <errno.h>
using namespace std;
#define MAXLINE 4096 /* max text line length */
int main(int argc, char ** argv)
{
int sockfd, n;
char recvline[MAXLINE+1];
struct sockaddr_in serveraddr;
if(argc != 2)
{
cout << "para error " << endl;
return 0;
}
if((sockfd=socket(AF_INET, SOCK_STREAM, 0))<0)
{
cout << "socket error" << endl;
return 0;
}
memset(&serveraddr,0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(10000);
if(inet_pton(AF_INET, argv[1], &serveraddr.sin_addr)<=0)
{
cout << "inet_pton error for " << argv[1] << endl;
return 0;
}
int tmp = connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
if(tmp <0)
{
cout << "connect error" << tmp << endl;
cout << "error info " << errno << endl;
return 0;
}
while((n=read(sockfd, recvline, MAXLINE)) > 0)
{
recvline[n] = 0;
if(fputs(recvline, stdout) == EOF)
{
cout << "fputs error" << endl;
}
}
close(sockfd);
if(n<0)
{
cout << "read error" << endl;
}
return 1;
}
先在192.168.11.220上運行服務端程序,然後在192.168.11.223上運行客戶端程序。同時在兩個服務器上以root用戶執行tcpdump工具,監控10000端口
tcpdump命令如下:
tcpdump 'port 10000' -i eth0 -S
建立連接時服務端220的監控內容如下:
14:52:19.772673 IP 192.168.11.223.55081 > npsc-220.ndmp: Flags [S], seq 1925249825, win 14600, options [mss 1460,sackOK,TS val 11741993 ecr 0,nop,wscale 6], length 0
14:52:19.772695 IP npsc-220.ndmp > 192.168.11.223.55081: Flags [S.], seq 821610649, ack 1925249826, win 14480, options [mss 1460,sackOK,TS val 20292985 ecr 11741993,nop,wscale 7], length 0
14:52:19.773256 IP 192.168.11.223.55081 > npsc-220.ndmp: Flags [.], ack 821610650, win 229, options [nop,nop,TS val 11741994 ecr 20292985], length 0
下面結合上圖和下面三次握手示意圖,解釋握手細節:
第一行顯示客戶端192.168.11.223先發送一個seq,1925249825給服務端,對應下面三次握手示意圖中的SYN J
第二行顯示服務端192.168.11.220(npsc-220)確認第一行的請求:seq 1925249825, ack的值爲第一行的seq值+1,即(ack 1925249826),同時發送一個請求序列號821610649。對應下圖三次握手中的(SYN K, ACK J+1)
第三行顯示客戶端192.168.11.223確認服務端的請求序號(第二行中的seq 821610649),對應下圖tcp三路握手中的 (ACK K+1)
下圖顯示了傳遞一次數據的通信過程
14:52:39.773434 IP npsc-220.ndmp > 192.168.11.223.55081: Flags [P.], seq 821610650:821610676, ack 1925249826, win 114, options [nop,nop,TS val 20312985 ecr 11741994], length 26
14:52:39.774208 IP 192.168.11.223.55081 > npsc-220.ndmp: Flags [.], ack 821610676, win 229, options [nop,nop,TS val 11761994 ecr 20312985], length 0
由於代碼中服務端建立連接後直接給客戶端發送本地時間的數據給客戶端,所以上面第一行中的信息可以看出由npsc-220(服務端)發送數據給客戶端192.168.11.223,請求序號爲:821610650:821610676 共26個字節,
第二行表示客戶端223收到數據後給服務端的發送了一個ack的確認信息
下圖顯示了斷開連接的通信過程(其中客戶端代表被動斷開的一端,服務端代表主動斷開的一端)
14:52:59.773523 IP npsc-220.ndmp > 192.168.11.223.55081: Flags [F.], seq 821610676, ack 1925249826, win 114, options [nop,nop,TS val 20332985 ecr 11761994], length 0
14:52:59.774342 IP 192.168.11.223.55081 > npsc-220.ndmp: Flags [F.], seq 1925249826, ack 821610677, win 229, options [nop,nop,TS val 11781994 ecr 20332985], length 0
14:52:59.774351 IP npsc-220.ndmp > 192.168.11.223.55081: Flags [.], ack 1925249827, win 114, options [nop,nop,TS val 20332986 ecr 11781994], length 0
由於我的程序是由服務端主動關閉連接,所以和下圖的四次握手示意圖稍微有些差別
第一行顯示:服務端(npsc-220)主動發送了一個FIN給客戶端192.168.11.223,對應下圖的FIN M ,其中M的值爲821610676
第二行顯示:客戶端192.168.11.223確認了服務端的821610676(即下圖的ACK M+1, 即ack 821610677),併發送了一個FIN給服務端,對應下圖的FIN N
第三行顯示:服務端(npsc-220)確認了客戶端的FIN ack 19252429827,即下圖的ACK N+1 ,
下圖是斷開連接的四次握手示意圖