本系列文章導航: 手把手寫C++服務器(0):專欄文章-匯總導航【更新中】
前言:本系列文章手把手寫C++服務器(15):網路編程入門第一個TCP專案以封裝好的網路庫為例,重點講解了如何正確的建立TCP連接,如何正確地銷毀TCP連接,如何在安全的時機關閉連接,如何處理丟包問題,本文在上一篇文章手把手寫C++服務器(21):Linux socket網路編程入門基礎的基礎上,從原生的socket角度出發,進一步深入玩轉TCP編程,
目錄
通過socket監聽來自用戶的請求——socket回應一般框架
內核監聽listen最大長度實驗
源代碼
編譯、運行
查看當前網路狀態
TCP修改緩沖區實驗
發送端
接收端
編譯、運行
參考
通過socket監聽來自用戶的請求——socket回應一般框架
大部分socket監聽的代碼都和這個差不多,后面真正放到專案中會補全完成的例外處理和日志系統,但整體的簡易框架大體不變了,
這里面包含的知識點有:
- 創建socket連接的一般流程
- 檔案描述符
- socket地址的表示方法
- socket埠復用
- socket選項
還不清楚的可以看一下前面兩篇文章:
- 手把手寫C++服務器(21):Linux socket網路編程入門基礎
- 手把手寫C++服務器(22):Linux 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://huixxi.github.io/2020/06/02/%E5%B0%8F%E7%99%BD%E8%A7%86%E8%A7%92%EF%BC%9A%E4%B8%80%E6%96%87%E8%AF%BB%E6%87%82%E7%A4%BE%E9%95%BF%E7%9A%84TinyWebServer/#more
- 《Linux高性能服務器編程》
- 《Linux多執行緒服務端編程》
- 《C++并發編程實戰》
- 《計算機網路——自頂向下方法》
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/294209.html
標籤:其他
上一篇:【網路通信與資訊安全】之深入決議TCP與UDP傳輸協議
下一篇:C++--模板&STL
