[Linux Programming] Echo(Based on UDP)

#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buff, size_t nbytes, int flags, struct sockaddr *
from, socklen_t *addrlen);
ssize_t sendto(int sockfd, const void *buff, size_t nbytes, int flags, const struct
sockaddr *to, socklen_t addrlen);
// @flags: set NULL for now.
// client
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>

const char* SVR_IP = "127.0.0.1";
const unsigned short SVR_PORT = 1234;
const unsigned int MAX_LINE = 1024;

void* recv_thread(void*);

int main() {
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0); // SOCK_DGRAM

    struct sockaddr_in svr_addr;
    bzero(&svr_addr, sizeof(svr_addr));
    svr_addr.sin_family = AF_INET;
    svr_addr.sin_addr.s_addr = inet_addr(SVR_IP);
    svr_addr.sin_port = htons(SVR_PORT);

    pthread_t tid;
    pthread_create(&tid, NULL, recv_thread, &sockfd);

    char msg[MAX_LINE];
    while(fgets(msg, MAX_LINE, stdin)) {
        sendto(sockfd, msg, strlen(msg), 0, (struct sockaddr*)&svr_addr, (socklen_t)sizeof(svr_addr));
    }

    return 0;
}

void *recv_thread(void* fd_addr) {
    int sockfd = *((int*)fd_addr);

    struct sockaddr_in c_addr;
    bzero(&c_addr, sizeof(sockaddr_in));
    socklen_t c_addr_len = sizeof(sockaddr_in);

    char msg[MAX_LINE];
    while(1) {
        int n = recvfrom(sockfd, msg, MAX_LINE, 0, (struct sockaddr*)&c_addr, &c_addr_len);
        if(n > 0) {
            write(2, msg, n);
        }
    }

    return NULL;
}
// server
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>

const unsigned short SVR_PORT = 1234;
const unsigned int MAX_LINE = 1024;

int main()
{
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);

    struct sockaddr_in svr_addr;
    bzero(&svr_addr, sizeof(svr_addr));
    svr_addr.sin_family = AF_INET;
    svr_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    svr_addr.sin_port = htons(SVR_PORT);

    bind(sockfd, (struct sockaddr*)&svr_addr, (socklen_t)sizeof(svr_addr));

    char msg[MAX_LINE];
    socklen_t cli_addr_len = sizeof(sockaddr_in);
    struct sockaddr_in c_addr;
    bzero(&c_addr, sizeof(c_addr));
    while(1)
    {
        // recvfrom could overflow, set udp socket recv buffer use SO_RCVBUF
        int n = recvfrom(sockfd, msg, MAX_LINE, 0, (struct sockaddr*)&c_addr, &cli_addr_len);
        if(n > 0) {
            sendto(sockfd, msg, n, 0, (struct sockaddr*)&c_addr, cli_addr_len);
        }
    }

    return 0;
}

当服务端未启动时, 如何处理?

sendto后, 将响应一个port unreachable ICMP消息, 但此消息不会返回到客户端进程 (why?).
此消息能够返回到客户端的前提是, 这个udp套接字是 已连接 的.

udp的连接同样是通过connect, 但不要和tcp的connect混淆, udp的connect只是检测是否存在连接错误, 如上面的unreachable便能够通过connect检测出来.

udp套接字在调用connect后

将发生变化:

  1. 不能在使用sendto, 因为sendto需要指定ip和port, 但这件事以及由connect做了, 新的发送方式和tcp相同.
  2. 同样的不能在使用recvfrom接收数据, 而改用与tcp相同的接收方式.
  3. 异步错误将返还到所在进程.

同时带来限制:

已连接的udp套接字只能和一个ip地址进行数据交换.

抉择:

当明确需求为使用udp给同一个地址发送多次数据, 使用connect后能够提升效率, 因为内核不再需要每次都复制ip和port来连接和释放socket.

// client
...
// 设定为已连接udp socket, 使用send和recv来发送和接收数据
connect(sockfd, (struct sockaddr*)&svr_addr, sizeof(svr_addr));
send(sockfd, msg, strlen(msg), 0);
int n = recv(sockfd, msg, MAX_LINE, 0); // 当第一次发送数据时会触发connect, 而不是如同tcp在connect触发三次握手
// 所以当服务端未启动时recv将返回-1, errno=111 -> Connection refused
...

发表评论

电子邮件地址不会被公开。必填项已用 * 标注