ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

socket编程---UDP

2021-07-08 13:51:32  阅读:152  来源: 互联网

标签:UDP socket int --- connect 接字 include servaddr


socket编程---UDP

  无论发多大的包,IP层和链路层都会把你的包进行分片发送,一般局域网就是1500左右,广域网就只有几十字节。分片后的包将经过不同的路由到达接收方,对于UDP而言,要是其中一个分片丢失,那么接收方的IP层将把整个发送包丢弃,这就形成丢包

头文件

#include <sys/types.h>
#include <sys/socket.h>

函数原型

int sendto (int s, const void *buf, int len, unsigned int flags, const struct sockaddr *to, int tolen);
int recvfrom(int s, void *buf, int len, unsigned int flags, struct sockaddr *from, int *fromlen);

server

/*************************************************************************
    > File Name: server.c
    > Author: Chen
    > Mail: 971859774@qq.com 
    > Created Time: 2018年11月12日 星期一 21时30分34秒
 ************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <string.h>
#include <strings.h>
#include "my_err.h"
#define SERV_PORT 9877

void dg_ser(int sockfd,struct sockaddr * cliaddr,socklen_t clilen)
{
    char msg[MAXLINE];
    memset(msg,sizeof(msg),0);
    int index=0;
    while(1)
    {
        socklen_t len=clilen;

        int n=recvfrom(sockfd,msg,MAXLINE,0,cliaddr,&len);
        char m[len];
        printf("server receive from %s, message: %s",inet_ntop(AF_INET,cliaddr,m,len),msg);
        sendto(sockfd,msg,n,0,cliaddr,len);
    }
    return;
}

int main(int argc,char **argv)
{
    int sockfd=socket(AF_INET,SOCK_DGRAM,0);

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

    bind(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
    char msg[sizeof(servaddr)];
    printf("server adress: %s\n",inet_ntop(AF_INET,(struct sockaddr *)&servaddr,msg,sizeof(servaddr)));
    dg_ser(sockfd,(struct sockaddr *)&cliaddr,sizeof(cliaddr));

    return 0;
}

调用connect的client

/*************************************************************************
    > File Name: client.c
    > Author: Chen
    > Mail: 971859774@qq.com 
    > Created Time: 2018年11月12日 星期一 20时47分06秒
 ************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <strings.h>
#include "my_err.h"
#define SERV_PORT 9877

void dg_cli(int sockfd,const struct sockaddr* servaddr,socklen_t servlen)
{
    char sendline[MAXLINE],recvline[MAXLINE+1];

    //调用connect的udp套接字
    int res=connect(sockfd,(struct sockaddr *)servaddr,servlen);
    if(res<0)
    {
        fprintf(stderr,"connect error\n");
        exit(0);
    }
    
    /*利用connect确定外出接口,因为connect udp套接字的时候,内核自动选
     *(假设未显示调用bind),本地ip地址通过为目的ip地址搜索路由表得到的
     *外出借口,然后选用该接口的主ip地址而选定
     */
    struct sockaddr_in cliaddr;
    socklen_t len=sizeof(cliaddr);
    getsockname(sockfd,(struct sockaddr *)&cliaddr,&len);
    char m[len];
    printf("local address: %s\n",inet_ntop(AF_INET,(struct sockaddr *)&cliaddr,m,len));

    while(fgets(sendline,MAXLINE,stdin)!=NULL)
    {
        /*
         * 如果此时服务器未启动,客户将永远阻塞在recvfrom中。
         * 愿意:在客户像服务器发送udp数据报之前,先进行一次arp的请求答应
         * 交换,这个 请求交换放在tcpdump中输出中,服务器相应一个端口不可
         * 达错误,这也成为异步错误,该错误有sendto引起,但sendto却成功返回
         * ,udp输出操作成功返回仅仅表示接口接口队列中具有存放形成的ip数据
         * 报的空间,对于udp套接字,由它引发的错误却不返回给他
         */
        
        size_t fw=write(sockfd,sendline,strlen(sendline));
        if(fw<0)
        {
            fprintf(stderr,"write error\n");
            exit(0);
        }

        size_t n=read(sockfd,recvline,MAXLINE);
        char *msg=strerror(errno);
        if(errno==111)
        {
            fprintf(stderr,"%s\n",msg);
            exit(1);
        }

        if(n<0)
        {
            fprintf(stderr,"read error\n");
        }

        recvline[n]='\0';
        fputs(recvline,stdout);

        bzero(sendline,MAXLINE);
        bzero(recvline,MAXLINE);
    }
    return;
}

int main(int argv,char **argc)
{
    if(argv!=2)
        err_quit("please input address");

    /*
     * 客户必须给出服务器的ip和端口号,一般来说客户的端口号和ip由内核自动
     * 选则,临时端口号是在第一次sendto时一次性选定的不能改,ip可以变动。
     * 也可用bind指定
     */
    struct sockaddr_in servaddr;
    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family=AF_INET;
    servaddr.sin_port=htons(SERV_PORT);
    inet_pton(AF_INET,argc[1],&servaddr.sin_addr);

    int sockfd=socket(AF_INET,SOCK_DGRAM,0);
    
    dg_cli(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
    return 0;
}

没有调用connect的client

/*************************************************************************
    > File Name: client.c
    > Author: Chen
    > Mail: 971859774@qq.com 
    > Created Time: 2018年11月12日 星期一 20时47分06秒
 ************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <strings.h>
#include "my_err.h"
#define SERV_PORT 9877

void dg_cli(int sockfd,const struct sockaddr* servaddr,socklen_t servlen)
{
    char sendline[MAXLINE],recvline[MAXLINE];

    while(fgets(sendline,MAXLINE,stdin)!=NULL)
    {
        /*
         * 如果此时服务器未启动,客户将永远阻塞在recvfrom中。
         * 原因:在客户像服务器发送udp数据报之前,先进行一次arp的请求答应
         * 交换,这个 请求交换放在tcpdump中输出中,服务器相应一个端口不可
         * 达错误,这也成为异步错误,该错误有sendto引起,但sendto却成功返回
         * ,udp输出操作成功返回仅仅表示接口接口队列中具有存放形成的ip数据
         * 报的空间,对于udp套接字,由它引发的错误却不返回给他
         */
        sendto(sockfd,sendline,strlen(sendline),0,servaddr,servlen);
        
        //接受返回来的套接字,判断返回的信息是不是服务器返回的信息
        struct sockaddr *rep_ser=malloc(servlen);
        socklen_t len=servlen;
        int n=recvfrom(sockfd,recvline,MAXLINE,0,rep_ser,&len);

        char msg[len];
        if(len!=servlen||memcmp(servaddr,rep_ser,len)!=0)
        {
            printf("reply from %s (ignore)\n",inet_ntop(AF_INET,rep_ser,msg,len));
            continue;
        }
        
        printf("server adress: %s\n",inet_ntop(AF_INET,rep_ser,msg,len));
        recvline[n]='\0';
        printf("refelect send message:");
        fputs(recvline,stdout);
    }
    return;
}

int main(int argv,char **argc)
{
    if(argv!=2)
        err_quit("please input address");

    /*
     * 客户必须给出服务器的ip和端口号,一般来说客户的端口号和ip由内核自动
     * 选则,临时端口号是在第一次sendto时一次性选定的不能改,ip可以变动。
     * 也可用bind指定
     */
    struct sockaddr_in servaddr;
    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family=AF_INET;
    servaddr.sin_port=htons(SERV_PORT);
    inet_pton(AF_INET,argc[1],&servaddr.sin_addr);

    int sockfd=socket(AF_INET,SOCK_DGRAM,0);
    
    dg_cli(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
    return 0;
}

udp的connect

  UDP的connect没有三次握手过程,内核只是检测是否存在立即可知的错误(如一个显然不可达的目的地),

  记录对端的的IP地址和端口号,然后立即返回调用进程。

  1. 未连接UDP套接字(unconnected UDP socket):新创建UDP套接字默认如此;
  2. 已连接UDP套接字(connected UDP socket):对UDP套接字调用connect的结果。

  对于已连接UDP套接字,与默认的未连接UDP套接字相比

  1. 再不能给输出操作指定目的IP地址和端口号。sendto改用write或send,写到已连接UDP套接字上的任何内容都自动发送到由connect指定的协议地址(如IP地址和端口号)。已连接UDP套接字调用sendto,不能指定目的地址。其第五个参数必须为空指针,第六个参数应为0或不考虑。
  2. 可不使用recvfrom以获悉数据报的发送者,改用read、recv或recvmsg。在已连接UDP套接字上,由内核为输入操作返回的数据报只有来自connect所制定协议地址的数据报。这样就限制一个已连接UDP套接字能且仅能与一个对端交换数据报。
  3. 由已连接UDP套接字引发的异步错误会返回给它们所在的进程,而未连接UDP套接字不接受任何异步错误。

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=  

POSIX规范指出,在未连接UDP套接字上不指定目的地址的输出操作应该返回ENOTCONN,而不是EDESTADDRREQ

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

  应用进程首先调用connect指定对端的IP地址和端口号,然后使用read和write与对端进程交换数据。来自任何其他IP地址或端口的数据报不投递给这个已连接套接字,UDP将丢弃它们并生成相应的ICMP端口不可达错误。

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

  调用connect的通常是UDP客户,不过有些网络应用中的UDP服务器会与单个客户长时间通信(如TFTP),此时客户和服务器都有可能调用connect。

  DNS客户、服务器与connect函数的例子:

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

  用于一个已连接UDP套接字的进程可出于以下目的之一会再次调用connect

  1. 指定新的IP地址和端口号
  2. 断开套接字

   断开一个已连接的udp套接字,再次调用connect函数时,把套接字的地址结构的地址族成员(IPv4:sin_family;IPv6:sin6_famliy)设置为AF_UNSPEC会返回一个EAFNOSUPPORT错误。

  在未连接到udp套接字上调用sendto时

  1. 连接套接字
  2. 输出第一个数据报
  3. 断开套接字链接
  4. 链接套接字
  5. 输出第二个数据报
  6. 断开套接字的连接

  connect后两次write

  1. 连接套接字
  2. 输出第一个数据报
  3. 输出第二个数据报

  性能:当进程知道自己要给同一个目的地址发送多个数据报时,显式连接套接字效率更高。而临时连接未连接的UDP套接字大约会消耗每个UDP传输三分之一的开销

void dg_cli(FILE *fp, int sockfd, SA *pservaddr, socklen_t servlen)
{
    int n;
    char sendline[MAXLINE], recvline[MAXLINE + 1];
    if (connect(sockfd, (SA*)pservaddr, servlen) < 0){
        cout<<"conn error!"<<endl;
        exit(0);
    }
    else
        cout<<"conn succ!"<<endl;

    while (fgets(sendline, MAXLINE, fp) != NULL){
        if (write(sockfd, sendline, strlen(sendline)) < 0){
            cout<<"write error"<<endl;
            exit(0);
        }

        if ( (n = read(sockfd, recvline, MAXLINE)) < 0){
            cout<<"read error"<<endl;
            exit(0);
        }

        recvline[n] = 0;
        fputs(recvline, stdout);

        bzero(sendline, MAXLINE);
        bzero(recvline, MAXLINE);
    }
}

 

 

标签:UDP,socket,int,---,connect,接字,include,servaddr
来源: https://blog.51cto.com/u_12580130/3012531

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有