GTK+實現linux聊天室程式碼詳解-clientr端
阿新 • • 發佈:2018-12-21
提供給識字的人學習。
#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; }