socket学习笔记

刚刚学习了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 来呼叫他,就能得到当前时间,就像下图所示:

linux下socket

程序代码如下:

[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]