socket網絡通信
本文注意基于socket來分析TCP連接配接建立過程。
先回顧一下TCP連接配接建立過程:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIyVGduV2QvwVe0lmdhJ3ZvwFM38CXlZHbvN3cpR2Lc1TPB10QGtWUCpEMJ9CXsxWam9CXwADNvwVZ6l2c052bm9CXUJDT1wkNhVzLcRnbvZ2Lc1DOXl1bKhlW1Z0VlZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39TM5MDOwATM3EDOwMDM3EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
主機A運作的是TCP用戶端程式,主機B運作的是TCP伺服器程式,最初兩端TCP程序處于Closed态,A主動打開連接配接,對應用戶端connect函數發起連接配接,B被動接受連接配接,對應于伺服器listen函數。
伺服器TCP程序先建立傳輸控制塊TCB,準備接受客戶程序的連接配接請求;
1 用戶端程序也首先建立TCB,然後通過connect函數向伺服器發送連接配接請求,同步位SYN=1(不攜帶資料),初始序列号seq=x,用戶端進入SYN-SENT态;
2 伺服器一直通過listen監控socket,接收到連接配接請求後,如同意連接配接,向用戶端發射确認,ACK=1,SYN=1,ack=x+1,seq=y;伺服器端進入SYN-RCVD;
3 用戶端程序收到确認後,這時connect函數傳回,對伺服器程序再确認,ACK=1,seq=x+1,ack=y+1,用戶端進入連接配接态ESTABLISHED 可讀寫;伺服器收到再次确認後也進入ESTABLISHED 可讀寫。
對應socket可總結如下:
總結:用戶端的connect在三次握手的第二次傳回,而伺服器端的accept在第三次握手的第三次傳回。
TCP連接配接釋放過程:
TCP程序雙方都處于ESTABLISHED态進行讀寫通信,如果要釋放連接配接:
1 用戶端程序向伺服器發出釋放連接配接封包段,FIN=1,seq=u,并停止發送資料,此時用戶端進入FIN-WAIT-1;
2 伺服器收到釋放封包請求後,發出确認ACK=1,ack=u+1,seq=v,然後進入CLOSED-WAIT态,用戶端收到确認消息後進入FIN-WAIT-2态,并等待伺服器發出連接配接釋放封包;
3 伺服器發出連接配接釋放請求,FIN=1,ACK=1,ack=u+1,seq=w,并進入LAST-ACK;
4 用戶端收到伺服器釋放連接配接請求後,發出确認,ACK=1,seq=u+1,ack=w+1,然後進入TIME-WAIT狀态,此時還需在等2MSL(最長封包段壽命)後再進入CLOSED。B收到确認消息後立刻進入CLOSED。
對應socket可總結如下:
某個應用程序首先調用close主動關閉連接配接,這時TCP發送一個FIN M;
另一端接收到FIN M之後,執行被動關閉,對這個FIN進行确認。它的接收也作為檔案結束符傳遞給應用程序,因為FIN的接收意味着應用程序在相應的連接配接上再也接收不到額外資料;一段時間之後,接收到檔案結束符的應用程序調用close關閉它的socket。這導緻它的TCP也發送一個FIN N;接收到這個FIN的源發送端TCP對它進行确認。這樣每個方向上都有一個FIN和ACK。
最後,通過例程實踐,例程内容:
下面編寫一個簡單的伺服器、用戶端(使用TCP)——伺服器端一直監聽本機的6666号端口,如果收到連接配接請求,将接收請求并接收用戶端發來的消息;用戶端與伺服器端建立連接配接并發送一條消息
伺服器端代碼:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#define MAXLINE 4096
int main(int argc, char** argv)
{
int listenfd, connfd;
struct sockaddr_in servaddr;
char buff[4096];
int n;
if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){
printf("create socket error: %s(errno: %d)\n",strerror(errno),errno);
exit(0);
}//調用socket函數
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//注意網絡位元組序與本機位元組序
servaddr.sin_port = htons(6666);
if( bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){
printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno);
exit(0);
}//将socket綁定IP和端口
if( listen(listenfd, 10) == -1){
printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno);
exit(0);
}//listen 該端口下的socket,輸入隊列額度10
printf("======waiting for client's request======\n");
while(1){
if( (connfd = accept(listenfd, (struct sockaddr*)NULL, NULL)) == -1){
printf("accept socket error: %s(errno: %d)",strerror(errno),errno);
continue;
}//accept來自用戶端程序,注意此時産生一個已建立socket描述符confd
n = recv(connfd, buff, MAXLINE, 0);//讀入到buff
buff[n] = '\0';
printf("recv msg from client: %s\n", buff);
close(connfd);//結束
}
close(listenfd);
}
用戶端代碼:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#define MAXLINE 4096
int main(int argc, char** argv)
{
int sockfd, n;
char recvline[4096], sendline[4096];
struct sockaddr_in servaddr;
if( argc != 2){
printf("usage: ./client <ipaddress>\n");
exit(0);
}
if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
printf("create socket error: %s(errno: %d)\n", strerror(errno),errno);
exit(0);
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(6666);
if( inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0){
printf("inet_pton error for %s\n",argv[1]);
exit(0);
}
if( connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){
printf("connect error: %s(errno: %d)\n",strerror(errno),errno);
exit(0);
}//發送請求
printf("send msg to server: \n");
fgets(sendline, 4096, stdin);
if( send(sockfd, sendline, strlen(sendline), 0) < 0)
{
printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
exit(0);
}//send函數,發送sendline裡内容
close(sockfd);
exit(0);
}
這是一個最基本socket通信例程,上面的伺服器使用的是疊代模式的,即隻有處理完一個用戶端請求才會去處理下一個用戶端的請求,這樣的伺服器處理能力是很弱的,現實中的伺服器都需要有并發處理。