傳統的TCP/IP通信過程依賴于socket,位于應用層和傳輸層之間,使得應用程式可以進行通信。相當于港口城市的碼頭,使得城市之間可以進行貨物流通。伺服器和用戶端各有不同的通信流程。
一、伺服器
1、建立連接配接階段
調用socket(),配置設定檔案描述符,即監聽套接字
調用bind(),将套接字與本地IP位址和端口綁定
調用listen(),監聽特定端口,socket()建立的套接字是主動的,調用listen使得該檔案描述符為監聽套接字,變主動為被動
調用accept(),阻塞等待用戶端連接配接
2、資料互動階段
調用read(),阻塞等待用戶端發送的請求,收到請求後從read()傳回,處理用戶端請求
調用write(),将處理結果發送給用戶端,然後繼續調用read()等待用戶端請求
3、關閉連接配接
當read()傳回0的時候,說明用戶端發來了FIN資料包,即關閉連接配接,也會調用close()關閉連接配接套接字和監聽套接字
二、用戶端
1、建立連接配接階段
調用socket(),配置設定檔案描述符
調用connect(),向伺服器發送建立連接配接請求
2、資料互動階段
調用write(),将請求發送給伺服器
調用read(),阻塞等待伺服器應答
3、關閉連接配接
當沒有資料發送的時候,調用close()關閉連接配接套接字,即關閉連接配接,向伺服器發送FIN資料報
三、TCP通信過程

對tcp/ip網絡協定棧不熟悉的朋友可以看看這個視訊:手把手帶大家實作一個tcp/ip網絡協定棧
四、C語言代碼
伺服器端
/* File name: server.c*/
//伺服器端
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#define MAXLINE 4096
#define PORT 8000
int main(void){
//定義伺服器監聽套接字和連接配接套接字
int listen_fd = -1, connect_fd = -1;//初始化為-1
struct sockaddr_in servaddr;//定義伺服器對應的套接字位址
//伺服器接收和發送緩沖區
char sendbuf[MAXLINE], recbuf[MAXLINE];
//初始化套接字位址結構體
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;//IPv4
servaddr.sin_port = htons(PORT);//設定監聽端口
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//表示接收任意IP的連接配接請求
//建立套接字
if((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
//如果建立套接字失敗,傳回錯誤資訊
//strerror(int errnum)擷取錯誤的描述字元串
printf("create socket error: %s(error: %d)n", strerror(errno), errno);
exit(0);
}
//綁定套接字和本地IP位址和端口
if(bind(listen_fd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){
//綁定出現錯誤
printf("bind socket error: %s(error: %d)n", strerror(errno), errno);
exit(0);
}
//使得listen_fd變成監聽描述符
if(listen(listen_fd, 10) == -1){
printf("listen socket error: %s(error: %d)n", strerror(errno), errno);
exit(0);
}
//accept阻塞等待用戶端請求
printf("等待用戶端發起連接配接n");
while(1){
if((connect_fd = accept(listen_fd, (struct sockaddr*)NULL, NULL)) == -1){
printf("accept socket error: %s(error: %d)n", strerror(errno), errno);
continue;
}
//可以一直保持連接配接
while(1){
//讀取用戶端發來的資訊
ssize_t len = read(connect_fd, recbuf, sizeof(recbuf));
if(len < 0){
if(errno == EINTR){
continue;
}
exit(0);
}
printf("接收用戶端的請求:%sn", recbuf);
//向用戶端發送資訊
printf("回複用戶端資訊:");
fgets(sendbuf, sizeof(sendbuf), stdin);
write(connect_fd, sendbuf, sizeof(sendbuf));
}
//關閉連接配接套接字
close(connect_fd);
}
//關閉監聽套接字
close(listen_fd);
}
用戶端
/* File name: client.c */
//用戶端
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#define MAXLINE 4096
#define PORT 8000
int main(void){
//定義用戶端套接字
int sockfd = -1;
//定義想連接配接的伺服器的套接字位址
struct sockaddr_in servaddr;
//發送和接收資料的緩沖區
char sendbuf[MAXLINE], recbuf[MAXLINE];
//初始化伺服器套接字位址
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;//IPv4
servaddr.sin_port = htons(PORT);//想連接配接的伺服器的端口
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");//伺服器的IP位址
//建立套接字
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
printf("create socket error: %s(error: %d)n", strerror(errno), errno);
exit(0);
}
//向伺服器發送連接配接請求
if(connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){
//連接配接失敗
printf("connect socket error: %s(error: %d)n", strerror(errno), errno);
exit(0);
}
while(1){
//向伺服器發送資訊
printf("向伺服器發送資訊:");
fgets(sendbuf, sizeof(sendbuf), stdin);
write(sockfd, sendbuf, sizeof(sendbuf));
//從伺服器接收資訊
ssize_t len = read(sockfd, recbuf, sizeof(recbuf));
if(len < 0){
if(errno == EINTR){
continue;
}
exit(0);
}
printf("伺服器回應:%sn", recbuf);
}
//關閉套接字
close(sockfd);
}
需要C/C++ Linux伺服器架構師學習資料加qun擷取(資料包括C/C++,Linux,golang技術,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協程,DPDK,ffmpeg等),免費分享