BSD Socket编程快速入门(二)
这是一篇简易的socket编程入门文章,可以让您快速建立起socket编程的概念。本文翻译自BSD Sockets: A Quick And Dirty Primer(http://www.frostbytes.com/~jimf/papers/sockets/sockets.html),如需转载本译本请务必注明原英文版出处,如果您愿意也请您请注明翻译译本来自学与思编程网,谢谢!
BSD Sockets: A Quick And Dirty Primer
Jim Frost(http://www.frostbytes.com/~jimf)
Software Tool & Die
Copyright (c) 1988, 1994 Jim Frost
All Rights Reserved
Last changed December 8, 1996
接上篇
跟打电话不一样,当您在处理前一个连接的同时,您还可以继续接受新的连接请求。因此,您可以fork()一个子进程来处理accept()返回的socket,当前进程继续调用accept()去等待新的连接请求。下面的代码可以同时处理多个连接:
#include <errno.h>/* obligatory includes */
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <netdb.h>
#define PORTNUM 50000 /* random port number, we need something */
void fireman(void);
void do_something(int);
main()
{
int s, t;
if ((s= establish(PORTNUM)) < 0) {/* plug in the phone */
perror(”establish”);
exit(1);
}
signal(SIGCHLD, fireman);/* this eliminates zombies */
for (;;) {/* loop for phone calls */
if ((t= get_connection(s)) < 0) {/* get a connection */
if (errno == EINTR) continue;
perror(”accept”);
exit(1);
}
switch(fork()) {/* try to handle connection */
case -1 : /* bad news. scream and die */
perror(”fork”);
close(s);
close(t);
exit(1);
case 0 : /* we’re the child, do something */
close(s);
do_something(t);
exit(0);
default : /* we’re the parent so look for */
close(t); /* another connection */
continue;
}
}
}
/* as children die we should get catch their returns or else we get * zombies, A Bad Thing. fireman() catches falling children. */
void fireman(void)
{
while (waitpid(-1, NULL, WNOHANG) > 0);
}
/* this is the function that plays with the socket. it will be called * after getting a connection. */
void do_something(int s)
{
/* do your thing with the socket here : : */
}
拨号(或则:如何请求与对方建立连接)
前面我们知道了如何建立一个socket,如何等待连接的到来。下面我们来看看如何请求与它建立连接。当你想打电话给别人时,您首先得有个电话啊,类似,当我们想去连接某个已经在等待连接的socket时,我们自己也需要创建一个socket,就跟前面一样,您也用socket()函数创建socket。有了socket您就可以用connect()函数请求与对方建立连接,当然,您在调用connect()时需要指定对方的地址(还记得前面讲的IP:Port地址对吗)。看看下面的代码:
int call_socket(char *hostname, unsigned short portnum)
{
struct sockaddr_in sa;
struct hostent *hp;
int a, s;
if ((hp= gethostbyname(hostname)) == NULL) {
errno= ECONNREFUSED;/* address? */
return(-1); /* no */
}
memset(&sa,0,sizeof(sa));
memcpy((char *)&sa.sin_addr,hp->h_addr,hp->h_length);
sa.sin_family= hp->h_addrtype;
sa.sin_port= htons((u_short)portnum);
if ((s = socket(hp->h_addrtype,SOCK_STREAM,0)) < 0)
return(-1);
if (connect(s, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
close(s);
return(-1);
}
return(s);
}
上面这个函数返回一个已连接的socket,现在就可以用它收发数据了。
交流(或则:如何在socket之间传递数据)
现在,sockets之间的连接已经建立好,接下来就可以用write()发送数据,用read()读取数据了。使用这两个函数读写socket跟读写普通文件有一点区别:读写socket时并不是每次您想读多少就能读多少,想写度少就能写多少,您必须用一个循环来处理socket的读写。看下面的代码:
int read_data(int s, char *buf, int n)
{
int bcount; /* counts bytes read */
int br; /* bytes read this pass */
bcount= 0;
br= 0;
while (bcount < n) { /* loop until full buffer */
if ((br= read(s,buf,n-bcount)) > 0) {
bcount += br; /* increment byte counter */
buf += br; /* move buffer ptr for next read */
} else if (br < 0) /* signal an error to the caller */
return(-1);
}
return(bcount);
}
write数据是我们也需要像上面的代码那样去处理。
挂机(或则:当不再需要通信时该怎么办)
就像您挂掉电话一样,当您不想再通信时,您必须关闭sockets之间的连接。您可以用close()函数来关闭一个连接,同时也会关闭这个socket。如果一方已经关闭了,那么另一方还继续给它发送数据,则会失败。当您关闭socket后,对方read()的话,它会返回0.
字节序很重要
现在您可以在电脑之间通信了,但需要注意的是字节序的问题。除非您总是传递单字节数据流,否则您不得不面对字节序的问题。庆幸的是,热门已经有了较好的解决办法。
很久以前,人们还在苦苦的确定需要使用哪种字节序才好好。现在已经有现存的字节序转换函数可供我们使用了。如htons()函数把短整型数据由本机字节序转换到网络字节序,ntohs()把短整型数据从网络字节序转换到本机字节序,还有htonl()和ntohl()负责转换长整型数据。在您想发送一个多字节整数时,您首先需要用函数把它转换成网络字节序。如:
int i = 12345678;
i = htonl(i);
write_data(s, &i, sizeof(i));
当您读到多字节整数是,你需要把它转换成本机字节序,如:
int i;
read_data(s, &i, sizeof(i));
i = ntohl(i);
请养成这种转换多字节数据的良好习惯,这样有时会给您减少很多麻烦。
现在该干什么了
利用前面所学,您可以通过socket在不同的机器之间通信了。但这些只是最简单的基础知识,如果您想利用好socket,那么还有很多东西要学。目前学习socket最好的书非《unix网络编程》这本经典之作莫属。还有多读一些开源的代码,那样您也可以学到很多socket编程技能。
需要注意的是,上面那些演示代码省略了很多错误检测代码,但错误检测是每个程序都必须做的,那样您的代码才会健壮,否则代码就是业余级别的。在用一些您不太熟悉的函数时,请多查阅其mannual手册。如果您对socket编程有什么疑惑,请致信jimf@world.std.com,我随时为您效劳!
