C-BlogServer博客-可并发的http服务器
博客专区 > tmj 的博客 > 博客详情
C-BlogServer博客-可并发的http服务器
tmj 发表于2年前
C-BlogServer博客-可并发的http服务器
  • 发表于 2年前
  • 阅读 215
  • 收藏 7
  • 点赞 0
  • 评论 0

腾讯云 新注册用户 域名抢购1元起>>>   

这一部分主要是先把现有代码讲述一下方便后面的博客描写

ps:我也是初学者,不保证代码的可靠性,只是能用,现有代码的问题也许会在后面修正,这取决于个人的学习深度,由于是学习向的,不采用任何的第三方库

运行在Linux环境下

首先来个单线程的

#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>

int main()
{
    int server_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);//IPV4+TCP
    struct sockaddr_in server_addr;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8080);//监听端口
    bind(server_fd, (struct sockaddr*)&server_addr, sizeof(struct sockaddr));
    listen(server_fd, 1);
    int len = sizeof(struct sockaddr);
    struct sockaddr_in client_addr;
    int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &len);
    char recvBuf[1024];
    recv(client_fd, recvBuf, sizeof(recvBuf), 0);//接收浏览器发过来的请求
    char sendBuf[]="HTTP/1.1 200 OK\r\nServer:BlogServer\r\nContent-Length:18\r\n\r\n<p>hello world</p>";//包括http响应头和正文<p>hello world</p>
    send(client_fd, sendBuf, sizeof(sendBuf), 0);//不对请求分析统一返回上面的字符串
    shutdown(client_fd, SHUT_RDWR);//关闭
    close(client_fd);
    close(server_fd);
    return 0;
}

Unnamed QQ Screenshot20150821174715

然后通过gcc编译一下

Unnamed QQ Screenshot20150821174908

运行

Unnamed QQ Screenshot20150821175155

通过chrome打开可以看到hello world

响应信息:

Unnamed QQ Screenshot20150821175228

 

由于这个程序并不是多线程,也没有做循环处理,所以该程序只能运行一次就会退出

然而再次打开的时候却无法继续使用,为什么呢?

首先使用netstat -an看一下端口的占用情况,然而并没有发现8080端口

但是有个看起来比较可疑的http-alt

Unnamed QQ Screenshot20150821181149
在查询一下服务发现确实是8080的端口的
Unnamed QQ Screenshot20150821181759
LISTEN:侦听来自远方的TCP端口的连接请求
SYN-SENT:再发送连接请求后等待匹配的连接请求
SYN-RECEIVED:再收到和发送一个连接请求后等待对方对连接请求的确认
ESTABLISHED:代表一个打开的连接
FIN-WAIT-1:等待远程TCP连接中断请求,或先前的连接中断请求的确认
FIN-WAIT-2:从远程TCP等待连接中断请求
CLOSE-WAIT:等待从本地用户发来的连接中断请求
CLOSING:等待远程TCP对连接中断的确认
LAST-ACK:等待原来的发向远程TCP的连接中断请求的确认
TIME-WAIT:等待足够的时间以确保远程TCP接收到连接中断请求的确认
CLOSED:没有任何连接状态

查一下资料发现TIME-WAIT是等待远程tcp的确认

经查询发现可以采用端口复用的方式来避免服务器重启等情况

int val = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(int));
setsockopt(server_fd, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(int));

在bind()之前加入这两句
Unnamed QQ Screenshot20150821194340

这时候可以重复地连续地进行启动操作

如果让两个终端同时开启程序,第一个开启的(pid比较小的?)会先执行accept()

然后我们再进行并发的编程

#include <pthread.h>

添加必要的头

int main(int argc,char* argv[])
{
    if(argc != 2) return 1;
    int server_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
    printf("socket_fd:%d\n", server_fd);
    struct sockaddr_in server_addr;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(atoi(argv[1]));
    int val = 1;
    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(int));
    setsockopt(server_fd, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(int));
    if(!bind(server_fd, (struct sockaddr*)&server_addr, sizeof(struct sockaddr))) {char buf[30];printf("bind:%s:%d\n",inet_ntop(AF_INET, &server_addr.sin_addr, buf, sizeof(buf)),(unsigned int)ntohs(server_addr.sin_port));}
    else return 1;
    if(!listen(server_fd, 1000)) {char buf[30];printf("listen:%s:%d\n",inet_ntop(AF_INET, &server_addr.sin_addr, buf, sizeof(buf)),(unsigned int)ntohs(server_addr.sin_port));}
    else return 1;

//accept()将在线程中完成
    pthread_t ntid;
    pthread_create(&ntid, NULL, ListenClient, &server_fd);

//主线程开始等待输入
    int loop;
    scanf("%d",&loop);
    close(server_fd);
    return 0;
}

监听线程

void *ListenClient(void *server)
{
    int server_fd = *(int*)server;
    int len = sizeof(struct sockaddr);
    struct sockaddr_in client_addr;
    pthread_t ntid;
    while(1)
    {
        int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &len);
        //printf("client_fd:%d\n", client_fd);
        char buf[30];
        printf("connect:%s:%d\n",inet_ntop(AF_INET, &client_addr.sin_addr, buf, sizeof(buf)),(unsigned int)ntohs(client_addr.sin_port));
        //printf("%lu:start new response\n",(unsigned long)pthread_self());
        //printf("%d\n",client_fd);
        pthread_create(&ntid, NULL, HttpResponse, &client_fd);//每一次响应都创建新的线程来处理
        pthread_detach(ntid);
    }   
}

响应线程

void *HttpResponse(void *client)
{
    int client_fd = *(int*)client;
    char recvBuf[1024];
    //printf("%lu:start recv\n",(unsigned long)pthread_self());
    recv(client_fd, recvBuf, sizeof(recvBuf), 0);
    //printf("%s\n",recvBuf);
    char sendBuf[]="HTTP/1.1 200 OK\r\nServer:BlogServer\r\nContent-Length:18\r\n\r\n<p>hello world</p>";
    //printf("%lu:start send\n",(unsigned long)pthread_self());
    send(client_fd, sendBuf, sizeof(sendBuf), 0);
    shutdown(client_fd, SHUT_RDWR);
    //printf("%lu:start close\n",(unsigned long)pthread_self());
    close(client_fd);
    pthread_exit((void *)0);
}

编译

gcc main.c -o httpserver -lpthread
这时候可以不停刷新浏览器运行了,还是很不错的

Unnamed QQ Screenshot20150821202203

但此时系统还是不完善的,接着看

Unnamed QQ Screenshot20150821202619

先用ps a看一下进程pid

再用top命令来观察(top –p 1431)

使用py脚本测试,这个比较简单

#coding=utf-8
import urllib
import thread
import time

i = 0
while 1:
    try:
                url = 'http://127.0.0.1:8080'
        res = urllib.urlopen(url)
                s = res.read()
        res.close()
        i = i + 1
        if i % 5 == 0:
                        print str(i)+':'+s
    except Exception as e:
        print str(e)

Unnamed QQ Screenshot20150821202920

在高速地循环运行下,系统依然是很稳健的,毕竟这个简单,但是这是在网络状况极好的状况下,那在网络异常等一下故障情况下,出现超时了是否能处理呢?

import socket
host='127.0.0.1'
port=8080
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect((host,port))
s.send('hello')
print s.recv(1024)
s.close

简单的一次socket连接

Unnamed QQ Screenshot20150821203719

成功

import socket
host='127.0.0.1'
port=8080
while 1:
    s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    s.connect((host,port))
    s.send('hello')
    s.recv(1024)
    s.close

循环,这时候跟http版本的相似,也是可以正常运行的

这时候要是把recv和close去掉,那么情况就不用乐观了,服务器几乎1秒就挂了

import socket
import time
host='127.0.0.1'
port=8080
while 1:
    s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    s.connect((host,port))
    s.send('hello')
    time.sleep(0.01)
    #s.recv(1024)
    #s.close

加上time.sleep(0.001)会缩短崩溃时间,但也不容乐观

问题出在哪里呢?大量的超时连接导致服务器资源过多奔溃吗?加入对客户端超时发生与接收的处理

struct timeval timeout = {5, 0};
setsockopt(client_fd,SOL_SOCKET,SO_RCVTIMEO,(char*)&timeout,sizeof(struct timeval));
setsockopt(client_fd,SOL_SOCKET,SO_SNDTIMEO,(char*)&timeout,sizeof(struct timeval));

实际上就算说对超时时间,短时间大量这样的连接还是会奔溃,问题到底出在哪里呢?
也晚了,等下一篇博客继续研究吧

共有 人打赏支持
粉丝 4
博文 14
码字总数 14830
×
tmj
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: