GTK+实现linux聊天室代码详解-clientr端
生活随笔
收集整理的這篇文章主要介紹了
GTK+实现linux聊天室代码详解-clientr端
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
查看原代碼請點擊此超鏈接
注意!!此聊天室對紅帽無兼容。需在其他linux系統(tǒng)上運行,如“深度”。
加油學習!
GTK+實現(xiàn)linux聊天室代碼詳解-server端:GTK+實現(xiàn)linux聊天室代碼詳解-server端_海上的雨-CSDN博客
#include <gtk/gtk.h> // 頭文件 #include <string.h> #include <stdio.h> #include <netinet/in.h> //定義數(shù)據(jù)結(jié)構(gòu)sockaddr_in #include <sys/socket.h> //定義socket函數(shù)以及數(shù)據(jù)結(jié)構(gòu) #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; //初始化信號量。 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操作函數(shù)。 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操作函數(shù)。 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; }//處理登錄 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); //向系統(tǒng)申請分配指定9個字節(jié)的內(nèi)存空間。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)個字節(jié)為零且包括‘\0’。clientaddr.sin_family = AF_INET; //服務器地址族為對于TCP/IP協(xié)議的AF_INET。clientaddr.sin_port = htons((uint16_t)atoi("8787")); //服務器端口將小端模式改變?yōu)榇蠖四J健lientaddr.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); // 創(chuàng)建頂層窗口window = gtk_window_new(GTK_WINDOW_TOPLEVEL);// 設(shè)置窗口的標題gtk_window_set_title(GTK_WINDOW(window), "登錄");// 設(shè)置窗口在顯示器中的位置為居中g(shù)tk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);// 設(shè)置窗口的最小大小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);//創(chuàng)建一個固定容器GtkWidget *fixed = gtk_fixed_new(); // 將布局容器放窗口中g(shù)tk_container_add(GTK_CONTAINER (window), fixed);// 創(chuàng)建標簽GtkWidget *label_one = gtk_label_new("請輸入昵稱");// 將按鈕放在布局容器里gtk_fixed_put(GTK_FIXED(fixed), label_one,120,30);// 行編輯的創(chuàng)建GtkWidget *entry = gtk_entry_new();//設(shè)置最大長度gtk_entry_set_max_length(GTK_ENTRY(entry),50);// 設(shè)置行編輯允許編輯gtk_editable_set_editable(GTK_EDITABLE(entry), TRUE);gtk_fixed_put(GTK_FIXED(fixed), entry,70,60); // 創(chuàng)建按鈕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);//啟動主循環(huán)gtk_main(); } //發(fā)送目標用戶窗口。 GtkWidget *entryname; // 文本框緩沖區(qū)。 GtkTextBuffer *bufferuser; GtkTextBuffer *buffernotice; //保存信息記錄buf。 void savelinetxt(char buf[]){struct flock lock3; //新建結(jié)構(gòu)體flock為lock3。lock3.l_whence = SEEK_SET; //相對位移量的起點設(shè)置為文件頭0。lock3.l_start = 0; //相對位移量。lock3.l_len = 0; //設(shè)置加鎖區(qū)域的長度。//以上三個設(shè)置可以為整個文件加鎖。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設(shè)置結(jié)構(gòu)為lock3的文件鎖。write(b_file, buf, strlen(buf)); //向b_file寫入buf。fcntl(b_file, F_UNLCK, &lock3); //給b_file解鎖。close(b_file); //關(guān)閉文件描述符b_file。} //根據(jù)button的值發(fā)送消息時的自我維護。 void sendtouser(GtkButton *button, gpointer entry){char *buf; //用來存儲內(nèi)容。buf = (char *)malloc(1024); //向系統(tǒng)申請分配指定1024個字節(jié)的內(nèi)存空間。memset(buf, 0, 1024); //將buf中當前位置后面的1024個字節(jié)用0替換。int sendbytes; //用來記錄發(fā)送的字節(jié)數(shù)。const gchar *text = gtk_entry_get_text(GTK_ENTRY(entry)); //獲得行編輯entry的內(nèi)容并靜態(tài)建立text指針進行指定。const char *but = gtk_button_get_label(button); //獲得獲取按鈕button文本內(nèi)容并靜態(tài)建立but指針進行指定。if(strlen(text)==0){ //如果text的長度為0printf("不能為空\n"); //打印內(nèi)容。return;}else{if(strcmp(but,"--發(fā)送--")==0){ //比較but與"--發(fā)送--",若相同返回0;若相同。const gchar *name = gtk_entry_get_text(GTK_ENTRY(entryname)); //獲得行編輯entryname的內(nèi)容并靜態(tài)建立name指針進行指定。if(strlen(name)==0){ //如果name的長度為0printf("name為空。\n"); //打印內(nèi)容。return;}sprintf(buf,"User:%d:%s%s\n",strlen(name),name,text); //將內(nèi)容寫入buf。if ((sendbytes = send(clientfd, buf, strlen(buf), 0)) == -1) //將buf由指定的socket端口clientfd傳給對方主機并將發(fā)送的字節(jié)數(shù)存進sendbytes;如果發(fā)送失敗。{perror("fail to send"); //把"fail to send"輸出到標準錯誤stderr。 }return ; }else{sprintf(buf,"%s%s\n","All::",text); //將內(nèi)容寫入buf。if ((sendbytes = send(clientfd, buf, strlen(buf), 0)) == -1) //將buf由指定的socket端口clientfd傳給對方主機并將發(fā)送的字節(jié)數(shù)存進sendbytes;如果發(fā)送失敗。{perror("fail to send"); //把"fail to send"輸出到標準錯誤 stderr。}return ;}} } //保存消息記錄 void savetxt(GtkButton *button, gpointer entry){struct flock lock1; //新建結(jié)構(gòu)體flock為lock1。lock1.l_whence = SEEK_SET; //相對位移量的起點設(shè)置為文件頭0。lock1.l_start = 0; //設(shè)置位移量。lock1.l_len = 0; //設(shè)置加鎖區(qū)域的長度。//以上三個設(shè)置可以為整個文件加鎖。lock1.l_type = F_WRLCK; //初始化l_type為寫入鎖。lock1.l_pid = -1; //初始化l_pid。struct flock lock2; //新建結(jié)構(gòu)體flock為lock2。lock2.l_whence = SEEK_SET; //相對位移量的起點設(shè)置為文件頭0。lock2.l_start = 0; //設(shè)置位移量。lock2.l_len = 0; //設(shè)置加鎖區(qū)域的長度。//以上三個設(shè)置可以為整個文件加鎖。lock2.l_type = F_RDLCK; //初始化l_type為讀取鎖。lock2.l_pid = -1; //初始化l_pid。int src_file, dest_file;//用來存儲源文件與目標文件的描述符。unsigned char buff[1024];//用來存儲內(nèi)容。int real_read_len;//用來存儲真實讀取的字節(jié)數(shù)。char txt_name[60];//用來存儲文本記錄名。sprintf(txt_name,"%s%s","./msgsave_",user_name); //將內(nèi)容寫入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設(shè)置結(jié)構(gòu)為lock1的文件鎖。fcntl(src_file, F_SETLKW, &lock2);//以阻塞的方式為src_file設(shè)置結(jié)構(gòu)為lock2的文件鎖。while ((real_read_len = read(src_file, buff, sizeof(buff))) > 0)//讀取源文件sec_file的sizeof(buff)個字節(jié)的數(shù)據(jù)并存在buff中,并記錄真實讀取的字節(jié)數(shù)。{write(dest_file, buff, real_read_len);//向dest_fie寫入buff內(nèi)real_read_len字節(jié)的數(shù)據(jù)。}fcntl(dest_file, F_UNLCK, &lock1);//給dest_file解鎖。fcntl(src_file, F_UNLCK, &lock2);//給src_file解鎖。close(dest_file);//關(guān)閉文件描述符dest_file。close(src_file);//關(guān)閉文件描述符src_file。 } GtkTextBuffer *buffers; //讀取消息記錄 void readtxt(GtkButton *button, gpointer entry){FILE *dest_file;char txt_name[60];sprintf(txt_name,"%s%s","./msgsave_",user_name);GtkTextIter start,end;char StrLine[1024];if((dest_file=fopen(txt_name,"r"))==NULL){return; }while(!feof(dest_file)){memset(StrLine,0,1024);fgets(StrLine,1024,dest_file);gtk_text_buffer_get_bounds(GTK_TEXT_BUFFER(buffers),&start,&end);gtk_text_buffer_insert(GTK_TEXT_BUFFER(buffers),&end,StrLine,strlen(StrLine));//將消息放入聊天信息框}fclose(dest_file); } ///處理接受到的消息 void *strdeal(void *arg){ char sign[10];char buf[1024];char s[1024];struct flock lock; //新建結(jié)構(gòu)體flock為lock1lock.l_whence = SEEK_SET; //相對位移量的起點設(shè)置為文件頭0。lock.l_start = 0; //設(shè)置位移量。lock.l_len = 0; //設(shè)置加鎖區(qū)域的長度。lock.l_type = F_WRLCK; //初始化l_type為寫入鎖。lock.l_pid = -2; //初始化l_pidwhile(1){memset(s, 0, strlen(s)); //將s中當前位置后面的strlen(s)個字節(jié)用0替換。memset(sign, 0, strlen(sign)); //將sign中當前位置后面的strlen(sign)個字節(jié)用0替換。memset(buf, 0, strlen(buf)); //將buf中當前位置后面的strlen(buf)個字節(jié)用0替換。if(recv(clientfd, s, 1024, 0) <= 0) //把clientfd的接收緩沖中的1024字節(jié)的數(shù)據(jù)copy到s中;如果接收錯誤。{perror("fail to recv"); //把"fail to recv"輸出到標準錯誤stderr。close(clientfd); //關(guān)閉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設(shè)置結(jié)構(gòu)為lock的文件鎖。 write(b_file, buf, strlen(buf)); //向b_file寫入buf。fcntl(b_file, F_UNLCK, &lock); //給b_file解鎖。close(b_file); //關(guān)閉文件描述符b_file。GtkTextIter start,end; //新建保存文字在buffer中位置的結(jié)構(gòu)start和end。sem_p(sem_id); //信號量減1。gtk_text_buffer_get_bounds(GTK_TEXT_BUFFER(bufferuser),&start,&end); //得到當前buffer中開始位置,結(jié)束位置的ITER。gtk_text_buffer_insert(GTK_TEXT_BUFFER(bufferuser),&start,buf,strlen(buf)); //向文本框插入文字。start,end分別為文本框文字開始位置和結(jié)束位置的iter,插入文本的長度為strlen(buf)。sem_v(sem_id); //信號量加1。}else{ //若sign與"User"不同。GtkTextIter start,end; //新建保存文字在buffer中位置的結(jié)構(gòu)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分別為文本框文字開始位置和結(jié)束位置的iter,插入文本的長度為strlen(buf)。sem_v(sem_id); //信號量加1。sleep(2); //休眠2秒。close(clientfd); //關(guān)閉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分別為文本框文字開始位置和結(jié)束位置的iter,插入文本的長度為strlen(buf)。sem_v(sem_id); //信號量加1。}} } } //主界面 void homepage(int argc,char *argv[]){char *buf;buf = (char *)malloc(1024);memset(buf, 0, 1024);pid_t pid;// 初始化gtk_init(&argc, &argv); // 創(chuàng)建頂層窗口home = gtk_window_new(GTK_WINDOW_TOPLEVEL);// 設(shè)置窗口的標題gtk_window_set_title(GTK_WINDOW(home), "歡迎");// 設(shè)置窗口在顯示器中的位置為居中g(shù)tk_window_set_position(GTK_WINDOW(home), GTK_WIN_POS_CENTER);// 設(shè)置窗口的最小大小gtk_widget_set_size_request(home, 820, 400);// 固定窗口的大小gtk_window_set_resizable(GTK_WINDOW(home), FALSE); // "destroy" 和 gtk_main_quit 連接g_signal_connect(home, "destroy", G_CALLBACK(gtk_main_quit), NULL);//創(chuàng)建一個固定容器GtkWidget *fixed = gtk_fixed_new(); gtk_container_add(GTK_CONTAINER(home), fixed);GtkWidget *label_two;GtkWidget *label_one;// 創(chuàng)建標簽label_one = gtk_label_new("聊天內(nèi)容:");// 將按鈕放在布局容器里gtk_fixed_put(GTK_FIXED(fixed), label_one,20,10); // 創(chuàng)建標簽label_two = gtk_label_new("系統(tǒng)通知:");gtk_fixed_put(GTK_FIXED(fixed), label_two,320,10);label_two = gtk_label_new("發(fā)送用戶:");gtk_fixed_put(GTK_FIXED(fixed), label_two,24,295);label_two = gtk_label_new("發(fā)送內(nèi)容:");gtk_fixed_put(GTK_FIXED(fixed), label_two,24,335);label_two = gtk_label_new("讀取消息記錄:");gtk_fixed_put(GTK_FIXED(fixed), label_two,500,10);// 行編輯的創(chuàng)建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); // 創(chuàng)建按鈕GtkWidget *bsend = gtk_button_new_with_label("--發(fā)送--");gtk_fixed_put(GTK_FIXED(fixed), bsend,325,300);GtkWidget *send_all = gtk_button_new_with_label("--群發(fā)--");gtk_fixed_put(GTK_FIXED(fixed), send_all,390,300);GtkWidget *save = gtk_button_new_with_label("--保存記錄--");gtk_fixed_put(GTK_FIXED(fixed), save,300,340); GtkWidget *read_m = gtk_button_new_with_label("--讀取記錄--");gtk_fixed_put(GTK_FIXED(fixed), read_m,390,340); // 綁定回調(diào)函數(shù)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);g_signal_connect(read_m, "pressed", G_CALLBACK(readtxt), 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);// 獲取文本緩沖區(qū)bufferuser=gtk_text_view_get_buffer(GTK_TEXT_VIEW(view)); //文本框系統(tǒng)公告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));GtkWidget *viewtxt = gtk_text_view_new(); gtk_widget_set_size_request (viewtxt, 300, 350);gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(viewtxt), FALSE);gtk_fixed_put(GTK_FIXED(fixed), viewtxt, 500, 30);buffers=gtk_text_view_get_buffer(GTK_TEXT_VIEW(viewtxt)); // 顯示窗口全部控件gtk_widget_show_all(home); int sendbytes, res;pthread_t thread;// 開啟線程監(jiān)聽收到的數(shù)據(jù)res = pthread_create(&thread, NULL, strdeal, NULL);if (res != 0){ exit(res);}usleep(10);gtk_main(); } //主函數(shù) int main(int argc,char *argv[]) { uuid_t uuid;//UUID含義是通用唯一識別碼 (Universally Unique Identifier),這 是一個軟件建構(gòu)的標準,這里用來標記每一個客戶端char str[36];uuid_generate(uuid);uuid_unparse(uuid, str);//生成的UUID放在參數(shù)uuid里面。此時得到的結(jié)果是一個8位數(shù)的16進制數(shù)。在UUID生成函數(shù)的過程中經(jīng)過了一些處理,才生成的是8位的16進制數(shù),原因在于,在它生成的過程中,本來生成的是32位的長整型,結(jié)果經(jīng)過uuid_parse進行轉(zhuǎn)換變成8位的16進制數(shù)。相反,我們有uuid_unparse函數(shù),可以反向?qū)?6進制數(shù)轉(zhuǎn)換為32位的整型。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; }展示(注:后期有所優(yōu)化,所提供代碼與展示效果細節(jié)處有所差別,但影響不大。)
運行結(jié)果截圖
不運行服務器而運行客戶端程序時不可登錄,客戶端自動退出。
運行服務器:
運行客戶端:
運行第二個客戶端時若用戶已登錄:
登錄第二個客戶端:
用戶test1向test2單發(fā)消息:
登錄第三個客戶端后使用第三個客戶端群發(fā)消息:
退出一個客戶端:
保存記錄:
讀取記錄
若退出后重新登錄后再讀取記錄:
服務器退出:
總結(jié)
以上是生活随笔為你收集整理的GTK+实现linux聊天室代码详解-clientr端的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。