基于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地址即可

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/58259.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

高德地图实现多点标注marker和动态信息窗体

先说一下项目对地图的需求&#xff1a;在后台新增地图管理模块&#xff0c;要求&#xff0c;每一辆车都在地图上有标注&#xff0c;而且点击标注时要显示出车辆的相关信息&#xff0c;比如车牌和车辆的当前状态。 下图就是实现的效果。 当然从高德地图api也能查看到这一块&am…

C#使用GDI+绘制高质量图和字体

对于GDI,在正常的操作,Bitmap-- Graphcis -- DrawImage或者DrawString ,生成图片的话,会产生很多杂点,或者是图片质量不稳定..尤其是在读取图片后,生成缩略图之后,文件会被压缩而失真..主要原因是因为没有重新设置Graphics的几个属性..1.Graphics.SmoothingMode属性: 例如Smoot…

软件设计师:09-软件工程

章节章节01 - 计算机组成原理与体系结构07 - 法律法规与标准化与多媒体基础02 - 操作系统基本原理08 - 设计模式03 - 数据库系统09 - 软件工程04 - 计算机网络10 - 面向对象05 - 数据结构与算法11 - 结构化开发与UML06 - 程序设计语言与语言处理程序基础12 - 下午题历年真题End…

【中级软件设计师】—(针对上午题)软件工程上(三十五)

【中级软件设计师】—&#xff08;针对上午题&#xff09;软件工程&#xff08;三十五&#xff09; 一、CMM 1 2 3 4 5 二、能力成熟度模型集成&#xff08;CMMI&#xff09; 6 7 8 9 三、软件过程模型 &#xff08;一&#xff09;、瀑布模型 &#xff08;二&#xff09;、V模…

软件工程_绘制数据流图

软件工程_绘制数据流图 实验目的&#xff1a; 1、掌握数据流的分析方法 2、掌握数据流图的绘制 实验原理&#xff1a; 数据流图&#xff08;DFD&#xff09;是软件系统系统的逻辑模型&#xff0c;仅仅描绘数据在软件中流动&#xff08;从输入移动到输出&#xff09;的过程中所…

20230120英语学习

How Animals May Have Conquered Snowball Earth “雪球地球”时期&#xff0c;动物是如何存活的&#xff1f; Planet Earth used to be something like a cross between a deep freeze and a car crusher.During vast stretches of the planet’s history, everything from p…

Time For Kids 很不错的英语学习周刊

英语&#xff0c;是全世界使用范围最广的一门语言&#xff0c;因此&#xff0c;学好英语是一件很重要的事情&#xff0c;而它的重要和存在形式也不仅仅是在学习分数上&#xff0c;英语是一种语言&#xff0c;也是一门工具&#xff0c;学好它&#xff0c;对于我们&#xff0c;尤…

大数据工程师应聘要求高么?好找工作么

大数据开发工程师该岗位对于技术要求较高&#xff0c;有一定的技术门槛。大数据工程师不少细分方向&#xff0c;不同的方向需要具备不同的知识结构&#xff0c;分别是大数据底层平台研发、大数据应用开发、大数据分析和大数据运维。 大数据开发工程师负责数据仓库建设、ETL开发…

大数据好找工作么?前景如何

大数据好不好找工作不是一概而论的&#xff0c;要根据你个人的学历情况&#xff0c;掌握技能程度&#xff0c;所在城市招聘需求&#xff0c;甚至是你的面试能力和简历是否突出优势有关。 但是毋庸置疑的是&#xff0c;大数据目前的发展前景还是相当优秀的。 我们知道&#xf…

都说大数据前景很好,那么大数据培训出来就业情况如何?

大数据的就业形式还是很不错&#xff0c;就业薪资还是很高&#xff0c;在网上随便一搜&#xff0c;如图 如果大数据还在自学中的同志们&#xff0c;赠送你们一张学习路线

如何读一个在职在线的海外名校的计算机硕士

对于参加工作几年的朋友&#xff0c;可能面临职场晋升压力&#xff0c;而身边的同事又都拥有硕士、博士学历&#xff0c;此时迫切想提升学历。 现在以某教育机构和伊利诺伊理工大学的在线硕士项目为例&#xff0c;作简单的科普介绍&#xff0c;事先说明此非推广宣传&#xff0…

计算机专业留学美国前景一如何,美国计算机专业国内外就业前景到底如何?和你想的一样吗?...

原标题&#xff1a;美国计算机专业国内外就业前景到底如何&#xff1f;和你想的一样吗&#xff1f; 在我们选择专业的时候&#xff0c;“就业前景”是几乎每个同学和家长都会考虑的问题。我们都知道美国大学有很多专业&#xff0c;其中计算机科学专业是绝对的优势也是申请的热门…

英国计算机专业申请条件有哪些?就业前景怎么样?

曾经有人说过&#xff0c;计算机是人类历史上最伟大的发明&#xff0c;因为计算机的出现使人们的工作效率大大提升了&#xff0c;而且自从计算机越来越普遍之后&#xff0c;学习计算机专业的学生也越来越多了&#xff0c;而英国在计算机领域的造诣非常高&#xff0c;因此去英国…

ChatGPT评中国最好就业的十大专业!人工智能,数据科学,网络安全排名前列!...

以下是ChatGPT认为未来考研最好就业的十大专业&#xff0c;并按照重要性从高到低排列&#xff08;仅供娱乐哦&#xff09; 需要注意的是&#xff0c;这只是ChatGPT的看法&#xff0c;其他人可能会有不同的观点和评价标准。 此外&#xff0c;不同的发展阶段和政策导向也会对专业…

商业创业计划书的21条重要事项

创业者们&#xff0c;商业计划书是你们找VC的敲门砖。没有一块有分量的敲门砖&#xff0c;怕你们敲不开VC的大门。 这世界上永远是来要钱的人多&#xff0c;能给出去的钱少&#xff0c;僧多粥少&#xff0c;融资是有门槛的。如果没有一份有分量的商业计划书&#xff0c;你根本就…

“ 奋斗者耕耘论坛 ” 商业计划书 | 内卷 | 大学生创业计划书 | 大创 | 创新杯项目 | 如何财富自由 | 怎么发财 | 赚钱方法

1. 1. 产品背景 产品背景 2020 年, 各行各业的奋斗者们都很焦虑. 他们在”内卷”的大环境下压力 从从. 无论是大学还是职场, 奋斗者们努力着挥洒汗水, 希望实现自己的梦想, 过上追求的美好生活. 无论是学业的压力还是职场的压力, 让这些曾经高考的 佼佼者, 毕业生中的优胜者也苦…

2021大学生创业计划书范例

想看更多算法题&#xff0c;可以扫描上方二维码关注我微信公众号“数据结构和算法”&#xff0c;截止到目前我已经在公众号中更新了500多道算法题&#xff0c;其中部分已经整理成了pdf文档&#xff0c;截止到目前总共有800多页&#xff08;并且还会不断的增加&#xff09;&…

“互联网+”创新创业计划书(二)

“智能便携式电动滑板”项目介绍&#xff1a; ------Intelligent portable electric skateboards

创业计划书

要求 清楚明了、真实可信、简明扼要、图表、逻辑性强、佐证材料 作用&#xff1a;指明目标 给员工绘制蓝图给投资方看 创业须知 创业创意选择要差异化、尽量避免同仁化 多了解法律知识 如何介绍自己的公司或产品 a. 模板法&#xff1a;(公司的名称)提供的(产品或服务)利…

TikTok剪辑系统升级:照片模式增加文案字数,达人合作平台更新

武汉瑞卡迪电子商务有限公司&#xff1a;此次TikTok针对应用内的剪辑功能进行了优化升级&#xff0c;为用户建立了一个更实用的剪辑系统&#xff0c;融合多种功能于一体&#xff0c;现在TikTok内可直接完成裁剪视频、编辑音频、设置画中画、调节视频播放速度、设置画面边框大小…