如何编写Linux下的客户机/服务器软件

简介:
 Linux以其源代码公开闻名于世,并以其稳定性和可靠性雄霸操作系统领域,在网络应用技术方面使用得更加广泛。很久以来它就是Windows的重要对手之一。随着网络时代的来临,Linux的这种优势已变得更加突出。本文将论述如何在Linux环境下利用Socket实现客户机/服务器通信。 
随着网络技术的发展,网络结构已从过去的主机/终端型、对等型发展到现在广为使用的客户机/服务器型。客户机/服务器模型应用十分广泛,在Internet上WWW,E-mail,FTP等都是基于这种模型的。在面向连接的通信模式下,服务器打开监听端口,监听网络上其它客户机向该服务器发出的连接请求,当收到一个请求信号时与该客户机建立一个连接,之后两者进行交互式的通信。具体步骤可这样组织: 

服务器: 
1.打开一个已知的监听端口,如smtp为25、pop3为110、ftp为21、telnet为23等。 
2.在监听端口上监听客户机的连接请求,如果有客户机请求连接则建立一个连接线路。 
3.在连接线路上与客户机通信。 
4.通信完毕后关闭连接线路并继续监听客户机的连接请求。 

客户机: 
1.向指定的服务器主机及端口发出连接请求。 
2.当服务器建立连接线路后与服务器进行通信。 
3.通信完毕后关闭连接线路。 

Linux的许多特性都非常有助于网络程序设计:首先Linux拥有POSIX.1标准库函数,socket()、bind()、listen()这几个库函数可以非常方便地实现服务器/客户机模型,有关这几个库函数的使用说明将在后边介绍。其次Linux的进程管理也非常符合服务器的工作原理,所谓进程就是程序在内存中运行时的状态,可以说进程是动态的程序。在运行着Linux操作系统的计算机中,每一个进程都有一个创建它的父进程,而且它也能创建多个子进程。在服务器端我们可以用父进程去监听客户机的连接请求,当有客户机的连接请求时父进程创建一个子进程去建立连接线路并与客户机通信,而它本身可继续监听其它客户机的连接请求,这样就可避免当有一个客户机与服务器建立连接后服务器就不能再与其它客户机通信的问题。Linux的另一个特性是它秉承了UNIX设备无关性这一优秀特征,即它通过文件描述符实现了统一的设备接口,磁盘、显示终端、音频设备、打印设备甚至网络通信都使用统一的I/O调用。这三个特性将使Linux下的网络程序设计变得易如反掌。上述三个特性的综合利用将是这篇文章所要讲述的真谛所在。下边的客户机/服务器实现过程可以说明一二,注意与上文所述步骤的不同。 

服务器: 
1.打开一个已知的监听端口。 
2.在监听端口上监听客户机的连接请求,当有一客户机请求连接时建立连接线路并返回通信文件描述符。 
4.父进程创建一子进程,父进程关闭通信文件描述符并继续监听端口上的客户机连接请求。 
3.子进程通过通信文件描述符与客户机进行通信,通信结束后终止子进程并关闭通信文件描述符。 

客户机: 
1.向指定的服务器主机及端口发出连接请求,请求成功将返回通信文件描述符。 
2.通过通信文件描述符与服务器进行通信。 
3.通信完毕后关闭通信文件描述符。 


Linux的以下几个库函数是网络程序设计的核心部分,它们分别是: 
(1)socket 
调用方式: 
#include 
#include 

int socket(int domain,int type,int protocol); 

简要说明: 
此函数为通信创建一个端口,正常调用将返回一个文件描述符,错误调用将返回-1。domain参数有两种选择:AF_UNIX与AF_INET,其中AF_INET为Internet通信协议。type参数也有两种选择:SOCK_STREAM用于TCP,SOCK_DGRAM用于UDP。protocol参数通常为0。可通过下列代码为基于TCP协议的Internet通信建立套接口传输端口: 

#include 
#include 
#include 
int sock; 

if((sock=socket(AF_INET,SOCK_STREAM,0))==-1) 
perror("Could not create socket"); 

(2)bind 
调用方式: 
#include 
#include 

int bind(int s,const struct sockaddr *address,size_t address_len); 

简要说明: 
bind英文含意是关联,捆绑。其目的就是把socket返回的套接口端口与网络上的物理位置相关联。 
bind正常调用返回0,出错返回-1。此函数有三个参数:其中s为socket调用返回的文件描述符,*address设置了与网络上的物理位置相关的信息,它的类型是struct sockaddr,但在Internet上它是struct sockaddr_in。在socket.h中struct sockaddr_in定义为: 
struct sockaddr_in{ 
short sin_family; 
u_short sin_port; 
struct in_addr sin_addr; 
char sin_zero[8]; 
}; 
sin_family一般为AF_INET,sin_port为端口号,由于使用不同字节顺序的机器必须作转换,故应使用宏命令htons(host to network short)来转换端口号,sin_addr将置为INADDR_ANY。这三个值设置完成后*address参数才有意义。在编写代码时,应先设置*address参数内部各成员变量的值,再调用bind。 

(3)listen 
调用方式: 
#include 
#include 

int listen(int s,int backlog); 

简要说明: 
本函数使socket端口能够接受从客户机来的连接请求,正常调用返回0,出错返回-1。 
s参数为socket产生的文件描述符,backlog为所能接受客户机的最大数目。 
socket,bind,listen 三个函数的综合调用最终在服务器上产生一个能接受客户机请求的监听文件描述符s。 

(4)accept 
调用方式: 
#include 
#include 

int accept(int s,struct sockaddr *address,int *address_len); 

简要说明: 
当有客户机发出连接请求时,此函数初始化这个连接。正常调用返回与客户机通信的通信文件描述符,出错返回-1。参数s为socket调用返回的文件描述符,address将用来存储客户机的信息,此信息由accept填入,当与客户机连接时,客户机的地址与端口将填到此处。address_len是客户机地址长度的字节数,也由accept填入。 

(5)connect 
调用方式: 
#include 
#include 

int connect(int s,struct sockaddr *address,size_t address_len); 

简要说明: 
客户机调用socket建立传输端口后,调用connect来建立与远程服务器相连的连接线路。 
此函数的参数调用同bind。 

(6)inet_addr 
调用方式: 
#include 
#include 
#include 

in_addr_t inet_addr(const char *addstring); 

简要说明: 
此函数将字符串addstring表示的网络地址(如192.168.0.1)转换成32位的网络字节序二进制值,若成功返回32位二进制的网络字节序地址,若出错返回 INADDR_NONE。INADDR_NONE是32位均为1的值(即255.255.255.255,它是Internet的有限广播地址),故如果要转换的addstring是255.255.255.255,函数调用将失败。 

(7)fork 
调用方式: 
#include 
#include 


pid_t fork(void); 

简要说明: 
fork的作用是拷贝父进程的内存映象来创建子进程,两个进程将接着fork后的指令继续执行。 事实上它返回两个进程控制号,对于父进程它返回子进程的进程ID,对于子进程它返回0。 

可用下边的代码调用fork: 

pid_t childpid; 
if((childpid=fork())=-1){ 
perror("The fork failed"); 
exit(1); 

else if(child==0){ 
调用子进程; 

else if(child>0){ 
调用父进程; 



以上介绍了网络编程的有关库函数的调用方法,下面举一个客户机/服务器程序的小例子具体说明如何设计网络程序。本例介绍如何查看服务器上的时间和日期,由于daytime服务器的通用端口为13,客户机程序将通过调用13号端口对服务器上的时间和日期进行操作。 
/*timeserve.c*/ 
/*服务器程序伪代码如下: 

打开daytime监听端口; 
while(客户机与服务器成功连接——成功返回通信文件描述符) 

fork() 
子进程: 

读出当前时间; 
将当前时间写入通信文件描述符; 
关闭通信文件描述符; 

父进程: 
关闭通信文件描述符; 

*/
 

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main( int argc, char *argv[]) 

int listenfd,communfd; 
struct sockaddr_in servaddr; 
pid_t childpid; 
time_t tick; 
char buf[1024]; 

if((listenfd=socket(AF_INET,SOCK_STREAM,0))==-1) 

perror("Could not create socket"); 
exit(1); 
}
 

servaddr.sin_family=AF_INET; 
servaddr.sin_addr.s_addr=INADDR_ANY; 
servaddr.sin_port=htons(13); 
if(bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr))==-1) 

perror("bind error"); 
exit(1); 
}
 
if(listen(listenfd,254)==-1) 

perror("listen error"); 
exit(1); 
}
 
while(communfd=accept(listenfd,(struct sockaddr*)NULL,NULL)) 

if((childpid=fork())==-1) 

perror("fork error"); 
exit(1); 
}
 
else if(childpid==0) 

tick=time(NULL); 
snprintf(buf,sizeof(buf),"%.24s\r\n",ctime(&tick)); 
write(communfd,buf,strlen(buf)); 
close(communfd); 
}
 
else if(childpid>0) 
close(communfd); 

}
 
exit(0); 
}
 


/*timeclient.h*/ 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main( int argc, char *argv[]) 

int communfd,n; 
struct sockaddr_in servaddr; 
char recieve[1024],buf[1024]; 

if(argc!=2) 

perror("Usage: client "); 
exit(1); 
}
 
if((communfd=socket(AF_INET,SOCK_STREAM,0))==-1) 

perror("socket error"); 
exit(1); 
}
 
servaddr.sin_family=AF_INET; 
servaddr.sin_port=htons(13); 
if((servaddr.sin_addr.s_addr=inet_addr(argv[1]))==INADDR_NONE) 

perror("inet_addr error"); 
exit(1); 
}
 
if(connect(communfd,(struct sockaddr*)&servaddr,sizeof(servaddr))==-1) 

perror("connect error"); 
exit(1); 
}
 
while((n=read(communfd,recieve,1024))>0) 

recieve[n]=0; 
if(fputs(recieve,stdout)==EOF) 
perror("fputs error"); 
}
 
close(communfd); 
exit(0); 
}
 
用gcc编译两个源程序分别取名为server和client,以根用户身份运行服务器程序(设服务器网络地址为192.168.0.1): 
server & 
然后运行客户机程序(设服务器网络地址为192.168.0.1): 
client 192.168.0.1 
在客户机上就会反映出服务器上当前的时间如(Tue Feb 29 21:46:19 2000)。 

以上程序代码在redhat 6.0上试验通过。在程序代码中有关库函数snprintf、fputs、read、write、close的用法就不在这里说明了,如想了解这些库函数的调用方法可到我的网页http://lzdx.yeah. net/pro_unix.html去查找。在我的网页http://lzdx.yeah.net/pro_uici.html中有关于通用Internet接口(UICI)专用库的介绍,通用Internet接口(UICI)利用Socket库函数提供了一个简化的独立于传输的接口,它从整体上简化了网络程序设计过程。有兴趣的人可到那里去看看。最后祝愿我们每个人都能编写出自己的网络程序。
目录
相关文章
|
7天前
|
运维 监控 Linux
服务器管理面板大盘点: 8款开源面板助你轻松管理Linux服务器
在数字化时代,服务器作为数据存储和计算的核心设备,其管理效率与安全性直接关系到业务的稳定性和可持续发展。随着技术的不断进步,开源社区涌现出众多服务器管理面板,这些工具以其强大的功能、灵活的配置和友好的用户界面,极大地简化了Linux服务器的管理工作。本文将详细介绍8款开源的服务器管理面板,包括Websoft9、宝塔、cPanel、1Panel等,旨在帮助运维人员更好地选择和使用这些工具,提升服务器管理效率。
|
15天前
|
安全 算法 Linux
Linux 服务器还有漏洞?建议使用 OpenVAS 日常检查!
在数字化时代,Linux 服务器的安全至关重要。OpenVAS 是一款优秀的开源漏洞扫描工具,可以帮助及时发现并修复服务器中的安全隐患。本文将介绍 OpenVAS 的主要功能、使用方法及应对漏洞的措施,帮助用户加强服务器安全管理,确保企业数字化安全。
37 7
|
18天前
|
监控 Ubuntu Linux
使用VSCode通过SSH远程登录阿里云Linux服务器异常崩溃
通过 VSCode 的 Remote - SSH 插件远程连接阿里云 Ubuntu 22 服务器时,会因高 CPU 使用率导致连接断开。经排查发现,VSCode 连接根目录 ".." 时会频繁调用"rg"(ripgrep)进行文件搜索,导致 CPU 负载过高。解决方法是将连接目录改为"root"(或其他具体的路径),避免不必要的文件检索,从而恢复正常连接。
|
24天前
|
存储 Prometheus 监控
服务器监控软件Prometheus
【10月更文挑战第19天】
44 6
|
24天前
|
监控 数据可视化 BI
服务器监控软件Zabbix
【10月更文挑战第19天】
38 6
|
24天前
|
运维 监控 数据可视化
服务器监控软件Grafana
【10月更文挑战第19天】
25 4
|
21天前
|
缓存 Unix Linux
服务器linux!!!
本文介绍了计算机的演变历史、硬件基础知识及服务器相关知识。从电子管时代的ENIAC到冯-诺伊曼架构,再到现代计算机系统组成,详细讲解了计算机的发展历程。此外,文章还介绍了服务器的分类、品牌、硬件组成以及IDC机房的上架流程,为读者提供了全面的技术背景知识。
38 0
服务器linux!!!
|
23天前
|
人工智能 安全 Linux
|
24天前
|
运维 监控 Unix
服务器监控软件Nagios
【10月更文挑战第19天】
40 2
|
25天前
|
Linux 数据库
Linux服务如何实现服务器重启后的服务延迟自启动?
【10月更文挑战第25天】Linux服务如何实现服务器重启后的服务延迟自启动?
131 3
下一篇
无影云桌面