一、简介
网络编程是Linux功能的强项,Gtk+2.0结合Linux系统的网络编程编写服务器和客户端。
二、详解
1、简单的ECHO程序
套接字编程是服务应用程序开发的关键和核心,GLIB在底层提供了对它们的支持。在此创建一个ECHO服务器,把用户发给它的数据原本地返回给客户端。(1)ECHO服务器
服务器的运行过程:
代码echo_server.c:
#include <glib.h>
#include <stdio.h>
#include <fcntl.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 8088
void do_service(gint sd)
{
gchar buf[1024] = {0};
while (read(sd, buf, 1024) != -1) {
write(sd, buf, 1024);
}
}
int main(int argc, char *argv[])
{
gint sd, newsd;
struct sockaddr_in *sin;
gint slen;
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_port = 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(;;) {
newsd = accept(sd, (struct sockaddr*)sin, &slen);
if (newsd == -1) {
g_print("accept error!\n");
break;
}
switch (fork()) {
case 0:
do_service(newsd);
break;
case -1:
g_print("fork error!\n");
break;
}
}
close(sd);
g_free(sin);
return TRUE;
}
编译运行:
gcc -o echo_server echo_server.c `pkg-config --cflags --libs glib-2.0`
运行后,客户端通过端口8088连接。
(2)ECHO客户端
代码echo_client.c:
#include <gtk/gtk.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define OURPORT 8088
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;
gboolean do_connect(void)
{
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;
s_in.sin_port = 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);
isconnected = TRUE;
return TRUE;
}
}
void on_send(GtkButton *button, gpointer data)
{
const char *message;
GtkTextIter iter;
if (isconnected == FALSE) return;
message = gtk_entry_get_text(GTK_ENTRY(message_entry));
sprintf(buf, "%s\n", message);
write(sd, buf, 1024);
gtk_entry_set_text(GTK_ENTRY(message_entry), "");
read(sd, buf, 1024);
sprintf(get_buf, "%s", buf);
gtk_text_buffer_get_end_iter(buffer, &iter);
gtk_text_buffer_insert(buffer, &iter, get_buf, -1);
}
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;
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), "ECHO客户端");
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);
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);
do_connect();
gtk_widget_show_all(window);
gtk_main();
return TRUE;
}
编译运行:
gcc -o echo_client echo_client.c `pkg-config --cflags --libs gtk+-2.0`
2、多人聊天程序
服务器利用GLIB线程功能和Linux的网络编程功能向多用户同时发送数据。(1)多人聊天服务器
代码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 8088
#define MAX_USERS 8
struct _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 = read(user[GPOINTER_TO_INT(id)].sd, user[GPOINTER_TO_INT(id)].buf, 1024)) {
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) {
write(user[j].sd, tobuf, 1024);
g_print("%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_port = 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);
}
编译运行:
gcc -o server server.c `pkg-config --cflags --libs glib-2.0 gthread-2.0`
运行,等待客户端的连接。
(2)多人聊天客户端
代码client.c:
#include <gtk/gtk.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define OURPORT 8088
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 = read(sd, buf, 1024)) {
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()
{
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;
s_in.sin_port = 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);
write(sd, buf, 1024);
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;
}
编译运行:
gcc -o client client.c `pkg-config --cflags --libs gtk+-2.0 gthread-2.0`
客户端界面:服务器端显示:
当用户访问量过多时(客户端退出时,服务器并未删除用户信息,须改进):
三、总结
(1)使用fork产生一个子进程与一个用户通讯,当用户较多时便会消耗大量的系统资源并且不能共享多用户间的数据,因此并不适合真正的系统服务仅测试使用。可以采用多线程的服务器。(2)Linux套接字编程的服务器可以采用非阻塞的方式,借鉴select、poll和epoll。
(3)若有建议,请留言,在此先感谢!