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

發佈了89 篇原創文章 · 獲贊 13 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章