资源描述
Linux下使用套接口和GTK编写网络通信程序
第一章、基础知识
1.1 套接口
1.1.1基础知识
套接口(socket)就是网络进程的ID,其可以简单的理解为网络地址(ip地址)和端口号。套接口分两种,流式套接口使用TCP协议,数据报套接口使用UDP协议。在Linux中套接口的操作类似于文件描述符,可以像操作文件一样操作他们。在Linux中使用套接口发送数据要注意字节顺序的问题,在网络传输中,是以高字节在前(big endian)的方式传送的,而本机解析这些数据,是和CPU的结构有关,在传输到网络之前,需将套接口数据结构sockaddr_in中的网络地址和端口转换为高字节在前的形式。
套接口的基本操作有绑定、连接、监听、应答、发送、接受、关闭等,以客户/服务器为主的网络机制其用socket进行TCP服务的简化图1-1。
一般给套接口分配的文件描述符从3开始,0为默认输入,1为默认输出,2为错误默认输出,这三个描述符固定分配给系统使用,如果将套接口文件描述符设置成相应的1/2/3,则获得的数据则输出到相应的地方。
图1-1
1.1.2 关于I/O
使用套接口主要进行数据传输,不免要与I/O打交道。其I/O操作主要有四种模型:阻塞式,非阻塞,多路复用以及信号驱动。本文的程序使用的是阻塞式的I/O,其简单但效率相对较低。阻塞在套接口应用中很重要,处理不好会导致双方均处于阻塞状态,造成死锁。比如客户端进行read操作,但是对方迟迟不发送信息,导致read一直没有返回值,程序便会一直等待。
1.2 GTK
GTK (Gimp Tool Kit) 为GNOME中使用的开源界面开发程序。相应的,在KDE中主要使用的是QT。GTK使用了事件触发的结构处理用户响应,其开发包包括GDK,支持包含C之内的多种编程语言
1.3 Linux下的c语言编写
这里Linux下的c语言编辑主要使用vi,编译程序使用gcc。Gcc编译过程中,有静态连接库等问题需要注意,使用的命令为
gcc -std=c99 `pkg-config --cflags --libs gtk+-2.0` -lpthread client.c -o client
gcc -std=c99 `pkg-config --cflags --libs gtk+-2.0` -lpthread service.c -o service
在处理字符串的时候,多应用stdlib.h和string.h里的标准库函数,区分指向常量区域的字符串指针以及字符串数组之间的区别,明确字符串数组的赋值方法(单个元素赋值或用strcat stpcpy),在Linux条件下,没有itoa函数将整型变为字符型,可以使用sprintf函数。
1.4 多线程
一个进程(process)派生另一个进程称为多进程,其相比单一进程具有较高的灵活性,能够更有效地利用CPU,但是其代价为较大的内存占用以及麻烦的进程间通信,在这样的前提下产生了多线程。
多进程和多线程的区别在于,多线程中的子线程可以访问共享内存区域中的数据,简化了线程间的通信,提高了程序特别是用户界面程序的响应。但是在使用线程中要注意同时访问变量的问题,应该使用一定的机制(比如互斥锁mutex),让各个子线程以串行的方式访问并修改内存中的数据,避免出现内存使用冲突的问题。
多个线程能够共享同一个进程代码段,共有数据(用来通讯),进程打开的文件描述符,信号的处理器,进程当前目录,进程用户ID等。当然每一个线程也有其独有的东西:线程ID,寄存器值,堆栈,错误返回码,信号屏蔽码和优先级。
创建线程需要pthread.h头文件,连接时要使用libpthread.a的库,这里简单讲解几个和线程有关的函数。
Int pthread_create(pthread_t *tid, const pthread_attr_r *attr, void *(*func)(void*), void *arg)
四个输入参数依次为线程ID标识符,创建线程的属性,线程将要执行的函数-返回void类型指针,以及传递给函数的参数(这里,参数需是void类型的,int等类别的参数需要进行转换,多参数传递建议使用结构体)。
Int pthread_join(pthread_t tid, void **status)
等待tid的线程终止,然后再执行下面的命令。Status指针内将保存线程的返回值。
pthread_t pthread_self(void)
返回线程ID。
pthread_cancel结束其他它由同一个进程产生的线程,这里注意,当子线程阻塞在I/O时(比如,read等待),是不可以用这个函数退出次线程的,这时就要考虑使用非阻塞的I/O获得较高的控制权和灵活性。
pthread_exit结束线程本身。
在使用Pthread时避免线程的资源在线程结束时不能得到正确释放,从而避免产生潜在的内存泄漏问题,在对待线程结束时,要确保该线程处于detached状态(pthread_detach()),否着就需要调用 pthread_join()函数来对其进行资源回收。
线程可以通过自身执行结束来结束,也可以通过调用pthread_exit()来结束线程的执行。另外,线程甲可以被线程乙被动结束,通过调用pthread_cancel()来达到目的,cancel并不等待线程终止, 它仅仅是提出请求。可以用如下两个函数设置本线程的应答属性。
int pthread_setcancelstate(int state, int *oldstate)
state 取值可以是PTHREAD_CNACEL_ENABLE,这个值允许线程接收取消请求。
int pthread_setcanceltype(int type, int *oldtype)
设置取消的类型,type取值可以是PTHREAD_CANCEL_ASYNCHRONOUS,它将使得线程在接收到取消请求后立即处理。
在默认的情况下,线程启动时的取消状态为PTHREAD_CNACEL_ENABLE,取消类型为PTHREAD_CANCEL_DEFERRED。
1.5 IP地址和端口
在笔者处的教育网段,每台电脑几乎都可以分配一个独立的IP地址,每一个学校的IP地址是在一段地址之间。
一般一台主机只有一个IP地址,除非有多块网卡。
IP地址分为内网地址和外网地址,内网地址用于局域网广播,多是C网。
在网络上唯一确定一个传输数据的地址,需要IP地址和端口的支持。在这里的通信程序中,服务器使用固定的IP和端口,客户端使用自己本机的IP和随机分配的端口进行访问。为避免冲突,一般端口设置在1024之上较为合理。
在Linux下运行ifconfig可以得到本机的以太网卡IP和本机IP。无论系统是否接入网络,本机网络这个设备总是存在的,除非你在内核编译的时候禁止了网络支持,这是一个称为回送设备的特殊设备,它自动由Linux 配置以提供网络的自身连接。IP地址127.0.0.1是一个特殊的回送地址(即默认的本机地址),您可以在自己的系统上用telnet对IP地址127.0.0.1进行测试。如果有inetd进程在运行的话您会从自己的机器上获得登录提示符。Linux 可以利用这个特征在进程与仿真网络之间进行通信。
1.6 TCP&UDP
TCP为面向连接的协议,其保证数据能够准确且按顺序地到达,在Socket连接选择类型为SOCK_STREAM时即使用此协议。由于其是面向连接的,通信时需要listen-connect的三步握手协议。在发送数据时,先将数据保存在缓冲区,等待对方对发送的数据进行确认后才清楚缓冲区中的数据。其实现为数据流类型的服务,需要应用程序控制数据的边界。
UDP为面向无连接的协议,其保证数据的及时性,但对消息的尺寸加以限制,且没有对准确性的保证,在Socket连接选择类型为SOCK_DGRAM时即使用此协议。其通信时不需要握手协议,发送数据时直接将数据从应用缓冲区复制到系统缓冲区中,然后子程序返回。数据包的生成由UDP协议实现。其缓冲区具有一定大小,为UDP数据报的最大长度。其实现的是数据报类型服务,采用面向记录的协议。
二者比较来看,UDP服务器通常是以循环方式对多个客户进行服务的,TCP通常使用轮询或其他多线程的方式服务多个客户。
第二章、基本实现原理
2.1 基本实现结构
基本的实现结构如图2-1所示,在发送登录,刷新信息时,使用互斥锁的多线程,保证当前信息的准确性,如图B;在发送消息的时候,使用直接转发的方式,如图A。但这样会有一个问题,当用户数量较多时,发送的登录和刷新信息会造成其他用户的白白等待,如图C所示。程序内部流程框图如图2-2
图2-1
图2-2
2.2 更多的想法
一个改进该问题的想法如下图所示,使用两个服务器,一个作为登录使用,另一个专门作为通信交换使用,两个服务器之间进行同步。当然这种实践叫前者复杂,这里没有完成其代码。
图2-3
第三章、部分关键代码解释
3.1 服务器代码
3.1.1 关于互斥锁
以下代码首行和末行分别取得互斥锁的所有权和释放有权。其他试图对数据加锁的线程将被阻塞直到当前线程释放对该数据的锁。
pthread_mutex_lock(&mutex);
USER_INFO[in_unpack.send_from].stat = 0;
USER_INFO[in_unpack.send_from].sd = 0;
in_unpack.tag = LOGOUT_AWK;
do_pack(&in_unpack,in_pack);
flag += write(new_sd[in_sd],in_pack,sizeof(in_pack));
sleep(0.1);
pthread_mutex_unlock(&mutex);
3.1.2 关于线程的参数传递
pthread_create(&THREAD[tmp_index],NULL,do_service,(void*)flag_find);
将int格式的flag_find变为void格式的指针传递给函数do_service。
3.1.3 关于数据结构
struct msg_pack{ //message pack struct
int tag; //msg type char size:2
int send_from; //msg send from char size:8
int send_to; //msg send to char size:8
char key[64]; //key used for login char size:8
char msg[PACK_MSG];//msg content char size:998
};
定义了一个包含五个元素的结构体用于write和read时的数据结构。
3.2 用户代码
3.2.1 关于显示文本框内容的添加
gtk_text_buffer_get_end_iter(text_buffer,&iter);
gtk_text_buffer_insert(text_buffer,&iter,"this is it",-1);
3.2.2防止缓存溢出
每次在进行对数组的赋值时,检查是否出现溢出,消除潜在的core错误。
char format_cash[9];
format_char(send_to,format_cash,8);
int tmp_send_from = atoi(format_cash);
从文本框中读入数据
char *login_ip = gtk_entry_get_text(GTK_ENTRY(message_entry_ip));
第四章、运行调试结果
编译service.c和client.c两个主文件,出现若干个警告,编译通过,调试基本成功。BUG报告:当服务器先于客户端关闭时会导致客户端的异常退出。程序运行截图如下。
图4-1
参考文献
宋国伟,《GTK+2.0编程范例》
张威,《Linux网络编程教程》
Warren W. Gay,《实战Linux Socket编程》
附录
define.h源代码
#include<string.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<sys/time.h>
#include<sys/select.h>
#include<netinet/in.h>
#include<glib.h>
#include<pthread.h>
#include<stdlib.h>
#include<thread_db.h>
#define MAXUSER 4
#define BUFSIZE 100
#define MYPORT 3490
#define BACKLOG 10
#define NO 0
#define LOGIN 1
#define LOGIN_AWK 2
#define LOGIN_NEG 3
#define REFRESH 4
#define REFRESH_AWK 5
#define REFRESH_NEG 6
#define LOGOUT 7
#define LOGOUT_AWK 8
#define LOGOUT_NEG 9
#define SEND 10
#define PACK 1024
#define PACK_HEAD 26
#define PACK_MSG 998
struct user_info{ //user info struct
int sd; //socket id
int stat; //on line?
int id; //user id
char key[12]; //user key
char time[128];//user last login time
};
struct user_info USER_INFO[MAXUSER] = {
{-1,0,0,"0",""},
{-1,0,1,"11111111",""},
{-1,0,2,"2",""},
{-1,0,3,"3",""},
};
struct msg_pack{ //message pack struct
int tag; //msg type char size:2
int send_from; //msg send from char size:8
int send_to; //msg send to char size:8
char key[64]; //key used for login char size:8
char msg[PACK_MSG];//msg content char size:998
};
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int new_sd[MAXUSER];
int tmp_index = -1;
int total_online = 0;
/*
//build socket,init,and bind it to a port
int build_ear(int ip){
//build socket
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd < 0){
printf("cannot build socket\n");
return -1;
};
struct sockaddr_in my_addr;
struct sockaddr_in their_addr;
//init
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(MYPORT);
my_addr.sin_addr.s_addr = htonl(ip);
bzero(&(my_addr.sin_zero),8);
//bind
int ret = bind(sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr));
if(ret < 0){
printf("cannot bind socket with port\n");
return -1;
};
return 0;
};
int parse_sig(char *in,char *flag,char *data){
char *p = NULL;
int i = 0;
p = strtok(in,SPLIT_TAG);
flag = stpcpy(flag,p);
p = strtok(NULL,SPLIT_TAG);
data = stpcpy(data,p);
return 0;
};*/
// used for pick number
int cut_char_int(char *in,int *out,int beg,int out_len){
int in_len = strlen(in);
char tmp[1024];
if(in_len < beg+out_len || beg < 0){
return -1;
};
int i = 0;
for(i=beg; i<=beg+out_len-1; i++){
*(tmp+i-beg) = *(in+i);
};
*(tmp+out_len) = '\0';
*out = atoi(tmp);
return 0;
};
// used for pick key,ignore blank
int cut_char_char(char *in,char *out,int beg,int out_len){
int in_len = strlen(in);
if(in_len < beg+out_len || beg < 0){
return -1;
};
int i = beg;
int j = 0;
for(i=beg; i<=beg+out_len-1; i++){
if(*(in+i) == ' '){
continue;
};
*(out+j) = *(in+i);
j++;
};
*(out+j) = '\0';
return 0;
};
// used for pick msg
int cut_char_talk(char *in,char *out){
int i = PACK_HEAD;
for(; *(in+i) != '\0'; i++){
*(out+i-PACK_HEAD) = *(in+i);
};
*(out+i-PACK_HEAD) = '\0';
return 0;
};
// used for format char to 8
int format_char(char *in,char *out,int size){
int i = 0;
int con = 0;
for(; *(in+i) != '\0' && i<size; i++){
*(out+i) = *(in+i);
};
*(out+i) = '\0';
return 0;
};
// pack data struct
int do_pack(struct msg_pack *in,char *out){
int flag = sprintf(out,"%02d%08d%08d%08s%s",in->tag,in->send_from,in->send_to,in->key,in->msg);
return flag;
};
// unpack data struct
int do_unpack(char *in,struct msg_pack *out){
int len = strlen(in);
if(len < PACK_HEAD){
return -1;
};
int flag_cut = 0;
flag_cut += cut_char_int(in,&out->tag,0,2);
flag_cut += cut_char_int(in,&out->send_from,2,8);
flag_cut += cut_char_int(in,&out->send_to,10,8);
flag_cut += cut_char_char(in,out->key,18,8);
flag_cut += cut_char_talk(in,out->msg);
if(flag_cut != 0){
return -1;
};
return 0;
};
// do service in thread
void *do_service(void* args){
int in_sd = (int) args;
char in[PACK];
// g_printf("in thread %d\n",new_sd[in_sd]);
// total_online++;
struct msg_pack in_unpack ;
while(read(new_sd[in_sd],in,PACK) > 0){
do_unpack(in,&in_unpack);
g_printf("\tread msg %s\n",in);
int flag = 0;
char in_pack[PACK];
switch(in_unpack.tag){
case LOGIN:{
g_printf("\tlogin\n");
pthread_mutex_lock(&mutex);
if(in_unpack.send_from < 0 || in_unpack.send_from >= MAXUSER){
in_unpack.tag = LOGIN_NEG;
stpcpy(in_unpack.msg,"wrong user id");
}else if(strcmp(USER_INFO[in_unpack.send_from].key,in_unpack.key) != 0){
in_unpack.tag = LOGIN_NEG;
stpcpy(in_unpack.msg,"wrong user key");
}else if(USER_INFO[in_unpack.send_from].stat != 0){
in_unpack.tag = LOGIN_NEG;
stpcpy(in_unpack.msg,"user already login");
}else{
in_unpack.tag = LOGIN_AWK;
USER_INFO[in_unpack.send_from].stat = 1;
USER_INFO[in_unpack.send_from].sd = new_sd[in_sd];
};
do_pack(&in_unpack,in_pack);
flag += write(new_sd[in_sd],in_pack,sizeof(in_pack));
pthread_mutex_unlock(&mutex);
};break;
case LOGOUT:{
pthread_mutex_lock(&mutex);
USER_INFO[in_unpack.send_from].stat = 0;
USER_INFO[in_unpack.send_from].sd = 0;
in_unpack.tag = LOGOUT_AWK;
do_pack(&in_unpack,in_pack);
flag += write(new_sd[in_sd],in_pack,sizeof(in_pack));
sleep(0.1);
pthread_mutex_unlock(&mutex);
};break;
case REFRESH:{
g_printf("\trefresh\n");
pthread_mutex_lock(&mutex);
for(int i=0; i<MAXUSER; i++){
if(USER_INFO[i].stat == 1){
char tmp_id[12];
sprintf(tmp_id,"%d",USER_INFO[i].id);
strcat(in_unpack.msg,tmp_id);
strcat(in_unpack.msg,"\n");
};
};
in_unpack.tag = REFRESH_AWK;
do_pack(&in_unpack,in_pack);
flag += write(new_sd[in_sd],in_pack,sizeof(in_pack));
sleep(0.1);
pthread_mutex_unlock(&mutex);
};break;
case SEND:{
g_printf("\tsend\n");
if(in_unpack.send_to < 0 || in_unpack.send_to >= MAXUSER){
g_print("\tsend to wrong user\n");
}else if(USER_INFO[in_unpack.send_to].stat != 1){
}else{
do_pack(&in_unpack,in_pack);
flag += write(USER_INFO[in_unpack.send_to].sd,in_pack,sizeof(in_pack));
};
};break;
};
if(flag < 0){
g_print("loop wrong flag %d\n",flag);
break;
};
};
g_printf("\tend of this client\n");
total_online--;
new_sd[in_sd] = -1;
USER_INFO[in_unpack.send_from].stat = 0;
close(new_sd[in_sd]);
};
service.c源代码
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/time.h>
#include<unistd.h>
#include<netdb.h>
#include<netinet/in.h>
#include<glib.h>
#include<stdio.h>
#include<signal.h>
#include<pthread.h>
#include"define.h"
pthread_t THREAD[MAXUSER];
int main(int argc, char *argv[]){
gint sd;
gchar buf[1024];
struct sockaddr_in *sin;
gint slen;
for(int i=0; i<MAXUSER; i++){
new_sd[i] = -1;
};
sd = socket(AF_INET,SOCK_STREAM,0);
if(sd < 0) {
g_print("cannot creat socket\n");
return -1;
};
sin = g_new(struct sockaddr_in,1);
sin->sin_family = AF_INET;
sin->sin_port = htons(MYPORT);
sin->sin_addr.s_addr = INADDR_ANY;
slen = sizeof(struct sockaddr_in);
if(bind(sd,(struct sockaddr *)sin,slen)<0){
g_print("cannot bind\n");
return -1;
};
while(1){
if(listen(sd,8)<0){
g_print("cannot listen\n");
};
int flag_accept = -1;
int flag_find = -1;
flag_accept = accept(sd,(struct sockaddr*)sin,&slen);
if(flag_accept < 0){
continue;
};
for(int i=0; i<MAXUSER; i++){
if(new_sd[i] == -1){
flag_find = i;
new_sd[i] = flag_accept;
break;
};
};
if(flag_find == -1){
sprintf(buf,"too many users now");
write(flag_accept,buf,1024);
close(flag_accept);
}else{
total_online++;
g_printf("new thread create id %d\n totoal %d user on line\n",new_sd[flag_find],total_online);
pthread_create(&THREAD[tmp_index],NULL,do_service,(void*)flag_find);
};
};
g_print("end all\n");
// close(sd);
// g_free(sin);
};
client.c源代码
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<gtk/gtk.h>
#include<arpa/inet.h>
#include<pthread.h>
#include"define.h"
// global
gint sd;
int user_id;
int refresh_flag = 0;
pthread_t GETMSG;
static GtkWidget *id_entry;
static GtkWidget *key_entry;
static GtkWidget *send_button, *refresh_button, *login_button;
static GtkWidget *login_window;
static GtkWidget *message_entry_ip, *message_entry_id, *message_entry_key;
static GtkWidget *message_entry_to, *message_entry_msg;
static GtkWidget *text;
static GtkTextBuffer *text_buffer;
static GtkTextIter iter;
struct sockaddr_in service_addr;
void on_delete_main_win(){
gtk_main_quit();
};
void on_delete_login_win(){
gtk_widget_set_sensitive(login_button,TRUE);
gtk_widget_destroy(login_window);
};
void on_open_login_win();
void on_send_msg_but();
void on_send_refresh_but();
void on_send_login_but();
void *get_msg();
int main(int argc,char* argv[]){
GtkWidget *window;
GtkWidget *vbox, *hbox;
GtkWidget *view;
GtkWidget *label_1, *label_2;
// main window
gtk_init(&argc,&argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
展开阅读全文