基于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 rm命令误操作的多场景防护方案与实践

《防止Linuxrm命令误操作的多场景防护方案与实践》在Linux系统中,rm命令是删除文件和目录的高效工具,但一旦误操作,如执行rm-rf/或rm-rf/*,极易导致系统数据灾难,本文针对不同场景... 目录引言理解 rm 命令及误操作风险rm 命令基础常见误操作案例防护方案使用 rm编程 别名及安全删除

Linux下MySQL数据库定时备份脚本与Crontab配置教学

《Linux下MySQL数据库定时备份脚本与Crontab配置教学》在生产环境中,数据库是核心资产之一,定期备份数据库可以有效防止意外数据丢失,本文将分享一份MySQL定时备份脚本,并讲解如何通过cr... 目录备份脚本详解脚本功能说明授权与可执行权限使用 Crontab 定时执行编辑 Crontab添加定

使用docker搭建嵌入式Linux开发环境

《使用docker搭建嵌入式Linux开发环境》本文主要介绍了使用docker搭建嵌入式Linux开发环境,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录1、前言2、安装docker3、编写容器管理脚本4、创建容器1、前言在日常开发全志、rk等不同

linux系统上安装JDK8全过程

《linux系统上安装JDK8全过程》文章介绍安装JDK的必要性及Linux下JDK8的安装步骤,包括卸载旧版本、下载解压、配置环境变量等,强调开发需JDK,运行可选JRE,现JDK已集成JRE... 目录为什么要安装jdk?1.查看linux系统是否有自带的jdk:2.下载jdk压缩包2.解压3.配置环境

Linux搭建ftp服务器的步骤

《Linux搭建ftp服务器的步骤》本文给大家分享Linux搭建ftp服务器的步骤,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录ftp搭建1:下载vsftpd工具2:下载客户端工具3:进入配置文件目录vsftpd.conf配置文件4:

Linux实现查看某一端口是否开放

《Linux实现查看某一端口是否开放》文章介绍了三种检查端口6379是否开放的方法:通过lsof查看进程占用,用netstat区分TCP/UDP监听状态,以及用telnet测试远程连接可达性... 目录1、使用lsof 命令来查看端口是否开放2、使用netstat 命令来查看端口是否开放3、使用telnet

Linux系统管理与进程任务管理方式

《Linux系统管理与进程任务管理方式》本文系统讲解Linux管理核心技能,涵盖引导流程、服务控制(Systemd与GRUB2)、进程管理(前台/后台运行、工具使用)、计划任务(at/cron)及常用... 目录引言一、linux系统引导过程与服务控制1.1 系统引导的五个关键阶段1.2 GRUB2的进化优

Linux查询服务器 IP 地址的命令详解

《Linux查询服务器IP地址的命令详解》在服务器管理和网络运维中,快速准确地获取服务器的IP地址是一项基本但至关重要的技能,下面我们来看看Linux中查询服务器IP的相关命令使用吧... 目录一、hostname 命令:简单高效的 IP 查询工具命令详解实际应用技巧注意事项二、ip 命令:新一代网络配置全

linux安装、更新、卸载anaconda实践

《linux安装、更新、卸载anaconda实践》Anaconda是基于conda的科学计算环境,集成1400+包及依赖,安装需下载脚本、接受协议、设置路径、配置环境变量,更新与卸载通过conda命令... 目录随意找一个目录下载安装脚本检查许可证协议,ENTER就可以安装完毕之后激活anaconda安装更

Linux查询服务器系统版本号的多种方法

《Linux查询服务器系统版本号的多种方法》在Linux系统管理和维护工作中,了解当前操作系统的版本信息是最基础也是最重要的操作之一,系统版本不仅关系到软件兼容性、安全更新策略,还直接影响到故障排查和... 目录一、引言:系统版本查询的重要性二、基础命令解析:cat /etc/Centos-release详