服务器项目问题总结

本文总结了服务器项目中的网络问题,包括HTTP协议的GET和POST方法区别、优雅关闭连接的方法,特别是TIME_WAIT状态的处理。此外,还探讨了socket编程中的非阻塞模式与线程池在高并发场景下的应用策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

网络

HTTP协议相关问题

图解 HTTP 常见的面试题

1、GET 、POST方法的区别
1、GET参数放在url中,POST参数放在消息体中,GET参数直接暴露在url中,所以GET相比POST不安全,不能传递敏感信息
2、GET 可以被缓存,POST不能被缓存
3、GET可以被存为书签,POST不能被存为书签
4、GET的url最大有2048个字符,POST无长度限制
5、后退刷新按钮,GET无害,POST会被重新提交
6、GET的数据类型只能是ASCII码,而POST没有限制,可以是二进制

在这里插入图片描述

2、优雅关闭连接

我理解的就是按照TCP四次挥手的流程正常关闭连接,调用close(int sockfd) 会同时关闭读和写两个方向,但是默认情况下会将TCP发送缓冲区的残余数据继续发送给对方,所以直接使用close关闭问题也不大。

设置SO_LINGER 选项可以控制close()系统调用的行为,设置SO_LINGER选项时需要传入一个结构体linger
struct linger
{
int l_onoff;
int l_linger;
};

struct linger tmp = {0, 1};//第一个参数等于0,默认行为,close(sockfd)直接返回,TCP将发送缓冲区的残余数据发送给对方
setsockopt(connfd, SOL_SOCKET, SO_LINGER, &tmp, sizeof(tmp));

设置linger的第一个成员为0时,SO_LINGER不起作用,close()是默认行为;
设置结构体linger{1,0}时,调用close()关闭连接时,会丢弃发送缓冲区的残余数据,同时给对方发送一个复位报文,RST,相当于直接终止了连接。这种情况就是直接关闭连接。

shutdown 可以关闭单个方向上的通道,比如shutdown(connfd,1)关闭发送通道,但是还可以读;shutdown(connfd,0)关闭读通道。但是我实际使用时,调用shutdown()关闭发送通道后,连接一直处于半关闭状态,主动关闭方一直处于FIN_WAIT_2,被动关闭方一直处于close_wait()状态,

3、TIME_WAIT状态的处理

TIME_WAIT只存在于主动关闭连接的一方。
TIME_WAIT存在的意义:

  1. 保证TCP正常的关闭连接,当最后一次确认丢失时,对方会重发FIN报文,TIME_WAIT存在的目的就是为了能收到对方重发的FIN 消息,保证连接有序的释放。如果没有TIME_WAIT状态,最后一次确认报文丢失,对方重发FIN消息,主动关闭方收到FIN消息,会发送一个复位报文,RST,被动关闭方会将这个当作一个错误处理。
  2. 是为了处理网络上还未到达的包,对方之前发送的包可能会在网络上逗留较长时间,慢慢悠悠才到达,如果没有TIME_WAIT状态,然后这个端口又立即被其他程序占用,则上一次连接的数据包可能会被当作这次连接的数据,显然这不是我们希望的。

解决大量TIME_WAIT:
time_wait过多,占用大量端口,浪费资源。

  1. 首先可以设置time_wait的上限,tcp_max_tw_buckets,当出现的time_wait数超过上限时,后续的连接释放时会跳过time_wait状态,直接关闭;
  2. 对于主动发起连接请求的,打开 tcp_tw_reuse 参数,可以立即复用time_wait状态的端口,但是这个只是对于主动发起tcp连接的一方才起作用,而服务器很少主动发起连接…
  3. 通过设置SO_LINGER选项,直接关闭连接,跳过了四次挥手,但是这时会放弃缓冲区中残留的数据,不建议。

二、socket编程

1、通信套接字要设置为非阻塞模式,否则读写操作recv、send可能会阻塞。

epoll注册通信套接字读事件为ET触发模式时,套接字默认为阻塞,通过recv函数读取套接字数据时,需要加上while(true)循环,当套接字当中的数据读完之后再次调用recv时(如果没有后续事件)会一直阻塞下去,比如下面的代码中,第一次调用recv读完了接收缓冲区中的数据,第二次调用recv会一直阻塞下去,直到关闭这次tcp连接,recv才返回。

while(true)
    {
        cout<<"recv:"<<endl;
        int ret = recv(fd,buf+bytes_read,read_buf_size-1,0);
        cout<<"recv next"<<endl;
        if(ret==-1){
            if(errno==EAGAIN || errno==EWOULDBLOCK){
                cout<<"数据读取完毕"<<endl;
                break;
            }
            cout<<errno<<endl;
            close(fd);
            break;
        }
        else if(ret==0){
            cout<<"对方关闭连接"<<endl;
            close(fd);
        }
        bytes_read +=ret;
        cout<<"reading..."<<endl;
    }

线程池相关

  1. 如果同时1000个客户端进行访问请求,线程数不多,怎么能及时响应处理每一个呢?

目前的并发模型中主线程通过一个epoll_wait()监听所有的文件描述符,一个线程同时只能处理一个客户端请求,如果同时请求的客户端较多,请求队列会堆积很多任务。
可以改变并发模型,在工作线程中增加工作线程自己的IO复用,主线程中收到新的连接就将通信套接字分发给某一个工作线程,此后这个套接字的所有请求都由该线程来处理,每个工作线程通过自己的epoll_wait()可以监听不同的通信套接字,每个工作线程都可以处理多个客户连接。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值