刚刚学习了Linux 下的管道机制,Maxiee 掌握了一种进程间通信的方法。但通过今天的学习,Maxiee 发现进程间通信的方法并不唯一,Unix 还提供了另外一种进程间的通信机制——socket。而这种方法,就跟网络有点关系了,感觉就像是一台服务器一样。
socket 的特点是:
允许在不相关的进程间创建类似管道的连接。
可以连接到其他主机上的进程。(管道只能连接单个主机上的进程)
知道了socket大体是什么了,Maxiee 看了一顿书还没搞明白,过程是怎样的呢?反复思考后,感觉应该是这样的:
1.申请socket
先通过socket函数,建立一个socket,并且返回一个标识符。在建立的过程中,socket函数原型是这样的:
sockid=socket(int domin,int type,int protocol)
有三个参数需要我们确定:
- domin:通信域,就是用于通信的系统,比如,Internet 算是一个通信域,Unix 内核则算另一个。PF_INET用于Internet socket。
- type:socket的数据流类型。SOCK_STREAM 跟双向管道类似。
- protocol:协议socket中所用的协议,默认为0代表标准协议。
2.给socket绑定地址
就好像网站要有网址一样,Maxiee申请了这个socket后,还要给它绑定一个地址,当进程要连接这个服务器时,就调用这个地址,这就要用到bind 函数。bind 函数的原型是这样的:
result=bind(int sockid,struct sockaddr *addrp,socklen_t addrlen)
它也要我们确认三个参数:
- sockid:这个不用说,当然是Maxiee 在第一步里建立socket 返回的描述符。注意,这个描述符只能作为参数,可不是地址哦!
- sockaddr:指向包含地址的结构的指针。这个参数是一个结构体,而要绑定的那个地址,就储存在这个机构体里面。
- addrlen:这个结构体的大小。
3. 允许接入呼叫
当上述两个步骤完成后,socket 已经进入了就绪状态,只需要我们发布命令,它就可以投入工作了,而这个命令,要调用listen 函数。
listen 函数请求内核允许制定的socket 接收接入呼叫。
同时我们还要定义接收队列的长度,在本例中,我们定义为1。listen函数的原型是这样的:
result=listen(int sockid,int qusize)
- sockid:我们申请的socket的id。
- qusize:要定义的队列大小,本例中为一。
4. 等待呼叫后怎样呢?
现在Maxiee已经启用了listen 开始监听,等待其他进程通过我们预定的地址来访问。
可是一旦真的有进程访问了,我们应该怎么办呢?在这段等待的时间里,我们做什么呢?
其实,程序在listen 之后的某一个位置里被阻塞了。也就是说,当我们listen 成功后,程序还会接着运行,但我们此时已经不需要再运行程序了,要记得Maxiee 要写的是一个服务器,现在只有有其它进程找到我们时,我们才要提供服务,才要接着运行程序。而在这之前,我们什么也不用做,等着就行,在这个位置,实现这个功能的函数,就是accept。它的原型如下:
fd=accept(int sockid,struct sockaddr *callerid,socklen_t *addrlenp)
它的各参数:
- sockid:还是我们申请socket 是返回的ID
- callerid:指向呼叫者地址的结构的指针。也就是说,程序在这里被阻塞了,一旦被唤醒,肯定是有了呼叫者。服务器从这里继续执行程序,而这个结构体里,已经有了呼叫者的地址信息。
- addrlenp:上述结构体的长度。
- 返回值,我们要注意这个accept 返回的是一个文件描述符。我们可以像操作文件一样,用fprint 向这个描述符代表的“文件”输出内容。如果我们用telnet 作为客户端连的话,这口老血就噴在了我们的屏幕上。
5. 示例程序及注释
这是书上的一个示例。我们写了一个显示当前时间的socket 程序,并且在自己的主机名上申请了5000 这个端口。我们在终端里运行这个程序后,他就没反映了。我们再开一个终端,用telnet 来呼叫他,就能得到当前时间,就像下图所示:
程序代码如下:
[c]
#include<stdio.h>
#include<unistd.h>
//申请socket、bind、listen、accept用
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netdb.h>
//显示时间用
#include<time.h>
//
#include<strings.h>
//端口号
#define PORTNUM 5000
#define HOSTLEN 256
#define opps(msg) {perror(msg);exit(1);}
int main(int ac,char av[])
{
struct sockaddr_in saddr;
struct hostent *hp;
char hostname[HOSTLEN];
int sock_id,sock_fd;
FILE sock_fp;
char *ctime();
time_t thetime;
//向内核申请socket
sock_id = socket(PF_INET,SOCK_STREAM,0);
if(sock_id == -1)
opps("socket");
printf("sock_id: %dn",sock_id);
//将地址绑定到socket上
//置字节字符串s的前n个字节为零且包括‘\0’
//也就是将saddr清零
//(void *)表示一个无类型的指针
bzero( (void *) &saddr,sizeof(saddr) );
printf("sizeof(saddr): %dn",sizeof(saddr));
//将主机名存入hostname中
gethostname(hostname,HOSTLEN);
printf("hostname: %sn",hostname);
//传入主机名,获得一个包含主机IP的结构体
hp=gethostbyname(hostname);
//将字符串src(前者)的前n个字节复制到dest(后者)中
//拷贝ip
bcopy( (void *)hp->h_addr,(void *)&saddr.sin_addr,hp->h_length);
saddr.sin_port = htons(PORTNUM);
saddr.sin_family = AF_INET;
//bind绑定一个地址到socket
//原型:result=bind(int sockid,struct sockaddr *addrp,socklen_t addrlen)
if( bind(sock_id,(struct sockaddr *)&saddr,sizeof(saddr))!=0)
opps("bind");
//允许接入,并设置队列为一
if(listen(sock_id,1)!=0)
opps("listen");
while(1){
//accept阻塞进程,直到建立连接
sock_fd = accept(sock_id,NULL,NULL);
printf("Wow!got a call!n");
if(sock_fd == -1)
opps("accept");
sock_fp = fdopen(sock_fd,"w");
if(sock_fp == NULL)
opps("fdopen");
thetime = time(NULL);
fprintf(sock_fp,"The time here is..");
fprintf(sock_fp,"%s",ctime(&thetime));
fclose(sock_fp);
}
}
[/c]