如何编写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号端口对服务器上的时间和日期进行操作。 
ExpandedBlockStart.gif /*timeserve.c*/ 
ExpandedBlockStart.gif /*服务器程序伪代码如下: 
InBlock.gif
InBlock.gif打开daytime监听端口; 
InBlock.gifwhile(客户机与服务器成功连接——成功返回通信文件描述符) 
InBlock.gif
InBlock.giffork() 
InBlock.gif子进程: 
InBlock.gif
InBlock.gif读出当前时间; 
InBlock.gif将当前时间写入通信文件描述符; 
InBlock.gif关闭通信文件描述符; 
InBlock.gif
InBlock.gif父进程: 
InBlock.gif关闭通信文件描述符; 
InBlock.gif
ExpandedBlockEnd.gif
*/
 
None.gif
None.gif#include 
None.gif#include 
None.gif#include 
None.gif#include 
None.gif#include 
None.gif#include 
None.gif#include 
None.gif#include 
None.gif#include 
None.gif#include 
None.gif#include 
None.gif#include 
None.gif#include 
None.gif#include 
None.gif
None.gif int main( int argc, char *argv[]) 
ExpandedBlockStart.gif
InBlock.gifint listenfd,communfd; 
InBlock.gifstruct sockaddr_in servaddr; 
InBlock.gifpid_t childpid; 
InBlock.giftime_t tick; 
InBlock.gifchar buf[1024]; 
InBlock.gif
InBlock.gifif((listenfd=socket(AF_INET,SOCK_STREAM,0))==-1) 
ExpandedSubBlockStart.gif
InBlock.gifperror("Could not create socket"); 
InBlock.gifexit(1); 
ExpandedSubBlockEnd.gif}
 
InBlock.gif
InBlock.gifservaddr.sin_family=AF_INET; 
InBlock.gifservaddr.sin_addr.s_addr=INADDR_ANY; 
InBlock.gifservaddr.sin_port=htons(13); 
InBlock.gifif(bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr))==-1) 
ExpandedSubBlockStart.gif
InBlock.gifperror("bind error"); 
InBlock.gifexit(1); 
ExpandedSubBlockEnd.gif}
 
InBlock.gifif(listen(listenfd,254)==-1) 
ExpandedSubBlockStart.gif
InBlock.gifperror("listen error"); 
InBlock.gifexit(1); 
ExpandedSubBlockEnd.gif}
 
InBlock.gifwhile(communfd=accept(listenfd,(struct sockaddr*)NULL,NULL)) 
ExpandedSubBlockStart.gif
InBlock.gifif((childpid=fork())==-1) 
ExpandedSubBlockStart.gif
InBlock.gifperror("fork error"); 
InBlock.gifexit(1); 
ExpandedSubBlockEnd.gif}
 
InBlock.gifelse if(childpid==0) 
ExpandedSubBlockStart.gif
InBlock.giftick=time(NULL); 
InBlock.gifsnprintf(buf,sizeof(buf),"%.24s\r\n",ctime(&tick)); 
InBlock.gifwrite(communfd,buf,strlen(buf)); 
InBlock.gifclose(communfd); 
ExpandedSubBlockEnd.gif}
 
InBlock.gifelse if(childpid>0) 
InBlock.gifclose(communfd); 
InBlock.gif
ExpandedSubBlockEnd.gif}
 
InBlock.gifexit(0); 
ExpandedBlockEnd.gif}
 
None.gif
None.gif
ExpandedBlockStart.gif /*timeclient.h*/ 
None.gif#include 
None.gif#include 
None.gif#include 
None.gif#include 
None.gif#include 
None.gif#include 
None.gif#include 
None.gif#include 
None.gif#include 
None.gif
None.gif int main( int argc, char *argv[]) 
ExpandedBlockStart.gif
InBlock.gifint communfd,n; 
InBlock.gifstruct sockaddr_in servaddr; 
InBlock.gifchar recieve[1024],buf[1024]; 
InBlock.gif
InBlock.gifif(argc!=2) 
ExpandedSubBlockStart.gif
InBlock.gifperror("Usage: client "); 
InBlock.gifexit(1); 
ExpandedSubBlockEnd.gif}
 
InBlock.gifif((communfd=socket(AF_INET,SOCK_STREAM,0))==-1) 
ExpandedSubBlockStart.gif
InBlock.gifperror("socket error"); 
InBlock.gifexit(1); 
ExpandedSubBlockEnd.gif}
 
InBlock.gifservaddr.sin_family=AF_INET; 
InBlock.gifservaddr.sin_port=htons(13); 
InBlock.gifif((servaddr.sin_addr.s_addr=inet_addr(argv[1]))==INADDR_NONE) 
ExpandedSubBlockStart.gif
InBlock.gifperror("inet_addr error"); 
InBlock.gifexit(1); 
ExpandedSubBlockEnd.gif}
 
InBlock.gifif(connect(communfd,(struct sockaddr*)&servaddr,sizeof(servaddr))==-1) 
ExpandedSubBlockStart.gif
InBlock.gifperror("connect error"); 
InBlock.gifexit(1); 
ExpandedSubBlockEnd.gif}
 
InBlock.gifwhile((n=read(communfd,recieve,1024))>0) 
ExpandedSubBlockStart.gif
InBlock.gifrecieve[n]=0; 
InBlock.gifif(fputs(recieve,stdout)==EOF) 
InBlock.gifperror("fputs error"); 
ExpandedSubBlockEnd.gif}
 
InBlock.gifclose(communfd); 
InBlock.gifexit(0); 
ExpandedBlockEnd.gif}
 
None.gif
用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天前
|
Java Linux 应用服务中间件
Windows和Linux的最佳Web服务器
【7月更文挑战第20天】Windows和Linux的最佳Web服务器
20 3
|
2天前
|
Linux 网络安全 Android开发
Termux-远程管理Linux服务器
在手机上通过termux管理Linux服务器
|
22天前
|
网络协议 Linux
云服务器内部端口占用,9090端口已经存在了,如何关闭,Linux查询端口,查看端口,端口查询,关闭端口写法-netstat -tuln,​fuser -k 3306/tcp​
云服务器内部端口占用,9090端口已经存在了,如何关闭,Linux查询端口,查看端口,端口查询,关闭端口写法-netstat -tuln,​fuser -k 3306/tcp​
|
22天前
|
Linux 网络安全 虚拟化
Ngnix04系统环境准备-上面软件是免费版的,下面是收费版的,他更快的原因使用了epoll模型,查看当前Linux系统版本, uname -a,VMWARE建议使用NAT,PC端电脑必须使用网线连接
Ngnix04系统环境准备-上面软件是免费版的,下面是收费版的,他更快的原因使用了epoll模型,查看当前Linux系统版本, uname -a,VMWARE建议使用NAT,PC端电脑必须使用网线连接
|
22天前
|
关系型数据库 MySQL Linux
Linux部署实战前言,MySQL在CentOS安装【单机软件】,MySQL的安装需要root权限,yum install mysql,systemctl enable mysqld开机自启的意思
Linux部署实战前言,MySQL在CentOS安装【单机软件】,MySQL的安装需要root权限,yum install mysql,systemctl enable mysqld开机自启的意思
|
22天前
|
Ubuntu Linux
Linux软件安装-Linux系统靠yum命令安装软件,yum命令是一个RPM包软件管理器,用于自动化安装配置Linux软件,.rpm是Linux包下的软件,yum install下载 wget re
Linux软件安装-Linux系统靠yum命令安装软件,yum命令是一个RPM包软件管理器,用于自动化安装配置Linux软件,.rpm是Linux包下的软件,yum install下载 wget re
|
22天前
|
Linux 调度
部署02-我们一般接触的是Mos和Wimdows这两款操作系统,很少接触到Linux,操作系统的概述,硬件是由计算机系统中由电子和机械,光电元件所组成的,CPU,内存,硬盘,软件是用户与计算机接口之间
部署02-我们一般接触的是Mos和Wimdows这两款操作系统,很少接触到Linux,操作系统的概述,硬件是由计算机系统中由电子和机械,光电元件所组成的,CPU,内存,硬盘,软件是用户与计算机接口之间
|
22天前
|
运维 监控 大数据
部署-Linux01,后端开发,运维开发,大数据开发,测试开发,后端软件,大数据系统,运维监控,测试程序,网页服务都要在Linux中进行部署
部署-Linux01,后端开发,运维开发,大数据开发,测试开发,后端软件,大数据系统,运维监控,测试程序,网页服务都要在Linux中进行部署
|
22天前
|
大数据 Linux 程序员
软件开发常见流程之服务器+Linux部署项目,会用服务器+Linux部署项目资料
软件开发常见流程之服务器+Linux部署项目,会用服务器+Linux部署项目资料
|
23天前
|
负载均衡 Java Linux
黑马头条01,环境搭建,今日头条的介绍,今日头条的功能架构图,技术栈的说明,服务层,nacos(奶靠丝)安装,安装在Linux服务器上环境准备,
黑马头条01,环境搭建,今日头条的介绍,今日头条的功能架构图,技术栈的说明,服务层,nacos(奶靠丝)安装,安装在Linux服务器上环境准备,

热门文章

最新文章