多线程socket服务器(c语言)

一.线程的基本概念

线程是操作系统OS能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位. 所有的线程都是在同一进程空间运行,这也意味着多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。 一个进程可以有很多线程,每条线程并行执行不同的任务。一个进程可以开启多个线程,其中有一个主线程来调用本进程中的其他线程。我们看到的进程的切换,切换的也是不同进程的主线程。一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。
在这里插入图片描述

二,进程与线程的关系和区别

2.1 进程

  • 一个在内存中运行的应用程序。每个进程都有自己独立的一块内存空间,一个进程可以有多个线程。
  • 进程是一个程序及其数据在处理机上顺序执行时所发生的活动。
  • 进程是具有独立功能的程序在其数据集合上运行的过程,他是系统调度和资源分配的一个独立单位。
    2.2 线程和进程的区别
  • 调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位
  • 资源开销:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器,线程之间切换的开销小
  • 内存分配:同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的
  • 并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行
  • 系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。

三.创建线程

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
pthread_create()函数是操作系统创建线程的函数,返回值为0表示创建成功。返回-1表示创建失败。
参数解析:

  • *- pthread_t thread
    他用来返回该线程的线程ID。每个线程都能够通过pthread_self()来获取自己的线程ID(pthread_t类型)。
  • *const pthread_attr_t attr:
    是线程的属性,其类型是pthread_attr_t类型,默认值为NULL。
  • ** void *(*start_routine) (void *)**:
    start_routine是一个函数指针,它指向的函数原型是 void *func(void *),这是所创建的子线程要执行的任务(函数);
  • void *arg:
    默认值为NULL,若上述函数需要参数,将参数放入结构中并将地址作为arg传入。

主线程和子线程的默认关系是:无论子线程执行完毕与否,一旦主线程执行完毕退出,所有
子线程执行都会终止。这时整个进程结束或僵死,部分线程保持一种终止执行但还未销毁的状态,而进程必须在其所有线程销毁后销毁,这时进程处于僵死状态。线程函数执行完毕退出,或以其他非常方式终止,线程进入终止态,但是为线程分配的系统资源不一定释放,可能在系统重启之前,一直都不能释放,终止态的线程,仍旧作为一个线程实体存在于操作系统中,什么时候销毁,取决于线程属性。在这种情况下,主线程和子线程通常定义以下两种关系:

  • 在任何一个时间点上,线程都是可分离或者可会合的。一个可会合的线程能够被其它线程收回其资源和杀死。在其他线程回收之前,他的存储器资源(例如栈)是不释放的。相反,一个分离的线程是不能被其他线程回收或者杀死,他的存储器资源在它终止时系统自动释放。
  • 在默认的情况下,线程是非分离状态的,这种情况下,原有的线程
    等待创建的线程结束,只有当pthread_join函数返回时,创建的线程才算终止,释放自己占用的系统资源,而分离线程没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。

四.多线程服务器流程

在这里插入图片描述

五.代码实例

多线程服务器代码:

/*********************************************************************************
 *      Copyright:  (C) 2021 jiaoer237
 *                  All rights reserved.
 *
 *       Filename:  socket_server_thread.c
 *    Description:  This file 
 *                 
 *        Version:  1.0.0(11/26/2021)
 *         Author:  yanp <2405204881@qq.com>
 *      ChangeLog:  1, Release initial version on "11/26/2021 10:19:29 AM"
 *                 
 ********************************************************************************/
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <getopt.h>
#include <pthread.h>
#include <ctype.h>

#define MSG_STR "Hello yanp\n"
#define BACKLOG 13

void printf_usage(char *program);/*打印帮助信息*/
int socket_init(char *listen_ip,int listen_port);/*socket 初始化(socket bind listen)*/
int thread_init(pthread_t *thread_id,void *(*start_routine) (void *),void *arg);
/*创建子线程并初始化线程属性*/
void *thread_worker(void *ctx);/*创建子线程之后需要处理的函数*/

int main(int argc,char **argv)
{
    int daemon_run=0;
    char *program;
    int serv_port=0;
    int listen_fd;
    int clifd=-1;
    int rv=-2;
    int opt;
    struct sockaddr_in cliaddr;
    socklen_t cliaddr_len;
    pthread_t tid;

    struct option long_options[] =/*参数解析主要设置监听端口和是否在后台运行*/
    {
        {"daemon", no_argument, NULL, 'b'},
        {"port", required_argument, NULL, 'p'},
        {"help", no_argument, NULL, 'h'},
        {NULL, 0, NULL, 0}
    };
    program=argv[0];
    while((opt = getopt_long(argc, argv, "bp:h", long_options, NULL)) != -1)
    {
        switch(opt)
        {
            case 'b':
                daemon_run=1;
                break;                     
            case'p': 
                serv_port = atoi(optarg);/* 将字符串转换成整型*/
                break;
            case 'h':
                printf_usage(program);
                break;
            default:
                 break;
            }
    }
    if(!serv_port)
    {
        printf_usage(argv[0]);
        return -1;
    }
               
    if((listen_fd=socket_init(NULL,serv_port))<0)/*socket初始化返回一个listenfd*/
    {
        printf("socket_init failure error:%s",strerror(errno));
        return -2;
    }
    if(daemon_run)/*设置在后台运行*/
    {
        daemon(0,0);
    }

    while(1)
    {
        printf("start accept new client income...\n");
        clifd=accept(listen_fd, (struct sockaddr *)&cliaddr, &cliaddr_len);/*主线程接收新的客户端的连接*/
        if(clifd < 0)
        {
            printf("Accept new client failure: %s\n", strerror(errno));
            continue;
        }
        printf("Accept new client[%s:%d] successfully\n", inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port));
        
        thread_init(&tid, thread_worker, &clifd);/*创建子线程并让子线程和client进行数据的收发*/
    }

    
}
        

void printf_usage(char *program)
{
    printf("使用方法:%s【选项】 \n", program);
    printf(" %s是一个服务器程序,用来等待客户端的连接\n",program);
    printf("\n传入参数\n");
    printf(" -b[daemon]设置程序在后台运行\n");
    printf(" -p[port ] 指定连接的端口号\n");
    printf(" -h[help ] 打印帮助信息\n");
    printf("\n例如: %s -b -p 8900\n", program);
    return;

}

int socket_init(char *listen_ip,int listen_port)
{
    int listenfd;
    struct sockaddr_in servaddr;                        
    if((listenfd=socket(AF_INET,SOCK_STREAM,0))<0)
    {
        printf("socket_server to create a TCP socket fd failure:[%s]\n",strerror(errno));
        return -1;
    }
    printf("create a tcp socket fd[%d] success\n",listenfd);
    int on=1;  
    if((setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)))<0)  
    {
        printf("setsockopt failure:%s",strerror(errno));
        return -2;
    }
    memset(&servaddr,0,sizeof(servaddr));
    servaddr.sin_family=AF_INET;
    servaddr.sin_port=htons(listen_port);
    if(!listen_ip)
    {
        servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
    }
    else                         
    { 
        servaddr.sin_addr.s_addr=htonl(listen_port);
    
    }                                               
    if(bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr))<0)
    {             
        printf("socket[%d] bind on port[%d] for ip address failure:%s\n",listenfd,listen_port,strerror(errno));
        return -2;
    }
    printf("socket[%d] bind on port[%d] for ip address success\n",listenfd,listen_port);
    listen(listenfd,BACKLOG);
    printf("start listen on port[%d]\n",listen_port);
    return listenfd;
}

int thread_init(pthread_t *thread_id,void *(*start_routine) (void *),void *arg)/*start_route是一个函数指针,指向返回值为void *类型,参数也是void类型的函数*/
{
    pthread_attr_t thread_attr;
    int rv = -1;

    if( pthread_attr_init(&thread_attr) )/*设置线程属性*/
    {
        printf("pthread_attr_init() failure: %s\n", strerror(errno));
        return -1;;
    }
    
    if( pthread_attr_setstacksize(&thread_attr, 120*1024) )
    {
        printf("pthread_attr_setstacksize() failure: %s\n", strerror(errno));
        return -2;
    }
    
    if( pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED) )
    {
        printf("pthread_attr_setdetachstate() failure: %s\n", strerror(errno));
        return -3;
    }

    if( pthread_create(thread_id, &thread_attr, start_routine, arg) )/*创建子线程并执行start_routine函数*/
    {
        printf("Create thread failure: %s\n", strerror(errno));
        return -4;
    }
    return 0;
}  

void *thread_worker(void *ctx)/*子线程处理和客户端数据交换的处理函数*/
{
    int cli_fd;
    int rv;
    char buf[1024];
    int i;

    if( !ctx )
    {
        printf("Invalid input arguments in %s()\n", __FUNCTION__);
        pthread_exit(NULL);
    }
    cli_fd = *(int *)ctx;/*强制类型转换成int *类型的并把ctx的值赋值给cli_fd*/

    printf("Child thread start to commuicate with socket client...\n");
    while(1)
    {
        memset(buf, 0, sizeof(buf));
        rv=read(cli_fd, buf, sizeof(buf));
        if( rv < 0)
        {
            printf("Read data from client sockfd[%d] failure: %s and thread will exit\n",cli_fd,strerror(errno));
            close(cli_fd);
            pthread_exit(NULL);
        }
        else if( rv == 0)
        {
            printf("Socket[%d] get disconnected and thread will exit.\n",cli_fd);
            close(cli_fd);
            pthread_exit(NULL);
        }
        else if( rv > 0 )
        {
            printf("Read %d bytes data from client[%d]: %s\n", rv,cli_fd, buf);
        }
        /*  convert letter from lowercase to uppercase */
        for(i=0; i<rv; i++)/*收到client发送的数据后把它转换成大写字母并发送给客户端*/
        {
            buf[i]=toupper(buf[i]);
        }
        rv=write(cli_fd, buf, rv);
        if(rv < 0)
        {
            printf("Write to client by sockfd[%d] failure: %s and thread will exit\n", cli_fd,strerror(errno));
            close(cli_fd);
            pthread_exit(NULL);
        }
    }
    
}

运行并测试:
在这里插入图片描述
客户端我们使用tcp test tools
在这里插入图片描述

  • 5
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值