Linux學習之網路程式設計(TCP三次握手四次揮手)
阿新 • • 發佈:2018-11-15
言之者無罪,聞之者足以戒。 - “詩序”
1、三次握手:
看一下三次握手的框圖:
(1)、伺服器必須準備好接受外來連線
(2)、客戶端呼叫connect來主動開啟一個連線,此時客戶端TCP將會發送一個SYN分節
(3)、伺服器必須確認客戶的SYN
(4)、客戶必須確認伺服器的SYN
下面我們看一下wireshark
對照著這張圖我們就可以理解上面的框圖中,三次握手的概念了。
下面我們來看一下server的程式:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <unistd.h> #include <fcntl.h> #define MAX_LISTEN_QUE 5 int main(int argc,char *argv[]) { int listenfd,sockfd,opt=1; struct sockaddr_in server,client; char buf[200]; socklen_t len; int timep; int ret; //建立套接字 listenfd = socket(AF_INET,SOCK_STREAM,0); if(listenfd < 0) { perror("Create socket fail"); return -1; } //設定套結字關聯的選項 if((ret = setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) < 0) { perror("Error ,set socket reuse addr failed"); return -1; } //清空結構體中的變數值 bzero(&server, sizeof(server)); //初始化結構體的變數 server.sin_family = AF_INET;//IPv4 server.sin_port = htons(8888);//主機轉換到網路 server.sin_addr.s_addr = htonl(INADDR_ANY);//同上(使用這個巨集套接字可以繫結到所有的 埠) //獲取結構體地址長度 len = sizeof(struct sockaddr); //繫結伺服器ip地址和埠到套接字 if(bind(listenfd, (struct sockaddr *)&server, len)<0) { perror("bind error."); return -1; } //設定伺服器的最大連線數 listen(listenfd, MAX_LISTEN_QUE); while(1) { //等待客戶端請求,如果請求到來,返回一個新的socket //伺服器和客戶端利用新的socket來通訊 sockfd = accept(listenfd, (struct sockaddr *)&client, &len); if(sockfd < 0) { perror("accept error."); return -1; } } sleep(10); //顯示系統的當前時間 timep = time(NULL); //將資料按照一定的格式轉換之後複製到buf //ctime返回一個表示當地時間的字串 snprintf(buf, sizeof(buf), "%s", ctime(&timep)); //向套接字中寫入buf儲存的時間 write(sockfd, buf, strlen(buf)); //列印儲存的位元組數 printf("Bytes:%d\n", strlen(buf)); //列印套接字的檔案描述符 printf("sockfd=%d\n", sockfd); printf("IP:0x%x, Port:%d\n",ntohl(client.sin_addr.s_addr),ntohs(client.sin_port)); sleep(10); //關閉套接字 //close(sockfd); return 0; }
在執行程式進行抓包的時候,sever函式我們是在:Linux系統下先執行,在通過windows系統下的命令提示符中連線sever。
備註:在windows系統下的搜尋中輸入:cmd就可以看到命令提示符程式
開啟之後輸入:telnet 192.168.177.133 8888就可以連線sever了(192.168.177.133是我的Linux的伺服器地址,8888是我設定的 埠號)。
幫助:如果你輸入上述命令之後提示:telnet不是內部或外部命令也不是可執行的程式或批處理,解決辦法可以參考下面的文章
https://blog.csdn.net/haijing1995/article/details/66475546
注意:在啟動客戶端和伺服器端的時候,要先啟動伺服器端之後再啟動客戶端,然後進行抓包。使用windows下的命令提示符一旦啟動程式是不會自己退出的。需要我們強制退出
2、四次揮手:
看一下四次揮手的框圖:
這裡我們還是先用上面所用到的命令提示符來用wireshark來抓包:
對照著這張圖我們就可以理解上面的框圖中,四次揮手的概念了。
下面我們在Linux中重複操作一下上面的過程:
先貼出server的程式:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <unistd.h> #include <fcntl.h> #define MAX_LISTEN_QUE 5 int main(int argc,char *argv[]) { int listenfd,sockfd,opt=1; struct sockaddr_in server,client; char buf[200],read_buf[100]; int bytes=0; socklen_t len; int timep; int ret; //建立套接字 listenfd = socket(AF_INET,SOCK_STREAM,0); if(listenfd < 0) { perror("Create socket fail"); return -1; } //設定套結字關聯的選項 if((ret = setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) < 0) { perror("Error ,set socket reuse addr failed"); return -1; } //清空結構體中的變數值 bzero(&server, sizeof(server)); //初始化結構體的變數 server.sin_family = AF_INET;//IPv4 server.sin_port = htons(8888);//主機轉換到網路 server.sin_addr.s_addr = htonl(INADDR_ANY);//同上(使用這個巨集套接字可以繫結到所有的 埠) //獲取結構體地址長度 len = sizeof(struct sockaddr); //繫結伺服器ip地址和埠到套接字 if(bind(listenfd, (struct sockaddr *)&server, len)<0) { perror("bind error."); return -1; } //設定伺服器的最大連線數 listen(listenfd, MAX_LISTEN_QUE); while(1) { //等待客戶端請求,如果請求到來,返回一個新的socket //伺服器和客戶端利用新的socket來通訊 sockfd = accept(listenfd, (struct sockaddr *)&client, &len); if(sockfd < 0) { perror("accept error."); return -1; } //顯示系統的當前時間 timep = time(NULL); //將資料按照一定的格式轉換之後複製到buf //ctime返回一個表示當地時間的字串 snprintf(buf, sizeof(buf), "%s", ctime(&timep)); //向套接字中寫入buf儲存的時間 write(sockfd, buf, strlen(buf)); //列印儲存的位元組數 printf("Bytes:%d\n", strlen(buf)); //列印套接字的檔案描述符 printf("sockfd=%d\n", sockfd); printf("IP:0x%x, Port:%d\n",ntohl(client.sin_addr.s_addr),ntohs(client.sin_port)); bytes = read(sockfd,read_buf,100); if(bytes < 0) { printf("read err\n"); return -1; } if(bytes == 0) { printf("client connection closed\n"); return 0; } // sleep(10); //關閉套接字 close(sockfd); } return 0; }
接下來看一下client的程式:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc,char *argv[])
{
int sockfd;
struct sockaddr_in servaddr;
char buf[100];//儲存讀取的內容
int bytes;//儲存讀取的位元組數
if((sockfd = socket(AF_INET,SOCK_STREAM,0)) < 0)//建立套接字
{
printf("socket error\n");
return -1;
}
//結構體中成員變數初始化為0
bzero(&servaddr,sizeof(servaddr));
//初始化成員變數
//初始化成員變數
servaddr.sin_family = AF_INET;//IPv4
servaddr.sin_addr.s_addr = inet_addr("192.168.177.133");//轉換為地址格式
servaddr.sin_port = htons(8888);//主機序轉換到網路序
if(connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) < 0)//繫結伺服器
ip和埠到套結字
{
perror("connect error");
return -2;
}
//儲存讀取的資料和讀取的位元組數
bytes == read(sockfd,buf,100);
if(bytes < 0)
{
printf("Error ,read failed\n");
return -3;
}
//如果讀取的位元組數為0 ,就說明連線已經關閉了
if(0 == bytes)
{
printf("Server close connection\n");
return -4;
}
//列印讀取的位元組數和讀取的內容
printf("read bytes %d\n",bytes);
printf("Time: %s\n",buf);
//關閉套結字
close(sockfd);
return 0;
}
之後看一下程式的執行結果: