UNP卷一chapter8 基本UDP套接字编程

以下知识点来均来自steven先生所著UNP卷一(version3),刚开始学习网络编程,如有不正确之处请大家多多指正。

1、下图是典型的UDP client/server程序所用套接字函数



需要注意的是,UDP不面向连接,故不需要使用connect函数,但使用connect也是可以的,只是作用不再是三次握手建立连接,其作用表现在检测异步错误(asychronous error)和绑定目的地IP和PORT。

2、recvfrom和sendto函数

#include<sys/socket.h>
ssize_t recvfrom(int sockfd, void* buff, size_t nbytes, int flags,
	struct sockaddr* from, socklen_t* addrlen);//后两个参数均为值-结果参数,将获得来自发端的IP地址和及其长度
ssize_t sendto(int sockfd, const void* buff, size_t nbytes, int flags,
	const struct sockaddr* to, socklen_t addrlen);//后两个参数为指定发送的接收端的IP地址及其长度
//上述函数均返回:若成功则为读或写的字节数,若出错则为-1

3、使用UDP的简单回射client/server


a、服务器代码如下(相对来说,比较简单,易读,不再作解释!)

void dg_echo(int,SA*,socklen_t);

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

	sockfd = Socket(AF_INET, SOCK_DGRAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family      = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port        = htons(SERV_PORT);

	Bind(sockfd, (SA *) &servaddr, sizeof(servaddr));

	dg_echo(sockfd, (SA *) &cliaddr, sizeof(cliaddr));
}

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);
	}
}
b、客户端代码如下
#include<unp.h>

void dg_cli(FILE* fp, int sockfd, const SA* pservaddr, socklen_t servlen);

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);

	sockfd = Socket(AF_INET, SOCK_DGRAM, 0);

	dg_cli(stdin, sockfd, (SA*)&servaddr, sizeof(servaddr));

	exit(0);
}
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);//后两个NULL表明不关心数据发送者的协议地址

		recvline[n] = 0;//字符串要以\0结尾
		Fputs(recvline, stdout);
	}
}

4、数据报的丢失

udp是一个不可靠不面向连接的协议,因而当发送内容大于接收缓冲区时就会产生数据报的丢失。另,当一个客户的数据报丢失,此时得不到服务器的应答,客户将永远阻塞于dg_cli函数中的recvfrom调用。此时可通过设置超时器防止客户永久阻塞在recvfrom函数上。但此时,又无法辨别区分请求丢失和应答丢失。

5、验证接收到的响应(上述客户端可以接收来自任何主机的信息,不具备信息的筛选,于是可设置验证接收到的响应,但又会出现问题,见下文分析)

此方法只针对单宿服务器主机才起作用,多宿主机行不通,具体实现见dg_代码

#include	"unp.h"

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

	preply_addr = Malloc(servlen);

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

		Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);

		len = servlen;
		n = Recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr, &len);//由内核返回的套接字地址结构是包含长度字段,故下面比较时也要提前设置好长度字段信息
		if (len != servlen || memcmp(pservaddr, preply_addr, len) != 0) {
			printf("reply from %s (ignored)\n",
					Sock_ntop(preply_addr, len));
			continue;
		}

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

弱端系统模型:大多数IP实现接受目的地址为本主机任一IP地址的数据,而不管数据报到达的接口;

强端系统模型:只接受到达接口与目的地址一致的数据报

5、UDP协议两个值得注意的地方

客户PORT一次性绑,而IP可随数据报变动

A的IP绑在sockfd上,但数据报从B的IP接口出,则IP报文包含源A的IP,但仍可以B的IP接口出去。即所谓IP数据报将包含一个不同于外出链路IP地址的源IP地址。

6、UDP的connect函数(使用UDP一对一通信,才调用connect函数吧)

检查是否存在立即可知错误(asyc error,异步错误)

记录对端IP和PORT(好处就是可以使用wirte和read代替sendto和recvfrom),但不会有三次握手过程

7、使用select函数的tcp和udp回射服务器程序(比较好理解,不详细解释),代码见下文
#include	"unp.h"

int
main(int argc, char **argv)
{
	int					listenfd, connfd, udpfd, nready, maxfdp1;
	char				mesg[MAXLINE];
	pid_t				childpid;
	fd_set				rset;
	ssize_t				n;
	socklen_t			len;
	const int			on = 1;
	struct sockaddr_in	cliaddr, servaddr;
	void				sig_chld(int);

		/* 4create listening TCP socket */
	listenfd = Socket(AF_INET, SOCK_STREAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family      = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port        = htons(SERV_PORT);

	Setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));//开启ReuseADDr套接字选项
	Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));

	Listen(listenfd, LISTENQ);

		/* 4create UDP socket */
	udpfd = Socket(AF_INET, SOCK_DGRAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family      = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port        = htons(SERV_PORT);

	Bind(udpfd, (SA *) &servaddr, sizeof(servaddr));


//--------------------------------------------------------------------------------------
	Signal(SIGCHLD, sig_chld);	/* must call waitpid() *///用于清理defunct进程

	FD_ZERO(&rset);//以下两行,初始化描述符集
	maxfdp1 = max(listenfd, udpfd) + 1;
	for ( ; ; ) {
		FD_SET(listenfd, &rset);
		FD_SET(udpfd, &rset);
		if ( (nready = select(maxfdp1, &rset, NULL, NULL, NULL)) < 0) {//通过select函数调用,判别是选用了tcp还是udp
			if (errno == EINTR)//检测中断
				continue;		/* back to for() */
			else
				err_sys("select error");
		}

		if (FD_ISSET(listenfd, &rset)) {//验证是否是tcp连接
			len = sizeof(cliaddr);
			connfd = Accept(listenfd, (SA *) &cliaddr, &len);
	
			if ( (childpid = Fork()) == 0) {	/* child process */
				Close(listenfd);	/* close listening socket */
				str_echo(connfd);	/* process the request */
				exit(0);
			}
			Close(connfd);			/* parent closes connected socket */
		}

		if (FD_ISSET(udpfd, &rset)) {//验证是否是udp连接
			len = sizeof(cliaddr);
			n = Recvfrom(udpfd, mesg, MAXLINE, 0, (SA *) &cliaddr, &len);

			Sendto(udpfd, mesg, n, 0, (SA *) &cliaddr, len);
		}
	}
}



猜你喜欢

转载自blog.csdn.net/tt_love9527/article/details/80209938