《UNIX网络编程》UDP网络编程基础

udp是无连接的,对于服务器,它只需要创建套接字,并绑定到地址:端口上,然后等待接收消息到来,对于客户端,只需要创建套接字然后向服务器发送消息。

udp服务器一般是迭代的。

下面是一个使用udp的简单echo程序:

/*
 *udp_server.c
 */
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <linux/in.h>
#include <string.h>

#define PORT 8888
#define BUFSIZE 1024

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

    servfd = 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(PORT);

    /*绑定地址结构到套接字描述符*/
    if (bind(servfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0)
    {
        printf("bind error\n");
        return -1;
    }

    udpserv_echo(servfd, (struct sockaddr *) &cliaddr);     //回显处理程序

    return 0;
}
/*
 *udp_client.c
 */
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>         //包含socket, bind
#include <unistd.h>
#include <linux/in.h>           //包含struct sockaddr_in
#include <string.h>

#define PORT 8888
#define BUFSIZE 1024

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

    if (argc != 2)
    {
        printf("usage: client <IPaddress>");
        return -1;
    }

    /*设置服务器地址*/
    bzero(&servaddr, sizeof(servaddr)); //清零
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(PORT);
    if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0)
    {
        printf("inet_pton: error\n");
        return -1;
    }

    if ((clifd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    {
        printf("socket error\n");
        return -1;
    }

    udpclie_echo(clifd, (struct sockaddr*) &servaddr);

    close(clifd);
    return 0;
}
/*
 *udp_process.c
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/in.h>

#define BUFSIZE 256

void 
udpserv_echo(int fd, struct sockaddr* cliaddr)
{
    ssize_t     size = 0;
    char        buffer[BUFSIZE];
    int         clilen;

    for (;;)
    {
        clilen = sizeof(*cliaddr);
        //接收数据放在buffer中,并获取客户端地址.
        //注意,recvfrom返回0是可接受的.
        if ( (size = recvfrom(fd, buffer, BUFSIZE, 0, cliaddr, &clilen)) < 0 )
        {
            printf("recvfrom: error\n");
            return;
        }
        if ( sendto(fd, buffer, size, 0, cliaddr, clilen) < 0 ) //发回给客户端
        {
            printf("sendto: error\n");
            return;
        }
    }
}


void 
udpclie_echo(int fd, struct sockaddr* to)
{
    struct sockaddr_in  from;
    char        buffer[BUFSIZE];
    int         len = sizeof(*to);
    ssize_t     size = 0;

    for (;;)
    {
        size = read(0, buffer, BUFSIZE);
        if (size > 0)
        {
            buffer[size++] = '\0';
            if ( sendto(fd, buffer, size, 0, to, len) < 0)
            {
                printf("sendto: error\n");
                return;
            }
            if ( recvfrom(fd, buffer, BUFSIZE, 0, (struct sockaddr*) &from, &len) < 0 )
            {
                printf("recvfrom: error\n");
                return;
            }
            printf("recved:%s\n", buffer);
        }
    }
}

UDP协议程序设计中的几个问题

1.UDP报文丢失数据

UDP是无连接的,不能保证发送数据的正确到达,因此在需要确认的情况下,我们得自己处理超时重传。

2.UDP数据发送中的乱序

针对这一点,我们可以采用发送端在数据段中加入数据报序号的方法。

3.UDP的connect函数

UDP协议中使用connect函数仅仅表示确定了另一方的地址,它并没有真正建立一个连接,即没有3次握手过程,只是维护了一种状态。

使用connect函数绑定套接字后,发送操作不能再使用sendto(recvfrom)函数,要使用write(read)函数直接操作套接字描述符,不再指定目标地址和端口号。

在使用多次connect函数的时候,会用新绑定的地址和套接字代替旧的,原有的绑定状态失效。我们可以使用这种特性来断开原来的连接。

4.UDP缺乏流量控制

当缓冲区满的时候,后面到来的数据会覆盖之前的数据而造成数据的丢失。

解决UDP接收缓冲区溢出的现象需要根据实际情况确定,一般可以用增大接收数据缓冲区和接收方接收单独处理的方法来解决局部的UDP数据接收缓冲区溢出问题。

5.UDP协议中的数据报文截断

当使用UDP协议接收数据的时候,如果应用程序传入的接收缓冲区的大小小于到来的数据大小时,接收缓冲区会保存最大可能接收到的数据,其他的数据将会丢失,并且有MSG_TRUNC的标志。

因此服务器和客户端程序需要进行配合,接收的缓冲区要比发送的数据大一些。

6.UDP协议的外出网络接口

知道客户临时端口号的任何进程都可以往客户发送数据报,而且这些数据报会与正常的服务器应答混杂。为解决这个问题,我们可以在recvfrom中返回数据报发送者的IP地址和端口号,只保留来自数据报所发往的服务器的应答,忽略其他数据报。

但是这样存在一些缺陷,当服务器主机有两个网卡和IP地址时,比如ip为172.24.37.94和135.197.17.100,当我们往135.197.17.100发送数据之后,服务器主机内核中的路由功能可能选择172.24.37.94作为外出接口(因为我们没有在套接字上绑定一个实际的IP地址)。

解决方法:

1.客户通过recvfrom得到服务器ip地址后,通过DNS查询主机名字来验证域名而不是IP地址。

2.为服务器的每个IP创建一个套接字,用bind绑定每个IP,然后在这些套接字上使用select。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux网络编程(总共41集) 讲解Linux网络编程知识,分以下四个篇章。 Linux网络编程之TCP/IP基础篇 Linux网络编程之socket编程篇 Linux网络编程之进程间通信篇 Linux网络编程之线程篇 Linux网络编程之TCP/IP基础篇 01TCPIP基础(一) ISO/OSI参考模型 TCP/IP四层模型 基本概念(对等通信、封装、分用、端口) 02TCPIP基础(二) 最大传输单元(MTU)/路径MTU 以太网帧格式 ICMP ARP RARP 03TCPIP基础(三) IP数据报格式 网际校验和 路由 04TCPIP基础(四) TCP特点 TCP报文格式 连接建立三次握手 连接终止四次握手 TCP如何保证可靠性 05TCPIP基础(五) 滑动窗口协议 UDP特点 UDP报文格式 Linux网络编程之socket编程篇 06socket编程(一) 什么是socket IPv4套接口地址结构 网络字节序 字节序转换函数 地址转换函数 套接字类型 07socket编程(二) TCP客户/服务器模型 回射客户 /服务器 socket、bind、listen、accept、connect 08socket编程(三) SO_REUSEADDR 处理多客户连接(process-per-conection) 点对点聊天程序实现 09socket编程(四) 流协议与粘包 粘包产生的原因 粘包处理方案 readn writen 回射客户/服务器 10socket编程(五) read、write与recv、send readline实现 用readline实现回射客户/服务器 getsockname、getpeername gethostname、gethostbyname、gethostbyaddr 11socket编程(六) TCP回射客户/服务器 TCP是个流协议 僵进程与SIGCHLD信号 12socket编程(七) TCP 11种状态 连接建立三次握手、连接终止四次握手 TIME_WAIT与SO_REUSEADDR SIGPIPE 13socket编程(八) 五种I/O模型 select 用select改进回射客户端程序 14socket编程(九) select 读、写、异常事件发生条件 用select改进回射服务器程序。 15socket编程(十) 用select改进第八章点对点聊天程序 16socket编程(十一) 套接字I/O超时设置方法 用select实现超时 read_timeout函数封装 write_timeout函数封装 accept_timeout函数封装 connect_timeout函数封装 17socket编程(十二) select限制 poll 18socket编程(十三) epoll使用 epoll与select、poll区别 epoll LT/ET模式 19socket编程(十四) UDP特点 UDP客户/服务基本模型 UDP回射客户/服务器 UDP注意点 20socket编程(十五) udp聊天室实现 21socket编程(十六) UNIX域协议特点 UNIX域地址结构 UNIX域字节流回射客户/服务 UNIX域套接字编程注意点 22socket编程(十七) socketpair sendmsg/recvmsg UNIX域套接字传递描述符字 Linux网络编程之进程间通信篇 23进程间通信介绍(一) 进程同步与进程互斥 进程间通信目的 进程间通信发展 进程间通信分类 进程间共享信息的三种方式 IPC对象的持续性 24进程间通信介绍(二) 死锁 信号量 PV原语 用PV原语解决司机与售票员问题 用PV原语解决民航售票问题 用PV原语解决汽车租赁问题 25System V消息队列(一) 消息队列 IPC对象数据结构 消息队列结构 消息队列在内核中的表示 消息队列函数 26System V消息队列(二) msgsnd函数 msgrcv函数 27System V消息队列(三) 消息队列实现回射客户/服务器 28共享内存介绍 共享内存 共享内存示意图 管道、消息队列与共享内存传递数据对比 mmap函数 munmap函数 msync函数 29System V共享内存 共享内存数据结构 共享内存函数 共享内存示例 30System V信号量(一) 信号量 信号量集结构 信号量集函数 信号量示例 31System V信号量(二) 用信号量实现进程互斥示例 32System V信号量(三) 用信号集解决哲学家就餐问题 33System V共享内存与信号量综合 用信号量解决生产者消费者问题 实现shmfifo 34POSIX消息队列 POSIX消息队列相关函数 POSIX消息队列示例 35POSIX共享内存 POSIX共享内存相关函数 POSIX共享内存示例 Linux网络编程之线程篇 36线程介绍 什么是线程 进程与线程 线程优缺点 线程模型 N:1用户线程模型 1:1核心线程模型 N:M混合线程模型 37POSIX线程(一) POSIX线程库相关函数 用线程实现回射客户/服务器 38POSIX线程(二) 线程属性 线程特定数据 39POSIX信号量与互斥锁 POSIX信号量相关函数 POSIX互斥锁相关函数 生产者消费者问题 自旋锁与读写锁介绍 40POSIX条件变量 条件变量 条件变量函数 条件变量使用规范 使用条件变量解决生产者消费者问题 41一个简单的线程池实现 线程池性能分析 线程池实现 网络编程, Linux
本书是一部UNIX网络API的经典之作!  本书是为那些希望能够通过套接字接口实现程序间的相互通信的人而编写的,旨在为网络编程的初学者以及专家提供指导。要建立高度分布式、网络化的应用程序和服务,就需要对套接字和其他关键的网络API有深入的理解。本书为在各种环境下建立健壮的、高性能的网络系统提供了全面的指导。  这个版本建立在W.Richard Stevens的传奇性工作的基础上,由两个最优秀的网络编程专家进行了完全更新。讨论了当今最关键的标准、实现和技术 讨论的新主题包括:  POSIX Single UNIX Specification Version 3  IPv6 APIs(包括更新了的对IPv6/IPv4互操作性的指南)  新的SCTP传输协议  基于IPsec的密钥管理套接字  FreeBSD 4.8/5.1、Red Hat Linux 9.x、Solaris 9、AIX 5.x、HPUX和Max OS X实现  新的网络编程调试技术  源端特定的组播API,正是这个API使得IP组播开发被广泛使用  本书还更新和扩展了Stevens对以下关键的UNIX网络标准和技术所做的权威性的概括:  TCP和UDP传输  基本的和高级的套接宇,路由的和原始的套接宇  I/O:复用高级函数,非阻塞和信号驱动  守护进程和inetd  UNIX域协议  ioctl操作  广播和组播  线程  流  设计:TCP迭代的、并发的、预先创建的和预先线程化的服务器

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值