天天看點

TCP/IP(8)-UDP Server與UDP Client(linux套接字)UDP用戶端伺服器模式1. socket函數建立套接字2. bind函數,綁定伺服器位址到套接字上3. sendto函數,發送資料給指定位址3. recvfrom函數,接收資料服務端程式用戶端程式UDP的connect函數UDP缺乏流量控制

上一篇文章已經讨論了linux套接字基于TCP的用戶端和伺服器端程式設計,這片文章詳細讨論linux套接字基于UDP的用戶端和伺服器端程式設計。

UDP用戶端伺服器模式

TCP/IP(8)-UDP Server與UDP Client(linux套接字)UDP用戶端伺服器模式1. socket函數建立套接字2. bind函數,綁定伺服器位址到套接字上3. sendto函數,發送資料給指定位址3. recvfrom函數,接收資料服務端程式用戶端程式UDP的connect函數UDP缺乏流量控制

UDP與TCP相比要簡潔很多,UDP不需要listen,accept和connect過程。

1. socket函數建立套接字

#include <sys/types.h>
#include <sys/socket.h>
sockfd = socket(AF_INET, SOCK_DGRAM, );
           

UDP是資料報的形式,是以在建立套接字時,是SOCK_DGRAM,這是與TCP不同的地方。

2. bind函數,綁定伺服器位址到套接字上

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
           

bind函數與TCP的使用相同,将伺服器的知名端口号和IP位址綁定到伺服器套接字位址上,IP位址可能有多個。

3. sendto函數,發送資料給指定位址

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

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
           

sendto函數比send函數多出兩個參數,一個是目的位址,一個是位址長度。告訴用戶端發送給哪個IP位址和哪個端口号。

3. recvfrom函數,接收資料

#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
           

recvfrom函數比recv函數多出兩個參數,相當于TCP的accept函數,告訴我們是誰發送了資料過來。

服務端程式

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <unistd.h>

#define BUFLEN 100

const char* IP = "127.0.0.1";
const unsigned int SERV_PORT = ;
void Chat(int sockfd);

int main(int argc, char *argv[])
{
    int sockfd;
    struct sockaddr_in s_addr, c_addr;
    char buf[BUFLEN];
    socklen_t len;

    /*建立socket*/
    if((sockfd = socket(AF_INET, SOCK_DGRAM, )) == -){
        perror("socket");
        exit(errno);
    }

    /*設定伺服器ip*/
    bzero(&s_addr, sizeof(s_addr));
    s_addr.sin_family = AF_INET;
    s_addr.sin_port = htons(SERV_PORT);
    s_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    /*把位址和端口幫定到套接字上*/
    if((bind(sockfd, (struct sockaddr*) &s_addr,sizeof(struct sockaddr))) == -){
        perror("bind");
        exit(errno);
    }
    printf("*****************server start***************\n");

    len = sizeof(struct sockaddr);

    while()
    {
        int n = ;

        /******接收消息*******/
        bzero(buf,BUFLEN);
        n = recvfrom(sockfd, buf, BUFLEN, , (struct sockaddr*)&c_addr, &len);
        if(n > )
            printf("receive massage:%s\n",buf);
        else
        {
            if(n <  )
                printf("receive failed\n");
            else//伺服器調用close函數後,系統阻塞函數調用,傳回0
                printf("client stop\n");
            break;        
        }

    _retry:
        /******發送消息*******/
        bzero(buf,BUFLEN);
        printf("enter your words:");
        /*fgets函數:從流中讀取BUFLEN-1個字元*/
        fgets(buf,BUFLEN,stdin);

        if(!strncasecmp(buf,"quit",))
        {
            printf("server stop\n");
            break;
        }

        /*如果輸入的字元串隻有"\n",即回車,那麼請重新輸入*/
        if(!strncmp(buf,"\n",))
        {
            goto _retry;
        }    

        /*如果buf中含有'\n',那麼要用strlen(buf)-1,去掉'\n'*/            
        if(strchr(buf,'\n'))
        {
            n = sendto(sockfd, buf, strlen(buf)-, , (struct sockaddr*)&c_addr, len);
        }
        /*如果buf中沒有'\n',則用buf的真正長度strlen(buf)*/    
        else
        {
            n = sendto(sockfd,buf,strlen(buf),, (struct sockaddr*)&c_addr, len);
        }

        if(n > )
            printf("send successful\n");            
        else{
            printf("send failed\n");
            break;            
        }


        }
    /*關閉已連接配接套接字*/
    close(sockfd);
    return ;
}
           

用戶端程式

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <unistd.h>

#define BUFLEN 100

const char* IP = "127.0.0.1";
const int SERV_PORT = ;

int main(int argc, char *argv[])
{
    int sockfd;
    struct sockaddr_in send_addr; //伺服器套接字位址
    struct sockaddr_in recv_addr;
    socklen_t send_len, recv_len;
    char send_buf[BUFLEN], recv_buf[BUFLEN];    

    /*建立socket*/
    if((sockfd = socket(AF_INET, SOCK_DGRAM, )) == -){
        perror("socket");
        exit(errno);
    }

    /*設定伺服器ip*/
    bzero(&send_addr, sizeof(send_addr));
    send_addr.sin_family = AF_INET;
    send_addr.sin_port = htons(SERV_PORT);

    if(inet_aton(IP, (struct in_addr*)&send_addr.sin_addr) == )
    {
        perror("IP error");
        exit(errno);
    }

    printf("*****************client start***************\n");

    send_len = sizeof(struct sockaddr);

    while()
    {
        int n = ;
    _retry:    
        /******發送消息*******/    
        bzero(send_buf,BUFLEN);
        printf("enter your words:");
        /*fgets函數:從流中讀取BUFLEN-1個字元*/
        fgets(send_buf,BUFLEN,stdin);

        if(!strncasecmp(send_buf,"quit",))
        {
            printf("client stop\n");
            break;
        }


        /*如果輸入的字元串隻有"\n",即回車,那麼請重新輸入*/
        if(!strncmp(send_buf,"\n",))
        {
            goto _retry;
        }


        /*如果buf中含有'\n',那麼要用strlen(buf)-1,去掉'\n'*/    
        if(strchr(send_buf,'\n'))
        {
            n = sendto(sockfd,send_buf,strlen(send_buf)-,, (struct sockaddr*)&send_addr, send_len);
        }
        /*如果buf中沒有'\n',則用buf的真正長度strlen(buf)*/    
        else
        {
            n = sendto(sockfd,send_buf,strlen(send_buf),, (struct sockaddr*)&send_addr, send_len);
        }


        if(n == -)
        {
            printf("send failed\n");            
            break;
        }
        else
        {
            printf("send successful\n");
        }

        /******接收消息*******/
        bzero(recv_buf,BUFLEN);
        n = recvfrom(sockfd,recv_buf,BUFLEN,, (struct sockaddr*)&recv_addr, &recv_len);

        if(n > )
        {
            printf("receive massage:%s\n",recv_buf);
        }
        else
        {
            if(n <  )
                printf("receive failed\n");
            else
                printf("server stop\n");
            break;    
        }
    }
    /*關閉連接配接*/
    close(sockfd);

    return ;
}
           

但是,在這個程式中,是以recvfrom函數可以接收來自任何位址的資料,如果該某個資料包本不是發送給該位址的,但是由于傳輸的過程中目的IP的端口,被錯誤的改成該位址,那麼這些資料就不應該被接收,是以需要進行判斷接收包的源位址是否是發送資料的目的位址。

//判斷接受位址是否為發送位址
if(send_len != recv_len || memcmp(&send_addr, &recv_addr, send_len) != )
{
    printf("recive from %s (ignored)\n", inet_ntoa(recv_addr.sin_addr));
    continue;
}
           

但是采用上述方法也是有問題的,如果客戶機是多宿,發送時核心選擇IP位址就可能是随機的。

UDP的connect函數

UDP的套接字分為已連接配接的套接字和未連接配接的套接字,預設的是未連接配接的套接字,上面的例程采用的是未連接配接的套接字。

UDP的connect函數形式與TCP的相同,但是作用的實作也是不同的。TCP的connect會完成三次握手,而UDP的connect不會,UDP的connect隻是告訴核心儲存了對端的IP和端口号,核心以後就将該套接字的資料發給這個對端位址,從這個對端位址收到的資料也會發送給客戶程式。

在伺服器未啟動的情況下,啟動用戶端并發送消息。對于未連接配接的套接字而言,用戶端不能收到伺服器主機傳回的ICMP差錯封包。而已連接配接的套接字可以收到。

UDP已連接配接的套接字隻能實作一對一的傳輸,如果要從多個地方接受資料和發送資料,則隻能使用未連接配接的套接字。是以,UDP用戶端多用已連接配接的套接字,服務端用未連接配接的套接字。

UDP缺乏流量控制

UDP沒有TCP那樣的視窗通知過程,是以,如果UDP的伺服器收到的資料過快,主機來不及處理,就可能導緻套接字緩沖區被占滿。套接字的緩沖區被填滿後,新到達的資料報就會被丢棄,導緻大量丢包。

繼續閱讀