《网络编程》基本 UDP 套接字编程

在前面文章中介绍了《UDP 协议》和《套接字数据传输》。UDP 协议和 TCP
协议不同,它是一种面向无连接、不可靠的传输层协议。在基于 UDP 套接字编程中,数据传输可用函数 sendto 和 recvfrom。以下是基本 UDP 套接字编程过程:

sendto 与 recvfrom 函数

这两个函数的功能类似于 write 和 read 函数,可用无连接的套接字编程。其定义如下:

/* 函数功能:发送数据;
 * 返回值:若成功则返回已发送的字节数,若出错则返回-1;
 * 函数原型:
 */
#include <sys/socket.h>

ssize_t sendto(int sockfd, void *buff, size_t nbytes, int flags,
                const struct sockaddr *destaddr, socklen_t addrlen);

/* 说明:
 * 该函数功能类似于write函数,除了有标识符flags和目的地址信息之外,其他参数一样;
 *
 * flags标识符取值如下:
 * (1)MSG_DONTROUTE   勿将数据路由出本地网络
 * (2)MSG_DONTWAIT    允许非阻塞操作
 * (3)MSG_EOR         如果协议支持,此为记录结束
 * (4)MSG_OOB         如果协议支持,发送带外数据
 *
 * 若sendto成功,则只是表示已将数据无错误的发送到网络,并不能保证正确到达对端;
 * 该函数通过指定目标地址允许在无连接的套接字之间发送数据(例如UDP套接字);
 */

 /* 函数功能:接收数据;
  * 返回值:以字节计数的消息长度,若无可用消息或对方已经按序结束则返回0,若出错则返回-1;
  * 函数原型:
  */
#include <sys/socket.h>

ssize_t recvfrom(int sockfd, void *buff, size_t nbytes, int flags,
                struct sockaddr *addr, socklen_t *addrlen);
 /* 说明:
  * 该函数功能与read类似;
  * 若addr为非空时,它将包含数据发送者的套接字地址;
  *
  * flags标识符取值如下:
  * (1)MSG_WAITALL     等待所有数据可用
  * (2)MSG_DONTWAIT    允许非阻塞操作
  * (3)MSG_PEEK        查看已读取的数据
  * (4)MSG_OOB         如果协议支持,发送带外数据
  */

基于 UDP 套接字编程

下面我们使用 UDP 协议实现简单的功能,客户端从标准输入读取数据并把它发送给服务器,服务器接收到数据并把该数据回射给客户端,然后客户端收到从服务器回射的数据把它显示到标准输出。其功能实现如下图所示:

服务器程序

/* UDP 服务器 */
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define SERV_PORT 9877 /* 通用端口号 */

extern void err_sys(const char *, ...);
extern void dg_echo(int sockfd, struct sockaddr *addr, socklen_t addrlen);

int main(int argc, char **argv)
{
    int sockfd;
    int err;
    struct sockaddr_in servaddr, cliaddr;

    /* 初始化服务器地址信息 */
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERV_PORT);
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

    /* 创建套接字,并将服务器地址绑定到该套接字上 */
    if( (sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
        err_sys("socket error");
    err =bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
    if(err < 0)
        err_sys("bind error");
    /* 服务器处理函数:读取套接字文本行,并把它回射给客户端 */
    dg_echo(sockfd, (struct sockaddr*) &cliaddr, sizeof(cliaddr));

}

处理函数

#include	"unp.h"

void
dg_echo(int sockfd, SA *pcliaddr, socklen_t clilen)
{
	int			n;
	socklen_t	len;
	char		mesg[MAXLINE];

	for ( ; ; ) {
		len = clilen;
		n = Recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &len);

		Sendto(sockfd, mesg, n, 0, pcliaddr, len);
	}
}

客户端程序

/* UDP 客户端 */
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define SERV_PORT 9877 /* 通用端口号 */

extern void err_sys(const char *, ...);
extern void err_quit(const char *, ...);
extern void dg_cli(FILE *fd, int sockfd, struct sockaddr *addr, socklen_t addrlen);

int main(int argc, char **argv)
{
	int					sockfd;
	struct sockaddr_in	servaddr;

	if (argc != 2)
		err_quit("usage: udpcli <IPaddress>");

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(SERV_PORT);
	inet_pton(AF_INET, argv[1], &servaddr.sin_addr);

	if( (sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
        err_sys("socket err");
/* 客户端处理函数:从标准输入读入文本行,发送给服务器;接收来自服务器的回射文本,并把它显示到标准输出 */
	dg_cli(stdin, sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr));

	exit(0);
}

客户端处理函数

#include	"unp.h"

void
dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{
	int	n;
	char	sendline[MAXLINE], recvline[MAXLINE + 1];

	while (Fgets(sendline, MAXLINE, fp) != NULL) {
/* 把从标准输入读取的文本行发送给服务器套接字 */
		Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
/* 接收来自服务器回射的文本行 */
		n = Recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);

		recvline[n] = 0;	/* null terminate */
		Fputs(recvline, stdout);
	}
}
 $./serv &
[1] 17911
$ ./client 127.0.0.1
sending text based on UDP
sending text based on UDP
goodbyte..
goodbyte..

数据报丢失

由于 UDP 是一种不可靠的传输协议。在上面的客户端 / 服务器 程序中,若数据报在传输的过程中丢失,那么客户端就是阻塞于 dg_cli 处理函数中的 recvfrom 函数调用,等待一个永远都不会达到的服务器应答。也有可能是,客户端数据报成功到达服务器,但是服务器的应答数据报丢失,同样,客户端也将永远阻塞于  recvfrom 函数调用。一般来说,会给客户端  recvfrom 函数调用设置一个超时时钟,但是超时时钟并不能确定是客户端数据报不能到达服务器还是服务器应答不能到达客户端。所以我们可以采用验证接收到的响应。即在  recvfrom
函数调用以返回数据报发送者的 IP 地址和端口号,保留来自数据报所发往服务器的应答。

UDP 中使用 connect 函数

在没有启动 UDP 服务器的情况下,客户端键入文本行之后,并不会回显该文本行。此时客户端永远阻塞于它的 recvfrom 调用,等待一个永远不会出现的服务器应答。由于服务器没有启动,因此会响应一个端口不可到达的 ICMP 错误消息(即异步错误),但是该 ICMP 错误消息并不会到达客户端进程,因此客户端进程根本不知道发生什么,一直阻塞于它的 recvfrom 调用。为了能使这个异步错误到达客户端进程,我们可以在 UDP 中调用 connect
函数,使其成为一个已连接的 UDP 套接字,但是该链接不会像 TCP 那样引起三次握手过程。内核只是检查是否存在立即可知的错误,并记录对端的 IP 地址和端口号,然后立即返回到调用进程。

下面要区分 未连接 UDP 套接字 和 已连接 UDP 套接字:

  1. 未连接 UDP 套接字:新创建 UDP 套接字默认为该情况;
  2. 已连接 UDP 套接字:对 UDP 套接字调用 connect 函数的结果;

已连接 UDP 套接字 相对于 未连接 UDP 套接字 会有以下的变化:

  1. 不能给输出操作指定目的 IP 地址和端口号(因为调用 connect 函数时已经指定),即不能使用 sendto 函数,而是使用 write 或 send 函数。写到已连接 UDP 套接字上的内容都会自动发送到由 connect 指定的协议地址;
  2. 不必使用 recvfrom 函数以获悉数据报的发送者,而改用 read、recv 或 recvmsg 函数。在一个已连接 UDP 套接字上,由内核为输入操作返回的数据报只有那些来自 connect 函数所指定的协议地址的数据报。目的地为这个已连接 UDP 套接字的本地协议地址,发源地不是该套接字早先 connect 到的协议地址的数据报,不会投递到该套接字。即只有发源地的协议地址与 connect 所指定的地址相匹配才可以把数据报传输到该套接字。这样已连接 UDP 套接字只能与一个对端交换数据报;
  3. 由已连接 UDP 套接字引发的异步错误会返回给它们所在的进程,而未连接 UDP 套接字不会接收任何异步错误;

UDP 客户端进程或服务器进程只在使用自己的 UDP 套接字与确定的唯一对端通信时,才可以调用 connect 函数。调用 connect 函数的通常是 UDP 客户端。以下是调用 connect 函数的客户端处理函数:

#include	"unp.h"

void
dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{
	int		n;
	char	sendline[MAXLINE], recvline[MAXLINE + 1];

	Connect(sockfd, (SA *) pservaddr, servlen);

	while (Fgets(sendline, MAXLINE, fp) != NULL) {

		Write(sockfd, sendline, strlen(sendline));

		n = Read(sockfd, recvline, MAXLINE);

		recvline[n] = 0;	/* null terminate */
		Fputs(recvline, stdout);
	}
}

此时若不启动服务器,只启动客户端,并键入文本行时,客户端会接收到 异步错误。

$ ./client 127.0.0.1
message...
read error: Connection refused

参考资料:

《Unix 网络编程》

时间: 2024-10-11 21:47:49

《网络编程》基本 UDP 套接字编程的相关文章

《网络编程》高级 UDP 套接字编程

概述 UDP 是一个无连接.不可靠的数据报协议,任何可靠传输都需由应用程序提供,例如:超时重传.序列号应答机制,但是它在某些场合使用效率高,方便.它支持广播和多播.有关<基本 UDP 套接字编程>参照该文,这里只是在那个基础上,记录一些在 UDP 编程中容易出现的问题. 辅助数据 辅助数据(也称为控制信息)可通过调用 recvmsg 和 sendmsg 函数使用,这里两个函数的定义可参考文章<高级 I/O>,使用 msghdr 结构体中的 msg_control 和 msg_con

【Python网络编程】利用Python进行TCP、UDP套接字编程

之前实现了Java版本的TCP和UDP套接字编程的例子,于是决定结合Python的学习做一个Python版本的套接字编程实验. 流程如下: 1.一台客户机从其标准输入(键盘)读入一行字符,并通过其套接字将该行发送到服务器. 2.服务器从其连接套接字读取一行字符. 3.服务器将该行字符转换成大写. 4.服务器将修改后的字符串(行)通过连接套接字再发回给客户机. 5.客户机从其套接字中读取修改后的行,然后将该行在其标准输出(监视器)上打印出来. [TCP]服务器端代码: import socket

探索UDP套接字编程

UDP和TCP处于同一层网络模型中,也就是运输层,基于二者之上的应用有很多,常见的基于TCP的有HTTP.Telnet等,基于UDP有DNS.NFS.SNMP等.UDP是无连接,不可靠的数据协议服务,而TCP提供面向流.提供可靠数据服务.注意,UDP和TCP没有好坏之分,只是二者的适用场景不同罢了. 典型的UDP套接字编程模型是客户端不予服务端建立连接,而只是调用sendto函数来向服务端发送数据,其中必须要指定服务端的信息,包括IP和端口等:服务端不接收来自客户端的连接,而只是调用recvfr

【转】 探索UDP套接字编程

UDP和TCP处于同一层网络模型中,也就是运输层,基于二者之上的应用有很多,常见的基于TCP的有HTTP.Telnet等,基于UDP有DNS.NFS.SNMP等.UDP是无连接,不可靠的数据协议服务,而TCP提供面向流.提供可靠数据服务.注意,UDP和TCP没有好坏之分,只是二者的适用场景不同罢了. 典型的UDP套接字编程模型是客户端不予服务端建立连接,而只是调用sendto函数来向服务端发送数据,其中必须要指定服务端的信息,包括IP和端口等:服务端不接收来自客户端的连接,而只是调用recvfr

apue和unp的学习之旅10——基本udp套接字编程

使用UDP编写的一些常见的应用程序有:DNS(域名系统),NFS(网络文件系统),SNMP(简单网络管理协议). //---------------------------------1.recvfrom函数和sendto函数---------------------------------- #include <sys/socket.h> ssize_t  recvfrom(int sockfd, void* buff, size_t nbytes, int flags, struct so

TCP和UDP套接字编程 (java实现)

在了解网络编程之前,我们先了解一下什么叫套接字 套接字即指同一台主机内应用层和运输层之间的接口 由于这个套接字是建立在网络上建立网络应用的可编程接口 因此也将套接字称为应用程序和网络之间的应用程序编程接口! 关于TCP和UDP这里就不作太多介绍了,我们知道TCP是面向连接的,UDP是不面向连接的,TCP可靠,UDP不可靠即可! 我们来设计一个应用来示范一下,流程: 客户机从键盘读取一行字符串,并通过套接字发送到服务器. 服务器从连接的套接字获取这行字符串,并对其进行修改(将小写转为大写),最后再

linux下的UDP套接字编程

一.相关过程以及知识请详见我的另一篇博客<winsock套接字编程>,这里不再累述. 二.相关代码: server.c:  1 /****************************************                                                                                                    2     > File Name:server.c   3     > A

linux网络环境下socket套接字编程(UDP文件传输)

今天我们来介绍一下在linux网络环境下使用socket套接字实现两个进程下文件的上传,下载,和退出操作! 在socket套接字编程中,我们当然可以基于TCP的传输协议来进行传输,但是在文件的传输中,如果我们使用TCP传输,会造成传输速度较慢的情况,所以我们在进行文件传输的过程中,最好要使用UDP传输. 在其中,我们需要写两个程序,一个客户端,一个服务端,在一个终端中,先运行服务端,在运行客户端,在服务端和客户端都输入IP地址和端口号,注意服务端和客户端的端口号要相同,然后选择功能,在linux

linux网络编程-(socket套接字编程UDP传输)

今天我们来介绍一下在linux网络环境下使用socket套接字实现两个进程下文件的上传,下载,和退出操作! 在socket套接字编程中,我们当然可以基于TCP的传输协议来进行传输,但是在文件的传输中,如果我们使用TCP传输,会造成传输速度较慢的情况,所以我们在进行文件传输的过程中,最好要使用UDP传输. 在其中,我们需要写两个程序,一个客户端,一个服务端,在一个终端中,先运行服务端,在运行客户端,在服务端和客户端都输入IP地址和端口号,注意服务端和客户端的端口号要相同,然后选择功能,在linux