1、socket——与远端进程相连
管道的不足:管道使得进程向其他进程发送数据就像向文件发送数据一样容易,但是管道具有两个重大的缺陷。管道在一个进程中被创建,通过fork来实现共享,因此管道只能连接相关的进程,也只能连接同一台主机上的进程。
Unix提供了另外一种进程间的通信机制socket,socket允许在不相关的进程间创建类似管道的连接,设置可以通过socket连接其他主机上的进程。
2、socket客户端/服务器工作原理
(1)socket服务器工作流程——socket、bind、listen、accept、read/write、close
socket服务器工作流程类似基于电话的服务,分为6步骤,每一步与一个系统调用相对应:
- 1、获取电话线:socket
- 2、分配号码:bind
- 3、允许接入调用:listen
- 4、等待电话:accept
- 5、传送数据:read/write
- 6、挂断电话:close
socket服务器工作流程详解:
- 向内核申请一个socket
系统调用socket创建一个socket,系统调用socket建立一个socket,sockid=socket(int domain,int type,int protocol),domain是通信域PF_INET用于Internet socket,type是socket类型,SOCK_STREAM跟管道类似,protocol是协议socket中所用的协议,默认为0。
socket调用创建一个通信端点并返回一个标识符。有很多种类型的通信系统,每个被称为一个通信域,Internet本身就是一个域,Unix内核是另一个域,Linux支持几个其他域的通信。socket的类型指出了程序将要使用的数据流类型。SOCK_STREAM类型跟双向的管道类似。数据作为连续的字节流从一端写入,再从另一端读出。还有SOCK_DGRAM类型。最后的参数protocol指的是内核中网络代码所使用的协议,并不是客户端和服务器之间的协议,0代表选择标准的协议。
- 绑定地址到socket上,地址包括主机、端口
把一个网络地址分配给socket,在Internet域中,地址由主机和端口构成。
系统调用bind绑定一个地址到socket,result=bind(int sockid,struct sockaddr *addrp,socklen_t addrlen),sockid是socket的id,addrp指向包含地址结构的指针,addrlen是地址长度。bind调用把一个地址分配给socket。每个地址族都有自己的格式,因特网地址族AF_INET使用主机和端口来标志,地址就是一个以主机和端口为成员的结构体。
- 在socket上,允许接入呼叫并设置队列长度为1
服务器接收接入的呼叫,程序使用listen。
系统调用listen监听socket上的连接,result=listen(int sockid,int qsize),sockid为接收请求的socket,qsize为允许接入连接的数目。listen请求内核允许指定的socket接收接入呼叫,并不是所用类型的socket都能接收接入呼叫,但SOCK_STREAM类型是可以的。第二个参数指出接收队列的长度。
- 等待/接收呼叫
服务器等待直到呼叫到来,使用系统调用accept来接收调用。
系统调用accept接收socket上的一个连接,fd=accept(int sockid,struct sockaddr *callerid,socklen_t *addrlenp),sockid是接收该socket上的呼叫,callerid指向呼叫者地址结构的指针,addrlenp指向呼叫者地址结构长度的指针。accept阻塞当前进程,一直到指定socket上的接入连接被建立起来,然后accept将返回文件描述符,并用该文件描述符来进行读写操作。此文件描述符实际上是连到呼叫进程的某个文件描述符的一个连接。如果callerid和addrlenp指针不为空的话,内核将把呼叫者地址填充到callerid所指向的结构中,并把该结构的长度填充到addrlenp所指向的内存单元中。
- 传输数据
accept调用所返回的文件描述符是一个普通文件的描述符,用fdopen将文件描述符定向到缓存的数据流,以便于使用fprintf调用来进行输出。
- 关闭连接
accept所返回的文件描述符可以由标准的系统调用close关闭。
(2)socket客户端工作流程——socket、connect、read/write、close
socket客户端工作流程类似基于电话的服务,分为4步骤,每一步与一个系统调用相对应:
- 1、获取电话线:socket
- 2、呼叫服务器:connect
- 3、传送数据:read/write
- 4、挂断电话:close
socket客户端工作流程详解:
- 向内核请求建立socket
客户端需要一个socket跟网络相连,就像时间服务中的客户端需要一条电话线跟电话网络相连一样,客户端必须建立Internet域(AF_INET)socket,并且它还必须是流socket(SOCK_STREAM)
- 与服务器相连
客户端使用系统调用connect连接到服务器。
系统调用connect连接到socket,result=connect(int sockid,struct sockaddr *serv_addrp,socklen_t addrlen),sockid用于建立连接的socket,serv_addrp指向服务器地址结构的指针,addrlen为结构的长度。connect调用试图把由sockid所标识的socket和由serv_addrp所指向的socket地址相连接,而此时sockid是一个合法的文件描述符,可以用来进行读写操作。
- 传送数据和挂断
在连接成功之后,进程可以从该文件描述符读写数据,就像与普通文件或管道相连接一样。读取数据之后,客户端关闭文件描述符然后退出,若客户退出而不关闭文件描述符,内核将完成关闭文件描述符的任务。
3、具体程序实现
timeserv.c
时间服务器
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netdb.h>
#include<time.h>
#include<strings.h>
#include<stdlib.h>
#define PORTNUM 13000
#define HOSTLEN 256
#define oops(msg) {perror(msg); exit(1);}
int main(int argc,char *argv[])
{
struct sockaddr_in saddr;
struct hostent *hp;
char hostname[HOSTLEN];
int sock_id,sock_fd;
FILE *sock_fp;
time_t thetime;
sock_id=socket(PF_INET,SOCK_STREAM,0);
if(sock_id==-1)
oops("socket");
bzero((void*)&saddr,sizeof(saddr));
gethostname(hostname,HOSTLEN);
hp=gethostbyname(hostname);
bcopy((void*)hp->h_addr,(void*)&saddr.sin_addr,hp->h_length);
saddr.sin_port=htons(PORTNUM);
saddr.sin_family=AF_INET;
if(bind(sock_id,(struct sockaddr*)&saddr,sizeof(saddr))!=0)
oops("bind");
if(listen(sock_id,1)!=0)
oops("listen");
while(1)
{
sock_fd=accept(sock_id,NULL,NULL);
printf("Wow!get a call!\n");
if(sock_fd==-1)
oops("accept");
sock_fp=fdopen(sock_fd,"w");
if(sock_fp==NULL)
oops("fdopen");
thetime=time(NULL);
fprintf(sock_fp,"The time here is..");
fprintf(sock_fp,"%s",ctime(&thetime));
fclose(sock_fp);
}
return 0;
}
timeclnt.c
时间服务客户端
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netdb.h>
#include<stdlib.h>
#include<strings.h>
#define oops(msg) {perror(msg); exit(1);}
int main(int argc,char *argv[])
{
struct sockaddr_in servadd;
struct hostent *hp;
int sock_id,sock_fd;
char message[BUFSIZ];
int messlen;
sock_id=socket(AF_INET,SOCK_STREAM,0);
if(sock_id==-1)
oops("socket");
bzero(&servadd,sizeof(servadd));
hp=gethostbyname(argv[1]);
if(hp==NULL)
oops(argv[1]);
bcopy(hp->h_addr,(struct sockaddr*)&servadd.sin_addr,hp->h_length);
servadd.sin_port=htons(atoi(argv[2]));
servadd.sin_family=AF_INET;
if(connect(sock_id,(struct sockaddr*)&servadd,sizeof(servadd))!=0)
oops("connect");
messlen=read(sock_id,message,BUFSIZ);
if(messlen==-1)
oops("read");
if(write(1,message,messlen)!=messlen)
oops("write");
close(sock_id);
return 0;
}
rlsd.c
远程ls系统服务器
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netdb.h>
#include<strings.h>
#include<stdlib.h>
#include<time.h>
#include<string.h>
#define oops(msg) {perror(msg); exit(1);}
#define PORTNUM 15000
#define HOSTLEN 256
sanitize(char *str)
{
char *src,*dest;
for(src=dest=str;*src;src++)
{
if(*src=='/'||isalnum(*src))
*dest++=*src;
}
*dest='\0';
}
int main(int argc,char *argv[])
{
struct sockaddr_in saddr;
struct hostent *hp;
char hostname[HOSTLEN];
int sock_id,sock_fd;
FILE *sock_fpi,*sock_fpo;
FILE *pipe_fp;
char dirname[BUFSIZ];
char command[BUFSIZ];
int dirlen,c;
sock_id=socket(AF_INET,SOCK_STREAM,0);
if(sock_id==-1)
oops("socket");
bzero(&saddr,sizeof(saddr));
gethostname(hostname,HOSTLEN);
hp=gethostbyname(hostname);
bcopy(hp->h_addr,(struct sockaddr*)&saddr.sin_addr,hp->h_length);
saddr.sin_family=AF_INET;
saddr.sin_port=htons(PORTNUM);
if(bind(sock_id,(struct sockaddr*)&saddr,sizeof(saddr))!=0)
oops("bind");
if(listen(sock_id,1)!=0)
oops("listen");
while(1)
{
sock_fd=accept(sock_id,NULL,NULL);
if(sock_fd==-1)
oops("accept");
if((sock_fpi=fdopen(sock_fd,"r"))==NULL)
oops("fdopen reading");
if(fgets(dirname,BUFSIZ-5,sock_fpi)==NULL)
oops("reading dirname");
sanitize(dirname);
if((sock_fpo=fdopen(sock_fd,"w"))==NULL)
oops("fdopen writing");
sprintf(command,"ls %s",dirname);
if((pipe_fp=popen(command,"r"))==NULL)
oops("popen");
while((c=getc(pipe_fp))!=EOF)
putc(c,sock_fpo);
fclose(pipe_fp);
fclose(sock_fpo);
fclose(sock_fpi);
}
return 0;
}
rls.c
远程ls系统客户端
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netdb.h>
#include<stdlib.h>
#include<strings.h>
#include<string.h>
#define oops(msg) {perror(msg); exit(1);}
#define PORTNUM 15000
int main(int argc,char *argv[])
{
struct sockaddr_in servadd;
struct hostent *hp;
int sock_id,sock_fd;
char buffer[BUFSIZ];
int n_read;
if(argc!=3)
exit(1);
sock_id=socket(AF_INET,SOCK_STREAM,0);
if(sock_id==-1)
oops("socket");
bzero(&servadd,sizeof(servadd));
hp=gethostbyname(argv[1]);
if(hp==NULL)
oops(argv[1]);
bcopy(hp->h_addr,(struct sockaddr*)&servadd.sin_addr,hp->h_length);
servadd.sin_family=AF_INET;
servadd.sin_port=htons(PORTNUM);
if(connect(sock_id,(struct sockaddr*)&servadd,sizeof(servadd))!=0)
oops("connect");
if(write(sock_id,argv[2],strlen(argv[2]))==-1)
oops("write");
if(write(sock_id,"\n",1)==-1)
oops("write");
while((n_read=read(sock_id,buffer,BUFSIZ))>0)
{
if(write(1,buffer,n_read)==-1)
oops("write");
}
close(sock_id);
return 0;
}