BSD Socket 简易入门手册 翻译:Wilbur Lang 目录
介绍当你进入 UNIX 的神秘世界后,立刻会发现越来越多的东西难以理解。对于大多数人来说,BSD socket 的概念就是其中一个。这是一个很短的教程来解释他们是什么、他们如何工作并给出一些简单的代码来解释如何使用他们。 类比 (什么是 socket ?)socket 是进行程序间通讯(IPC)的 BSD 方法。这意味着 socket 用来让一个进程和其他的进程互通信息,就象我们用电话来和其他的人交流一样。 用电话来比喻是很恰当的,我们在后面将一直用电话这个概念来描叙 socket 。 装上你的新电话(怎样侦听?)一个人要能够收到别人打给他的电话,首先他要装上一门电话。同样,你必须先建立 socket 以侦听线路。这个过程包含几个步骤。首先,你要建立一个新的 socket,就象先装上电话一样。 因为 sockets 有几种类型,你要注明你要建立什么类型的。你要做一个选择是 socket 的地址格式。如同电话有音频和脉冲两种形式一样,socket 有两个最重要的选项是 AF_UNIX 和 IAF_INET。AF_UNIX 就象 UNIX 路径名一样识别 sockets。这种形式对于在同一台机器上的 IPC 很有用。而 AF_INET 使用象 192.9.200.10 这样被点号隔开的四个十进制数字的地址格式。除了机器地址以外,还可以利用端口号来允许每台机器上的多个 AF_INET socket。我们这里将着重于 AF_INET 方式,因为他很有用并广泛使用。 另外一个你必须提供的参数是 socket 的类型。两个重要的类型是 SOCK_STREAM 和 SOCK_DGRAM。 SOCK_STREAM 表明数据象字符流一样通过 socket 。而 SOCK_DGRAM 则表明数据将是数据报(datagrams)的形式。我们将讲解 SOCK_STREAM sockets,他很常见并易于使用。 在建立 socket 后,我们就要提供 socket 侦听的地址了。就象你还要个电话号码来接电话一样。 SOCK_STREAM sockets 让连接请求形成一个队列。如果你忙于处理一个连接,别的连接请求将一直等待到该连接处理完毕。 下面的代码说明如何利用 /* code to establish a socket; originally from bzs@bu-cs.bu.edu <br> */ <br> <br>int establish(unsigned short portnum) <br>{ char myname[MAXHOSTNAME+1]; <br> int s; <br> struct sockaddr_in sa; <br> struct hostent *hp; <br> <br> memset(&sa, 0, sizeof(struct sockaddr_in)); /* clear our address */ <br> gethostname(myname, MAXHOSTNAME); /* who are we? */ <br> hp= gethostbyname(myname); /* get our address info */ <br> if (hp == NULL) /* we don't exist !? */ <br> return(-1); <br> sa.sin_family= hp->h_addrtype; /* this is our host address */ <br> sa.sin_port= htons(portnum); /* this is our port number */ <br> if ((s= socket(AF_INET, SOCK_STREAM, 0)) < 0) /* create socket */ <br> return(-1); <br> if (bind(s,&sa,sizeof(struct sockaddr_in)) < 0) { <br> close(s); <br> return(-1); /* bind address to socket */ <br> } <br> listen(s, 3); /* max # of queued connects */ <br> return(s); <br>} <br> 在建立完 socket 后,你要等待对该 socket 的调用了。 下面的代码演示使用是个演示。 /* wait for a connection to occur on a socket created with establish() <br> */ <br>int get_connection(int s) <br>{ int t; /* socket of connection */ <br> <br> if ((t = accept(s,NULL,NULL)) < 0) /* accept connection if there is one */ <br> return(-1); <br> return(t); <br>} <br> 和电话不同的是,在你处理先前的连接的时候,你还可以接受调用。为此,一般用 fork 来处理每个连接。下面的代码演示如何使用 #include <errno.h></errno.h> /* obligatory includes */ <br>#include <signal.h></signal.h> <br>#include <stdio.h></stdio.h> <br>#include <unistd.h></unistd.h> <br>#include <sys types.h=""></sys> <br>#include <sys socket.h=""></sys> <br>#include <sys wait.h=""></sys> <br>#include <netinet in.h=""></netinet> <br>#include <netdb.h></netdb.h> <br> <br>#define PORTNUM 50000 /* random port number, we need something */ <br> <br>void fireman(void); <br>void do_something(int); <br> <br>main() <br>{ int s, t; <br> <br> if ((s= establish(PORTNUM)) < 0) { /* plug in the phone */ <br> perror("establish"); <br> exit(1); <br> } <br> <br> signal(SIGCHLD, fireman); /* this eliminates zombies */ <br> <br> for (;;) { /* loop for phone calls */ <br> if ((t= get_connection(s)) < 0) { /* get a connection */ <br> if (errno == EINTR) /* EINTR might happen on accept(), */ <br> continue; /* try again */ <br> perror("accept"); /* bad */ <br> exit(1); <br> } <br> switch(fork()) { /* try to handle connection */ <br> case -1 : /* bad news. scream and die */ <br> perror("fork"); <br> close(s); <br> close(t); <br> exit(1); <br> case 0 : /* we're the child, do something */ <br> close(s); <br> do_something(t); <br> exit(0); <br> default : /* we're the parent so look for */ <br> close(t); /* another connection */ <br> continue; <br> } <br> } <br>} <br> <br>/* as children die we should get catch their returns or else we get <br> * zombies, A Bad Thing. fireman() catches falling children. <br> */ <br>void fireman(void) <br>{ <br> while (waitpid(-1, NULL, WNOHANG) > 0) <br> ; <br>} <br> <br>/* this is the function that plays with the socket. it will be called <br> * after getting a connection. <br> */ <br>void do_something(int s) <br>{ <br> /* do your thing with the socket here <br> : <br> : <br> */ <br>} <br> 拨号 (如何调用 socket)现在你应该知道如何建立 socket 来接受调用了。那么如何调用呢?和电话一样,你要先有个电话。用 在给 socket 地址后,你可以用 int call_socket(char *hostname, unsigned short portnum) <br>{ struct sockaddr_in sa; <br> struct hostent *hp; <br> int a, s; <br> <br> if ((hp= gethostbyname(hostname)) == NULL) { /* do we know the host's */ <br> errno= ECONNREFUSED; /* address? */ <br> return(-1); /* no */ <br> } <br> <br> memset(&sa,0,sizeof(sa)); <br> memcpy((char *)&sa.sin_addr,hp->h_addr,hp->h_length); /* set address */ <br> sa.sin_family= hp->h_addrtype; <br> sa.sin_port= htons((u_short)portnum); <br> <br> if ((s= socket(hp->h_addrtype,SOCK_STREAM,0)) < 0) /* get socket */ <br> return(-1); <br> if (connect(s,&sa,sizeof sa) < 0) { /* connect */ <br> close(s); <br> return(-1); <br> } <br> return(s); <br>} <br> 这个函数返回一个可以流过数据的 socket 。 谈话(如何通过 sockets 交谈)好了,你在要传输数据的双方建立连接了,现在该传输数据了。 int read_data(int s, /* connected socket */ <br> char *buf, /* pointer to the buffer */ <br> int n /* number of characters (bytes) we want */ <br> ) <br>{ int bcount; /* counts bytes read */ <br> int br; /* bytes read this pass */ <br> <br> bcount= 0; <br> br= 0; <br> while (bcount < n) { /* loop until full buffer */ <br> if ((br= read(s,buf,n-bcount)) > 0) { <br> bcount += br; /* increment byte counter */ <br> buf += br; /* move buffer ptr for next read */ <br> } <br> else if (br < 0) /* signal an error to the caller */ <br> return(-1); <br> } <br> return(bcount); <br>} <br> 相同的函数也可以写数据,留给我们的读者吧。 挂起(结束)和你通过电话和某人交谈后一样,你要在 socket 间关闭连接。一般 世界语(交流的语言很重要)现在你可以在机器间联络了,可是要小心你所说的话。许多机器有自己的方言,如 ASCII 和 EBCDIC。更常见的问题是字节顺序问题。除非你一直传输的都是文本,否则你一定要注意这个问题。幸运的是,人们找出了解决的办法。 在很久以前,人们争论哪种顺序更“正确”。现在必要时有相应的函数来转换。其中有 i= htonl(i); <br>write_data(s, &i, sizeof(i)); <br> 在读数据后,再变回来。 read_data(s, &i, sizeof(i)); <br>i= ntohl(i); <br> 如果你一直坚持这个习惯,你将比别人少出错的机会。 未来在你的掌握了(下一步?)就用我们刚才讨论的东西,你就可以写自己的通讯程序了。和对待所有的新生事物一样, 最好还是看看别人已经做了些什么。这里有许多关于 BSD socket 的东西可以参考。 请注意,例子中没有错误检查,这在“真实”的程序中是很重要的。你应该对此充分重视。
|
BSD Socket 简易入门手册
最新推荐文章于 2023-11-24 09:47:14 发布