手把手寫C++服務器(24):socket響應一般框架、TCP修改緩沖區、內核監聽listen最大長度

沉迷單車的追風少年 2021-08-15 23:20:38 阅读数:936

本文一共[544]字,预计阅读时长:1分钟~
手把手 手把 把手 c++ socket

本系列文章導航: 手把手寫C++服務器(0):專欄文章-匯總導航【更新中】 

前言:本系列文章手把手寫C++服務器(15):網絡編程入門第一個TCP項目以封裝好的網絡庫為例,重點講解了如何正確的建立TCP連接,如何正確地銷毀TCP連接,如何在安全的時機關閉連接,如何處理丟包問題。本文在上一篇文章手把手寫C++服務器(21):Linux socket網絡編程入門基礎的基礎上,從原生的socket角度出發,進一步深入玩轉TCP編程。

目錄

通過socket監聽來自用戶的請求——socket響應一般框架

內核監聽listen最大長度實驗

源代碼

編譯、運行

查看當前網絡狀態

TCP修改緩沖區實驗

發送端

接收端

編譯、運行

參考


通過socket監聽來自用戶的請求——socket響應一般框架

大部分socket監聽的代碼都和這個差不多,後面真正放到項目中會補全完成的异常處理和日志系統,但整體的簡易框架大體不變了。

這裏面包含的知識點有:

  • 創建socket連接的一般流程
  • 文件描述符
  • socket地址的錶示方法
  • socket端口複用
  • socket選項

還不清楚的可以看一下前面兩篇文章:

#include <sys/socket.h>
#include <netinet/in.h>
/* 創建監聽socket文件描述符 */
int listenfd = socket(PF_INET, SOCK_STREAM, 0);
/* 創建監聽socket的TCP/IP的IPV4 socket地址 */
struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_family = AF_INET;
address.sin_addr.s_addr = htonl(INADDR_ANY); /* INADDR_ANY:將套接字綁定到所有可用的接口 */
address.sin_port = htons(port);
int flag = 1;
/* SO_REUSEADDR 允許端口被重複使用 */
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));
/* 綁定socket和它的地址 */
ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address));
/* 創建監聽隊列以存放待處理的客戶連接,在這些客戶連接被accept()之前 */
ret = listen(listenfd, 5);

內核監聽listen最大長度實驗

創建監聽隊列用來存放待處理的客戶連接,用backlog參數提示內核監聽隊列的最大長度。如果監聽隊列的長度超過了backlog,服務器將不會再受理新的客戶連接客戶端也將收到錯誤信息。

源代碼

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <string.h>
static bool stop = false;
/*SIGTERM信號的處理函數, 觸發時結束主程序中的循環*/
static void handle_term(int sig)
{
stop=true;
}
int main(int argc,char*argv[])
{
signal(SIGTERM, handle_term);
// 輸入格式
if(argc <= 3)
{
printf("usage:%s ip_address port_number backlog\n", basename(argv[0]));
return 1;
}
const char* ip = argv[1];
int port = atoi(argv[2]);
int backlog = atoi(argv[3]);
int sock = socket(PF_INET, SOCK_STREAM, 0);
assert(sock >= 0);
/*創建一個IPv4 socket地址*/
struct sockaddr_in address;
bzero(&address,sizeof(address));
address.sin_family=AF_INET;
inet_pton(AF_INET,ip, &address.sin_addr);
address.sin_port=htons(port);
int ret = bind(sock,(struct sockaddr*)&address,sizeof(address));
//assert(ret != -1);
// 內核監聽隊列的最大長度
ret = listen(sock, backlog);
assert(ret != -1);
/*循環等待連接, 直到有SIGTERM信號將它中斷*/
while(!stop)
{
sleep(1);
}
//關閉socket
close(sock);
return 0;
}

編譯、運行

g++ -std=c++11 backlog.cpp -o backlog

查看當前網絡狀態

netstat -nt 

TCP修改緩沖區實驗

這個實驗主要用於熟悉socket選項。不熟悉的可以參考:手把手寫C++服務器(21):Linux socket網絡編程入門基礎手把手寫C++服務器(22):Linux socket網絡編程進階第一彈

我們可以通過SO_RCVBUF和SO_SNDBUF這兩個選項設置TCP接受和發送緩沖區的大小。

TCP接收緩沖區和發送緩沖區的最小值是256字節,最大值是2048個字節。確保一個TCP連接有足够的空間來處理擁塞。

可以通過修改內核參數/proc/sys/net/ipv4/tcp_rmem和/proc/sys/net/ipv4/tcp_wmem來强制TCP接收緩沖區和發送緩沖區的大小限制。

發送端

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#define BUFFER_SIZE 512
int main(int argc,char*argv[])
{
// 正確的輸入格式
if(argc <= 2) {
printf("usage:%s ip_address port_number send_bufer_size\n",basename(argv[0]));
return 1;
}
// 轉換IP和端口號
const char*ip = argv[1];
int port = atoi(argv[2]);
// 地址格式
struct sockaddr_in server_address;
bzero(&server_address,sizeof(server_address));
server_address.sin_family=AF_INET;
inet_pton(AF_INET,ip, &server_address.sin_addr);
server_address.sin_port=htons(port);
// 創建socket
int sock=socket(PF_INET,SOCK_STREAM,0);
assert(sock >= 0);
// 發送區buffer
int sendbuf=atoi(argv[3]);
int len=sizeof(sendbuf);
/*先設置TCP發送緩沖區的大小, 然後立即讀取之*/
setsockopt(sock, SOL_SOCKET,SO_SNDBUF, &sendbuf, sizeof(sendbuf));
getsockopt(sock, SOL_SOCKET,SO_SNDBUF, &sendbuf, (socklen_t*)&len);
printf("the tcp send buffer size after setting is%d\n", sendbuf);
// 連接、發送
if(connect(sock,(struct sockaddr*)&server_address,sizeof(server_address)) != -1) {
char buffer[BUFFER_SIZE];
memset(buffer, 'a', BUFFER_SIZE);
send(sock, buffer, BUFFER_SIZE,0);
}
close(sock);
return 0;
}

接收端

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
// 緩沖區大小
#define BUFFER_SIZE 1024
int main(int argc, char*argv[])
{
// 正確格式
if(argc <= 2) {
printf("usage:%s ip_address port_number recv_buffer_size\n", basename(argv[0]));
return 1;
}
// IP
const char*ip = argv[1];
// 端口號
int port = atoi(argv[2]);
// sock地址
struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_family = AF_INET;
inet_pton(AF_INET, ip, &address.sin_addr);
address.sin_port = htons(port);
// 創建socket連接
int sock = socket(PF_INET,SOCK_STREAM,0);
assert(sock >= 0);
// 接收區大小
int recvbuf = atoi(argv[3]);
int len = sizeof(recvbuf);
/*先設置TCP接收緩沖區的大小, 然後立即讀取之*/
setsockopt(sock,SOL_SOCKET,SO_RCVBUF, &recvbuf,sizeof(recvbuf));
getsockopt(sock,SOL_SOCKET,SO_RCVBUF, &recvbuf,(socklen_t*)&len);
printf("the tcp receive buffer size after settting is%d\n",recvbuf);
// 綁定端口
int ret=bind(sock,(struct sockaddr*)&address, sizeof(address));
assert(ret != -1);
// 監聽
ret=listen(sock,5);
assert(ret != -1);
// 接受地址
struct sockaddr_in client;
socklen_t client_addrlength=sizeof(client);
int connfd = accept(sock, (struct sockaddr*)&client, &client_addrlength);
if(connfd < 0) {
printf("errno is:%d\n",errno);
} else {
char buffer[BUFFER_SIZE];
memset(buffer,'\0',BUFFER_SIZE);
while(recv(connfd, buffer, BUFFER_SIZE-1,0) > 0){}
close(connfd);
}
close(sock);
return 0;
}

編譯、運行

不會編譯的趕緊看第二講和第四講的教程吧:

https://xduwq.blog.csdn.net/article/details/117307884

https://xduwq.blog.csdn.net/article/details/117429821

參考

版权声明:本文为[沉迷單車的追風少年]所创,转载请带上原文链接,感谢。 https://gsmany.com/2021/08/20210815232032979F.html