18.2 多线程服务器端的实现2

1. 线程同步

a. 需要线程同步的情况:1)同时访问同一内存空间;2)需要指定访问同一内存空间的线程执行顺序。

b. 同步技术:互斥量Mutex和信号量Semaphore


将临界区比喻成洗手间,线程同步理解成一把锁。

为了保护个人隐私,进洗手间时锁上门,出来再打开;

如果有人使用洗手间,其他人需要在外面等待;

等待的人数可能很多,这些人需要排队进入洗手间。


2. 互斥量Mutual Exclusion,创建和销毁函数

#include <pthread.h>

int pthread_mutex_init(pthread_mutex_t * mutex,const pthread_mutexattr_t * attr);

int pthread_mutex_destroy(pthread_mutex_t * mutex);

成功返回0,失败返回其他值
mutex:创建/销毁互斥量时传递保存互斥量的变量地址值
attr:创建的互斥量属性


创建互斥量时,如果第二个参数为NULL,也可以通过宏PTHREAD_MUTEX_INITIALIZER创建。

但是最好还是使用函数,宏比较难检查错误。

3. 互斥量锁住、释放临界区

int pthread_mutex_lock(pthread_mutex_t * mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

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


注意:临界区锁住后,忘记解锁,则其他尝试进入临界区的线程将会“死锁”。


4. 信号量的创建、销毁

int sem_init(sem_t *sem,int pshared,unsigned int value);
int sem_destroy(sem_t * sem);

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

sem:创建信号量时传递保存信号量的变量地址值
pshared:传递其他值时,可创建多个进程共享的信号量。0时,只允许一个进程内部使用该信号量
value:指定信号量初始值。

5. post/wait

int sem_post(sem_t *sem);
int sem_wait(sem_t *sem);

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

sem:传递保存信号量读取值得变量地址值


调用sem_init时,操作系统创建信号量对象,并赋初始值。

调用sem_post函数值,对象+1,sem_wait函数时-1.

信号量的值不能小于0,在信号量为0的情况,sem_wait函数会进入阻塞直到值大于0。


6. 关于控制访问顺序的同步

代码:

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>

void * read(void * arg);
void * accu(void * arg);

static sem_t sem_one;
static sem_t sem_two;
static int num;

int main(int argc, char *argv[]){
	pthread_t id_t1,id_t2;

	sem_init(&sem_one,0,0);
	sem_init(&sem_two,0,1);

	pthread_create(&id_t1,NULL,read,NULL);
	pthread_create(&id_t2,NULL,accu,NULL);

	pthread_join(id_t1,NULL);
	pthread_join(id_t2,NULL);

	sem_destroy(&sem_one);
	sem_destroy(&sem_two);

	return 0;
}


void * read(void * arg){
	int i;

	for(i=0;i<5;i++){
		printf("Input num %d: ",i+1);
		sem_wait(&sem_two);
		scanf("%d",&num);
		sem_post(&sem_one);
	}

	return NULL;
}

void *accu(void *arg){
	int sum = 0,i;
	for(i=0;i<5;i++){
		sem_wait(&sem_one);
		sum += num;
		printf("sum = %d \n",sum);
		sem_post(&sem_two);
	}

	printf("result : %d \n",sum);

	return NULL;
}

执行结果:

alex@alex-VirtualBox:~/Share/Test/tcpip$ ./sema
Input num 1: 123
sum = 123
Input num 2: 123
Input num 3: sum = 246
1
Input num 4: sum = 247
1
Input num 5: sum = 248
1
sum = 249
result : 249

7. 线程的销毁

线程不是在线程main返回时自动销毁,而是使用下面的方法销毁,不然会一直存在内存空间:

a. pthread_join函数//线程一直等待,阻塞

b. pthread_detach函数//通常使用这种方法销毁线程,不会引起线程终止或进入阻塞。

调用个了pthread_detach后,不能再调用pthread_join

int pthread_detach(pthread_t thread);

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

8. 多线程并发服务器端的实现

chat_server.c

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

#define BUF_SIZE 	100
#define MAX_CLNT	256

void * handle_clnt(void * arg);
void send_msg(char *msg,int len);
void error_handling(char *message);

int clnt_cnt = 0;
int clnt_socks[MAX_CLNT];
pthread_mutex_t mutx;

int main(int argc, char *argv[]){
	int serv_sock;
	int clnt_sock;
	struct sockaddr_in serv_adr;
	struct sockaddr_in clnt_adr;
	int clnt_adr_sz;
	pthread_t t_id;

	if(argc != 2){
		printf("Usage : %s <port>\n",argv[0]);
		exit(1);
	}
	pthread_mutex_init(&mutx,NULL);
	
	serv_sock = socket(PF_INET,SOCK_STREAM,0);
	if(serv_sock == -1){
		error_handling("socket() error");
	}

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

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

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

	while(1){
		clnt_adr_sz = sizeof(clnt_adr);
		clnt_sock = accept(serv_sock,(struct sockaddr*)&clnt_adr,&clnt_adr_sz);
		pthread_mutex_lock(&mutx);
		clnt_socks[clnt_cnt++] = clnt_sock;
		pthread_mutex_unlock(&mutx);

		pthread_create(&t_id,NULL,handle_clnt,(void *)&clnt_sock);
		pthread_detach(t_id);
		printf("Connetcted client IP :%s \n",inet_ntoa(clnt_adr.sin_addr));
	}

	close(serv_sock);

	return 0;
}

void error_handling(char *message){

	fputs(message,stderr);
	fputs("\n",stderr);
	exit(1);
}

void * handle_clnt(void * arg){
	int clnt_sock = *((int *)arg);
	int str_len =0;
	int i;
	char msg[BUF_SIZE];

	while((str_len = read(clnt_sock,msg,sizeof(msg))) != 0)
		send_msg(msg,str_len);

	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;
}

//send to all
void send_msg(char *msg,int len)
{
	int i;
	
	pthread_mutex_lock(&mutx);
	for(i=0;i<clnt_cnt;i++){
		write(clnt_socks[i],msg,len);
	}

	pthread_mutex_unlock(&mutx);
}

执行结果:

alex@alex-VirtualBox:~/Share/Test/tcpip$ gcc chat_server.c -o server -D_REENTRANT -lpthread
alex@alex-VirtualBox:~/Share/Test/tcpip$ ./server 9190
Connetcted client IP :127.0.0.1
Connetcted client IP :192.168.1.104

chat_client.c

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

#define BUF_SIZE	100
#define NAME_SIZE	20

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

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

int main(int argc,char *argv[]){
	int sock;
	struct sockaddr_in serv_adr;
	pthread_t snd_thread,rcv_thread;
	void * thread_return;

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

	sprintf(name,"[%s]",argv[3]);
	sock = socket(PF_INET,SOCK_STREAM,0);
	if(sock == -1){
		error_handling("socket() error");
	}
	
	memset(&serv_adr,0,sizeof(serv_adr));
	serv_adr.sin_family = AF_INET;
	serv_adr.sin_addr.s_addr = inet_addr(argv[1]);
	serv_adr.sin_port = htons(atoi(argv[2]));

	if(connect(sock,(struct sockaddr *)&serv_adr,sizeof(serv_adr)) == -1){
		error_handling("connect() error\r\n");
	}else{
		printf("Connected....");
	}

	pthread_create(&snd_thread,NULL,send_msg,(void *)&sock);
	pthread_create(&rcv_thread,NULL,recv_msg,(void *)&sock);

	pthread_join(snd_thread,&thread_return);
	pthread_join(rcv_thread,&thread_return);

	close(sock);
	return 0;
}

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

void * send_msg(void *arg){
	int sock = *((int *)arg);
	char name_msg[NAME_SIZE + BUF_SIZE];

	while(1){
		fgets(msg,BUF_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 + BUF_SIZE];
	int str_len;

	while(1){
		str_len = read(sock,name_msg,NAME_SIZE+BUF_SIZE-1);

		if(str_len == -1){
			return (void *) -1;
		}
	
		name_msg[str_len] = 0;
		fputs(name_msg,stdout);
	}
	
	return NULL;
}

执行结果:


alex@alex-VirtualBox:~/Share/Test/tcpip$ gcc chat_client.c -o client -D_REENTRANT -lpthread
alex@alex-VirtualBox:~/Share/Test/tcpip$ ./client 192.168.1.104 9190 alex
Connected....[stoney] hello
hi
[alex] hi


alex@alex-VirtualBox:~/Share/Test/tcpip$ ./client 127.0.0.1 9190 stoney
Connected....hello
[stoney] hello
[alex] hi

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值