我们在Linux网络编程基础之一里说了流socket和数据报socket的联系和区别。我们在这里简单说一下基于UDP协议的网络编程。
两个常用的函数
int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr * from int *fromlen)
int sendto(int sockfd,const void *msg,int len,unsigned int flags,struct sockaddr *to int tolen)
其中sockfd,buf,len的意义和read,write一样,分别表示套接字描述符,发送或接收的缓冲区及大小.
recvfrom 负责从 sockfd接收数据,如果from不是NULL,那么在from里面存储了信息来源的情况,如果对信息的来源不感兴趣,可以将from和fromlen 设置为NULL.sendto负责向to发送信息.此时在to里面存储了收信息方的详细资料.
下面是一个简单的例子(选自Understanding Unix/Linux Programming):
服务器端(接收数据报,dgrecv.c使用命令行传过来的端口号建立socket,然后进入循环,接收和打印从客户端发来的数据报):
/************************************************************************
* dgrecv.c - datagram receiver
* usage: dgrecv portnum
* action: listens at the specfied port and reports messages
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define oops(m,x) { perror(m);exit(x);}
/*辅助函数make_dgram_server和get_internet_address将在后面给出的文件dgram.c中定义*/
int make_dgram_server_socket(int);
int get_internet_address(char *, int, int *, struct sockaddr_in *);
void say_who_called(struct sockaddr_in *);
int main(int ac, char *av[])
{
int port; /* use this port */
int sock; /* for this socket */
char buf[BUFSIZ]; /* to receive data here */
size_t msglen; /* store its length here */
struct sockaddr_in saddr; /* put sender's address here */
socklen_t saddrlen; /* and its length here */
if ( ac == 1 || (port = atoi(av[1])) <= 0 ){
fprintf(stderr,"usage: dgrecv portnumber\n");
exit(1);
}
/* get a socket and assign it a port number */
if( (sock = make_dgram_server_socket(port)) == -1 )
oops("cannot make socket",2);
/* receive messaages on that socket */
saddrlen = sizeof(saddr);
while( (msglen = recvfrom(sock,buf,BUFSIZ,0,
(struct sockaddr *) &saddr,&saddrlen))>0 ) {
buf[msglen] = '\0';
printf("dgrecv: got a message: %s\n", buf);
say_who_called(&saddr);
}
return 0;
}
void say_who_called(struct sockaddr_in *addrp)
{
char host[BUFSIZ];
int port;
get_internet_address(host,BUFSIZ,&port,addrp);
printf(" from: %s:%d\n", host, port);
}
客户端(创建一个socket,然后用它发送消息到以命令行参数传入的特定的主机和端口号):
/*********************************************************************
* dgsend.c - datagram sender
* usage: dgsend hostname portnum "message"
* action: sends message to hostname:portnum
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define oops(m,x) { perror(m);exit(x);}
int make_dgram_client_socket();
int make_internet_address(char *,int, struct sockaddr_in *);
int main(int ac, char *av[])
{
int sock; /* use this socket to send */
char *msg; /* send this messag */
struct sockaddr_in saddr; /* put sender's address here */
if ( ac != 4 ){
fprintf(stderr,"usage: dgsend host port 'message'\n");
exit(1);
}
msg = av[3];
/* get a datagram socket */
if( (sock = make_dgram_client_socket()) == -1 )
oops("cannot make socket",2);
/* combine hostname and portnumber of destination into an address */
if ( make_internet_address(av[1], atoi(av[2]), &saddr) == -1 )
oops("make addr",4);
/* send a string through the socket to that address */
if ( sendto(sock, msg, strlen(msg), 0,
(struct sockaddr *)&saddr,sizeof(saddr)) == -1)
oops("sendto failed", 3);
return 0;
}
辅助函数
/***************************************************************
* dgram.c
* support functions for datagram based programs
*/
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#define HOSTLEN 256
int make_internet_address();
int make_dgram_server_socket(int portnum)
{
struct sockaddr_in saddr; /* build our address here */
char hostname[HOSTLEN]; /* address */
int sock_id; /* the socket */
sock_id = socket(PF_INET, SOCK_DGRAM, 0); /* get a socket */
if ( sock_id == -1 ) return -1;
/** build address and bind it to socket **/
gethostname(hostname, HOSTLEN); /* where am I ? */
make_internet_address(hostname, portnum, &saddr);
if ( bind(sock_id, (struct sockaddr *)&saddr, sizeof(saddr)) != 0 )
return -1;
return sock_id;
}
int make_dgram_client_socket()
{
return socket(PF_INET, SOCK_DGRAM, 0);
}
int make_internet_address(char *hostname, int port, struct sockaddr_in *addrp)
/*
* constructor for an Internet socket address, uses hostname and port
* (host,port) -> *addrp
*/
{
struct hostent *hp;
bzero((void *)addrp, sizeof(struct sockaddr_in));
hp = gethostbyname(hostname);
if ( hp == NULL ) return -1;
bcopy((void *)hp->h_addr, (void *)&addrp->sin_addr, hp->h_length);
addrp->sin_port = htons(port);
addrp->sin_family = AF_INET;
return 0;
}
int get_internet_address(char *host, int len, int *portp, struct sockaddr_in *addrp)
/*
* extracts host and port from an internet socket address
* *addrp -> (host,port)
*/
{
strncpy(host, inet_ntoa(addrp->sin_addr), len );
*portp = ntohs(addrp->sin_port);
return 0;
}
Makefile如下:
all:dgrecv.exe dgsend.exe
dgrecv.exe:
gcc dgrecv.c dgram.c -o dgrecv.exe
dgsend.exe:
gcc dgsend.c dgram.c -o dgsend.exe
运行结果:
$./dgrecv.exe 8901&
$./dgsend.exe ComputerName 8901 "Yeah,I am on fire"
dgrecv:got a message: Yeah,I am on fire
from:192.168.0.1:1018
总结:
服务器:socket -> (填充结构) -> bind -> recvfrom
客户端:socket -> (填充结构) -> sendto