本文介绍Linux系统网络编程的内容,如套接口的概念与使用、网络编程的结构等。
1 什么是套接口
简单地说,套接口就是一种使用UNIX系统中的文件描述符和系统进程通信的一种方法。
因为在UNIX系统中,所有的I/O操作都是通过读写文件描述符而产生的。文件描述符就是
一个和打开的文件相关连的整数。但文件可以是一个网络连接、一个FIFO、一个管道、一个
终端、一个真正存储在磁盘上的文件或者UNIX系统中的任何其他的东西。所以,如果你希望
通过Internet和其他的程序进行通信,你只有通过文件描述符。
使用系统调用socket(),你可以得到socket()描述符。然后你可以使用send() 和recv()调用而
与其他的程序通信。你也可以使用一般的文件操作来调用read() 和write()而与其他的程序进行
通信,但send() 和recv()调用可以提供一种更好的数据通信的控制手段。下面我们讨论Internet
套接口的使用方法。
2 两种类型的Internet套接口
有两种最常用的Internet 套接口,“数据流套接口”和“数据报套接口”,以后我们用
“SOCK_STREAM” 和“SOCK_DGRAM”分别代表上面两种套接口。数据报套接口有时也
叫做“无连接的套接口”。
数据流套接口是可靠的双向连接的通信数据流。如果你在套接口中以“ 1, 2”的顺序放入
两个数据,它们在另一端也会以“1, 2”的顺序到达。它们也可以被认为是无错误的传输。
经常使用的telnet应用程序就是使用数据流套接口的一个例子。使用HTTP的WWW浏览器
也使用数据流套接口来读取网页。事实上,如果你使用telnet 登录到一个WWW站点的8 0端口,
然后键入“GET 网页名”,你将可以得到这个HTML页。数据流套接口使用TCP得到这种高质
量的数据传输。数据报套接口使用UDP,所以数据报的顺序是没有保障的。数据报是按一种应
答的方式进行数据传输的。
3 网络协议分层
由于网络中的协议是分层的,所以上层的协议是依赖于下一层所提供的服务的。也就是说,
你可以在不同的物理网络中使用同样的套接口程序,因为下层的协议对你来说是透明的。
UNIX系统中的网络协议是这样分层的:
• 应用层( telnet、ftp等)。
• 主机到主机传输层( TCP、UDP )。
• Internet层( IP和路由)。
• 网络访问层(网络、数据链路和物理层)。
4 数据结构
下面我们要讨论使用套接口编写程序可能要用到的数据结构。
首先是套接口描述符。一个套接口描述符只是一个整型的数值: i n t。
第一个数据结构是struct sockaddr,这个数据结构中保存着套接口的地址信息。
struct sockaddr {
unsigned short sa_family; /* address family, AF_xxx */
char sa_data[14]; /* 14 bytes of protocol address */
} ;
sa_family 中可以是其他的很多值,但在这里我们把它赋值为“ A F _ I N E T”。s a _ d a t a包括一
个目的地址和一个端口地址。
你也可以使用另一个数据结构s o c k a d d r _ i n,如下所示:
struct sockaddr_in {
short int sin_family; /* Address family */
unsigned short int sin_port; /* Port number */
struct in_addr sin_addr; /* Internet address */
unsigned char sin_zero[8]; /* Same size as struct sockaddr */
} ;
这个数据结构使得使用其中的各个元素更为方便。要注意的是sin_zero应该使用bzero() 或
者memset()而设置为全0。另外,一个指向sockaddr_in数据结构的指针可以投射到一个指向数
据结构sockaddr的指针,反之亦然。
5 IP地址和如何使用IP地址
有一系列的程序可以使你处理I P地址。
首先,你可以使用inet_addr()程序把诸如“132.241.5.10”形式的IP地址转化为无符号的整
型数。
ina.sin_addr.s_addr = inet_addr("132.241.5.10");
如果出错,inet_addr()程序将返回- 1。
也可以调用inet_ntoa()把地址转换成数字和句点的形式:
printf ("%s", inet_ntoa (ina.sin_addr));
这将会打印出I P地址。它返回的是一个指向字符串的指针。
5.1 socket()
我们使用系统调用socket()来获得文件描述符:
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
第一个参数domain设置为“AF_INET”。第二个参数是套接口的类型:SOCK_STREAM 或
SOCK_DGRAM。第三个参数设置为0。
系统调用socket()只返回一个套接口描述符,如果出错,则返回- 1。
5.2 bind()
一旦你有了一个套接口以后,下一步就是把套接口绑定到本地计算机的某一个端口上。但
如果你只想使用connect()则无此必要。
下面是系统调用bind()的使用方法:
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
第一个参数sockfd 是由socket()调用返回的套接口文件描述符。第二个参数my_addr 是指向
数据结构sockaddr的指针。数据结构sockaddr中包括了关于你的地址、端口和I P地址的信息。
第三个参数addrlen可以设置成sizeof(struct sockaddr)。
下面是一个例子:
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#define MYPORT 3490
m a i n ( )
{
int sockfd;
struct sockaddr_in my_addr;
sockfd = socket(AF_INET, SOCK_STREAM, 0); /* do some error checking! */
m y _ a d d r.sin_family = AF_INET; /* host byte order */
m y _ a d d r.sin_port = htons(MYPORT); /* short, network byte order */
m y _ a d d r. s i n _ a d d r.s_addr = inet_addr("132.241.5.10");
b z e r o ( & ( m y _ a d d r.sin_zero), 8); /* zero the rest of the struct */
/* don't forget your error checking for bind(): */
bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));
如果出错,bind() 也返回- 1。
如果你使用connect()系统调用,那么你不必知道你使用的端口号。当你调用connect()时,
它检查套接口是否已经绑定,如果没有,它将会分配一个空闲的端口。
5.3 connect()
系统调用connect()的用法如下:
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
第一个参数还是套接口文件描述符,它是由系统调用socket()返回的。第二个参数是
serv_addr是指向数据结构sockaddr的指针,其中包括目的端口和I P地址。第三个参数可以使用
sizeof(struct sockaddr)而获得。下面是一个例子:
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#define DEST_IP "132.241.5.10"
#define DEST_PORT 23
main()
{
int sockfd;
struct sockaddr_in dest_addr; /* will hold the destination addr */
sockfd = socket(AF_INET, SOCK_STREAM, 0); /* do some error checking! */
d e s t _ a d d r.sin_family = AF_INET; /* host byte order */
d e s t _ a d d r.sin_port = htons(DEST_PORT); /* short, network byte order */
d e s t _ a d d r. s i n _ a d d r.s_addr = inet_addr(DEST_IP);
b z e r o ( & ( d e s t _ a d d r.sin_zero), 8); /* zero the rest of the struct */
/* don't forget to error check the connect()! */
connect(sockfd, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr));
同样,如果出错, c o n n e c t ( )将会返回- 1。
5.4 listen()
如果你希望不连接到远程的主机,也就是说你希望等待一个进入的连接请求,然后再处理
它们。这样,你通过首先调用l i s t e n ( ),然后再调用a c c e p t ( )来实现。
系统调用l i s t e n ( )的形式如下:
int listen(int sockfd, int backlog);
第一个参数是系统调用s o c k e t ( )返回的套接口文件描述符。第二个参数是进入队列中允许
的连接的个数。进入的连接请求在使用系统调用a c c e p t ( )应答之前要在进入队列中等待。这个
值是队列中最多可以拥有的请求的个数。大多数系统的缺省设置为2 0。你可以设置为5或者1 0。
当出错时,l i s t e n ( )将会返回- 1值。
当然,在使用系统调用l i s t e n ( )之前,我们需要调用b i n d ( )绑定到需要的端口,否则系统内
核将会让我们监听一个随机的端口。所以,如果你希望监听一个端口,下面是应该使用的系统
调用的顺序:
s o c k e t ( ) ;
b i n d ( ) ;
l i s t e n ( ) ;
/* accept() goes here */
5.5 accept()
系统调用a c c e p t ( )比较起来有点复杂。在远程的主机可能试图使用c o n n e c t ( )连接你使用
l i s t e n ( )正在监听的端口。但此连接将会在队列中等待,直到使用a c c e p t ( )处理它。调用a c c e p t ( )
之后,将会返回一个全新的套接口文件描述符来处理这个单个的连接。这样,对于同一个连接
来说,你就有了两个文件描述符。原先的一个文件描述符正在监听你指定的端口,新的文件描
述符可以用来调用s e n d ( )和r e c v ( )。
调用的例子如下:
#include <sys/socket.h>
int accept(int sockfd, void *addr, int *addrlen);
第一个参数是正在监听端口的套接口文件描述符。第二个参数a d d r是指向本地的数据结构
s o c k a d d r _ i n的指针。调用c o n n e c t ( )中的信息将存储在这里。通过它你可以了解哪个主机在哪个
端口呼叫你。第三个参数同样可以使用sizeof(struct sockaddr_in)来获得。
如果出错,a c c e p t ( )也将返回- 1。下面是一个简单的例子:
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#define MYPORT 3490 /* the port users will be connecting to */
#define BACKLOG 10 /* how many pending connections queue will hold */
m a i n ( )
{
int sockfd, new_fd; /* listen on sock_fd, new connection on new_fd */
struct sockaddr_in my_addr; /* my address information */
struct sockaddr_in their_addr; /* connector's address information */
int sin_size;
sockfd = socket(AF_INET, SOCK_STREAM, 0); /* do some error checking! */
m y _ a d d r.sin_family = AF_INET; /* host byte order */
m y _ a d d r.sin_port = htons(MYPORT); /* short, network byte order */
m y _ a d d r. s i n _ a d d r.s_addr = INADDR_ANY; /* auto-fill with my IP */
b z e r o ( & ( m y _ a d d r.sin_zero), 8); /* zero the rest of the struct */
/* don't forget your error checking for these calls: */
bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));
listen(sockfd, BACKLOG);
sin_size = sizeof(struct sockaddr_in);
new_fd = accept(sockfd, &their_addr, &sin_size);
下面,我们将可以使用新创建的套接口文件描述符n e w _ f d来调用s e n d ( )和r e c v ( )。
5.6 send() 和recv()
系统调用s e n d ( )的用法如下:
int send(int sockfd, const void *msg, int len, int flags);
第一个参数是你希望给发送数据的套接口文件描述符。它可以是你通过s o c k e t ( )系统调用
返回的,也可以是通过a c c e p t ( )系统调用得到的。第二个参数是指向你希望发送的数据的指针。
第三个参数是数据的字节长度。第四个参数标志设置为0。
下面是一个简单的例子:
char *msg = "Beej was here!";
int len, bytes_sent;
len = strlen(msg);
bytes_sent = send(sockfd, msg, len, 0);
系统调用s e n d ( )返回实际发送的字节数,这可能比你实际想要发送的字节数少。如果返回
的字节数比要发送的字节数少,你在以后必须发送剩下的数据。当s e n d ( )出错时,将返回- 1。
系统调用r e c v ( )的使用方法和s e n d ( )类似:
int recv(int sockfd, void *buf, int len, unsigned int flags);
第一个参数是要读取的套接口文件描述符。第二个参数是保存读入信息的地址。第三个参
数是缓冲区的最大长度。第四个参数设置为0。
系统调用r e c v ( )返回实际读取到缓冲区的字节数,如果出错则返回- 1。
这样使用上面的系统调用,你可以通过数据流套接口来发送和接受信息。
5.7 sendto() 和recvfrom()
因为数据报套接口并不连接到远程的主机上,所以在发送数据包之前,我们必须首先给出
目的地址,请看:
int sendto(int sockfd, const void *msg, int len, unsigned int flags,
const struct sockaddr *to, int tolen);
除了两个参数以外,其他的参数和系统调用s e n d ( )时相同。参数t o是指向包含目的I P地址
和端口号的数据结构s o c k a d d r的指针。参数t o l e n可以设置为sizeof(struct sockaddr)。
系统调用s e n d t o ( )返回实际发送的字节数,如果出错则返回- 1。
系统调用r e c v f r o m ( )的使用方法也和r e c v ( )的十分近似:
int recvfrom(int sockfd, void *buf, int len, unsigned int flags
struct sockaddr *from, int *fromlen);
参数f r o m是指向本地计算机中包含源I P地址和端口号的数据结构s o c k a d d r的指针。参数
f r o m l e n设置为sizeof(struct sockaddr)。
系统调用r e c v f r o m ( )返回接收到的字节数,如果出错则返回- 1。
5.8 close() 和shutdown()
你可以使用c l o s e ( )调用关闭连接的套接口文件描述符:
c l o s e ( s o c k f d ) ;
这样就不能再对此套接口做任何的读写操作了。
使用系统调用s h u t d o w n ( ),可有更多的控制权。它允许你在某一个方向切断通信,或者切
断双方的通信:
int shutdown(int sockfd, int how);
第一个参数是你希望切断通信的套接口文件描述符。第二个参数h o w值如下:
0—Further receives are disallowed
1—Further sends are disallowed
2—Further sends and receives are disallowed (like close())
shutdown() 如果成功则返回0,如果失败则返回- 1。
5.9 getpeername()
这个系统的调用十分简单。它将告诉你是谁在连接的另一端:
#include <sys/socket.h>
int getpeername(int sockfd, struct sockaddr *addr, int *addrlen);
第一个参数是连接的数据流套接口文件描述符。第二个参数是指向包含另一端的信息的数
据结构s o c k a d d r的指针。第三个参数可以设置为sizeof(struct sockaddr)。
如果出错,系统调用将返回- 1。
一旦你获得了它们的地址,你可以使用inet_ntoa() 或者g e t h o s t b y a d d r ( )来得到更多的信息。
5.10 gethostname()
系统调用g e t h o s t n a m e ( )比系统调用g e t p e e r n a m e ( )还简单。它返回程序正在运行的计算机的
名字。系统调用g e t h o s t b y n a m e ( )可以使用这个名字来决定你的机器的I P地址。
下面是一个例子:
#include <unistd.h>
int gethostname(char *hostname, size_t size);
如果成功,g e t h o s t n a m e将返回0。如果失败,它将返回- 1。
6 DNS
DNS 代表“Domain Name Service”,即域名服务器。它可以把域名翻译成相应的I P地址。
你可以使用此I P地址调用b i n d ( )、c o n n e c t ( )、s e n d t o ( )或者用于其他的地方。
系统调用g e t h o s t b y n a m e ( )可以完成这个函数:
#include <netdb.h>
struct hostent *gethostbyname(const char *name);
它返回一个指向数据结构h o s t e n t的指针,数据结构h o s t e n t如下:
struct hostent {
char *h_name;
char **h_aliases;
int h_addrtype;
int h_length;
char **h_addr_list;
} ;
#define h_addr h_addr_list[0]
h_name —主机的正式名称。
h_aliases —主机的别名。
h _ a d d r t y p e—将要返回的地址的类型,一般是A F _ I N E T。
h _ l e n g t h—地址的字节长度。
h _ a d d r _ l i s t—主机的网络地址。
h_addr —h _ a d d r _ l i s t中的第一个地址。
系统调用g e t h o s t b y n a m e ( )返回一个指向填充好的数据结构h o s t e n t的指针。当发生错误时,
则返回一个N U L L指针。下面是一个实际例子:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
int main(int argc, char *argv[])
{
struct hostent *h;
if (argc != 2) { /* error check the command line */
f p r i n t f ( s t d e r r,"usage: getip address\n");
e x i t ( 1 ) ;
}
if ((h=gethostbyname(argv[1])) == NULL) { /* get the host info */
h e r r o r ( " g e t h o s t b y n a m e " ) ;
e x i t ( 1 ) ;
}
printf("Host name : %s\n", h->h_name);
printf("IP Address : %s\n",inet_ntoa(*((struct in_addr *)h->h_addr)));
return 0;
}
在使用g e t h o s t b y n a m e ( )时,你不能使用p e r r o r ( )来打印错误信息。你应该使用的是系统调用
h e r r o r ( )。
7 客户机/服务器模式
在网络上大部分的通信都是在客户机/服务器模式下进行的。例如t e l n e t。当你使用t e l n e t连
接到远程主机的端口2 3时,主机上的一个叫做t e l n e t d的程序就开始运行。它处理所有进入的
t e l n e t连接,为你设置登录提示符等。
应当注意的是客户机/服务器模式可以使用S O C K _ S T R E A M、S O C K _ D G R A M或者任何其
他的方式。例如t e l n e t / t e l n e t d、f t p / f t p d和b o o t p / b o o t p d。每当你使用f t p时,远程计算机都在运
行一个f t p d为你服务。
一般情况下,一台机器上只有一个服务器程序,它通过使用f o r k ( )来处理多个客户端程序
的请求。最基本的处理方法是:服务器等待连接,使用a c c e p t ( )接受连接,调用f o r k ( )生成一个
子进程处理连接。
8 简单的数据流服务器程序
此服务器程序所作的事情就是通过一个数据流连接发送字符串“ Hello, Wo r l d ! \ n”。你可以
在一个窗口上运行此程序,然后在另一个窗口使用t e l n e t:
$ telnet remotehostname 3490
其中,r e m o t e h o s t n a m e是你运行的机器名。下面是此程序的代码:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#define MYPORT 3490 /* the port users will be connecting to */
#define BACKLOG 10 /* how many pending connections queue will hold */
m a i n ( )
{
int sockfd, new_fd; /* listen on sock_fd, new connection on new_fd */
struct sockaddr_in my_addr; /* my address information */
struct sockaddr_in their_addr; /* connector's address information */
int sin_size;
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
p e r r o r ( " s o c k e t " ) ;
e x i t ( 1 ) ;
}
m y _ a d d r.sin_family = AF_INET; /* host byte order */
m y _ a d d r.sin_port = htons(MYPORT); /* short, network byte order */
m y _ a d d r. s i n _ a d d r.s_addr = INADDR_ANY; /* auto-fill with my IP */
b z e r o ( & ( m y _ a d d r.sin_zero), 8); /* zero the rest of the struct */
if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) \
== -1) {
p e r r o r ( " b i n d " ) ;
e x i t ( 1 ) ;
}
if (listen(sockfd, BACKLOG) == -1) {
p e r r o r ( " l i s t e n " ) ;
e x i t ( 1 ) ;
}
while(1) { /* main accept() loop */
sin_size = sizeof(struct sockaddr_in);
if ((new_fd = accept(sockfd, (struct sockaddr *)&their_addr, \
&sin_size)) == -1) {
p e r r o r ( " a c c e p t " ) ;
c o n t i n u e ;
}
printf("server: got connection from %s\n", \
i n e t _ n t o a ( t h e i r _ a d d r. s i n _ a d d r ) ) ;
if (!fork()) { /* this is the child process */
if (send(new_fd, "Hello, world!\n", 14, 0) == -1)
perror("send");
close(new_fd);
exit(0);
}
close(new_fd); /* parent doesn't need this */
while(waitpid(-1,NULL,WNOHANG) > 0); /* clean up child processes */
}
}
你也可以使用下面的客户机程序从服务器上得到字符串。
9 简单的数据流客户机程序
客户机所做的是连接到你在命令行中指定的主机的3 4 9 0端口。它读取服务器发送的字符
串。
下面是客户机程序的代码:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define PORT 3490 /* the port client will be connecting to */
#define MAXDATASIZE 100 /* max number of bytes we can get at once */
int main(int argc, char *argv[])
{
int sockfd, numbytes;
char buf[MAXDATA S I Z E ] ;
struct hostent *he;
struct sockaddr_in their_addr; /* connector's address information */
if (argc != 2) {
f p r i n t f ( s t d e r r,"usage: client hostname\n");
e x i t ( 1 ) ;
}
if ((he=gethostbyname(argv[1])) == NULL) { /* get the host info */
h e r r o r ( " g e t h o s t b y n a m e " ) ;
e x i t ( 1 ) ;
}
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
p e r r o r ( " s o c k e t " ) ;
e x i t ( 1 ) ;
}
t h e i r _ a d d r.sin_family = AF_INET; /* host byte order */
t h e i r _ a d d r.sin_port = htons(PORT); /* short, network byte order */
t h e i r _ a d d r.sin_addr = *((struct in_addr *)he->h_addr);
b z e r o ( & ( t h e i r _ a d d r.sin_zero), 8); /* zero the rest of the struct */
if (connect(sockfd, (struct sockaddr *)&their_addr, \
sizeof(struct sockaddr)) == -1) {
p e r r o r ( " c o n n e c t " ) ;
e x i t ( 1 ) ;
}
if ((numbytes=recv(sockfd, buf, MAXDATASIZE, 0)) == -1) {
p e r r o r ( " r e c v " ) ;
e x i t ( 1 ) ;
}
buf[numbytes] = '\0';
printf("Received: %s",buf);
c l o s e ( s o c k f d ) ;
return 0;
}
如果你在运行服务器程序之前运行客户机程序,则将会得到一个“ Connection refused”的
信息。
10 数据报套接口
程序l i s t e n e r在机器中等待端口4 9 5 0到来的数据包。程序t a l k e r向指定的机器的4 9 5 0端口发
送数据包。
下面是l i s t e n e r. c的代码:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#define MYPORT 4950 /* the port users will be sending to */
#define MAXBUFLEN 100
m a i n ( )
{
int sockfd;
struct sockaddr_in my_addr; /* my address information */
struct sockaddr_in their_addr; /* connector's address information */
int addr_len, numbytes;
char buf[MAXBUFLEN];
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
perror("socket");
exit(1);
}
my_addr.sin_family = AF_INET; /* host byte order */
my_addr.sin_port = htons(MYPORT); /* short, network byte order */
my_a d d r.s i n_addr.s_addr = INADDR_ANY; /* auto-fill with my IP */
bzero(&(my_addr.sin_zero), 8); /* zero the rest of the struct */
if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) \
== -1) {
perror("bind ") ;
exit(1);
}
addr_len = sizeof(struct sockaddr);
if ((numbytes=recvfrom(sockfd, buf, MAXBUFLEN, 0, \
(struct sockaddr *)&their_addr, &addr_len)) == -1) {
perror( "recvfrom" );
exit(1) ;
}
printf("got packet from %s\n",inet_ntoa(their_addr. s i n _ a d d r ) ) ;
printf("packet is %d bytes long\n",numbytes);
buf[numbytes] = '\0';
printf("packet contains \"%s\"\n",buf);
close(sockfd);
}
下面是t a l k e r. c的代码:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/wait.h>
#define MYPORT 4950 /* the port users will be sending to */
int main(int argc, char *argv[])
{
int sockfd;
struct sockaddr_in their_addr; /* connector's address information */
struct hostent *he;
int numbytes;
if (argc != 3) {
fprintf (stderr, "usage: talker hostname message\n");
exit(1);
}
if ((he=gethostbyname(argv[1])) == NULL) { /* get the host info */
h e r r o r ( " g e t h o s t b y n a m e " ) ;
e x i t ( 1 ) ;
}
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
perror("socket") ;
exit(1);
}
their_addr.sin_family = AF_INET; /* host byte order */
their_addr.sin_port = htons(MYPORT); /* short, network byte order */
their_addr.sin_addr = *((struct in_addr *)he->h_addr);
bzero(&(their_addr.sin_zero), 8); /* zero the rest of the struct */
if ((numbytes=sendto(sockfd, argv[2], strlen(argv[2]), 0, \
(struct sockaddr *)&their_addr, sizeof(struct sockaddr))) == -1) {
perror ("s e n d t o");
exit(1);
}
printf("sent %d bytes to %s\n",numbytes,inet_ntoa(their_addr. s i n _ a d d r ) ) ;
close(sockfd);
return 0;
}
你可以在一台机器上运行l i s t e n e r程序,在另一台机器上运行t a l k e r程序,然后观察它们之
间的通信。
21.11 阻塞
当使用上面的listener程序时,此程序在等待直到一个数据包到来。这是因为它调用了
recvform(),如果没有数据,recvform ( )就一直阻塞,直到有数据到来。
很多函数都有阻塞。系统调用accept ( )阻塞,所有的类似recv*()的函数也可以阻塞。它们
之所以可以阻塞是因为系统内核允许它们阻塞。当你第一次创建一个套接口文件描述符时,系
统内核将它设置为可以阻塞。如果你不希望套接口阻塞,你可以使用系统调用fcntl():
#include <unistd.h>
#include <fcntl.h>
.
sockfd = socket(AF_INET, SOCK_STREAM, 0);
fcntl(sockfd, F_SETFL, O_NONBLOCK);
.
如果你设置为不阻塞,那么就得频繁地询问套接口以便检查有无信息到来。如果你试图读
取一个没有阻塞的套接口,同时它又没有数据,那么你将得到- 1。
询问套接口以检查有无信息得到来可能会占用太多的C P U时间。另一个可以使用的方法是
select()。
select()用于同步I / O多路复用。这个系统调用十分有用。考虑一下下面的情况:你是一个
服务器,你希望监听进入的连接,同时还一直从已有的连接中读取信息。
也许你认为可以使用一个accept()调用和几个recv()调用。但如果调用accept()阻塞了怎么
办?如果在这时你希望调用recv()接受数据呢?
系统调用select()使得你可以同时监视几个套接口。它可以告诉你哪一个套接口已经准备好
了以供读取,哪一个套接口已经可以写入。
下面是select()的用法:
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int numfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
此函数监视几个文件描述符,特别是r e a d f d s、w r i t e f d s和e x c e p t f d s。如果你希望检查是否
可以从标准输入中和一些其他的套接口文件描述符s o c k f d中读取数据,只需把文件描述符0和
s o c k f d添加到r e a d f d s中。参数n u m f d s应该设置为最高的文件描述符的值加1。
当s e l e c t ( )返回时,r e a d f d s将会被修改以便反映你选择的那一个文件描述符已经准备好了以
供读取。你可以使用F D _ I S S E T ( )测试。
FD_ZERO(fd_set *set)—清除文件描述符集。
FD_SET(int fd, fd_set *set)—把fd 添加到文件描述符集中。
FD_CLR(int fd, fd_set *set)—把fd 从文件描述符中移走。
FD_ISSET(int fd, fd_set *set)—检测fd 是否在文件描述符集中。
数据结构t i m e v a l包含下面的字段:
struct timeval {
int tv_sec; /* seconds */
int tv_usec; /* microseconds */
} ;
把t v _ s e c设置成需要等待的时间秒数,t v _ u s e c设置成需要等待的微秒数。一秒中包括1 000
0 0 0 μ s 。下面的程序等待2 . 5 s:
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#define STDIN 0 /* file descriptor for standard input */
m a i n ( )
{
struct timeval tv;
fd_set readfds;
t v.tv_sec = 2;
t v.tv_usec = 500000;
F D _ Z E R O ( & r e a d f d s ) ;
FD_SET(STDIN, &readfds);
/* don't care about writefds and exceptfds: */
select(STDIN+1, &readfds, NULL, NULL, &tv);
if (FD_ISSET(STDIN, &readfds))
printf("A key was pressed!\n");
else
printf( " Timed out.\n");
摘自:
Linux系统高级编程-china pub
posted on 2009-11-15 21:40
chatler 阅读(716)
评论(0) 编辑 收藏 引用 所属分类:
Socket