一起来写web server 03 -- 多线程版本

一起来写web server 03 -- 多线程版本


错误的代码和正确的代码总是非常相似的!

好吧,我们继续开干,这一次,我们来写一个多线程版本的web服务器.

这次代码的思想十分简单,那就是一旦从客户端来了一个连接,就生成一个线程来处理这个连接.这种想法和之前的多进程版本非常类似.但是请注意进程和线程之间的差异.

进程和线程的区别

进程这种东西,一旦父进程调用fork函数,生成了一个子进程,那么子进程基本上不和父进程共享任何东西了,当然,子进程保留了父进程打开文件的指针,复制了父进程的代码区,数据区,寄存器的值,几乎所有的东西,但是一旦fork,两者之间除了父子关系,它们两者的关系就和普通的两个进程一样了.

线程不一样,父线程创建了子线程之后,父子线程之间共享很多东西,当然,子线程有自己的堆栈(堆栈这个玩意自然不能够共享,如果共享,会造成混乱.).寄存器的值也不会共享,应该还有一些我没有提到的东西不能共享吧,不过这些足够了.除此之外,全部共享,也就是说,如果在子线程里关闭了某个文件描述符,那么这个文件描述符在父线程里面一样被关闭了,事实上:

如果你在父(子)进程中能够得到子(父)线程的堆栈的指针的话,父(子)进程也能够访问子(父)进程的堆栈空间了.

这里有一个微小的错误,你能发现吗?

在推进代码的时候,我曾经写过这样一个主函数:

int main(int argc, char *argv[])
{
    int listenfd = Open_listenfd(8080); /* 8080号端口监听 */
    signal(SIGPIPE, SIG_IGN); /* 忽略SIGPIPE消息 */
    while (true) /* 无限循环 */
    {
        struct sockaddr_in clientaddr;
        socklen_t len = sizeof(clientaddr);
        
        int *fdp = (int *)Malloc(sizeof(int));
        int connfd = Accept(listenfd, (SA*)&clientaddr, &len);
        pthread_t tid;
        Pthread_create(&tid, NULL, handle, (void *)connfd);
        //close(connfd);
    }
    return 0;
}

你能发现哪里出错了吗?对了这是处理函数:

void* handle(void* arg)
{
    Pthread_detach(pthread_self()); // 脱离父线程
    int fd = (*(int *)arg);
    printf("%d: fd = %d\n", pthread_self(), fd);
    doit(fd);
    printf("%d: close fd = %d\n", pthread_self(), fd);
    close(fd);
}

这个错误非常地隐蔽,下面是一次打印的结果:

-142670080: fd = 4
-142670080:GET /cpp/concep
t.html HTTP/1.1
-142670080: close fd = 4


-151062784: fd = 5
-151062784:GET /common/ext
.css HTTP/1.1
-151062784: close fd = 5

-142670080: fd = 4
-142670080:GET /common/sit
e_modules.css HTTP/1.1
-142670080: close fd = 4

-142670080: fd = 4
-142670080:GET /common/ski
n_scripts.js HTTP/1.1

-151062784: fd = 6
-142670080: close fd = 4
-151062784:GET /common/sit
e_scripts.js HTTP/1.1

-151062784: close fd = 6

-159455488: fd = 6
Rio_readlineb error: Bad f
ile descriptor

错误的原因

好了,我这里就不卖关子了,代码错在了这里:

Pthread_create(&tid, NULL, handle, (void *)connfd);

我们可以发现,这里的connfd是栈上的一个变量,当while循环了一遍之后,这个connfd的值我们就不再确定了,他有可能是原来的值,这样的话,线程运行不会出错,有可能是任何值,这个时候就会出现打印的bad file diesriptor啦.

有一点我们是要注意的,共享的资源会导致竞争状态的产生,考虑这样一种极端情况,Pthread_create函数之后,子进程并没有得到cpu的运行时间,而主线程一直在运行,很快,主线程获得了一个新的连接,connfd的值被新的文件描述符的值填充了,这个时候子线程才开始运行,子线程获得的值是新的connfd的值吗?

所以,正确的代码示例是这样的:

/*- 
* 多线程版本的web server.
*/
void* handle(void* arg)
{
    Pthread_detach(pthread_self());
    int fd = (*(int *)arg);
    Free(arg); /* 防止内存泄露,释放 */
    printf("%d: fd = %d\n", pthread_self(), fd);
    doit(fd);
    printf("%d: close fd = %d\n", pthread_self(), fd);
    close(fd);
}

int main(int argc, char *argv[])
{
    int listenfd = Open_listenfd(8080); /* 8080号端口监听 */
    signal(SIGPIPE, SIG_IGN); 
    int connfd;
    while (true) /* 无限循环 */
    {
        struct sockaddr_in clientaddr;
        socklen_t len = sizeof(clientaddr);
        
        int *fdp = (int *)Malloc(sizeof(int)); /* 重新分配一块地址 */
        *fdp = Accept(listenfd, (SA*)&clientaddr, &len);
        pthread_t tid;
        Pthread_create(&tid, NULL, handle, (void *)fdp);
        //close(connfd);
    }
    return 0;
}

存在的小bug

这里的signal(SIGPIPE, SIG_IGN);其实对于线程来说并没有起作用,以后的版本会改进.

当然,这种模式效率依旧不是很高.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,978评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,954评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,623评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,324评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,390评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,741评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,892评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,655评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,104评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,451评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,569评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,254评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,834评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,725评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,950评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,260评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,446评论 2 348

推荐阅读更多精彩内容

  • 又来到了一个老生常谈的问题,应用层软件开发的程序员要不要了解和深入学习操作系统呢? 今天就这个问题开始,来谈谈操...
    tangsl阅读 4,098评论 0 23
  • 一、温故而知新 1. 内存不够怎么办 内存简单分配策略的问题地址空间不隔离内存使用效率低程序运行的地址不确定 关于...
    SeanCST阅读 7,783评论 0 27
  • Object C中创建线程的方法是什么?如果在主线程中执行代码,方法是什么?如果想延时执行代码、方法又是什么? 1...
    AlanGe阅读 1,721评论 0 17
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,734评论 25 707
  • 这次东北之行,收获丰硕。仅仅5天,经历竟然有这么多,学到的太多,不论是人还是知识经验,现在把它写下来,纪念来时路,...
    小静小静小静静啊阅读 195评论 0 0