手把手写C++服务器(24):socket响应一般框架TCP修改缓冲区内核监听listen最大长度
Posted 沉迷单车的追风少年
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手把手写C++服务器(24):socket响应一般框架TCP修改缓冲区内核监听listen最大长度相关的知识,希望对你有一定的参考价值。
本系列文章导航: 手把手写C++服务器(0):专栏文章-汇总导航【更新中】
前言:本系列文章手把手写C++服务器(15):网络编程入门第一个TCP项目以封装好的网络库为例,重点讲解了如何正确的建立TCP连接,如何正确地销毁TCP连接,如何在安全的时机关闭连接,如何处理丢包问题。本文在上一篇文章手把手写C++服务器(21):Linux socket网络编程入门基础的基础上,从原生的socket角度出发,进一步深入玩转TCP编程。
目录
通过socket监听来自用户的请求——socket响应一般框架
通过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://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++并发编程实战》
- 《计算机网络——自顶向下方法》
以上是关于手把手写C++服务器(24):socket响应一般框架TCP修改缓冲区内核监听listen最大长度的主要内容,如果未能解决你的问题,请参考以下文章
手把手写C++服务器(22):Linux socket网络编程进阶第一弹
手把手写C++服务器(22):Linux socket网络编程进阶第一弹
手把手写C++服务器(38):面试必背!Linux网络socket编程必会十问!
手把手写C++服务器(21):Linux socket网络编程入门基础