基於TCP協議實現Linux下客戶端與伺服器之間的通訊,實現多執行緒、多程序伺服器
TCP是TCP/IP協議族中一個比較重要的協議,這是一種可靠、建立連結、面向位元組流的傳輸,工作在傳輸層。和TCP相對的不可靠、無連結、面向資料報的協議UDP,瞭解UDP客戶端與伺服器之間通訊請戳UDP協議實現的伺服器與客戶端通訊
TCP協議建立連線
首先我們通過一個大概的圖來了解。
建立連線首先必須是伺服器啟動,這沒什麼好說的,伺服器為被動方,客戶端為主動方,當客戶端發起請求建立連線,伺服器被動接受,經過上圖三次握手建立連線,注意這三次連線都是在作業系統內部實現的。
那麼我們就來介紹建立連線的相關API
socket獲取通訊的檔案描述符。
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
埠號的繫結
#include <sys/socket.h>
int bind(int sock, const struct sockaddr* address, socklen_t address_len);
作為伺服器首先要進行監聽
#include <sys/socket.h>
int listen(int socket, int backog);
引數介紹:
socket: 為socket函式返回的檔案描述符
backlog: 建立連線過程中等待建立的請求個數
返回值:
成功返回0,失敗返回-1;
這裡我們再介紹這個backlog引數的含義:
這就相當與我們去銀行取錢,到了發現人比較多,這個時候就需要坐在凳子上等,那麼這裡的凳子就是backlog的含義,就是現在最大的等待處理的個數。
接受請求:
#include <sys.socket.h>
int accept(int socket, struct sockaddr* address, socklen_t* address_len);
引數:
socket:檔案描述符
address:輸出型引數,用來接受對方的IP 埠號,是一個結構體。
address_len:是一個輸入輸出型引數,輸入進去為當前address的大小,輸出為實際的大小。
返回值:
返回值成功為一個檔案描述符,失敗為-1;
這裡要解釋一下,socket不是已經創建出一個檔案描述符,怎麼還有?
accept的檔案描述符,使用來直接進行資料的傳送與收取,處理請求。
前面socket建立的檔案描述符,在listen函式中用,是為了來接受請求.
舉個例子:
socket建立的檔案描述符,就像一個飯店,拉客人(listen函式)就是在外面進行監聽,是否有客人要來飯店,當listen接受到客人,就會帶到飯店,然後交給服務員(accept)來進行對客人吃飯請求的處理。這時候accpet就會是這個客人的請求的處理。
客戶端連線函式:
#include <sys/socket.h>
int connect(int socketfd, const struct sockaddr* addr, socklen_t addrlen);
引數:
socketfd: 檔案描述符;
addr :為要建立連線伺服器的IP、埠號、使用網路協議IPV4;
addrlen:addr結構體的大小;
引數:
建立成功返回0,失敗返回-1;
相關API介紹完我們開始實現一個簡單的客戶端與伺服器
我們實現最簡單的沒有業務處理邏輯的通訊,就把接受到的訊息,傳送給客戶端
伺服器
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
void Response(int new_sock, struct sockaddr_in * client)
{
while (1)
{
char buf[512] = {0};
// 接受請求
ssize_t rd = read(new_sock, buf, sizeof(buf)-1);
if (rd < 0)
{
perror("read error\n");
return;
}
if (rd == 0)
{
printf("read done!\n");
return;
}
buf[rd] = '\0';
// 處理請求
printf("client say [%s:%d] # %s\n",inet_ntoa(client->sin_addr), ntohs(client->sin_port), buf);
// response 響應
write(new_sock, buf, strlen(buf));
}
}
int main(int argc, char* argv[])
{
if (argc != 3)
{
printf("usage:./server_tcp [ip] [port]");
return 1;
}
int sock = socket(AF_INET, SOCK_STREAM, 0); // 檔案描述符
if (sock < 0)
{
perror("sock error\n");
return 2;
}
// 繫結埠號
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(argv[1]);
server.sin_port = htons(atoi(argv[2]));
int b = bind(sock, (struct sockaddr*)&server, sizeof(server));
if (b < 0)
{
perror("bind error\n");
return 3;
}
// 監聽
int l = listen(sock, 3); // 最大的連線數量
if (l < 0)
{
perror("listen error\n");
return 4;
}
printf("bind and listen .... \n");
// 接受請求處理
struct sockaddr_in client;
socklen_t len = sizeof(client);
int new_sock = accept(sock, (struct sockaddr*)&client, &len);
Response(new_sock, &client); // 處理函式
return 0;
}
客戶端:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
int main(int argc, char* argv[])
{
if (argc != 3)
{
perror("usage:./client_tcp [ip] [port]");
return 1;
}
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0)
{
perror("scoket error\n");
return 2;
}
// 建立連線
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(argv[1]);
server.sin_port = htons(atoi(argv[2]));
socklen_t len = sizeof(server);
int co = connect(sock, (struct sockaddr*)&server, len);
if (co < 0)
{
perror("connect error\n");
return 3;
}
while (1)
{
char buf[512] = {0};
ssize_t rd = read(0, buf, sizeof(buf)-1);
if (rd < 0)
{
perror("read error\n");
return 4;
}
if (rd == 0)
{
printf("read done!\n");
return 5;
}
buf[rd-1] = '\0';
write(sock, buf, strlen(buf)); // 會阻塞的寫與讀
ssize_t sockrd = read(sock, buf, sizeof(buf)-1);
if (sockrd < 0)
{
perror("read error\n");
return 4;
}
if (sockrd == 0)
{
printf("read done!\n");
return 5;
}
buf[rd] = '\0';
printf("server say # %s\n", buf);
}
return 0;
}
結果:
客戶端
伺服器
TCP協議的的斷開連線
建立連線需要三次互動,斷開連線需要四次互動。
我們用圖分析一下
當資料傳送完客戶端呼叫close 這時候就會發送 FIN 伺服器核心立刻恢復ACK 伺服器再呼叫close 傳送FIN 客戶端回覆ACK 進入等待。一段時間後退出。