1. 程式人生 > >send()、sendto()和recv()、recvfrom()的使用

send()、sendto()和recv()、recvfrom()的使用

udp通訊中的sendto()需要在引數裡指定接收方的地址/埠,recvfrom()則在引數中存放接收發送方的地址/埠,與之對應的send()和recv()則不需要如此,但是在呼叫send()之前,需要為套接字指定接收方的地址/埠(這樣該函式才知道要把資料發往哪裡),在呼叫recv()之前,可以為套接字指定傳送方的地址/埠,這樣該函式就只接收指定的傳送方的資料,當然若不指定也可,該函式就可以接收任意的地址的資料。(這些內容前面文章udp通訊中的connect()和bind()函式
有詳細講過)

這4個函式的使用比較簡單,但在一個例項中,遇到一個小問題。
實現功能: udp伺服器建立一個套接字接收客戶端的連線,連線成功後,伺服器再建立一個套接字與客戶端進行資料互動,要求儘量使用connect()和recv()、send()函式。

udp伺服器程式碼:

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

#define BUFSZ   1024
#define PORT    6567

int main(void)
{
    int srv_sd, cli_sd;
    int new_sd;
    int
ret; struct sockaddr_in svr_addr, cli_addr; socklen_t addrlen = sizeof(struct sockaddr_in); char buf[BUFSZ] = {}; //建立套接字 if ((srv_sd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { perror("socket"); exit(EXIT_FAILURE); } //為伺服器套接字繫結埠 svr_addr.sin_family = AF_INET; svr_addr.sin_port = htons(PORT); svr_addr.sin_addr.s_addr = 0
; if ((ret = bind(srv_sd, (struct sockaddr* )&svr_addr, addrlen)) < 0) { perror("bind"); exit(EXIT_FAILURE); } //接收客戶端的連線 ret = recvfrom(srv_sd, buf, BUFSZ, 0, (struct sockaddr* )&cli_addr, &addrlen); if (ret < 0) { perror("recvfrom"); exit(EXIT_FAILURE); } printf("server output msg:\n"); printf("client IPAddr = %s, Port = %d, buf = %s\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port), buf); close(srv_sd); //建立與客戶端資料互動的套接字 cli_sd = socket(AF_INET, SOCK_DGRAM, 0); if (cli_sd < 0) { perror("socket"); exit(EXIT_FAILURE); } //為新套接字繫結地址資訊 svr_addr.sin_family = AF_INET; svr_addr.sin_port = htons(6666); svr_addr.sin_addr.s_addr = 0; if ((ret = bind(cli_sd, (struct sockaddr* )&svr_addr, addrlen)) < 0) { perror("bind"); exit(EXIT_FAILURE); } //為新套接字指定目的地址,接下來的資料互動將可以採用recv()和send() if ((ret = connect(cli_sd, (struct sockaddr* )&cli_addr, addrlen)) < 0) { perror("connect"); exit(EXIT_FAILURE); } //伺服器先發資料再收資料 while (1) { memset(buf, 0, BUFSZ); printf("ple input: "); fgets(buf, BUFSZ, stdin); //sendto(sd, buf, BUFSZ, 0, (struct sockaddr* )&svr_addr, addrlen); send(cli_sd, buf, BUFSZ, 0); ret = recv(cli_sd, buf, BUFSZ, 0); printf("server output msg:\n"); printf("client IPAddr = %s, Port = %d, buf = %s\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port), buf); } close(cli_sd); return 0; }

udp客戶端程式碼:

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

#define BUFSZ   1024
#define PORT    6567

int main(int argc, char *argv[])
{
    int sd;
    struct sockaddr_in svr_addr, cli_addr;
    int ret;
    socklen_t addrlen = sizeof(struct sockaddr_in);
    char buf[BUFSZ] = {};

    //建立套接字
    if ((sd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    //為套接字繫結本地地址資訊
    cli_addr.sin_family = AF_INET;
    cli_addr.sin_port = htons(9693);
    cli_addr.sin_addr.s_addr = 0;
    if ((ret = bind(sd, (struct sockaddr* )&cli_addr, addrlen)) < 0)
    {
        perror("bind");
        exit(EXIT_FAILURE);
    }

    //為套接字指定目的地址資訊,接下來的與伺服器的資料互動就可以使用
    svr_addr.sin_family = AF_INET;
    svr_addr.sin_port = htons(PORT);
    svr_addr.sin_addr.s_addr = inet_addr("192.168.1.166");
    if ((ret = connect(sd, (struct sockaddr* )&svr_addr, addrlen)) < 0)
    {
        perror("connect");
        exit(EXIT_FAILURE);
    }

    //資料互動
    while (1)
    {           
        memset(buf, 0, BUFSZ);
        printf("ple input: ");
        fgets(buf, BUFSZ, stdin);
        //sendto(sd, buf, BUFSZ, 0, (struct sockaddr* )&svr_addr, addrlen);
        send(sd, buf, BUFSZ, 0);

        memset(buf, 0, BUFSZ);
        //ret = recvfrom(sd, buf, BUFSZ, 0, (struct sockaddr* )&svr_addr, &addrlen);
        ret = recv(sd, buf, BUFSZ, 0);
        printf("client output msg:\n");
        printf("server IPAddr = %s, Port = %d, buf = %s\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port), buf);
    }
    close(sd);
    return 0;
}

執行結果:
這裡寫圖片描述

這裡寫圖片描述

這裡寫圖片描述

觀察程式流程,可以得出:
這裡寫圖片描述

客戶端發起連線給伺服器,伺服器接收到後建立新的套接字並呼叫connect()函式為該套接字指定目標地址資訊,這個目標地址資訊雖然確實是客戶端的,但是客戶端的目標地址卻是伺服器,那麼伺服器新的套接字的目標地址不是客戶端而是伺服器,所以伺服器發出的資料還是自己收到。

程式的問題出現在客戶端,客戶端建立了套接字後,就立即為其制定目標(伺服器)的地址資訊,而這個目標地址資訊並非作為接下來資料互動的地址,所以應該把為客戶端指定目標地址操作放在伺服器建立新的sd之後返回資料到客戶端之後,但是注意,客服端建立完套接字後不能馬上為其connect()以指定目的地址資訊,那麼就發資料給伺服器時就要使用sendto()、接收資料則是用recvfrom(),流程圖改為:
這裡寫圖片描述
客戶端程式碼實現為:

int main(int argc, char *argv[])
{
    int sd;
    struct sockaddr_in svr_addr, cli_addr;
    int ret;
    socklen_t addrlen = sizeof(struct sockaddr_in);
    char buf[BUFSZ] = {};

    //建立套接字
    if ((sd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    cli_addr.sin_family = AF_INET;
    cli_addr.sin_port = htons(9693);
    cli_addr.sin_addr.s_addr = 0;
    if ((ret = bind(sd, (struct sockaddr* )&cli_addr, addrlen)) < 0)
    {
        perror("bind");
        exit(EXIT_FAILURE);
    }

    //為sendto()函式的使用指定目標地址
    svr_addr.sin_family = AF_INET;
    svr_addr.sin_port = htons(PORT);
    svr_addr.sin_addr.s_addr = inet_addr("192.168.1.166");

    //傳送資料
    memset(buf, 0, BUFSZ);
    printf("ple input: ");
    fgets(buf, BUFSZ, stdin);
    sendto(sd, buf, BUFSZ, 0, (struct sockaddr* )&svr_addr, addrlen);

    //接收資料,此時svr_addr的地址資訊是服務端新建的專為資料互動使用的sd
    memset(buf, 0, BUFSZ);
    ret = recvfrom(sd, buf, BUFSZ, 0, (struct sockaddr* )&svr_addr, &addrlen);
    printf("client output msg:\n");
    printf("server IPAddr = %s, Port = %d, buf = %s\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port), buf);

    //為套接字繫結目標地址,目標地址是服務端專為資料互動使用的sd
    if ((ret = connect(sd, (struct sockaddr* )&svr_addr, addrlen)) < 0)
    {
        perror("connect");
        exit(EXIT_FAILURE);
    }

    //資料互動
    while (1)
    {           
        memset(buf, 0, BUFSZ);
        printf("ple input: ");
        fgets(buf, BUFSZ, stdin);
        //sendto(sd, buf, BUFSZ, 0, (struct sockaddr* )&svr_addr, addrlen);
        send(sd, buf, BUFSZ, 0);

        memset(buf, 0, BUFSZ);
        //ret = recvfrom(sd, buf, BUFSZ, 0, (struct sockaddr* )&svr_addr, &addrlen);
        ret = recv(sd, buf, BUFSZ, 0);
        printf("client output msg:\n");
        printf("server IPAddr = %s, Port = %d, buf = %s\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port), buf);
    }
    close(sd);
    return 0;
}

執行結果:
這裡寫圖片描述

結論: connect()和send()、recv()三個函式的搭配使用並不能說一定能代替sendto()、recvfrom(),具體使用還要依據程式碼場景。