基于Linux的消息队列及多线程编程实现的聊天室(二)代码分析

先将代码贴出来,然后慢慢再解释.

@Makefile

OBJS := server client
all: $(OBJS)

server: msg_svr.c msg.h 
	gcc -o $@ $^ -D_DEBUG

client: msg_client.c msg.h
	gcc -o $@ $^ -lpthread

clean:
	$(RM) $(OBJS)

@msg.h

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#include <time.h>
#include <sys/msg.h>

#define   MSG_FILE "/tmp/msg_server"    
#define   BUFFER 255    
#define   PERM S_IRUSR | S_IWUSR    

#define 	OK			1 
#define 	ERR			0

// msg type
#define   TYPE_SERVER		1000
#define   TYPE_SERVER_STR	"1000"

// msg target string
#define   SERVER_STR		"SERVER"
#define   TO_ALL_STR		"ALL"

// send cmd
// L, I, O is send to server
#define 	CMD_LIST		'L'
#define 	CMD_LOGIN		'I'
#define 	CMD_LOGOUT		'O'
#define 	CMD_TOALL 		'A'

// C, F send to others
#define 	CMD_CHAT		'C'
#define 	CMD_SEND_FILE	'F'

// CMD:FROM:TIME:DATA 
#define		DATA_LEN	4
#define		OFT_CMD		0
#define		OFT_FRM		1
#define		OFT_TIM		2
#define		OFT_DAT		3
#define		DATA_TOK	":"

// USR_ID:USR_NAME:TIME
#define		USER_DATA_LEN	3
#define		OFT_USR_ID		0
#define		OFT_USR_NM		1
#define		OFT_LOGIN_TM	2
#define		USER_DATA_TOK	"#"

// id admin
#define 	START_ID	1

//-------------------------------------+
// List operations
struct list_head {
	struct list_head *next;
};

// init a new list named name
#define LIST_INIT(name)	\
	struct list_head name = {NULL}

static inline int list_add(struct list_head * head, struct list_head* new){
	new->next = head->next;
	head->next = new;
}

static inline int list_delete(struct list_head * head, struct list_head* target){
	while(head){
		if(head->next == target){
			head->next = target->next;
			return 0;
		}
		head = head->next;
	}
	return -1;
}
//-------------------------------------+

// online status
enum status{ online, offline, invisible };

// available id 
int available_id = START_ID;

// user struct to save user informations
struct user{
	struct list_head list;
	int id;
	char name[32];
	enum status status;;
	long login_time;
};

// message struct
struct message
{    
	long mtype;
	char buffer[BUFFER+1];    
}msg_snd, msg_rcv;    

int msgid = 0;

// send a format message to client
// CMD:FROM:TIME:DATA 
int send_msg(long type, int cmd, int from_id, char * data){
	sprintf(msg_snd.buffer, "%c:%d:%d:%s", cmd, from_id, time(NULL), data);
	msg_snd.mtype = type;
	if(msgsnd(msgid, &msg_snd, strlen(msg_snd.buffer)+1, 0) < 0)
		return ERR;
	else
		return OK;
}

inline char *time2str(long time, char* buf){
	struct tm *t = localtime(&time);
	strftime(buf, 32, "%Y-%m-%d-%H:%M:%S", t);
	return buf;
}

int init_msg_queue(){
	key_t key;
	if((key = ftok(MSG_FILE, 'a')) == -1){
		perror("ftok");
		exit(1);
	} 

	printf("Key:%d\n", key);
	if((msgid = msgget(key, PERM | IPC_CREAT)) == -1)
	{   
		perror("msgget");
		exit(1);
	}       
	printf("msgid = %d\n", msgid);
	return msgid;
}       

int process_msg(char* buffer);  



@msg_svr.c

/* msg_svr.c */  

#include "msg.h"   

LIST_INIT(msg_list_head); 

int main()    
{    
	msgid = init_msg_queue();

	while(1)   
	{
		if(msgrcv(msgid, &msg_rcv, sizeof(struct message), TYPE_SERVER, 0) == -1)
			perror("msgrcv");
		else
			printf("Get: %s\n", msg_rcv.buffer);

		// process message
		process_msg(msg_rcv.buffer);
	}    
	exit(0);    
}

// process message received from message queue
// message format:
// CMD:TARGET:FROM:TIME:DATA
int process_msg(char* buffer){
	char * data[DATA_LEN];
	char * str, *subtoken;
	int i;
	memset(data, NULL, sizeof(data));
	for(str = buffer, i = 0; ; str = NULL, i++){
		subtoken = strtok(str, DATA_TOK);
		if(subtoken == NULL)
			break;
		data[i] = subtoken;
#ifdef _DEBUG
		printf("> data[%d] = %s\n", i, subtoken);
#endif
	}
	// data format error
	if(i != DATA_LEN)
		return ERR;

	char info[256];
	char buf[256];
	struct user* u ;
	struct list_head* p;

	// send to server cmd
	switch(data[OFT_CMD][0]){
	case CMD_LIST:
		bzero(buf, sizeof(buf));
		p = (&msg_list_head)->next;
		while(p){
			u= (struct user*)p;
			sprintf(info, "%d#%s#%ld#", u->id, u->name, u->login_time);
#ifdef _DEUBG
			printf("u->name = %s\n", u->name);
#endif
			strcat(buf, info);
			p = p->next;
		}
		if(p != msg_list_head.next){
			// delete the end '#'
			buf[strlen(buf) - 1] = 0;
		}
		send_msg(atol(data[OFT_FRM]), CMD_LIST, TYPE_SERVER, buf);
		break;

	case CMD_LOGIN:
		u = (struct user *)malloc(sizeof(struct user));
		if(NULL == u){
			perror("malloc");
			return ERR;
		}
		// add to list
		list_add(&msg_list_head, &(u->list));
		u->id = available_id++;
		strcpy(u->name, data[OFT_FRM]);
		u->status = online;
		u->login_time = atol(data[OFT_TIM]);

		// login ok echo msg
		msg_snd.mtype = atol(data[OFT_DAT]);                
		sprintf(msg_snd.buffer, "%d", u->id);
		msgsnd(msgid, &msg_snd, strlen(buf)+1, 0);  
		break;

	case CMD_LOGOUT:
		p = (&msg_list_head)->next;
		while(p){
			u= (struct user*)p;
			// find the user who request logout
			if(u->id == atoi(data[OFT_FRM])){
				if(send_msg(u->id, CMD_LOGOUT, TYPE_SERVER, "Logout OK!") == OK){
					list_delete(&msg_list_head, p);
					free(p);
				}else{
					return ERR;
				}
				break;
			}
			p = p->next;
		}
		break;

	case CMD_TOALL:
		// send to all online client
		p = (&msg_list_head)->next;
		while(p){
			u= (struct user*)p;
			send_msg(u->id, CMD_CHAT, data[OFT_FRM], data[OFT_DAT]);
			p = p->next;
		}
		break;
	default:
		return ERR;
	}
	return OK;
}

@msg_client.c

/* msg_client.c */  
#include "msg.h"   

int userid = 0;

char name[32] = "";

void print_menu(void){
	printf("\t+----------------------------------+\n");
	printf("\t+    Chat Room V1.0 2013.01.08     +\n");
	printf("\t+----------------------------------+\n");
	printf("\t+ User Commands as follows:        +\n");
	printf("\t+                                  +\n");
	printf("\t+ l: list all online user          +\n");
	printf("\t+ i: Login                         +\n");
	printf("\t+ o: logOut                        +\n");
	printf("\t+ c: Chat with other online user   +\n");
	printf("\t+ a: Chat with all online user     +\n");
	printf("\t+ f: transfer a File to others     +\n");
	printf("\t+ h: Help                          +\n");
	printf("\t+----------------------------------+\n");
}

int get_choice(){
	printf("%s# ", name);
	int answer = getchar();	// eat <Enter>
	while(getchar() != '\n');	// eat <Enter>
	//putchar(answer);
	return answer;
}

void func(int sig){
	printf("\n%s# ", name);
	fflush(stdout);
}

int send_to(int target, int cmd, char *data){
	return send_msg(target, cmd, userid, data);
}

int send_server(int cmd, char *data){
	return send_msg(TYPE_SERVER, cmd, userid, data);
}

int chat(){
	if(strlen(name) == 0){
		printf("You are not login!\n");
		return ERR;
	}

	char id[32];
	char data[256];
	char buf[256];
	printf("To: [USR_ID] ");
	fflush(stdout);
	if(fgets(id, sizeof(id), stdin) == NULL){
		perror("fgets");
		return ERR;
	}
	sprintf(data, " %s > ", name);
	id[strlen(id) - 1] = 0;

	printf(">> ");
	fflush(stdout);
	if(fgets(buf, sizeof(buf), stdin) == NULL){
		perror("fgets");
		return ERR;
	}
	strcat(data, buf);
	data[strlen(data) - 1] = 0;
	send_to(atoi(id), CMD_CHAT, data);
}

int chat_all(){
	if(strlen(name) == 0){
		printf("You are not login!\n");
		return ERR;
	}

	char data[256];
	char buf[256];
	sprintf(data, " %s To all > ", name);

	printf("To all >> ");
	fflush(stdout);
	if(fgets(buf, sizeof(buf), stdin) == NULL){
		perror("fgets");
		return ERR;
	}
	strcat(data, buf);
	data[strlen(data) - 1] = 0;
	send_to(TYPE_SERVER, CMD_TOALL, data);
}

int login(){
	printf("username: \n");
	if(fgets(name, sizeof(name), stdin) == NULL){
		perror("fgets");
		return ERR;
	}
	name[strlen(name) - 1] = 0;
	int rand_type = random();
	time_t t;
	time(&t);
	sprintf(msg_snd.buffer, "%c:%s:%ld:%d", CMD_LOGIN, name, t, rand_type);
#ifdef _DEBUG
	printf("%s\n", msg_snd.buffer);
#endif
	// get a random type to login server
	msg_snd.mtype = TYPE_SERVER;
	if(msgsnd(msgid, &msg_snd, strlen(msg_snd.buffer)+1, 0) < 0){
		perror("msgsnd");
		return ERR;
	}

	// wait server response
	if(msgrcv(msgid, &msg_rcv, sizeof(msg_rcv), rand_type, 0) < 0){
		return ERR;
	} else{
		userid = atol(msg_rcv.buffer);
		printf("Login OK id = %d\n", userid);
		return OK;
	}
}

int logout(){
	if(strlen(name) == 0){
		return ERR;
	}
	send_server(CMD_LOGOUT, "Logout");
	// wait server response
	if(msgrcv(msgid, &msg_rcv, sizeof(msg_rcv), userid, 0) < 0){
		return ERR;
	}else{
		userid = 0;
		printf("Logout OK\n");
		return OK;
	}
}

void format_user_list(char * buffer){
	char * data[USER_DATA_LEN];
	char * str, *subtoken;
	char time_buf[24];
	int i, n;
	memset(data, NULL, sizeof(data));
	printf("----------------------------------\n");	
	printf(" _ID_  USR_NAME  Login_Time \n");	
	printf("----------------------------------\n");	
	for(str = buffer, i = 0, n = 1; ; str = NULL, i++){
		subtoken = strtok(str, USER_DATA_TOK);
		if(subtoken == NULL)
			break;
		data[i] = subtoken;
#ifdef _DEBUG
		printf("> data[%d] = %s\n", i, subtoken);
#endif
		if(i != 0 && i % 2 == 0){
			printf(" %4s  %8s  %s\n", data[OFT_USR_ID], data[OFT_USR_NM], time2str(atol(data[OFT_LOGIN_TM]), time_buf));	
			n++;
			i = -1;
		}
	}
}

void * receiver_looper(void * p){
	if(userid == 0)
		return NULL;
	char * data[DATA_LEN];
	char * str, *subtoken;
	int i;
	while(1){
		if(msgrcv(msgid, &msg_rcv, sizeof(msg_rcv), userid, 0) < 0){
			perror("msgrcv");
			continue;
		}else{

#ifdef _DEBUG
			printf("%s received: %s\n", __func__, msg_rcv.buffer);
#endif
			memset(data, NULL, sizeof(data));
			for(str = msg_rcv.buffer, i = 0; ; str = NULL, i++){
				subtoken = strtok(str, DATA_TOK);
				if(subtoken == NULL)
					break;
				data[i] = subtoken;
#ifdef _DEBUG
				printf("> data[%d] = %s\n", i, subtoken);
#endif
			}
			// process received data
			// data format error
			if(i != DATA_LEN)
				continue;

			switch(data[OFT_CMD][0]){
			case CMD_LIST:
				if(strcmp(data[OFT_FRM], TYPE_SERVER_STR)){
					continue;
				}
				format_user_list(data[OFT_DAT]);
				break;
			case CMD_LOGOUT:
				if(strcmp(data[OFT_FRM], TYPE_SERVER_STR)){
					continue;
				}
				printf("> %s ", data[OFT_DAT]);
				printf("%s\n", time2str(atol(data[OFT_TIM]), data[OFT_DAT]));
				exit(0);
			case CMD_CHAT:		// print chat content
				printf("\n%s \n\t\t", data[OFT_DAT]);
				printf("%s\n", time2str(atol(data[OFT_TIM]), data[OFT_DAT]));
				printf("\n%s# ", name);
				fflush(stdout);
				break;
			case CMD_SEND_FILE:
				break;
			}
		}
	}
}


int main(int argc, char **argv)    
{    
	signal(SIGINT, func);
	//	signal(SIGQUIT, func);

	msgid = init_msg_queue();

	pthread_t thread;
	// print help menu
	print_menu();
	while(1){
		switch(get_choice()){
		case 'i':
			if(login() == OK)
			    while(pthread_create(&thread, NULL, receiver_looper, NULL) < 0);
			break;
		case 'l':
			send_server(CMD_LIST, "List");
			sleep(1);
			break;
		case 'o':
			if(logout() == OK){
				pthread_join(thread, NULL);
				return 0;
			}else{
				printf("Not login quit\n");
				return 0;
			}
			break;
		case 'c':
			chat();
			break;
		case 'a':
			chat_all();
			break;
			break;
		case 'h':
			print_menu();
			break;
		case 'f':
			break;
		}
	}
}     



你可能感兴趣的:(基于Linux的消息队列及多线程编程实现的聊天室(二)代码分析)