Linux下的TCP/IP编程----线程及多线程服务端

之前有讲过进程及多进程服务端的实现,现在我们来看看更为广泛而且实用的线程及多线程服务端的实现。


那么什么是线程呢?

线程是操作系统能够进行运算调度的最小单位,它被包涵在进程之中,是行程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并行多个线程,每条线程并行执行不同的任务。

这是比较正式的解释,简单点来说,线程就是进程的更进一步的细化。

由于进程代表的是一个正在运行的程序,所以进程的切换其实也就是程序的切换,而在切换过程中要移除当前运行进程在内存中的数据,移入将要运行的进程所需要的数据,所以进程的切换会消耗很多的系统资源,尤其是请求量大时,cpu必须来回切换不同的进程。而线程的出现解决了这一问题,线程是比进程更小的执行单元,一个进程中可以包含若干个线程,每一个线程有一个任务执行的顺序。这样,线程本身并不保有资源,而是在需要时向所在的进程进行申请,以满足运行需要。这样我们就可以用线程来实现多客户端的访问,每个访问都放到一个线程中去执行。


线程的创建和销毁:

int pthread_create(pthread_t * restrict thread , const pthread_attr_t * restrict attr, void * (* struct_routine)(void ) , void restrict arg):创建一个新线程

  • thread(线程):保存新创建的线程ID的变量地址,用于区分
  • attr(参数):用于创建线程时指定一些参数,传入null时使用默认参数
  • start_routine(函数指针):用于保存新线程所执行的函数的地址,相当于新线程中main()函数的地址
  • arg(变量指针):第三个参数中函数所传入的参数的地址值,即新线程中main()函数中参数的地址值

成功时返回0,失败时返回其他值

int pthread_detach(pthread_t thread):线程销毁

  • thread(线程ID):要销毁的线程id

成功时返回0,失败时返回其他值

int pthread_join(pthread_t thread, void ** status):线程联合

  • thread(线程):该参数值id的线程终止时才会从该函数返回
  • status(结果):用于保存线程main()函数返回值的地址

成功时返回0,失败时返回其他值


pthread_join和pthread_detach的区别:

调用pthread_join函数的线程会进入等待状态,直到指定id的线程结束,而且可以得到结束线程的返回值
而调用pthread_detach函数不会使得调用该函数的线程进入等待状态,起作用也是可以释放已经运行结束的线程所占用的内存空间

线程同步:

由于线程所调用的资源是同一个进程内的资源,所以当线程数较多时可能会发生线程对资源的争夺,若是不对其进行规则的约束,则有可能导致程序出错,产生严重后果。例如:两个线程同时对一个变量X进行++的运算,当两个线程读取到初始变量后,若线程A先运行,并已经给X执行了++,但是结果尚未写回X中,此时线程B立即抢占上了cpu,并且给X执行++,而且结果写回X中,最后A又把结果写回X中。在这个各流程中,线程A和B其实发生来数据的覆盖,导致本应该+2的变量最后只+1,这就是在使用线程时会遇到的问题,而这也是线程同步的来源。而在程序中,容易发生线程同步问题的语句称为临界区。

解决线程同步常见的是两种方式:互斥量和信号量

互斥量:如名字所示,互斥,即不允许多个线程同时对临界区进行访问,当一个线程在访问时,另一个必须要进行等待。只有当另一个线程释放互斥量时,等待中的线程才能访问资源。

信号量:可以用来保证两个或多个关键代码段不被并发调用。在进入一个关键代码段之前,线程必须获取一个信号量;一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待直到第一个线程释放信号量。

我们可以通过这两种机制来很好的保证线程执行的同步。

int pthread_mutex_init(pthread_mutex_t * mutex , const pthread_mutexattr_t * attr):初始化互斥量

  • mutex(型号量):创建互斥信号量时传递保存互斥量的变量地址。
  • attr(互斥量属性):传入null时创建默认的互斥量

成功时返回0,失败时返回其他值

int pthread_mutex_destroy(pthread_mutex_t * mutex)销毁互斥量

  • mutex(互斥量):要销毁的互斥量

成功时返回0,失败时返回其他值

int pthread_mutex_lock(pthread_mutex_t * mutex):锁定互斥量

  • mutex(信号量):要操作的互斥量

成功时返回0,失败时返回其他值

int pthread_mutex_unlock(pthread_mutex_t * mutex):解锁互斥量

  • mutex(信号量):要操作的互斥量

成功时返回0,失败时返回其他值

例如:

pthread_mutex_lock(&mutex);
//临界区开始
......
.....
..
//临界区结束
pthread_mutex_unlock(&mutex);

int sem_init(sem_t * sem , int pthread , unsigned int value):初始化信号量

  • sem(信号量):创建信号量时保存信号量的变量地址值
  • pthread(控制参数):若传入0,则只能在一个进程中使用,若是其他值,则可以由多个信号量共享
  • value(初始值):指定创建的信号量的初始值

成功时返回0,失败时返回其他值

int sem_destroy(sem_t * sem):销毁信号量

  • sem(信号量):要销毁的信号量

成功时返回0,失败时返回其他值

int sem_post(sem_t * sem)释放信号量

  • sem(信号量):要释放的信号量

成功时返回0,失败时返回其他值

int sem_wait(sem_t * sem)申请信号量

  • sem(信号量):要申请的信号量

成功时返回0,失败时返回其他值

例如:

sem_wait(&sem)//信号量减为0
//临界区域开始
........
.......
....
...
//临界区域结束
sem_post(&sem)//信号量增1

在了解了线程以及线程使用中需要注意的问题之后可以改写一下服务端的程序,使其通过多线程的方式来实现并发访问。

chat_setver.c

#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<semaphore.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>

#define BUFF_SIZE 100
#define MAX_CLNT 256

//客户端处理函数
void *handle_clent(void *arg);
//消息发送函数
void send_message(char * msg,int len);
//出错处理函数
void error_handling(char *msg);

//定义全局的客户端socket变量
int clnt_cnt=0;
//定义全局的socket数组,保存客户端套接字
int clnt_socks[MAX_CLNT];
//定义的全局的互斥信息号量
pthread_mutex_t  mutx;

int main(int argc,char *argv[]){
    int server_sock,client_sock;

    struct sockaddr_in server_addr;
    struct sockaddr_in client_addr;

    int client_addr_size;
    //用于记录线程id
    pthread_t t_id;
    if(argc!=2){
        printf("Usage : %s <port>\n",argv[0]);
        exit(1);
    }
    //初始化一个互斥信号量
    pthread_mutex_init(&mutx,NULL);

    server_sock = socket(PF_INET,SOCK_STREAM,0);

    memset(&server_addr,0,sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(atoi(argv[1]));

    if(bind(server_sock,(struct sockaddr *)&server_addr,sizeof(server_addr)) == -1){
        error_handling("bind() error");
    }

    if(listen(server_sock,5) == -1){
        error_handling("listen() error");
    }

    while(1){
        client_addr_size = sizeof(client_addr);
        client_sock = accept(server_sock,(struct sockaddr *)&client_addr,&client_addr_size);

        //上锁
        pthread_mutex_lock(&mutx);
        //将接收到的客户端socket保存到全局变量中
        clnt_socks[clnt_cnt++] = client_sock;
        //创建一个新的线程,用于处理接收到的客户端
        pthread_create(&t_id,NULL,handle_clent,(void *)&client_sock);
        //处理线程的结束
        pthread_detach(t_id);
        printf("Connected client IP: %s \n",inet_ntoa(client_addr.sin_addr));
    }

    close(server_sock);
return 0;
}

/**
用于处理接收到的客户端
**/
void *handle_clent(void *arg){
    //将参数强转成int型
    int clnt_sock = *((int *)arg);

    int str_len = 0 ,i;
    char msg[BUFF_SIZE];

    while((str_len = read(clnt_sock,msg,sizeof(msg))) != 0){
        send_message(msg,str_len);
    }
    //上锁,修改全局的socket数组,去除当前的socket客户端
    pthread_mutex_lock(&mutx);
    for(i=0;i<clnt_cnt;i++){
        if(clnt_sock == clnt_socks[i]){
            while(i++<clnt_cnt-1){
                clnt_socks[i] = clnt_socks[i+1];
            }
            break;
        }
    }
    //客户端数量修改
    clnt_cnt--;
    pthread_mutex_unlock(&mutx);
    close(clnt_sock);
    return NULL;
    }

/**
    用于消息处理,给所有已连接的客户端发送消息
**/
    void send_message(char *msg,int len){
        int i;
        //上锁,使用全局的clnt_socks变量来输出数据
        pthread_mutex_lock(&mutx);
        //遍历客户端数组
        for(i = 0;i<clnt_cnt;i++){
            write(clnt_socks[i],msg,len);
        }
        //解锁
        pthread_mutex_unlock(&mutx);
    }

void error_handling(char * message){
    fputs(message,stderr);
    fputc('\n',stderr);
    exit(1);
}

chat_client.c

#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<semaphore.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>

#define BUFF_SIZE 100
#define NAME_SIZE 20


void * send_msg(void *arg);
void * recv_msg(void *arg);
void  error_handling(char *msg);

char name[NAME_SIZE]="[DEFAULT]";
char msg[BUFF_SIZE];

int main(int argc ,char * argv[]){
    int sock;
    struct sockaddr_in server_addr;
    pthread_t send_thread;
    pthread_t recv_thread;
    void *thread_return;

    if(argc != 4){
        printf("Usage : %s <port> <name>\n",argv[0]);
        exit(1);
    }

    sprintf(name,"[%s]",argv[3]);
    sock = socket(PF_INET,SOCK_STREAM,0);

    memset(&server_addr,0,sizeof(server_addr));
    server_addr.sin_family=AF_INET;
    server_addr.sin_addr.s_addr = inet_addr(argv[1]);
    server_addr.sin_port = htons(atoi(argv[2]));

    if(connect(sock,(struct sockaddr *)&server_addr,sizeof(server_addr))  == -1){
        error_handling("connect() error");
    }

    pthread_create(&send_thread,NULL,send_msg,(void *)&sock);
    pthread_create(&recv_thread,NULL,recv_msg,(void *)&sock);
    pthread_join(send_thread,&thread_return);
    pthread_join(recv_thread,&thread_return);

    close(sock);
return 0;
}

/**
用于发送消息的线程
**/
void * send_msg(void *arg){
    int sock=*(((int *)arg));
    char name_msg[NAME_SIZE+BUFF_SIZE];

    while(1){
        fgets(msg,BUFF_SIZE,stdin);
        if(!strcmp(msg,"q\n") || !strcmp(msg,"Q\n")){
            close(sock);
            exit(0);
        }
        sprintf(name_msg," %s %s",name,msg);
        write(sock,name_msg,strlen(name_msg));
    }
    return NULL;
}

/**
    用于接收数据的线程
**/
void * recv_msg(void *arg){
    int sock=*(((int *)arg));
    char name_msg [NAME_SIZE+BUFF_SIZE];
    int str_len;

    while(1){
        str_len=read(sock,name_msg,NAME_SIZE+BUFF_SIZE-1);
        if(str_len == -1){
            return (void *) -1;
        }
        name_msg[str_len] = 0;
        fputs(name_msg,stdout);
    }
    return NULL;

}

void error_handling(char * message){
    fputs(message,stderr);
    fputc('\n',stderr);
    exit(1);
}

最后编译运行:

编译客户端:gcc chat_client.c -DREENTRANT -o chat_client -lpthread
编译服务端: gcc chat_server.c -D_REENTRANT -o chat_server -lpthread

运行服务端:./chat_server 9090
运行客户端:./chat_client 127.0.0.1 9090 wei

这样就完成了一个简易基于多线程的服务端/客户端程序

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值