`

Unix 网络编程_阅读笔记四 线程、客户/服务器程序设计范式

 
阅读更多

Unix 网络编程_阅读笔记 四 (Socket高级篇之线程、客户/服务器程序设计范式)

--Unix Network Programming

 王宇 原创并发布


本文代码,在以下环境下编译通过

  • CentOS 6.4
  • Kernal version: 2.6.32
  • GCC version: 4.4.7

一、 线程

父进程accept一个连接,fork一个子进程,该子进程处理与该连接对端的客户之间的通信,这种范式多少年来一直用的挺好,fork调用却存在一些问题:

  • fork是昂贵的。
  • fork返回之后父子进程之间信息的传递需要进程间通信(IPC)机制。调用fork之前父进程向尚未存在的子进程传递信息相当容易,因为子进程将从父进程数据空间及所有描述符的一个副本开始运行。然而从子进程往父进程返回信息却比较费力。

线程有助于解决这两个问题。线程有时称为轻权进程(lightweight process)。同一进程内的所有线程共享相同的全局内存。这使得线程之间易于共享信息,然而伴随这种容易性而来的却是同步问题。

线程除了共享全局变量外还共享:

  • 进程指令
  • 大多数数据
  • 打开的文件(即描述符)
  • 信号处理函数和信号处置
  • 当前工作目录
  • 用户ID和组ID

不过每个线程有各自的:

  • 线程ID
  • 寄存器集合,包括程序计数器和栈指针
  • 栈(用于存放局部变量和返回地址)
  • errno
  • 信号掩码
  • 优先级

1、基本线程函数:创建和终止

  • pthread_create 函数 创建并启动一个线程
  • pthread_join 函数 等待给定线程终止
  • pthread_self 函数 获得线程自身线程ID
  • pthread_detach 函数 转变为脱离状态(detached)
  • pthread_exit 函数 终止线程

2、使用线程的str_cli函数

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<pthread.h>
#include<errno.h>

#define SERV_PORT 51000
#define MAXLINE 4096 
#define SA struct sockaddr

char* Ip_address = "192.168.153.130";

static int socket_fd;
static FILE *fp;


size_t readline(int fd, void *vptr, size_t maxlen)
{
    size_t n, rc;
    char c, *ptr;

    ptr = vptr;

    for(n = 1; n < maxlen; n++)
    {
again:
        if( (rc = read(fd, &c, 1)) == 1)
        {
            *ptr++ = c;     
            if(c == '\n')
            {
                break;
            }
        }
        else if(rc == 0)
        {
            return(n - 1);
        }
        else
        {
            if(errno == EINTR)
            {
                goto again; 
            }     
            return (-1);
        }

    }

    return(n);
}

ssize_t writen(int fd, const void *vptr, size_t n)
{
    size_t nleft;
    ssize_t nwritten;
    const char *ptr;

    ptr = vptr;
    nleft = n;
    while(nleft > 0)
    {
        if((nwritten = write(fd, ptr, nleft)) <= 0)
        {
            if(nwritten < 0 && errno == EINTR)
            {
                nwritten = 0;
            }
            else
            {
                return(-1);
            }

        }

        nleft -= nwritten;
        ptr += nwritten;    
    }

    return(n);
}

void* copyto(void *arg)
{
    char sendline[MAXLINE];

    while(fgets(sendline, MAXLINE, fp) != NULL )
    {
        if(writen(socket_fd, sendline, strlen(sendline)) == -1)
        {
            perror("Error: write socket!\n");
        }

    }
    if(shutdown(socket_fd, SHUT_WR) == -1)
    {
        perror("Error: shutdown!\n");
    }

    return(NULL);
}


void str_cli(FILE *fp_arg, int socket_fd_arg)
{
    char recvline[MAXLINE];
    pthread_t tid;
    socket_fd = socket_fd_arg;
    fp = fp_arg;

    if(pthread_create(&tid, NULL, copyto, NULL) != 0)
    {
        perror("Error: pthread_create!\n");
    }

    while(readline(socket_fd, recvline, MAXLINE) > 0)
    {
        fputs(recvline, stdout);
    }
}


int main()
{
    int socket_fd, connect_rt;
    struct sockaddr_in servaddr;
    char err_message[MAXLINE];

    socket_fd = socket(AF_INET, SOCK_STREAM, 0);

    if(socket_fd == -1)
    {
        printf("Error: created socket!\n");
        exit(1);
    }

    bzero(&servaddr, sizeof(servaddr));

    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERV_PORT);
    inet_pton(AF_INET, Ip_address, &servaddr.sin_addr);

    connect_rt = connect(socket_fd, (SA *)&servaddr, sizeof(servaddr));

    if(connect_rt != 0)
    {
        perror(err_message);    
        printf("Error: connect socket!\n");
        printf("%s\n", err_message);
        exit(1);
    }

    str_cli(stdin, socket_fd);


    exit(0);
}

 

 

3、使用线程的TCP回射服务程序

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<netinet/in.h>
#include<errno.h>
#include<pthread.h>

#define SA struct sockaddr
#define SERV_PORT 51000
#define MAXLINE 4096 
#define LISTENQ 1024


void str_echo(int socket_fd)
{
    ssize_t n;
    char buf[MAXLINE];

again:
    while( (n = read(socket_fd, buf, sizeof(buf))) > 0)
    {
        if(write(socket_fd, buf, n) == -1 )
        {
            perror("Error:write.\n");
        }
    }

    if( n < 0 && errno == EINTR)
    {
        goto again;
    }
    else if(n < 0)
    {
        perror("ERROR: str_echo\n");
    }

}


static void* doit(void *arg)
{
    int conn_fd;
    conn_fd = *((int *)arg);
    free(arg);

    pthread_detach(pthread_self());
    str_echo(conn_fd);

    close(conn_fd);
    return (NULL);
}


int main()
{
    int socket_fd, *connect_fd; 
    struct sockaddr_in servaddr, clientaddr;
    socklen_t client_len;
    pid_t child_pid;
    pthread_t tid;

    socket_fd = socket(AF_INET, SOCK_STREAM, 0);

    if(socket_fd == -1)
    {
        perror("Error: created socket!\n");
        exit(1);
    }

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);

    if((bind(socket_fd, (SA *)&servaddr, sizeof(servaddr))) == -1)
    {
        perror("Error: Bind port!\n");
        exit(1);
    }

    if((listen(socket_fd, LISTENQ)) == -1)
    {
        perror("Error: Listen!\n");
        exit(1);
    }

    for(;;)
    {
        client_len = sizeof(clientaddr);
        connect_fd = (int*)malloc(sizeof(int));

        if(connect_fd == NULL)
        {
            perror("Error: malloc!\n");
            exit(1);
        }

        *connect_fd = accept(socket_fd, (SA *)&clientaddr, &client_len);

        if(pthread_create(&tid, NULL, &doit, connect_fd) == -1)
        {
            perror("Error: pthread!\n");
            exit(1);
        }

    }

    exit(0);
}

 

 

4、线程特定数据

把一个未线程化的程序转换成使用线程的版本时,有时会碰到因其中有函数使用静态变量而引起的一个常见编程错误。 有以下几种解决方案:

  • 使用线程特定数据。这个办法并不简单,而且转换成了只能在支持线程的系统上工作的函数。本办法的优点是调用顺序无需变动,所有变动都体现在库函数中而非调用这些函数的应用程序中。
  • 改变调用顺序
  • 改变接口的结构,避免使用静态变量,这样函数就可以是线程安全的。

每个系统支持有限数量的线程特定数据元素。POSIX要求这个限制不小于128(每个进程)

5、互斥锁

#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mptr);
int pthread_mutex_unlock(pthread_mutex_t *mptr);
Both return: 0 if OK, positive Exxx value on error
 
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>

#define NLOOP 200
int counter;
pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;


void* doit(void *vptr)
{
    int i, value;

    for(i = 0; i < NLOOP; i++)
    {
        if(pthread_mutex_lock(&counter_mutex) !=0)  
        {
            perror("Error: lock\n");
        }

        value = counter;
        printf("%d: %d\n", pthread_self(), value + 1);
        counter = value + 1;

        if(pthread_mutex_unlock(&counter_mutex) !=0)    
        {
            perror("Error: unlock\n");
        }
    }

    return(NULL);
}


int main()
{

    pthread_t t_id_A, t_id_B;

    pthread_create(&t_id_A, NULL, &doit, NULL);
    pthread_create(&t_id_B, NULL, &doit, NULL);

    pthread_join(t_id_A, NULL);
    pthread_join(t_id_B, NULL);

    exit(0);
}
 

6、条件变量

修改以上代码,使counter变量每增加10,则打印一个分隔线:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>

#define NLOOP 100
int counter;
pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

void* doit(void *vptr)
{
    int i, value;

    for(i = 0; i < NLOOP; i++)
    {
        if(pthread_mutex_lock(&counter_mutex) !=0)  
        {
            perror("Error: lock\n");
        }

        value = counter;
        printf("%d: %d\n", pthread_self(), value + 1);
        counter = value + 1;

        if((counter%10) == 0)
        {
            pthread_cond_signal(&cond);
        }

        if(pthread_mutex_unlock(&counter_mutex) !=0)    
        {
            perror("Error: unlock\n");
        }

        sleep(1);
    }

    return(NULL);
}

void* do_split(void *vptr)
{
    for(;;)
    {
        if(pthread_mutex_lock(&counter_mutex) !=0)  
        {
            perror("Error: lock\n");
        }

        pthread_cond_wait(&cond, &counter_mutex);

        if(counter == (NLOOP * 2))
        {
            pthread_exit(0);
        }

        printf("%d: ----------\n", pthread_self());

        if(pthread_mutex_unlock(&counter_mutex) !=0)    
        {
            perror("Error: unlock\n");
        }
    }
}


int main()
{

    pthread_t t_id_A, t_id_B, t_id_C;

    pthread_create(&t_id_A, NULL, &do_split, NULL);
    pthread_create(&t_id_B, NULL, &doit, NULL);
    pthread_create(&t_id_C, NULL, &doit, NULL);

    pthread_join(t_id_A, NULL);
    pthread_join(t_id_B, NULL);
    pthread_join(t_id_C, NULL);

    exit(0);
}

 


二、客户/服务器程序设计范式

1、服务器程序设计范式:

  • 1、迭代服务器(无进程控制,用作测量基准)
  • 2、并发服务器,每个客户请求fork一个子进程
  • 3、预先派生子进程,每个子进程无保护地调用accept
  • 4、预先派生子进程,使用文件上锁保护accept
  • 5、预先派生子进程,使用线程互斥锁上锁保护accept
  • 6、预先派生子进程,父进程向子进程传递套接字描述符
  • 7、并发服务器,每个客户请求创建一个线程
  • 8、预先创建线程服务器,使用互斥锁保护accept
  • 9、预先创建线程服务器,由主线程调用accept

2、总结:

  • 当系统负载较轻时,每来一个客户请求现场派生一个子进程为之服务的传统并发服务器程序模型就足够了。这个模型甚至可以与inetd结合使用,也就是inetd处理每个连接的接受。我们的其他意见是就重负荷运行的服务器而言的,譬如Web服务器。
  • 相比传统的每个客户fork一次设计范式,预先创建一个子进程池或一个线程池的设计范式能够把进程控制CPU时间降低10倍或以上。编写这些范式的程序并不复杂,不过需超越本章所给例子的是:监视闲置子进程个数,随着所服务客户数的动态变化而增加或减少这个数目
  • 某些实现允许多个子进程或线程阻塞在同一个accept调用中,另一些实现却要求包绕accept调用安置某种类型的锁加以保护。文件锁或Pthread互斥锁都可以使用。
  • 让所有子进程或线程自行调用accept通常比让父进程或主线程独自调用accept并把描述符传递给子进程或线程来的简单而快速。
  • 由于潜在select冲突的原因,让所有子进程或线程阻塞在同一个accept调用中比让它们阻塞在同一个select调用中更可取。
  • 使用线程通常远快于使用进程。不过选择每个客户一个子进程还是每个客户一个线程取决于操作系统提供什么支持,还可能取决于为服务每个客户需激活其他什么程序(若有其他程序需激活的话)举例子说,如果accept客户连接的服务器调用fork和exec,那么fork一个单线程的进程可能快于fork一个多线程的进程
分享到:
评论

相关推荐

    UNIX网络编程_卷2_进程间通信第二版.pdf

    UNIX网络编程_卷2_进程间通信第二版.pdf,绝对高清,绝对正版,不用积分下载

    unix网络编程读书笔记

    UNIX 网络编程读书笔记内容涵盖了 UNIX 网络编程的基本概念、Socket 编程、TCP/IP 协议、网络服务器设计思想、并发服务器设计等多个方面。整体来说,这篇读书笔记对 UNIX 网络编程的知识点进行了系统的总结和概括。 ...

    网络编程合集 TCP-IP详解 windows Linux UNIX Ace 网络编程 7_2

    网络编程合集 TCP-IP详解 windows Linux UNIX Ace 网络编程 搜索关键字:网络编程 TCP-IP详解 windows Linux UNIX 网络编程1_TCP-IP详解卷1.rar 网络编程2_TCP-IP详解卷2-1.rar 网络编程3_windows网络编程电子书及...

    exercise_one_due2v3_Linux/Unix编程_

    【标题】"exercise_one_due2v3_Linux/Unix编程_" 暗示这是一个与Linux/Unix操作系统编程相关的练习或项目,可能是一个教学资源或竞赛的作业,版本为due2v3。这个标题可能代表着练习的阶段或者更新迭代,重点在于理解...

    UNIX网络编程_卷2_第2版_进程间通信

    UNIX网络编程_卷2_第2版_进程间通信

    UNIX网络编程_卷2_进程间通信

    《UNIX网络编程.卷2:进程间通信(第2版)》这本书是W.Richard Stevens所著的网络编程领域的权威之作。本书详细介绍了UNIX系统中进程间通信(IPC)的各种形式,这些通信机制对于提高UNIX程序性能至关重要,同时是开发...

    UNIX网络编程_卷1_套接字联网API第3版源代码

    2. **多线程编程**:在处理并发连接时,多线程技术常被用到。源代码可能包含线程的创建、同步和通信,比如使用pthread_create()创建新线程,pthread_join()等待线程结束,以及互斥锁和条件变量来保证数据一致性。 3...

    LinuxProbe(20180610)_WithBookmark_linux_Linux/Unix编程_

    《LinuxProbe(20180610)_WithBookmark》是一本专注于Linux/Unix编程领域的书籍,其高质量的内容和清晰的布局深受读者喜爱。这本书不仅涵盖了基础的Linux操作系统原理,还深入探讨了Unix系统编程的各个方面,为读者...

    UNIX网络编程_卷2

    根据提供的标题、描述和部分上下文...总之,《UNIX网络编程_卷2》深入探讨了进程间通信的各种技术细节及其在网络应用程序设计中的应用。理解并熟练掌握这些概念和技术,对于开发高性能、高可靠性的软件系统至关重要。

    unix.rar_Socket网络编程_UNIX_unix socket_unix 网络编程

    UNIX系统为开发者提供了强大的网络编程接口,即UNIX Socket,使得程序能够通过网络进行通信。本篇文章将深入探讨"Socket网络编程在UNIX环境下的实现",并结合提供的资源进行详细讲解。 1. **UNIX Socket基础** - *...

    Linux系统下实现多线程客户/服务器

    在Linux系统下,实现多线程客户/服务器模式是一种高效且灵活的方法,尤其适用于网络服务程序,能够提高并发处理能力并降低系统开销。传统的Unix模型依赖于进程间的通信(IPC),通过`fork()`创建子进程来处理客户端...

    Linux_UNIX 网络编程_12052635_linux并发编程_linux网络编程_源码.zip

    《Linux与UNIX网络编程》是一本深入探讨操作系统层面网络编程的经典教材,主要涵盖了Linux和UNIX系统下的并发编程以及网络通信的实现。这份压缩包中包含的源码是学习这一主题的重要参考资料,它允许读者深入理解网络...

    UNIX网络编程(第1卷)-源码

    全书不但介绍了基本编程内容,还涵盖了与套接字编程相关的高级主题,对于客户/服务器程序的各种设计方法也作了完整的探讨,最后还深入分析了流这种设备驱动机制。 结合源代码(C语言)学习效果更好。 《UNIX网络...

    UNIX网络编程 第2卷 进程间通信.pdf(带书签)

    第四部分是本卷的补充,提供了多个附录,这些附录包含了作者认为对理解本书内容有帮助的额外信息,比如客户/服务器程序设计范式、流的概念等。这些内容有助于读者深化对进程间通信各个方面的理解,并能够更好地应用...

    UNIX网络编程_第2版_第2卷

    7. **并发服务器设计**:单线程、多线程和异步非阻塞I/O模型是实现并发服务器的常见方法,每种都有其优缺点,需要根据实际需求选择。 8. **安全性**:网络编程中要考虑安全问题,如加密通信(SSL/TLS)、防止拒绝...

    C++学习源码_C++_c++源码网_Linux/Unix编程_

    理解TCP/IP协议栈,学会使用socket函数进行客户端和服务器端的编程,是网络应用开发的基础。 9. **内存管理**:C++提供了动态内存分配和释放的函数,如new和delete。理解内存泄漏和野指针问题,以及如何有效地管理...

    精通UNIX下C语言编程与项目实践+源代码+笔记.zip_C语言项目linux_Linux/Unix编程_Linux下的c语言开

    5. **线程**:在多线程编程中,`pthread_create()`, `pthread_join()`等函数用于创建和管理线程。 6. **内存管理**:`malloc()`, `calloc()`, `realloc()`, `free()`等函数用于动态分配和释放内存,理解内存管理是...

    UNIX网络编程 卷1:套接字联网API

    第一部分 简介和TCP/IP ...第30章 客户/服务器程序设计范式 第31章 流 附录A IPv4、IPv6、ICMPv4和ICMPv6 附录B 虚拟网络 附录C 调试技术 附录D 杂凑的源代码 附录E 精选习题答案 参考文献 索引

    auto_user_C++_Linux/Unix编程_

    5. **信号处理**:C++可以通过`&lt;signal.h&gt;`处理Unix/Linux的信号,例如捕获中断(SIGINT)、挂断(SIGHUP)等,使程序能优雅地响应用户或系统的请求。 6. **进程通信**:熟悉进程间通信(IPC)的方式,如管道(pipe...

    UNIX网络编程第1卷(1-19章)

    作者在讨论TCP和UDP时,首先介绍了它们的基本函数,然后通过完整的TCP和UDP客户-服务器程序例子,来演示如何在实际编程中使用这些协议。 I/O复用是提高网络服务器性能的关键技术之一,书中介绍了select和poll这两个...

Global site tag (gtag.js) - Google Analytics