c服务端通过多线程来处理不同请求

下面的例子示意了服务端使用多线程来处理socket请求的例子。
在《Unix网络编程》中称这种方式为网络服务器。要区别“监听套接字”和“已连接套接字”
原文在这里:http://www.wangafu.net/~nickm/libevent-book/01_intro.html#_footnote_1

多线程处理的不利之处在于:
1、创建线程开销较大。
2、线程池不易扩展。
3、如果要处理成千上百个连接,创建这么多线程时系统承受不了

/* For sockaddr_in */
#include <netinet/in.h>
/* For socket functions */
#include <sys/socket.h>

#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#define MAX_LINE 16384

//将一个字符转化成对应的rot13字符,是一种简单的加密方式,这里用这个干嘛?
//A换成N,B换成O,以此类推,M换成Z,然后序列反转,N换成A,O换成B...
char rot13_char(char c) {
    /* We don't want to use isalpha here; setting the locale would change
     * which characters are considered alphabetical. */
    if ((c >= 'a' && c <= 'm') || (c >= 'A' && c <= 'M'))
        return c + 13;
    else if ((c >= 'n' && c <= 'z') || (c >= 'N' && c <= 'Z'))
        return c - 13;
    else
        return c;
}

//在子进程里执行这个函数
void child(int fd) {

    //缓冲区
    char outbuf[MAX_LINE + 1];

    size_t outbuf_used = 0;    //size_t long unsiged int
    ssize_t result;            //ssize_t long int

    while (1) {
        char ch;
        result = recv(fd, &ch, 1, 0);  //第三个参数为len,第四个参数为符号位,一般设置为0。这里一次只读一个字符
        if (result == 0) {      //返回0表示读完了
            break;
        } else if (result == -1) {
            perror("read");
            break;
        }

        /* We do this test to keep the user from overflowing the buffer. */
        if (outbuf_used < sizeof(outbuf)) {
            outbuf[outbuf_used++] = rot13_char(ch);  //这里将字符转码
        }

        //当遇到换行符的时候,将编码后的结果返回给客户端
        if (ch == '\n') {
            send(fd, outbuf, outbuf_used, 0);
            outbuf_used = 0;
            continue;
        }
    }
}

void run(void) {
    int listener;
    struct sockaddr_in sockAddr_in;

    sockAddr_in.sin_family = AF_INET;
    sockAddr_in.sin_addr.s_addr = 0;        //所以服务端socket地址就是设置为0
    sockAddr_in.sin_port = htons(40713);   //直接指定端口号

    listener = socket(AF_INET, SOCK_STREAM, 0);

    //如果不是WIN32系统,则做下面的事
#ifndef WIN32
    {
        int one = 1;

//      我们知道,在TCP断开链接的时候我们需要四次握手来断开,而且当两端都关闭了read/write通道以后我们还是要等待一个TIME_WAIT时间。
//      这就是SO_REUSEADDR的作用所在.
//      其实这个选项就是告诉OS如果一个端口处于TIME_WAIT状态, 那么我们就不用等待直接进入使用模式, 不需要继续等待这个时间结束.

//参考地址:http://www.cnblogs.com/linehrr-freehacker/p/3309156.html
//      SO_REUSEADDR可以用在以下四种情况下。
//      (摘自《Unix网络编程》卷一,即UNPv1)
//      1、当有一个有相同本地地址和端口的socket1处于TIME_WAIT状态时,而你启
//      动的程序的socket2要占用该地址和端口,你的程序就要用到该选项。
//      2、SO_REUSEADDR允许同一port上启动同一服务器的多个实例(多个进程)。但
//      每个实例绑定的IP地址是不能相同的。在有多块网卡或用IP Alias技术的机器可
//      以测试这种情况。
//      3、SO_REUSEADDR允许单个进程绑定相同的端口到多个socket上,但每个soc
//      ket绑定的ip地址不同。这和2很相似,区别请看UNPv1。
//      4、SO_REUSEADDR允许完全相同的地址和端口的重复绑定。但这只用于UDP的
//      多播,不用于TCP。
        setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
    }
#endif

    if (bind(listener, (struct sockaddr*) &sockAddr_in, sizeof(sockAddr_in)) < 0) {
        perror("bind");
        return;
    }

    //对于服务端程序,使用bind()绑定套接字之后,还需要使用listen函数让套接字进入被动监听状态
    //再调用accept()函数,就可以随时响应客户端的请求了
    //第二个参数为请求队列的最大长度
    //参考:http://c.biancheng.net/cpp/html/3036.html
    //当套接字正在处理客户端请求时,如果有新的请求进来,套接字是没法处理的,
    //只能把它放进缓冲区,待当前请求处理完毕后,再从缓冲区中读取出来处理。
    //如果不断有新的请求进来,它们就按照先后顺序在缓冲区中排队,直到缓冲区满。这个缓冲区,就称为请求队列(Request Queue)。


        //一个服务器通常仅仅创建一个监听套接字,他在该服务器的生命周期内一直存在。“监听套接字”
    if (listen(listener, 16) < 0) {    
        perror("listen");
        return;
    }

    while (1) {
        struct sockaddr_storage ss;
        socklen_t slen = sizeof(ss);

                //如果accept成功,那么其返回值是由内核自动生成的一个全新描述符,代表与所返回客户的TCP连接;返回“已连接套接字”
        int connfd = accept(listener, (struct sockaddr*) &ss, &slen);  
        if (fd < 0) {
            perror("accept");
        } else {
            //fork之前,只有一个进程在执行这段代码,但在执行这条语句后,就变成两个进程在执行了,这两个进程几乎完全相同
            //,将要执行的下一条语句都是 if(pid==0)。这两个进程的执行没有先后顺序,靠系统的进程调度策略
            int pid = fork();
            if (pid == 0) {
                child(connfd);
                exit(0);
            }
        }
        //在这个地方关闭fd并没有断开socket连接,因为子进程也有这个socket的复制,
        //这个socket的引用计数是2,这里调用close将其计数减小为1
        close(connfd);
    }
}

int main(int c, char **v) {
    run();
    return 0;
}

整个过程连接的状态变化图如下,connfd表示新的socket连接:
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值