《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。

你可能感兴趣的:(unix,服务器,网络编程,UDP)