Archive for the ‘linux/server’ Category

安装jabberd时,出现Libidn >= 0.3.0 not found 问题

Posted by 机器人 on 30th 十月 2009 in linux/server

安装jabberd时,出现:

checking for stringprep_check_version in -lidn... yes
checking for Libidn version >= 0.3.0... no
configure: error: Libidn >= 0.3.0 not found

这样的问题呢?libidn这个库已经存在。

[~@serv_1 jabberd-2.2.9]# rpm -qa | grep libidn
libidn-0.6.5-1.1
libidn-devel-0.6.5-1.1

libidn这个库我确实也已经安装了,为什么还是会出现这样的问题呢?不明白?
查了资料说,要么没有libidn这个库,要么就是库的路径不对,但我的库都是装在系统默认的环境变量所指的目录里的。/usr/include /usr/lib。没有理由找不到啊!!
不解!!

机器人 2009-10-30 17:10 于 北京 阴

Linux命令行下svn常用命令

Posted by 机器人 on 14th 九月 2009 in linux/server

1、Linux命令行下将文件checkout到本地目录

svn checkout path(path是服务器上的目录)

例如:svn checkout svn://192.168.1.1/pro/domain

简写:svn co
Read the rest of this entry »

GDB怎么调试运行着的程序

Posted by 机器人 on 10th 九月 2009 in c/c++, linux/server

对于GDB这里就不作介绍了,随便找个搜索引擎一搜GDB,介绍就已经是很详细了,这篇文章主要是来谈怎么使用GDB来调试一个运行着的程序,或者说怎么调试一个进程,似乎标题有些拗口,其次也会对fork()分离出现的多子进程的调试加以说明。
下面是一段测试代码。
test.c

#include < stdio.h >
#include < unistd.h >
 
static void PrintMessage(int i);
static void GoToSleep(void);
 
 
int main(void)
{
	int i = 100000;
 
	while ( 1 )
	{
		PrintMessage( i );
		GoToSleep();
		i -= 1;
	}
 
	return 0;
}
 
 
 
void PrintMessage(int i)
{
		char buf[1024];
		sprintf(buf,"%d bottles of beer on the wall.\n", i);
		printf("%s",buf);
 
}
 
 
 
static void GoToSleep(void)
{
	sleep(3);
}

接下来是编译时使用的Makefile文件.

TARGET = test
SRC    =  $(TARGET).c
OBJ    =  $(TARGET).o
CC     =  gcc
CFLAGS = -g3 -W -Wall -std=c99
 
$(TARGET): $(OBJS)
 
 
.PHONY: clean
 
clean:
	$(RM) $(TARGET) $(OBJS)

此程序是一个服务程序,程序一旦启动,将作为一个进程永驻内存,可以通过

~@hqlong ps -ef | grep "test"

来查看该进程的信息。
此程序主要实现每3秒钟向墙上打印一瓶啤酒。对于这样的一个启动就作为一个进程进驻内存的程序应该怎么来进行调试呢?接下来的事情就是要来回来这个问题,
通过make来对源文件进行编译。

~@hqlong make

这里会在当前目录下产生一个test的可执行文件。
在对程序进行正式调试之前来回忆一个使用GDB调试一个非服务程序的步骤。假设test这个可执行文件是一个非服务程序,那么一般是通过如下几步方式来进行调试的。

hqlong@ubuntu:/tmp$ gdb test
GNU gdb 6.8-debian
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http ://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-linux-gnu"...
(gdb) b 1
Breakpoint 1 at 0x80483f4: file beer-process.c, line 1.
(gdb) r
Starting program: /tmp/test 
 
Breakpoint 1, main () at beer-process.c:9
warning: Source file is more recent than executable.
9	{
(gdb) n
main () at beer-process.c:10
10		int i = 100000;
(gdb) q
The program is running.  Exit anyway? (y or n) y
</http>

首先是通过gdb test来调试程序。然后使用b(break) 1在第一行设置断点,然后使用r(run) 来运行程序,最后使用n来单步运行程序,如果想要查看运行中某变量的值,可能通过p(print)来打印。如查看i的值,就可以通过p i。最后使用q(quit)来退出程序。

由于服务程序一旦启动,就以进程的方式进驻内存,不退出,所以和非服务程序的调试方式有一些区别。
服务一旦启动后,系统会分配一个pid,然后使用gdb绑定上这个pid,最后就可以通过通用方式进行调试了。
绑定进程的方式有下几种。

hqlong@ubuntu:/tmp$ ./test &
100000 bottles of beer on the wall.
[1] 25292

方式一
通过–pid参数来绑定指定的进程程序。

~@hqlong gdb --pid 25552

方式二
通过程序和进程号来绑定。

~@hqlong gdb test 25552

方式二
先启动gdb后,通过attach来绑定pid

~@hqlong gdb
gdb) attach 25552

将pid和gdb绑定后,就可以来对每一段代码进行调试。
下面是对上面例子的完整调试过程。
1. 启动进程

hqlong@ubuntu:/tmp$ ./test &
[1] 25615
hqlong@ubuntu:/tmp$ 100000 bottles of beer on the wall.
99999 bottles of beer on the wall.
99998 bottles of beer on the wall.

可以看见,启动test后,系统所分配的pid(进程号)为25615,然后每隔3秒钟就打印出一条信息。
2.使用gdb来绑定test进程。
这里需要重新启动一个新的shell来进行调试,也就是新开一个窗口,然后使用

hqlong@ubuntu:/tmp$ gdb --pid 25615
GNU gdb 6.8-debian
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http ://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-linux-gnu".
Attaching to process 25615
Reading symbols from /tmp/test...done.
Reading symbols from /lib/tls/i686/cmov/libc.so.6...done.
Loaded symbols for /lib/tls/i686/cmov/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
0xb7ffb430 in __kernel_vsyscall ()
(gdb) 
</http>

来将gdb与已启动的test进行绑定,从而进入调试状态,这里会发现,当进入gdb调试状态后,先前启动程序的窗口就不再每隔3秒钟打印信息了,而是将控制权将给了gdb.
3.使用bt来显示当前程序的函数调用栈结构(也就是函数的调用顺序)。

(gdb) bt
#0  0xb7ffb430 in __kernel_vsyscall ()
#1  0xb7f23780 in nanosleep () from /lib/tls/i686/cmov/libc.so.6
#2  0xb7f235be in sleep () from /lib/tls/i686/cmov/libc.so.6
#3  0x0804851e in GoToSleep () at beer-process.c:35
#4  0x080484ac in main () at beer-process.c:15
(gdb)

可知函数的调用过程为:main调用GoToSleep,GoToSleep调用sleep等。
使用frame来选择程序的调试起点,也可以使用break来选择指定的行或者函数来作为断点。

(gdb) frame 4
#4  0x080484ac in main () at beer-process.c:15
15			GoToSleep();
(gdb)

这里我们选择最底层的栈,也就是整个程序的入口main函数来作为起点。
4. 调试跟踪过程
使用n(next)来一步一步的跟踪。

(gdb) n
Single stepping until exit from function sleep, 
which has no line number information.
GoToSleep () at beer-process.c:36
36	}
(gdb) 
main () at beer-process.c:16
16			i -= 1;
(gdb) 
14			PrintMessage( i );
(gdb)

这里有个技巧,如果要重复执行上一次执行的操作,可以直接回车。
通过上面的调试,发现程序运行到了14行,也就是PrintMessage函数处,如果这个时候我们继续n(next),程序就直接跳过函数,运行到函数的下一行,但如果想进入函数内部去调试,可以使用s(step),来进入函数体内进行调试。
进入函数体

(gdb) 
14			PrintMessage( i );
(gdb) s
PrintMessage (i=99897) at beer-process.c:25
25	{
(gdb) n
27	    sprintf(buf,"%d bottles of beer on the wall.\n", i);
(gdb) n
28		printf("%s",buf);
(gdb)

以上是进入PrintMessage体内,然后继续使用n(next)来一步一步的运行。进行到了28行,发现有两个变量,一个为i,另一个为buf,可以通过p(print)来打印该变量的值。

(gdb) p i
$1 = 99897
(gdb) p buf
$2 = "99897 bottles of beer on the wall.\n\000\000R\000�0u������$u���\227\001�\000\000\000\000p���\000\000\000\000\000\000\000\000\001\000\000\000��DB�x��8���\000\000\000\000�\217\001�p\226\001�0u��$u���\226\000�\001\000\000\000\000\000\000\000�v���\222\001�\205���", '\0' <repeats 12 times>, "�\226\000��\217\001�\000\000\000\000\000u���t���\020\001�\b\000\000\000\f\000\000\000\000����v��b\221\000�\000���\020���\f\000\000\000�\217"...
(gdb) 
</repeats>

发现通过p i 来打印i的值,结果是正确的,但通过p buf来打印buf 的值,却显示了很多不可读的乱码。这里因为buf代表这个变量在内存中的首地址,所以p不知道变量在什么时候结束,而i则不同,因为i是整型,在内存中一般占4个字节,所以p直接从首地址向后数4位就行了。所以这里打印字符指针或者数组时,需要指定一个长度。语法为 p *buf@len. 这里的len可以通过strlen(buf)来取得。

(gdb) p *buf@strlen(buf)
$3 = "99897 bottles of beer on the wall.\n"
(gdb)

这一下子就对了。
接下来继续使用n(next)来单步运行。

(gdb) 
28		printf("%s",buf);

发现运行到这一步时,启动程序的那个窗口打印出了一条信息。

99897 bottles of beer on the wall.

这说明,程序正好运行到了打印字符的地方。
如果这个时候想退出函数,直接返回,可能通过 finish.

(gdb) finish 
Run till exit from #0  PrintMessage (i=99895) at beer-process.c:28
main () at beer-process.c:15
15			GoToSleep();
(gdb)
退出PrintMessage函数,接着运行下一行代码GoToSleep.
如果调试完毕,或能通过q(quit)来退出GDB。
(gdb) q
The program is running.  Quit anyway (and detach it)? (y or n) y
Detaching from program: /tmp/test, process 25615

以上是调试一个进程的过程。

下面附带说明一下怎么调试一个需要通过fork()分离进程的程序。下面是一段一个Web服务器的在接受请求时的代码片断。

for (;;) {
	if ((connfd = accept(listenfd, (struct sockaddr *) &clientaddr,
					&clientaddrlen)) == -1) {
		perror("http server: accept error");
		continue;
	}
	if (fork() == 0)
		accept_request(connfd);
	else close(connfd);
 
	int statloc;
	waitpid(-1, &statloc, WNOHANG);
}

当单步运行到if(fork() == 0)这一步时,那怕是分离进程成功,也不会运行accept_request(connfd);而是直接跳到跳过,运行下面的代码。在调试这样的程序里,需要在进入gdb控制台时,需要将follow-fork-mode的值设成child.

(gdb) set follow-fork-mode child

本文主要是体验一下使用GDB的调试过程,对一些指令和俗语没有作过多的解释,如果有不明白可直接留言,或者参考相关资料。
参数资料
http://www.gnu.org/software/gdb/
http://dirac.org/linux/gdb/06-Debugging_A_Running_Process.php

结束!

机器人 2009-09-10 19:00 于 北京 晴

抽空做的一个网址缩短服务

Posted by 机器人 on 2nd 九月 2009 in linux/server, mylife, php/javascript

用过 twitter的用户都知道,整个输入框只能输入140个字符,如果需要发布一些URL地址,但这些地址又比较长的话,会占用很多字符,然后自己能输入的字符就十分有限,为了解决这个问题,随之就兴起了很多第三方应用,它可以将一个很长的地址缩短成很短的字符串,在发布信息时,只需要发现这些很短的字符串即可,只要点击这些短地址,就可以跳转到原地址。
国外比较著名的网址缩短服务提供商有。
TinyURL Qurl Jitty 等。

国内也有相应的服务,比如新浪为了配合它的新产品新浪围脖,也启用 sinaurl.cn这个域名来提供网址缩短服务。

昨天也抽空做了一个这样的服务。 体验地址。 http://cut.hqlong.com/

机器人 2009年09月02日 13:23 于 北京 阴

对于c/c++二次封装第三方类库的使用

Posted by 机器人 on 24th 八月 2009 in c/c++, linux/server

设计模块里中的适配器模式就谈到了为了实现接口的统一,需要对现有的类库进行第二次封装,进而达到接口统一的目的。
在 c/c++中,同样我们会采用这一策略,对接口不友好的或者需要统一的接口类库进行第二次封装。
一般来讲,会将二次封装后的库同样也做成一个静态或者动态库,但在对自己的库进行使用时,一定要加裁所封装的第三方库。
假如现在手上有一个第三方库叫libxx.a,二次封装后的库叫libself.a,在使用自己的库时,一般会采用如下编译参数选项编译。
假设:头文件目录为/inc 类库目录为/lib

g++ .... -lself -I/inc -L/lib

但这样会提示xx这个库未定义,正确的做法是同样需要加裁所封装的第三方库。

g++ .... -lself -lxx -I/inc -L/lib

结束!

机器人 2009年08月24日 17:28 于 北京 阴

ubuntu下chrome浏览器的安装

Posted by 机器人 on 11th 八月 2009 in linux/server

chrome浏览器至推出后,官方一直只提供window版,据说过后会推出其它操作系统版,但官方一直没有给明确的时间表。Linux版本的Chromium是google浏览器的开源版本,虽然很多功能暂时还不能用,但我们可以先体验一下。具体安装步骤如下:
1. 从 http://www.codeweavers.com/services/ports/chromium/ 右侧下载 .deb包。

2. 双击安装或者用命令来安装。

sudo dpkg -i cxchromium_0.9.0-1_i386.deb

3. 安装成功.
效果图
ubuntu chrome
图1

ubuntu chrome

虽然是安装上了.但有很多功能都不能用.先说说我发现的吧.

1. 安装好了后,本准备用它来写本文的,打开后,才发现在使用时输入不了汉字。(估计是和opera一样,和输入法有冲突)

2. 地址栏输入网址,不垂直居中,看起来很别扭.

3. 最大化和最小化必需用鼠标调整,快捷键基本上不能用.

据说还有其它很多问题,不过也没关系,重在体验.不过虽然有这么多问题,但速度这一优势还是继承下来了,比如只是浏览,不用输入汉字时,可以考虑使用它.

机器人 2009年08月11日 11:56 于 北京 晴

重定向的几个组合应用

Posted by 机器人 on 24th 六月 2009 in linux/server

cmd 2>file      把文件描述符2重定向到file,即把错误输出存到file中。

cmd > file 2>&1     把标准错误重定向到标准输出,再重定向到file,即stderr和stdout都被输出到file中

cmd &> file     功能与上一个相同,更为简便的写法。

cmd >& file     功能仍与上一个相同。

cmd > f1 2>f2     把stdout重定向到f1,而把stderr重定向到f2

tee files     把stdout原样输出的同时,复制一份到files中。

tee files     把stderr和stdout都输出到files中,同时输出到屏幕。

cmd >> file 把 stdout 重定向到 file 文件中(追加);

cmd 1> fiel 把 stdout 重定向到 file 文件中;

cmd > file 2>&1 把 stdout 和 stderr 一起重定向到 file 文件中;

cmd 2> file 把 stderr 重定向到 file 文件中;

cmd 2>> file 把 stderr 重定向到 file 文件中(追加);

cmd >> file 2>&1 把 stderr 和 stderr 一起重定向到 file 文件中(追加);

cmd < file >file2 cmd 命令以 file 文件作为 stdin,以 file2 文件作为 stdout;

cat <>file 以读写的方式打开 file;

cmd < file cmd 命令以 file 文件作为 stdin;

cmd << delimiter Here document,从 stdin 中读入,直至遇到 delimiter 分界符。

机器人 2009-06-24 14:56 于 北京 晴

vim执行php或者c/c++的快捷键

Posted by 机器人 on 23rd 六月 2009 in c/c++, linux/server, php/javascript, phpoo, vim

经常我们会编写一些测试代码,这些代码一般只是临时测试某一个函数,测试过后基本上就没什么用了。
如果使用IDE,这好办,基本上都有一些快速运行的键,比如在ZendStudio里,将代码编写好了以后,只需要按一下F5,就可以马上出来执行结果,而且这个文件都可以不用保存。

如果使用VIM来编写代码,可能很多朋友就会想了,有没有什么方法来建立一个快捷键,能快速的运行我们所编写的代码呢?!!
答案当然是肯定了的,不然也不会写这篇日志来讨论这个问题了。

在介绍方法之前,先来谈一下自己以前的执行方式吧!

先拿PHP来讲吧,最传统的方式就是将编写好的代码放在apache的根目录,然后通过浏览器来运行,或者使用php命令来运行php.可以使用php的r参数

hqlong@~$ php -r "echo 'hqlong';"

来快速测试,或者使用q参数

hqlong@~$ php -q test.php

来运行。

接下来讲怎么将vim支持一键执行。
大致原理是使用键盘映射,将快捷键映射到执行的命令。

在.vimrc中添加

nmap < F5 > < ESC >:!clear && php -q < C-R >%< cr >

即当我们按下F5时,就相当于执行了如下操作。
1.按下ESC,然后再按下:,进入vim命令行模式
2.执行clear清屏操作。
3.然后运行当前脚本。
4.最后执行回车操作.
下面有几个指令需要解释一下:
< C - R >%:得到当前文件的名字,包括路径.(按ctrl+r,然后再按%)

接下来可以测试一下.
vim /tmp/test.php

< ? php
    echo "hqlong";
?>

保存后,按F5,
就是快速的得出执行结果。

基于c/c++也同样可以做出映射来,比如我们用F6来执行C++代码。
那么就只需在.vimrc中添加

nmap < F6 > < ESC >:!clear && g++ < C-R >% -o test && ./test< cr >

这里如果我们测试如下代码。
vim test.cpp

# include < iostream >
using namespace std;
 
int main() {
     cout << "hqlong";
    return 0;
}

将会在当前上当下生成一个test可执行文件,并执行它。
所以的这一切都是由于nmap的强大映射功能。

希望对大家有所帮助,欢迎对不明白的地方进行讨论。
机器人 2009-06-23 22:59 于 北京 晴

几步搞定makefile文件的编写

Posted by 机器人 on 16th 六月 2009 in c/c++, linux/server

对于一个简单的程序。如下:
hello.c

#include < stdio.h>
int main() {
    printf("hello world\n");
    return 0;
}

可通过

~@ubuntu:~$ gcc hello.c -o hello

来进行编译。
当一个程序有3个文件时。假设有如下几个文件。
hello.c global.h global.c。
源码如下
hello.c

#include < stdio.h>
#include "global.h"
 
int main() {
    echo("hello\n");
    return 0;
}

global.h

void echo (char* str);

global.c

#include < stdio.h>
#include "global.h"
 
void echo (char* str) {
    printf("%s",str);
}

一般的编译方式如下

~@ubuntu:~$ gcc global.c hello.c -o hello

或者先编译再连接

~@ubuntu:~$ gcc -o global.c
~@ubuntu:~$ gcc global.o hello.c -o hello

我们也可以使用make命令来对它进行编译,不过这里需要一个makefile文件来描述make为何种方式来编译源代码和链接程序。
makefile文件的缩写规则如下:
TARGET… : PREREQUISITES…
COMMAND


target:规则的目标
prerequisites:规则的依赖
command:规则的命令行。值得注意的是每一个命令必需是以[TAB]开始。
上面的程序的makefile简单编写如下
Makefile

hello:global.o hello.o
    gcc hello.o global.o -o hello
hello.o:hello.c global.h
    gcc -c hello.c -o hello.o
global.o:global.c global.h
    gcc -c global.c -o global.o
clean:
    rm *.o hello

使用make编译

~@ubuntu:~$ make
gcc -c global.c -o global.o
gcc -c hello.c -o hello.o
gcc hello.o global.o -o hello

这里我们可以发现当前目录下生成了hello这个可执行文件。

从上面的Makefile文件中,能看出有三条规则,第一条的目标就生成可执行文件hello,第二条目标就生成hello.o文件,第三条则是生成global.o文件。而每个冒号后台的部分为生成达到该目标所需要的依赖文件,比如:第一条需要依赖hello.o和global.o这两个文件,第二条需要依赖hello.c和 global.h这两上文件,第三条需要依赖global.c和global.h这两上文件。接下来下面每条规则下面的一行就是需要执行的命令了。

最后还有一个clean,当用户键入make clean时,就清除所有的*.o文件。

关于make使用上面Makefile文件的工作原理如下:
默认情况下,make执行的是Makefile文件中的第一个规则,此规则的目标也就是整个程序的终极目标,我们上面的文件中生成hello这个可执行文件就是我们的最终目标,但要生成hello,又必需要依赖于hello.o和global.o这两个链接文件,make在处理这个规则之前,需要先处理hello所依赖文件的更新规则。
对这些.o(链接文件)规则的处理有三种情况。
1. 目标.o文件不存在,使用其描述规则创建它。如:如果hello.o不存在,则使用gcc -c hello.c -o hello.o来创建它。
2. 目标.o文件存在,但它所信赖的源文件中的任何一个比它都要新,则使用其描述规则创建它。如:hello.c或者global.h有更新时,gcc -c hello.c -o hello.o就得重新执行一次。
3. 目标.o文件存在 ,但它比所依赖的所有源文件都要新时,则什么也不做。

完成了对所有的.o文件的创建和更新后,make将处理第一条规则(终极目标),同样也分三种情况下进行处理。
1. 目标文件”hello”不存在,则执行描述规则创建hello.
2. 目标文件”hello”存在,但它所信赖的文件中的任何一个比它都要新,则根据规则重新链接生成hello文件.
3. 目标文件 “hello”存在,它比它的任何一个链接文件都要新,则什么也不做.

更多细节可参考:
GNU make 中文手册
http://www.diybl.com/course/3_program/c++/cppjs/2008622/127546.html

机器人 2009-06-16 14:17 于 北京 雨

linux c/c++ socket程序的编写

Posted by 机器人 on 14th 六月 2009 in linux/server

这篇日志还是先从一个能够运行起来的例子出发,一旦能顺利的看到程序的成功运行,那么接下来的事件我想应该是问为什么了?似乎这样更加容易理解和掌握。

对于socket程序的概念这里就不多写了,但我相信,如果能看完这整篇文章,我相信不会再有这样的疑问的。

下面将编写一个c/s结构的程序,主要功能是client将向server发送一些消息,而当server收到client的请求时,并向client发送一条回应信息。

server.c代码如下:

#include <stdio .h>
#include < stdlib .h>
#include < errno .h>
#include < string .h>
#include < sys /types.h>
#include < netinet /in.h>
#include < sys /socket.h>
#include < sys /wait.h>
 
#define SERVPORT 3333 
#define BACKLOG 10
#define MAXSIZE 1024
 
int main() {
    int sockfd,client_fd;
    struct sockaddr_in my_addr;
    struct sockaddr_in remote_addr;
    //创建套接字
    if ((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1) {
        perror("socket create failed!");
        exit(1);
    }
 
    //绑定端口地址
    my_addr.sin_family      = AF_INET;
    my_addr.sin_port        = htons(SERVPORT);
    my_addr.sin_addr.s_addr = INADDR_ANY;
    bzero(&(my_addr.sin_zero),8);
    if (bind(sockfd, (struct sockaddr*)&my_addr, sizeof(struct sockaddr)) == -1) {
        perror("bind error!");
        exit(1);
    }
 
    //监听端口
    if (listen(sockfd, BACKLOG) == -1) {
        perror("listen error");
        exit(1); 
    }
 
    while (1) {
        int sin_size = sizeof(struct sockaddr_in);    
        if ((client_fd = accept(sockfd, (struct sockaddr*)&remote_addr,&sin_size)) == -1) {
            perror("accept error!");
            continue;
        }
        printf("Received a connection from %s\n", (char*)inet_ntoa(remote_addr.sin_addr));
 
       //子进程段
        if (!fork()) {
            //接受client发送的请示信息
            int rval;
            char buf[MAXSIZE];   
            if ((rval = read(client_fd, buf, MAXSIZE)) < 0) {
                perror("reading stream error!");
                continue;
            }
            printf("%s\n",buf);
 
            //向client发送信息
            char* msg = "Hello,Mr hqlong, you are connected!\n";
            if (send(client_fd, msg, strlen(msg), 0) == -1) perror("send error!");
            close(client_fd);
            exit(0);
        } 
        close(client_fd);
    }
    return 0;
}

编译并启动服务

hqlong@ubuntu:~$ gcc server.c -o server
hqlong@ubuntu:~$./server &

这里我们的server已经作为一个服务后台运行,如果想知道后台的服务的运行状态,可能使用netstat来查看.

hqlong@ubuntu:~/t$ netstat -nl | grep 3333
tcp        0      0 0.0.0.0:3333            0.0.0.0:*               LISTEN

可以看出3333端口已经在监听,这说明服务已经启动。
为了测试server是否可以接受client的请求,可以使用telnet来进行测试。

hqlong@ubuntu:~$ telnet 127.0.0.1 3333
Trying 127.0.0.1...
Received a connection from 127.0.0.1
Connected to 127.0.0.1.
Escape character is '^]'.
test
test
 
Hello,Mr hqlong, you are connected!
Connection closed by foreign host.

可以看出,我们使用telnet来连接刚所启动的server,然后向该server发送了一条信息”test”,server收到了这条信息后,向client发送了一条响应信息,告诉我们,我们已经连接上了。

接下来来编写自己的client程序,完成的功能和上面的telnet的测试功能一样,向server发送一条信息,server在收到这条信息后,向client发送一条响应信息。
代码如下:client.c

#include < stdio .h>
#include < stdlib .h>
#include < errno .h>
#include < string .h>
#include < sys /types.h>
#include < netinet /in.h>
#include < sys /socket.h>
#include < sys /wait.h>
 
#define SERVPORT 3333
#define MAXDATASIZE 100
#define SERVER_IP "127.0.0.1"
#define DATA  "this is a client message"
 
int main(int argc, char* argv[]) {
    int sockfd, recvbytes;
    char buf[MAXDATASIZE];
    struct hostent *host;
    struct sockaddr_in serv_addr;
 
    if (( sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        perror("socket error!");
        exit(1);
    }
    bzero(&serv_addr,sizeof(serv_addr));
    serv_addr.sin_family    = AF_INET;
    serv_addr.sin_port      = htons(SERVPORT);
    serv_addr.sin_addr.s_addr= inet_addr(SERVER_IP);
 
    if (connect(sockfd, (struct sockaddr *)&serv_addr,sizeof(struct sockaddr)) == -1) {
        perror("connect error!");
        exit(1);
    }
 
    write(sockfd,DATA, sizeof(DATA));
   if ((recvbytes = recv(sockfd, buf, MAXDATASIZE,0)) == -1) {
        perror("recv error!");
        exit(1);
    }
 
    buf[recvbytes] = '\0';
    printf("Received: %s",buf);
    close(sockfd);
    return 0;
}

编译运行

hqlong@ubuntu:~$ gcc client.c -o client
hqlong@ubuntu:~$ ./client
Received a connection from 127.0.0.1
Hello,Mr hqlong, you are connected!
Connection closed by foreign host.

以上就是整个服务器端和客户端程序的编写。


上图显示了程序使用面向连接协议(tcp)时,进行的典型socket系统调用。服务器程序建立了一个socket,并调用bind函数将此socket和本地协议端口联系起来,然后用listen和accept函数将此socket参数置于被动的监听模式并接收到建立连接。
客户程序也建立一个socket,接着调用connect函数启动网络对话。在客户和服务器建立连接以后,就可以用read、write等函数进行通信了。
具体函数细节请参考linux c函数手册
http://man.chinaunix.net/develop/c&c++/linux_c/default.htm

机器人 2009-06-14 18:04 于 北京 晴