基于GTK+的Linux聊天室设计

2024-04-20 03:38
文章标签 linux 设计 聊天室 gtk

本文主要是介绍基于GTK+的Linux聊天室设计,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1.聊天窗口的设计

本聊天室分为服务器端和客户端两部分,采用GTK+2.0,即可用简短的代码来编写窗口并向窗口中插入各个控件,通过灵活地使用信号/回调函数机制,实现用户登录、通信连接、信息发送、信息接收等功能。首先运行服务器端(如图1)等待客户端连接,再运行客户端,单击“登录”按钮输入用户名即可与服务器端连接,然后双方或多方就可以进行通信了(如图2),注意服务器只进行聊天信息的转发。

图1  服务器端界面
图2  客户端界面

2.聊天过程的实现

2.1系统流程图

聊天过程采用TCP/IP协议下的Client/Server网络通信模式实现,通过套接字(Socket)接口可方便的实现TCP、UDP传输协议完成数据的网络传输,对可靠性要求高的数据通讯系统往往使用TCP协议传输数据,故该聊天室采用TCP协议传输数据。在双方进行通信前,要先运行服务器端程序,等待客户端的连接。

聊天室程序设计的思路是由一个服务器端程序和一个聊天者端程序组成。服务器端程序主要负责记录所有进入本聊天室的聊天者的IP地址,并且接收所有聊天者的信息,将每个聊天者发来的信息转发给所有聊天者。聊天者程序可以发送聊天信息给服务器,同时可以接收服务器发送回来的信息,并显示到聊天记录界面上。根据TCP传输控制协议,Socket编程的基本函数socket()、bind()、listen()、accept()、send()、recv(),结合该聊天室的具体情况,给出系统的工作流程图如图3所示。

图3  系统工作流程图

2.2具体步骤

上一节主要介绍了系统的工作流程图,这一节将对每个函数进行说明,并给出具体的步骤。

 (1)建立一个socket通信

调用函数socket(int family, int type, int protocol)来建立一个套接字,即向系统注册,通知系统建立一个通信端口。参数family表示所采用的协议族,此处取值为AF_INET,即IPv4协议;type为套接字接口的类型描述,取值为SOCK_STREAM,表示字节流套接字,可以理解为TCP套接字接口类型;protocol表示socket所使用传输协议的编号,通常取值为0。若成功则返回一个socket描述符。

(2)对socket定位

调用函数bind(int sockfd, struct sockaddr *my_addr, int addrlen)将新建的套接字与本地IP地址联系起来,若绑定其他IP地址则不能成功。sockfd即为调用socket函数后所返回的socket描述符;my_addr为包含本机IP地址和端口号的指针;addrlen 为地址长度,即sockaddr的结构长度。

(3)等待客户端的连接

调用函数listen(int sockfd, int backlog)使socket处于监听模式,会创建一个等待队列,在其中存放未处理的客户端连接请求。参数backlog表示请求队列中允许的最大请求数,大多数系统默认值为5。listen0并未开始接收连线,只是设置socket为监听模式,调用accept()成功后才是真正接收客户端的连线。所以listen()应该在socket(),bind()之后,在acept0之前调用。

(4)接收客户端socket连线

调用函数accept(int sockfd, struct sockaddr *addr, int *addrlen)来接收客户端的连线请求。它通常从由listen()所创建的等待队列中取出第一个未处理的连接请求。sockfd是被监听的socket的描述符:连线成功后,addr所指的结构会被填入客户端主机的地址数据;addrlen是sockaddr的结构长度。若连接成功则返回新的socket处理代码。

相对服务器端复杂的过程而言,客户端的工作比较简单。

运行客户端程序后,在登录框输入用户名,然后程序会向提前绑定了IP地址的服务器端发送连接请求。首先调用函数socket(),像服务器端那样建立一个套接字,然后调用函数connect(int sockfd, struct sockaddr *serv_ addr, int addrlen)将客户端连接至服务器端。sockfd即为新建的客户端的socket描述符;serv_addr所指向的结构为服务器端的地址;参数addrlen为sockaddr的结构长度。

当两个及两个以上的客户端与服务器端连接成功后,双方或多方即可进行通信,服务器端只进行转发消息。send()和 recv()这两个函数用来在面向连接的socket上进行数据传输,其调用方式如下:

1)函数send(int sockfd, const void *msg, int len, int fags)用来发送数据。sockfd 是建立好连接的socket描述符;msg指针指向要发送的数据;len是以字节为单位的数据的长度,flags设置为0。

2)函数recv(int sockfd, void *buf, int len, unsigned int flags)用来接收数据。sockfd是建立好连接的socket描述符;buf指向存放接收数据的缓冲区;len是缓冲的长度; flags设置为0。

2.3多线程技术

进入聊天程序后,程序要不断检测是否有新的信息发送过来,如果只是简单的采用无限循环这个操作,程序会进入死机状态,此时就无法进行发送信息等其他的操作了,而使用多线程技术[6],就可将接收信息的操作置于一个新的线程,从而避免无法发送信息的情况。所谓多线程,就是将一个进程分成多个执行线程,各个线程可以独立运行。多线程程序采用一种多任务、并发的工作方式,主要优点有:提高应用程序的响应;更有效的使用多处理器;改善程序结构;占用较少的系统资源。

本系统客户端采用主线程发送信息,并且开辟一个新的线程用于接收信息。这样当程序运行时,就不至于产生阻塞而导致无法发送信息的情况发生。而服务器端由于只进行信息的转发,所以收发信息可以都放到在一个线程内,提高信息转发效率。每个线程共享CPU,操作系统为每个线程分配不同的CPU时间片,由于每个时间片的时间很短,虽然实际上同一时刻只有一个线程在运行,但是看上去好像多个线程是并发运行。

若使用线程,在初始化GTK+库函数之前必须运行g_thread_init(NULL)和gdk_threads_init()来初始化线程应用。创建线程调用函数g_thread_create(),如g_thread_create((GThreadFunc)get_message,NULL,FALSE, NULL)中,g _thread_create()函数用来生成接收消息的线程,get_ message()即为线程的具体事件回调函数。当线程例程(即线程执行的代码)开始时,通过gdk_threads_enter()来获得一个唯一的全局锁,当线程例程返回时,通过gdk_ threads_leave()释放该全局锁。线程创建成功后,新创建的线程开始运行回调函数且不影响原来的线程继续运行。

3.代码实现

服务器端代码(server.c):

#include <glib.h>
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/in.h>#define OURPORT 4321
#define MAX_USERS 8struct _client
{gint sd;gboolean in_use;gchar name[64];gchar buf[1024];
};
typedef struct _client client;
client user[MAX_USERS];void do_service(gpointer id)
{gint j;gchar tobuf[1024] = {0};gint num = -1;while(num = recv(user[GPOINTER_TO_INT(id)].sd, user[GPOINTER_TO_INT(id)].buf, 1024,0)) {if (num == -1 || num == 0) break;sprintf(tobuf, "%s:%s\n", user[GPOINTER_TO_INT(id)].name, user[GPOINTER_TO_INT(id)].buf);for(j = 0; j < MAX_USERS; j++) {if (user[j].in_use) {send(user[j].sd, tobuf, 1024,0);printf("%s", tobuf);}}}user[GPOINTER_TO_INT(id)].in_use = FALSE;close(user[GPOINTER_TO_INT(id)].sd);
}
int main(int argc, char *argv[])
{gint sd, newsd;struct sockaddr_in *sin;gint slen;gint count = 0;gint flags;gchar buf[1024];gchar tobuf[1024];gint length, i, j;if (!g_thread_supported()) {g_thread_init(NULL);}else {g_print("thread not supported\n");}sd = socket(AF_INET, SOCK_STREAM, 0);if (sd == -1) {g_print("create socket error!\n");return -1;}sin = g_new(struct sockaddr_in, 1);sin->sin_family = AF_INET;sin->sin_addr.s_addr=inet_addr("192.168.0.23");sin->sin_port = htons(OURPORT);slen = sizeof(struct sockaddr_in);if (bind(sd, (struct sockaddr*)sin, slen) < 0) {g_print("bind error!\n");return -1;}if (listen(sd, 8) < 0) {g_print("listen error!\n");return -1;}for (i = 0; i < MAX_USERS; i++) {user[i].in_use = FALSE;}flags = fcntl(sd, F_GETFL);fcntl(sd, F_SETFL, flags &~O_NDELAY);for(;;) {newsd = accept(sd, (struct sockaddr*)sin, (socklen_t *)&slen);if (newsd == -1) {g_print("accept error!\n");break;}else {if (count >= MAX_USERS) {sprintf(buf, "用户数量过多服务器不能通讯。\n");write(newsd, buf, 1024);close(newsd);}else {flags = fcntl(user[i].sd, F_GETFL);fcntl(user[i].sd, F_SETFL, O_NONBLOCK);user[count].sd = newsd;user[count].in_use = TRUE;read(newsd, user[count].name, 64);g_thread_create((GThreadFunc)do_service, (gpointer)count, TRUE, NULL);count++;}}}close(sd);g_free(sin);
}

客户端代码(client.c):

#include <gtk/gtk.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>#define OURPORT 4321
gint sd;
struct sockaddr_in s_in;
gchar username[64];
gchar buf[1024];
gchar get_buf[1048];
gboolean isconnected = FALSE;static GtkWidget *text;
static GtkTextBuffer *buffer;
static GtkWidget *message_entry;
static GtkWidget *name_entry;
static GtkWidget *login_button;void get_message()
{GtkTextIter iter;gchar get_buf[1024];gchar buf[1024];gint num = -1;while(num = recv(sd, buf, 1024,0)) {if (num == -1 || num == 0) break;sprintf(get_buf, "%s", buf);gdk_threads_enter();gtk_text_buffer_get_end_iter(buffer, &iter);gtk_text_buffer_insert(buffer, &iter, get_buf, -1);gdk_threads_leave();}
}gboolean do_connect_run()
{struct hostent *host;GtkTextIter iter;gint slen;sd = socket(AF_INET, SOCK_STREAM, 0);if (sd < 0) {gtk_text_buffer_get_end_iter(buffer, &iter);gtk_text_buffer_insert(buffer, &iter, "打开套接字时出错!\n", -1);return FALSE;}s_in.sin_family = AF_INET;host=gethostbyname("192.168.0.23");s_in.sin_addr=*((struct in_addr *)host->h_addr);s_in.sin_port = htons(OURPORT);slen = sizeof(s_in);if (connect(sd, (struct sockaddr*)&s_in, slen) < 0) {gtk_text_buffer_get_end_iter(buffer, &iter);gtk_text_buffer_insert(buffer, &iter, "连接服务器时出错!\n", -1);return FALSE;}else {gtk_text_buffer_get_end_iter(buffer, &iter);gtk_text_buffer_insert(buffer, &iter, username, -1);gtk_text_buffer_get_end_iter(buffer, &iter);gtk_text_buffer_insert(buffer, &iter, "成功与服务器连接...\n", -1);write(sd, username, 64);isconnected = TRUE;return TRUE;}
}
void on_destroy(GtkWidget *widget, GdkEvent *event, gpointer data)
{sprintf(username, "guest");if(do_connect_run() == TRUE) {gtk_widget_set_sensitive(login_button, FALSE);g_thread_create((GThreadFunc)get_message, NULL, FALSE, NULL);}gtk_widget_destroy(widget);
}
void on_button_clicked(GtkButton *button, gpointer data)
{const gchar *name;name = gtk_entry_get_text(GTK_ENTRY(name_entry));sprintf(username, "%s", name);if (do_connect_run()) {gtk_widget_set_sensitive(login_button, FALSE);g_thread_create((GThreadFunc)get_message, NULL, FALSE, NULL);}gtk_widget_destroy(GTK_WIDGET(data));
}
void create_win()
{GtkWidget *win, *vbox;GtkWidget *button;win = gtk_window_new(GTK_WINDOW_TOPLEVEL);g_signal_connect(G_OBJECT(win), "delete_event", G_CALLBACK(on_destroy), NULL);gtk_window_set_title(GTK_WINDOW(win), "输入用户名");gtk_window_set_position(GTK_WINDOW(win), GTK_WIN_POS_CENTER);gtk_container_set_border_width(GTK_CONTAINER(win), 10);gtk_window_set_modal(GTK_WINDOW(win), TRUE);vbox = gtk_vbox_new(FALSE, 0);gtk_container_add(GTK_CONTAINER(win), vbox);name_entry = gtk_entry_new();gtk_box_pack_start(GTK_BOX(vbox), name_entry, TRUE, TRUE, 5);button = gtk_button_new_from_stock(GTK_STOCK_OK);g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(on_button_clicked), win);gtk_box_pack_start(GTK_BOX(vbox), button, FALSE, FALSE, 5);gtk_widget_show_all(win);
}
void on_send(GtkButton *button, gpointer data)
{const gchar *message;if (isconnected == FALSE) return;message = gtk_entry_get_text(GTK_ENTRY(message_entry));if (g_strcmp0(message, "") == 0) return;sprintf(buf, "%s", message);send(sd, buf, 1024,0);gtk_entry_set_text(GTK_ENTRY(message_entry), "");
}
void on_login(GtkWidget *button, gpointer data)
{create_win();
}
void on_delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
{close(sd);gtk_main_quit();
}
int main(int argc, char *argv[])
{GtkWidget *window;GtkWidget *vbox, *hbox, *button, *label, *view;if (!g_thread_supported()) {g_thread_init(NULL);}else {g_print("thread not supported\n");}gtk_init(&argc, &argv);window = gtk_window_new(GTK_WINDOW_TOPLEVEL);g_signal_connect(G_OBJECT(window), "delete_event", G_CALLBACK(on_delete_event), NULL);gtk_window_set_title(GTK_WINDOW(window), "客户端");gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);gtk_container_set_border_width(GTK_CONTAINER(window), 10);vbox = gtk_vbox_new(FALSE, 0);gtk_container_add(GTK_CONTAINER(window), vbox);hbox = gtk_hbox_new(FALSE, 0);gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);label = gtk_label_new("点击登录按钮连接服务器");gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 5);login_button = gtk_button_new_with_label("登录");gtk_box_pack_start(GTK_BOX(hbox), login_button, FALSE, FALSE, 5);g_signal_connect(G_OBJECT(login_button), "clicked", G_CALLBACK(on_login), NULL);view = gtk_scrolled_window_new(NULL, NULL);gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(view), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);text = gtk_text_view_new();gtk_box_pack_start(GTK_BOX(vbox), view, TRUE, TRUE, 5);gtk_container_add(GTK_CONTAINER(view), text);buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));hbox = gtk_hbox_new(FALSE, 0);gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);label = gtk_label_new("输入信息:");gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 5);message_entry = gtk_entry_new();gtk_box_pack_start(GTK_BOX(hbox), message_entry, FALSE, FALSE, 5);button = gtk_button_new_with_label("发送");gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 5);g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(on_send), NULL);gtk_widget_show_all(window);gdk_threads_enter();gtk_main();gdk_threads_leave();return TRUE;
}

运行环境:CentOS6.6
编译说明:
对server.c进行编译,打开终端,用cd切换到文件对应的路径,输入命令:
gcc -o server server.c `pkg-config --cflags --libs glib-2.0 gthread-2.0`
对client.c进行编译,打开终端,用cd切换到文件对应的路径,输入命令:
gcc -o client client.c `pkg-config --cflags --libs gtk+-2.0 gthread-2.0`
生成server和client文件,运行这两个文件,打开两个终端,输入以下两个命令:
./server
./client
注意:若自己本地电脑即作为服务器端又作为客户端,只需要将服务器端和客户端代码中IP地址都改为自己本地电脑的IP地址即可

 

这篇关于基于GTK+的Linux聊天室设计的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/919205

相关文章

Linux join命令的使用及说明

《Linuxjoin命令的使用及说明》`join`命令用于在Linux中按字段将两个文件进行连接,类似于SQL的JOIN,它需要两个文件按用于匹配的字段排序,并且第一个文件的换行符必须是LF,`jo... 目录一. 基本语法二. 数据准备三. 指定文件的连接key四.-a输出指定文件的所有行五.-o指定输出

Linux jq命令的使用解读

《Linuxjq命令的使用解读》jq是一个强大的命令行工具,用于处理JSON数据,它可以用来查看、过滤、修改、格式化JSON数据,通过使用各种选项和过滤器,可以实现复杂的JSON处理任务... 目录一. 简介二. 选项2.1.2.2-c2.3-r2.4-R三. 字段提取3.1 普通字段3.2 数组字段四.

Linux kill正在执行的后台任务 kill进程组使用详解

《Linuxkill正在执行的后台任务kill进程组使用详解》文章介绍了两个脚本的功能和区别,以及执行这些脚本时遇到的进程管理问题,通过查看进程树、使用`kill`命令和`lsof`命令,分析了子... 目录零. 用到的命令一. 待执行的脚本二. 执行含子进程的脚本,并kill2.1 进程查看2.2 遇到的

Linux云服务器手动配置DNS的方法步骤

《Linux云服务器手动配置DNS的方法步骤》在Linux云服务器上手动配置DNS(域名系统)是确保服务器能够正常解析域名的重要步骤,以下是详细的配置方法,包括系统文件的修改和常见问题的解决方案,需要... 目录1. 为什么需要手动配置 DNS?2. 手动配置 DNS 的方法方法 1:修改 /etc/res

Linux创建服务使用systemctl管理详解

《Linux创建服务使用systemctl管理详解》文章指导在Linux中创建systemd服务,设置文件权限为所有者读写、其他只读,重新加载配置,启动服务并检查状态,确保服务正常运行,关键步骤包括权... 目录创建服务 /usr/lib/systemd/system/设置服务文件权限:所有者读写js,其他

Linux下利用select实现串口数据读取过程

《Linux下利用select实现串口数据读取过程》文章介绍Linux中使用select、poll或epoll实现串口数据读取,通过I/O多路复用机制在数据到达时触发读取,避免持续轮询,示例代码展示设... 目录示例代码(使用select实现)代码解释总结在 linux 系统里,我们可以借助 select、

Linux挂载linux/Windows共享目录实现方式

《Linux挂载linux/Windows共享目录实现方式》:本文主要介绍Linux挂载linux/Windows共享目录实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录文件共享协议linux环境作为服务端(NFS)在服务器端安装 NFS创建要共享的目录修改 NFS 配

linux系统中java的cacerts的优先级详解

《linux系统中java的cacerts的优先级详解》文章讲解了Java信任库(cacerts)的优先级与管理方式,指出JDK自带的cacerts默认优先级更高,系统级cacerts需手动同步或显式... 目录Java 默认使用哪个?如何检查当前使用的信任库?简要了解Java的信任库总结了解 Java 信

Linux命令rm如何删除名字以“-”开头的文件

《Linux命令rm如何删除名字以“-”开头的文件》Linux中,命令的解析机制非常灵活,它会根据命令的开头字符来判断是否需要执行命令选项,对于文件操作命令(如rm、ls等),系统默认会将命令开头的某... 目录先搞懂:为啥“-”开头的文件删不掉?两种超简单的删除方法(小白也能学会)方法1:用“--”分隔命

Linux五种IO模型的使用解读

《Linux五种IO模型的使用解读》文章系统解析了Linux的五种IO模型(阻塞、非阻塞、IO复用、信号驱动、异步),重点区分同步与异步IO的本质差异,强调同步由用户发起,异步由内核触发,通过对比各模... 目录1.IO模型简介2.五种IO模型2.1 IO模型分析方法2.2 阻塞IO2.3 非阻塞IO2.4