17 socket——socket、bind、listen、accept、connect

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;
}

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值