网络编程--UDP
说明:只供学习交流,转载请注明出处
面向连接的套接字使用的通信协议是TCP。TCP协议可以保证信息的可靠传输,但是在建立连接前需要进行3次握手过程。网络通信在很多时候只是传输很少的数据,并且对数据传输的可靠性没有很高的要求,在这种情况下,可以使用UDP协议。UDP协议是无连接的数据通信协议。由于UDP协议没有建立连接的过程,UDP协议通信效率要高于TCP协议。
一,工作流程
无连接套接字通信不需要服务器与客户机之间建立连接,因此也就没有使用listen函数实现监听和调用accept函数建立服务器与客户机之间连接的过程。要在服务器和客户机之间实现通信必须指定另一方的IP地址。
无连接的套接字通信具体工作流程如下图所示。首先服务器和客户机都需要建立用于通信的套接字,并将其地址信息进行绑定。在完成这些操作之后,就可以通过sendto和recvfrom函数进行通信了。
二,recvfrom函数
recvfrom函数用于接收通过套接字发送来的消息,该函数的具体信息如下表:
头文件 |
#include <sys/types.h> #include <sys/socket.h> |
||
函数原型 |
ssize_t recvfrom(int s, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t fromlen); |
||
返回值 |
成功 |
失败 |
是否设置errno |
接收到的字节数 |
-1 |
是 |
说明:recvfrom函数可用于无连接或面向连接的套接字通信中。参数s为套接字文件描述符。参数buf为接收缓冲区。参数len为接收缓冲区长度。flags为控制参数,用于控制是否接收带外数据,通常设为0,常用的值还有:
MSG_PEEK:返回的数据不会在系统系统内删除。
MSG_WAITALL:强迫接收到len大小的数据后才返回。
MSG_NOSIGNAL:不会被SIGPIPE信号中断。
from用于记录发送端的地址结构。fromlen为发送段地址结构的长度。
错误信息:
EAGAIN:套接字处于非阻塞状态。
EBADF:非法的文件描述符。
ECONNABORTED:远程主机拒绝网络连接。
EFAULT:指向接收数据的缓冲区指针指向了非法地址空间。
EINTR:系统调用被信号中断。
EINVAL:非法参数。
ENOTCONN:套接字使用了面向连接的协议,但是并没有建立连接。
ENOTSOCK:文件描述符为文件的文件描述符。
三,sendto函数
sendto函数用于发送信息给指定的主机,该函数的具体信息如下表:
头文件 |
#include <sys/types.h> #include <sys/socket.h> |
||
函数原型 |
ssize_t sendto(int s, const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen); |
||
返回值 |
成功 |
失败 |
是否设置errno |
发送的字节数 |
-1 |
是 |
说明:sendto函数可用于无连接或面向连接的套接字通信中。当用于面向连接的通信(套接字类型为SOCK_STREAM或SOCK_SEQPACKET),参数to和tolen将被忽略。
sendto函数中参数s为套接字文件描述符。参数buf为指向要发送信息地址空间的指针。参数len为要发送的字节数。参数flags为相关控制参数,用于控制是否接收数据以及是否预览报文。参数from为指向存放接收方地址信息的指针。fromlen为接收端的地址结构的长度。
错误信息:
EBADF:非法的文件描述符。
ECONNRESET:连接重置。
EDESTADDRREQ:在套接字操作中没有指定目标地址。
EFAULT:参数指向了非法的地址空间。
EINTR:数据发送前,捕获到信号。
EINVAL:非法参数。
ENOTSOCK:参数非套接字的文件描述符。
ENOMEM:内存不足。
UNIX domain中面向无连接的通信实例:
Server.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#define UNIX_DOMAIN "UNIX.domain"
int main(void)
{
socklen_t addr_len;
int listen_fd;
int com_fd;
int ret;
int i;
static char recv_buf[1024];
int len;
struct sockaddr_un clt_addr;
struct sockaddr_un srv_addr;
com_fd = socket(PF_UNIX, SOCK_DGRAM, 0);
if (com_fd < 0)
{
perror("Cannot create listening socket");
return (1);
}
srv_addr.sun_family = AF_UNIX;
strcpy(srv_addr.sun_path, UNIX_DOMAIN);
unlink(UNIX_DOMAIN);
ret = bind(com_fd, (struct sockaddr*)&srv_addr, sizeof(srv_addr.sun_family)+
strlen(srv_addr.sun_path));
if (ret == -1)
{
perror("Cannot bind server socket");
close(com_fd);
unlink(UNIX_DOMAIN);
return (1);
}
for (i = 0; i < 4; i++)
{
memset(recv_buf, 0, 1024);
int num = recvfrom(com_fd, recv_buf, 1024, 0, (struct sockaddr*)&clt_addr, &addr_len);
printf("Message from client (%d) : %s\n", num, recv_buf);
}
close(com_fd);
unlink(UNIX_DOMAIN);
return (0);
}
Client.c
与服务器类似,程序也是首先创建通信的套接字,然后使用bind函数将文件信息与套接字绑定。值得注意的是,为了保证客户端的用于通信的文件的唯一性,使用了mkstemp函数。使用mkstemp函数将获得文件名唯一的临时文件。
之所以要保证文件名的唯一性,是因为服务器进程没有向客户端进程发送消息,而只是读取了客户端发送来的消息。对服务器而言,可以通过recvfrom中获得的值来判断是哪个客户端发送的消息。但是,如果客户端使用相同的文件的话,在多客户端的情况下,服务器就无法判断要将消息发送给哪个客户端了。通过文件名的唯一性,这一个问题得到了解决。
具体代码如下:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#define UNIX_DOMAIN "UNIX.domain"
int main(void)
{
int com_fd;
int ret;
char snd_buf[1024] = {'\0'};
int i;
static struct sockaddr_un srv_addr;
static struct sockaddr_un clt_addr;
char clt_file[] = "xxxx";
srv_addr.sun_family = AF_UNIX;
strcpy(srv_addr.sun_path, UNIX_DOMAIN);
com_fd = socket(PF_UNIX, SOCK_DGRAM, 0);
if ( com_fd < 0 )
{
perror("Cannot create communication socket");
return (1);
}
mkstemp(clt_file);
clt_addr.sun_family = AF_UNIX;
strcpy(clt_addr.sun_path, clt_file);
unlink(clt_file);
ret = bind(com_fd, (struct sockaddr*)&clt_addr, sizeof(clt_addr.sun_family)+strlen(clt_addr.sun_path));
if (ret == -1)
{
perror("Cannot bind server socket");
return (1);
}
memset(snd_buf, 0, 1024);
sprintf(snd_buf, "%ld : message from client", (long)getpid());
for (i = 0; i < 4; i++)
{
sleep(1);
int num = sendto(com_fd, snd_buf, sizeof(snd_buf), 0, (struct sockaddr*)&srv_addr,
sizeof(struct sockaddr));
printf("send %d characters\n", num);
}
close(com_fd);
unlink(clt_file);
return (0);
}
ternet domain中面向无连接通信实现实例:
Server.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[])
{
int sock;
int length;
int fromlen;
int n;
struct sockaddr_in server;
struct sockaddr_in from;
char buf[1024] = {'\0'};
if (argc != 2)
{
printf("Usage: %s port_num\n", argv[0]);
return (1);
}
sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0)
{
perror("Cannot create communicating socket");
return (1);
}
length = sizeof(server);
bzero(&server, length);
server.sin_family = AF_INET;
server.sin_addr.s_addr = INADDR_ANY;
server.sin_port=htons(atoi(argv[1]));
if (bind(sock, (struct sockaddr*)&server, length) < 0)
{
perror("Cannot bind the socket");
close(sock);
return (1);
}
fromlen = sizeof(struct sockaddr_in);
while ( 1 )
{
n = recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr*)&from, &fromlen);
if ( n < 0 )
{
perror("Cannot receive date from client");
break;
}
write(STDOUT_FILENO, "server: Received a datagram: ", 29);
write(STDOUT_FILENO, buf, n);
n = sendto(sock, "send message to client\n", 22, 0, (struct sockaddr*)&from, fromlen);
if (n < 0)
{
perror("Cannot send data to the client");
break;
}
}
close(sock);
return (0);
}
Client.c
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int sock;
int length;
int n;
struct sockaddr_in server;
struct sockaddr_in from;
struct hostent *hp;
char buffer[256] = {'\0'};
if (argc != 3)
{
printf("Usage : %s server_ip port_num\n", argv[0]);
return (1);
}
sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0)
{
perror("Cannot create communicating socket");
return (1);
}
server.sin_family = AF_INET;
hp = gethostbyname(argv[1]);
if (hp == 0)
{
perror("cannot get the server ip address");
return (1);
}
bcopy((char *)hp->h_addr, (char *)&server.sin_addr, hp->h_length);
server.sin_port = htons(atoi(argv[2]));
length = sizeof(struct sockaddr_in);
printf("(client) enter the message: ");
bzero(buffer, 256);
fgets(buffer, 255, stdin);
n = sendto(sock, buffer, strlen(buffer), 0, &server, length);
if (n < 0)
{
perror("Cannot send message to the server");
return (1);
}
bzero(buffer, 256);
n = recvfrom(sock, buffer, 256, 0, &from, &length);
if ( n < 0 )
{
perror("Cannot get message from server");
}
printf("Client get message : %s\n", buffer);
close(sock);
return (0);
}