1. 程式人生 > >GTK+實現linux聊天室程式碼詳解-clientr端

GTK+實現linux聊天室程式碼詳解-clientr端

檢視原文請點選此超連結

提供給識字的人學習。

#include <gtk/gtk.h>	// 標頭檔案
#include <string.h>
#include <stdio.h>
#include <netinet/in.h> //定義資料結構sockaddr_in
#include <sys/socket.h> //定義socket函式以及資料結構
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <netdb.h>
#include<sys/sem.h>
#include <unistd.h>
#include <pthread.h> 
#include <uuid/uuid.h>
GtkWidget *window;   //登入視窗
GtkWidget *home;	//主視窗
int clientfd,b_file;
struct sockaddr_in clientaddr; 
char user_name[50];
char fname[]="/var/tmp/";
int sem_id; 
//↓↓↓↓↓↓↓↓以下為實驗8講稿內sem_com.c定義資訊量操作的程式碼↓↓↓↓↓↓↓↓↓↓↓
//初始化訊號量。
int init_sem(int sem_id, int init_value){
	union semun{
		int val;
		struct semid_ds *buf;
		unsigned short *array;
	};
	union semun sem_union;
	sem_union.val = init_value;
	if(semctl(sem_id, 0, SETVAL, sem_union) == -1){
		perror("Initialize semaphore");    //把"Initialize semaphore"輸出到標準錯誤stderr。
		return -1;
	}
	return 0;
}
//刪除訊號量。
int del_sem(int sem_id){
	union semun{
		int val;
		struct semid_ds *buf;
		unsigned short *array;
	};
	union semun sem_union;
	if(semctl(sem_id, 1, IPC_RMID, sem_union)==-1){
		perror("Delete semaphore");    //把"Delete semaphore"輸出到標準錯誤stderr。
		return -1;
	}
}
//p操作函式。
int sem_p(int sem_id){
	struct sembuf sem_b;
	sem_b.sem_num = 0;
	sem_b.sem_op = -1;
	sem_b.sem_flg = SEM_UNDO;
	if(semop(sem_id, &sem_b, 1)==-1){
		perror("P operation");    //把"P operation"輸出到標準錯誤stderr。
		return -1;
	}
	return 0;
}
//v操作函式。
int sem_v(int sem_id){
	struct sembuf sem_b;
	sem_b.sem_num = 0;
	sem_b.sem_op = 1;
	sem_b.sem_flg = SEM_UNDO;
	if(semop(sem_id, &sem_b, 1) == -1){
		perror("V operation");    //把"V operation"輸出到標準錯誤stderr。
		return -1;
	}
	return 0;
}
//↑↑↑↑↑↑↑↑↑↑↑以上為實驗8講稿內sem_com.c定義資訊量操作的程式碼↑↑↑↑↑↑↑↑↑↑↑

//處理登入
void deal_pressed(GtkWidget *button, gpointer entry){
	int sendbytes;
	char *buff;
	struct hostent *host;
	char wel[]="Welcome";
	host = gethostbyname("127.0.0.1");   //本地地址,可改為伺服器埠地址。
	buff = (char *)malloc(9);    //向系統申請分配指定9個位元組的記憶體空間。
	
	const gchar  *text = gtk_entry_get_text(GTK_ENTRY(entry));
	if(strlen(text)==0){
		printf("不能為空\n");         // 提示 不能為空
	}
	else{	
		if ((clientfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
		{
			perror("fail to create socket");    //把"fail to create socket"輸出到標準錯誤stderr。
			exit(1);
		}
		bzero(&clientaddr, sizeof(clientaddr));    //置clientaddr的前sizeof(clientaddr)個位元組為零且包括‘\0’。
		clientaddr.sin_family = AF_INET;    //伺服器地址族為對於TCP/IP協議的AF_INET。
		clientaddr.sin_port = htons((uint16_t)atoi("8787"));    //伺服器埠將小端模式改變為大端模式。
		clientaddr.sin_addr = *((struct in_addr *)host->h_addr);    //伺服器地址為0.0.0.0,即任意地址。
	
		if (connect(clientfd, (struct sockaddr *)&clientaddr, sizeof(struct sockaddr)) == -1)
		{
			perror("fail to connect");    //把"fail to connect"輸出到標準錯誤stderr。
			exit(1);
		}
		if ((sendbytes = send(clientfd, text, strlen(text), 0)) == -1)
		    {
			perror("fail to send");    //把"fail to send"輸出到標準錯誤stderr。
			exit(1);
		    }

		if (recv(clientfd, buff, 7, 0) == -1)
		{
			perror("fail to recv");    //把"fail to recv"輸出到標準錯誤stderr。
			exit(1);
		}
		if(strcmp(buff,wel)==0){
			strcpy(user_name,text);
			gtk_widget_destroy(window);		
		}else{
			//  彈窗 提醒 提示 暱稱重複
			GtkWidget *dialog;
			dialog = gtk_message_dialog_new((gpointer)window,
			GTK_DIALOG_DESTROY_WITH_PARENT,
			GTK_MESSAGE_ERROR,
			GTK_BUTTONS_OK,
			"暱稱重複,拒絕登入");
			gtk_window_set_title(GTK_WINDOW(dialog), "拒絕");
			gtk_dialog_run(GTK_DIALOG(dialog));
			gtk_widget_destroy(dialog);	
			close(clientfd);
		}
	}
}
//登入介面
void login(int argc,char *argv[]){
	// 初始化。
	gtk_init(&argc, &argv);		
	// 建立頂層視窗。
	window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
	// 設定視窗的標題。
	gtk_window_set_title(GTK_WINDOW(window), "登入");
	// 設定視窗在顯示器中的位置為居中。
	gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
	// 設定視窗的最小大小。
	gtk_widget_set_size_request(window, 300, 200);
	// 固定視窗的大小。
	gtk_window_set_resizable(GTK_WINDOW(window), FALSE); 
	// "destroy" 和 gtk_main_quit 連線。
	g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
	//建立一個固定容器。
 	GtkWidget *fixed = gtk_fixed_new(); 	
	// 將佈局容器放視窗中。
	gtk_container_add(GTK_CONTAINER (window), fixed);
	// 建立標籤。
	GtkWidget *label_one = gtk_label_new("請輸入暱稱");
	// 將標籤放在佈局容器裡。
	gtk_fixed_put(GTK_FIXED(fixed), label_one,120,30);
	// 行編輯的建立。
	GtkWidget *entry = gtk_entry_new();
	//設定最大長度。
	gtk_entry_set_max_length(GTK_ENTRY(entry),50);
	// 設定行編輯允許編輯。
	gtk_editable_set_editable(GTK_EDITABLE(entry), TRUE);
	// 將行編輯放在佈局容器裡
	gtk_fixed_put(GTK_FIXED(fixed), entry,70,60); 
	// 建立按鈕。
	GtkWidget *button = gtk_button_new_with_label("  登入  "); 
    // 將按鈕放在佈局容器裡
	gtk_fixed_put(GTK_FIXED(fixed), button,130,110);
	//繫結點選事件。
	g_signal_connect(button, "pressed", G_CALLBACK(deal_pressed), entry);  
	// 顯示視窗全部控制元件。
	gtk_widget_show_all(window);
	//啟動主迴圈。
 	gtk_main();	
}
//傳送目標使用者視窗。
GtkWidget *entryname;
// 文字框緩衝區。
GtkTextBuffer *bufferuser;
GtkTextBuffer *buffernotice;
//儲存資訊記錄buf。
void savelinetxt(char buf[]){
	struct flock lock3;    //新建結構體flock為lock3。
	lock3.l_whence = SEEK_SET;    //相對位移量的起點設定為檔案頭0。
	lock3.l_start = 0;    //相對位移量。
	lock3.l_len = 0;    //設定加鎖區域的長度。
	//以上三個設定可以為整個檔案加鎖。
	lock3.l_type = F_WRLCK;	//初始化l_type為寫入鎖。
	lock3.l_pid = -2;	//初始化l_pid。
	b_file = open(fname,O_WRONLY|O_CREAT|O_APPEND, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);    //開啟fname,並將檔案描述符存在b_file。
	fcntl(b_file, F_SETLKW, &lock3);    //以阻塞的方式為b_file設定結構為lock3的檔案鎖。
	write(b_file, buf, strlen(buf));    //向b_file寫入buf。
	fcntl(b_file, F_UNLCK, &lock3);    //給b_file解鎖。
	close(b_file);    //關閉檔案描述符b_file。
	
}
//根據button的值傳送訊息時的自我維護。
void sendtouser(GtkButton  *button, gpointer entry){
	char *buf;	//用來儲存內容。
	buf = (char *)malloc(1024);    //向系統申請分配指定1024個位元組的記憶體空間。
	memset(buf, 0, 1024);    //將buf中當前位置後面的1024個位元組用0替換。
	int sendbytes;	//用來記錄傳送的位元組數。
	const gchar  *text = gtk_entry_get_text(GTK_ENTRY(entry));	//獲得行編輯entry的內容並靜態建立text指標進行指定。
	const char *but = gtk_button_get_label(button);	//獲得獲取按鈕button文字內容並靜態建立but指標進行指定。
	if(strlen(text)==0){	//如果text的長度為0
		printf("不能為空\n");	//列印內容。
		return;
	}else{
		if(strcmp(but,"--傳送--")==0){	//比較but與"--傳送--",若相同返回0;若相同。
			const gchar  *name = gtk_entry_get_text(GTK_ENTRY(entryname));	//獲得行編輯entryname的內容並靜態建立name指標進行指定。
			if(strlen(name)==0){	//如果name的長度為0
					printf("name為空。\n");	//列印內容。
					return;
			}
			sprintf(buf,"%s%s%s%s\n","User:",name,":",text);	//將內容寫入buf。
			if ((sendbytes = send(clientfd, buf, strlen(buf), 0)) == -1)	//將buf由指定的socket埠clientfd傳給對方主機並將傳送的位元組數存進sendbytes;如果傳送失敗。
			{
				perror("fail to send");    //把"fail to send"輸出到標準錯誤stderr。       
			}
			memset(buf, 0, 1024);	//將buf中當前位置後面的1024個位元組用0替換。
			sprintf(buf,"你對< %s >說:%s\n",name,text);	//將內容寫入buf。
			savelinetxt(buf);	//儲存資訊記錄buf。
			GtkTextIter start,end;    //新建儲存文字在buffer中位置的結構start和end。
			sem_p(sem_id);	//訊號量減1。
			gtk_text_buffer_get_bounds(GTK_TEXT_BUFFER(bufferuser),&start,&end);    //得到當前buffer中開始位置,結束位置的ITER。
			gtk_text_buffer_insert(GTK_TEXT_BUFFER(bufferuser),&start,buf,strlen(buf));
			//向文字框插入文字。start,end分別為文字框文字開始位置和結束位置的iter,插入文字的長度為strlen(buf)。
			sem_v(sem_id);	//訊號量加1。
			return ;		
		}else{
			sprintf(buf,"%s%s\n","All::",text);	//將內容寫入buf。
			if ((sendbytes = send(clientfd, buf, strlen(buf), 0)) == -1)	//將buf由指定的socket埠clientfd傳給對方主機並將傳送的位元組數存進sendbytes;如果傳送失敗。
			{
				perror("fail to send");	//把"fail to send"輸出到標準錯誤 stderr。
			}
			sprintf(buf,"你對所有人說:%s\n",text);	//將內容寫入buf。
			savelinetxt(buf);	//儲存資訊記錄buf。
			GtkTextIter start,end;	//新建儲存文字在buffer中位置的結構start和end。
			sem_p(sem_id);	//訊號量減1。
			gtk_text_buffer_get_bounds(GTK_TEXT_BUFFER(bufferuser),&start,&end);	//得到當前buffer中開始位置,結束位置的ITER。
			gtk_text_buffer_insert(GTK_TEXT_BUFFER(bufferuser),&start,buf,strlen(buf));	//向文字框插入文字。start,end分別為文字框文字開始位置和結束位置的iter,插入文字的長度為strlen(buf)。
			sem_v(sem_id);	//訊號量加1。
			return ;
		}
		
	}
}
//
void savetxt(GtkButton  *button, gpointer entry){
	struct flock lock1;    //新建結構體flock為lock1。
	lock1.l_whence = SEEK_SET;    //相對位移量的起點設定為檔案頭0。
	lock1.l_start = 0;    //設定位移量。
	lock1.l_len = 0;    //設定加鎖區域的長度。
	//以上三個設定可以為整個檔案加鎖。
	lock1.l_type = F_WRLCK;    //初始化l_type為寫入鎖。
	lock1.l_pid = -1;    //初始化l_pid。
	struct flock lock2;    //新建結構體flock為lock2。
	lock2.l_whence = SEEK_SET;    //相對位移量的起點設定為檔案頭0。
	lock2.l_start = 0;    //設定位移量。
	lock2.l_len = 0;    //設定加鎖區域的長度。
	//以上三個設定可以為整個檔案加鎖。
	lock2.l_type = F_RDLCK;    //初始化l_type為讀取鎖。
	lock2.l_pid = -1;    //初始化l_pid。
	int src_file, dest_file;//用來儲存原始檔與目標檔案的描述符。
	unsigned char buff[1024];//用來儲存內容。
	int real_read_len;//用來儲存真實讀取的位元組數。
	char txt_name[60];//用來儲存文字記錄名。
	sprintf(txt_name,"%s%s","./msgsave_",user_name);	//將內容寫入buf。
	src_file = open(fname, O_RDONLY);//開啟fname,並將檔案描述符存在src_file。
	dest_file = open(txt_name,O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);//開啟txt_name,並將檔案描述符存在dest_file。
	if (src_file< 0 || dest_file< 0)//如果原始檔與目標檔案開啟失敗。
	{
		return;
	}	
	fcntl(dest_file, F_SETLKW, &lock1);//以阻塞的方式為dest_file設定結構為lock1的檔案鎖。
	fcntl(src_file, F_SETLKW, &lock2);//以阻塞的方式為src_file設定結構為lock2的檔案鎖。
	while ((real_read_len = read(src_file, buff, sizeof(buff))) > 0)//讀取原始檔sec_file的sizeof(buff)個位元組的資料並存在buff中,並記錄真實讀取的位元組數。
	{
		write(dest_file, buff, real_read_len);//向dest_fie寫入buff內real_read_len位元組的資料。
	}
	fcntl(dest_file, F_UNLCK, &lock1);//給dest_file解鎖。
	fcntl(src_file, F_UNLCK, &lock2);//給src_file解鎖。
	close(dest_file);//關閉檔案描述符dest_file。
	close(src_file);//關閉檔案描述符src_file。

}
//處理arg並呼叫相應的函式轉發;
void *strdeal(void *arg){  
	char sign[10];
	char buf[1024];
	char s[1024];
	struct flock lock;    //新建結構體flock為lock1
	lock.l_whence = SEEK_SET;    //相對位移量的起點設定為檔案頭0。
	lock.l_start = 0;    //設定位移量。
	lock.l_len = 0;    //設定加鎖區域的長度。
	lock.l_type = F_WRLCK;    //初始化l_type為寫入鎖。
	lock.l_pid = -2;    //初始化l_pid
	while(1){
		memset(s, 0, strlen(s));    //將s中當前位置後面的strlen(s)個位元組用0替換。
		memset(sign, 0, strlen(sign));    //將sign中當前位置後面的strlen(sign)個位元組用0替換。
		memset(buf, 0, strlen(buf));    //將buf中當前位置後面的strlen(buf)個位元組用0替換。
		if(recv(clientfd, s, 1024, 0) <= 0)    //把clientfd的接收緩衝中的1024位元組的資料copy到s中;如果接收錯誤。
		    {
			perror("fail to recv");    //把"fail to recv"輸出到標準錯誤stderr。
			close(clientfd);    //關閉socket埠。
			exit(1);
		    }
		int i=0;
		int n=0;
		int j=0;
		for(i;i<strlen(s);i++){
			if(n==1){
				buf[j]=s[i];
				j++;
			}else{
				if(s[i]==':'){
					n++;
					sign[j]='\0';
					j=0;
					continue;				
				}
				sign[j]=s[i];
				j++;
			}	
		}
		if(strcmp(sign,"User")==0){    //比較sign與"User",若相同返回0;若相同。
			b_file = open(fname,O_WRONLY|O_CREAT|O_APPEND, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);    //開啟fname,並將檔案描述符存在b_file。
			fcntl(b_file, F_SETLKW, &lock);    //以阻塞的方式為b_file設定結構為lock的檔案鎖。		
			write(b_file, buf, strlen(buf));    //向b_file寫入buf。
			fcntl(b_file, F_UNLCK, &lock);    //給b_file解鎖。
			close(b_file);    //關閉檔案描述符b_file。
			GtkTextIter start,end;    //新建儲存文字在buffer中位置的結構start和end。
			sem_p(sem_id);    //訊號量減1。
			gtk_text_buffer_get_bounds(GTK_TEXT_BUFFER(bufferuser),&start,&end);    //得到當前buffer中開始位置,結束位置的ITER。
			gtk_text_buffer_insert(GTK_TEXT_BUFFER(bufferuser),&start,buf,strlen(buf));    //向文字框插入文字。start,end分別為文字框文字開始位置和結束位置的iter,插入文字的長度為strlen(buf)。
			sem_v(sem_id);    //訊號量加1。
		}else{    //若sign與"User"不同。
			GtkTextIter start,end;    //新建儲存文字在buffer中位置的結構start和end。
			gtk_text_buffer_get_bounds(GTK_TEXT_BUFFER(buffernotice),&start,&end);
			if(strcmp(buf,"over")==0){   // 收到over說明服務停止,所以程式退出  
				strcpy(buf,"服務停止,程式即將退出\n");    //將"服務停止,程式即將退出\n"複製進buf。 
				sem_p(sem_id);    //訊號量減1。
	     		gtk_text_buffer_insert(GTK_TEXT_BUFFER(buffernotice),&start,buf,strlen(buf));    //向文字框插入文字。start,end分別為文字框文字開始位置和結束位置的iter,插入文字的長度為strlen(buf)。
				sem_v(sem_id);    //訊號量加1。
				sleep(2);    //休眠2秒。
				close(clientfd);    //關閉socket埠。
				unlink(fname);  //刪除fname。 
				exit(0);
			}else{
				sem_p(sem_id);    //訊號量減1。
				gtk_text_buffer_insert(GTK_TEXT_BUFFER(buffernotice),&start,buf,strlen(buf));    //向文字框插入文字。start,end分別為文字框文字開始位置和結束位置的iter,插入文字的長度為strlen(buf)。
				sem_v(sem_id);    //訊號量加1。
			}
		}	
	}	
}
//主介面
void homepage(int argc,char *argv[]){
	char *buf;
	buf = (char *)malloc(1024);    //向系統申請分配指定1024個位元組的記憶體空間。
	memset(buf, 0, 1024);    //將buf中當前位置後面的1024個位元組用0替換。
    gtk_init(&argc, &argv);    //初始化。		
	home = gtk_window_new(GTK_WINDOW_TOPLEVEL);    //建立頂層視窗。
	gtk_window_set_title(GTK_WINDOW(home), "歡迎");    //設定視窗的標題。	
	gtk_window_set_position(GTK_WINDOW(home), GTK_WIN_POS_CENTER);    //設定視窗在顯示器中的位置為居中	
	gtk_widget_set_size_request(home, 500, 400);    //設定視窗的最小大小	
	gtk_window_set_resizable(GTK_WINDOW(home), FALSE);    //固定視窗的大小 	
	g_signal_connect(home, "destroy", G_CALLBACK(gtk_main_quit), NULL);    // "destroy" 和 gtk_main_quit 連線	
	
 	GtkWidget *fixed = gtk_fixed_new();    //建立一個固定容器 	
	gtk_container_add(GTK_CONTAINER(home), fixed);    // 將佈局容器放視窗中。
	
	GtkWidget *label_one;
	GtkWidget *label_two;	
	label_one = gtk_label_new("聊天內容:");    // 建立標籤
	gtk_fixed_put(GTK_FIXED(fixed), label_one,20,10);    // 將按鈕放在佈局容器裡	  
	//以下建立標籤。
	label_two = gtk_label_new("系統通知:");
	gtk_fixed_put(GTK_FIXED(fixed), label_two,320,10);
	label_two = gtk_label_new("傳送使用者:");
	gtk_fixed_put(GTK_FIXED(fixed), label_two,24,295);
	label_two = gtk_label_new("傳送內容:");
	gtk_fixed_put(GTK_FIXED(fixed), label_two,24,335);
	entryname = gtk_entry_new();    //行編輯的建立。
	gtk_entry_set_max_length(GTK_ENTRY(entryname),500);    //最大長度
	gtk_editable_set_editable(GTK_EDITABLE(entryname), TRUE);    //設定行編輯不允許編輯。
	gtk_fixed_put(GTK_FIXED(fixed), entryname,90,290); 

	GtkWidget *entry = gtk_entry_new();		
	gtk_entry_set_max_length(GTK_ENTRY(entry),500);
	gtk_editable_set_editable(GTK_EDITABLE(entry), TRUE);
	gtk_fixed_put(GTK_FIXED(fixed), entry,90,330);
	
	//建立按鈕bsend
	GtkWidget *bsend = gtk_button_new_with_label("--傳送--");
	gtk_fixed_put(GTK_FIXED(fixed), bsend,325,300);
	
    //建立按鈕send_all
	GtkWidget *send_all = gtk_button_new_with_label("--群發--");
	gtk_fixed_put(GTK_FIXED(fixed), send_all,390,300);
	
	//建立按鈕save
	GtkWidget *save = gtk_button_new_with_label("--儲存記錄--");
	gtk_fixed_put(GTK_FIXED(fixed), save,345,340);	
	// 繫結回撥函式
	g_signal_connect(bsend, "pressed", G_CALLBACK(sendtouser), entry);
	g_signal_connect(send_all, "pressed", G_CALLBACK(sendtouser), entry);
	g_signal_connect(save, "pressed", G_CALLBACK(savetxt), entry);

	//文字框
	GtkWidget *view = gtk_text_view_new(); 
	gtk_widget_set_size_request (view, 280, 250);
	gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(view), FALSE);
	gtk_fixed_put(GTK_FIXED(fixed), view, 20, 30);
	// 獲取文字緩衝區
	bufferuser=gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));     	
	

	GtkWidget *name_view = gtk_text_view_new(); 
	gtk_widget_set_size_request (name_view, 150, 230);
	gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(name_view), FALSE);
  	gtk_fixed_put(GTK_FIXED(fixed), name_view, 320, 30);
	buffernotice=gtk_text_view_get_buffer(GTK_TEXT_VIEW(name_view));
	// 顯示視窗全部控制元件
	gtk_widget_show_all(home);	
	
	int sendbytes, res;
	pthread_t thread;
	
	res = pthread_create(&thread, NULL, strdeal, NULL);    //開啟執行緒監聽收到的資料
	if (res != 0)
	{          
		exit(res);
	}
	usleep(10);    //休眠10位秒。
	gtk_main();    	//啟動主迴圈。
}

//主函式
int main(int argc,char *argv[])
{	
	
	uuid_t uuid;
	char str[36];
	uuid_generate(uuid);
	uuid_unparse(uuid, str);
	strcat(fname,str);
	sem_id = semget(ftok("/", 'a'), 1, 0666|IPC_CREAT);

	init_sem(sem_id, 1);

	login(argc,argv);

	homepage(argc,argv);
	
	unlink(fname);   //刪除臨時儲存訊息記錄檔案
	return 0;
}