Socket编程(Linux)

Last Updated: 2023-04-25 09:51:26 Tuesday

-- TOC --

本文尝试总结在Linux下,用C语言进行socket编程的相关知识点。

地址结构体

struct sockaddr {
    unsigned short sa_family;  // 地址族,2字节
    char sa_data[14];          // 存放地址和端口
}

struct in_addr {
    in_addr_t s_addr;          //in_addr_t为32位的unsigned int
};

struct sockaddr_in {
    short int sin_family;           // 地址族
    unsigned short int sin_port;    // 端口号
    struct in_addr sin_addr;        // 地址
    // 8字节数组,全为0,
    // 该字节数组的作用是为了让两种数据结构大小相同
    unsigned char sin_zero[8];
}

struct sockaddrstruct sockaddr_in这两个结构体的size一样。

UDP示例

下面是UDP Server,将收到的消息打印出来:

#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>
#include <unistd.h>
#include <errno.h>


#define _PEXIT \
    do {\
        char ebuf[64] = {0};\
        sprintf(ebuf, "%s: %d", __FILE__, __LINE__);\
        perror(ebuf);\
        exit(errno);\
    }while(0)


int main(void) {
    int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (sock == -1) _pee();

    struct sockaddr_in myaddr;
    memset(&myaddr, 0, sizeof(myaddr));
    myaddr.sin_family = AF_INET;
    myaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    myaddr.sin_port = htons(56789);
    if (bind(sock, (struct sockaddr*)&myaddr, sizeof(myaddr)) == -1)
        _PEXIT;

    struct sockaddr_in paddr;
    socklen_t paddr_len = sizeof(paddr);
    char rbuf[1024];

    int v;
    while (1) {
        memset(rbuf, 0, 1024);
        v = recvfrom(sock, rbuf, 1024, 0,(struct sockaddr*)&paddr, &paddr_len);
        if (v == -1)
            _PEXIT;
        else if (v > 0)
            printf("[recv from %s:%d] %s\n",
                        inet_ntoa(*(struct in_addr*)&paddr.sin_addr.s_addr),
                        ntohs(paddr.sin_port),
                        rbuf);
    }

    close(sock);
    return 0;
}

下面是UDP Client,发送数据后退出:

#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>
#include <unistd.h>
#include <errno.h>


#define _PEXIT \
    do {\
        char ebuf[64] = {0};\
        sprintf(ebuf, "%s: %d", __FILE__, __LINE__);\
        perror(ebuf);\
        exit(errno);\
    }while(0)


int main(void) {
    int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (sock == -1)
        _PEXIT;

    struct sockaddr_in paddr;
    memset(&paddr, 0, sizeof(paddr));
    paddr.sin_family = AF_INET;
    paddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    paddr.sin_port = htons(56789);

    socklen_t paddr_len = sizeof(paddr);
    char sbuf[] = "Hello Socket!";
    if (sendto(sock,
                sbuf,
                sizeof(sbuf),
                0,
                (struct sockaddr*)&paddr,
                paddr_len) == -1)
        _PEXIT;

    close(sock);
    return 0;
}

TCP Client示例

#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>
#include <unistd.h>
#include <errno.h>
#include <sys/select.h>
#include <fcntl.h>


#define _PEXIT \
    do {\
        char ebuf[64] = {0};\
        sprintf(ebuf, "%s: %d", __FILE__, __LINE__);\
        perror(ebuf);\
        exit(errno);\
    }while(0)


int main(void) {
    int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (sock < 0) {
        printf("Create socket failed.\n");
        return 1;
    }

    struct sockaddr_in dest;
    memset(&dest, 0, sizeof(dest));
    dest.sin_family = AF_INET;
    dest.sin_addr.s_addr = inet_addr("127.0.0.1");
    dest.sin_port = htons(12345);

    /*if (connect_with_timeout(sock, &dest, 2) == 1) {
        printf("Connect timeout.\n");
        return 1;
    }*/

    struct timeval timeo = {2,0};
    if(setsockopt(sock,SOL_SOCKET,SO_SNDTIMEO,&timeo,sizeof(timeo)))
        _PEXIT;

    if (connect(sock,(struct sockaddr*)&dest,sizeof(dest)) < 0) {
        if (errno != EINPROGRESS)
            _PEXIT;
        else {
            printf("Connect timeout.\n");
            return 1;
        }
    }

    sleep(0.5);
    char rbuf[64] = {0};
    recv(sock, rbuf, 64, 0);
    printf("recv: %s\n", rbuf);

    char msg[] = "hello socket in c!";
    printf("send: %s\n", msg);
    send(sock, msg, strlen(msg), 0);

    sleep(0.5);
    memset(rbuf, 0, 64);
    recv(sock, rbuf, 64, 0);
    printf("recv: %s\n", rbuf);

    close(sock);
    return 0;
}

默认的connect超时时间太长了。connect接口的timeout,可以用setsockopt来设置。

send,sendto,sendmsg,connect,适用SO_SNDTIMEO选项;

recv,recvfrom,recvmsg,accept,适用SO_RCVTIMEO选项。

这段代码,没有考虑TCP socket发送接收数据的所谓粘包问题,即send和recv只是返回成功的byte数,与接口的输入长度不一定一致。

用select实现connect的timeout

/*
* connect a socket destination with timeout,
* the intput socket is in block mode, and also in block mode after return.
*/
int connect_with_timeout(int sock,
                         struct sockaddr_in *dest,
                         int timeout_in_second) {
    int v;

    /* save flags and set O_NONBLOCK */
    int flags = fcntl(sock, F_GETFL, 0);
    fcntl(sock, F_SETFL, flags | O_NONBLOCK);

    /* initiate connect */
    if ((v=connect(sock,(struct sockaddr*)dest,sizeof(*dest))) == 0) {
        fcntl(sock, F_SETFL, flags);
        return 0;
    }
    else if ((v<0) && (errno!=EINPROGRESS)) _pee();

    /* wait by select */
    struct timeval timeout = {timeout_in_second, 0};
    fd_set read_set;
    FD_ZERO(&read_set);
    FD_SET(sock, &read_set);
    if ((v=select(sock+1, &read_set, NULL, NULL, &timeout)) > 0) {
        fcntl(sock, F_SETFL, flags);
        return 0;
    }
    else if (v == 0) {
        fcntl(sock, F_SETFL, flags);
        return 1; /* timeout */
    }
    else _pee();

    return 0; /* never reach here */
}

貌似在Windows下,只能用这个方法实现connect的timeout。

TCP Server示例

$ cat ctcp_server.c
#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>
#include <unistd.h>
#include <errno.h>


#define _PEXIT \
    do {\
        char ebuf[64] = {0};\
        sprintf(ebuf, "%s: %d", __FILE__, __LINE__);\
        perror(ebuf);\
        exit(errno);\
    }while(0)


int main(void) {
    struct sockaddr_in myaddr;
    socklen_t socklen = sizeof(myaddr);
    memset(&myaddr, 0, sizeof(myaddr));
    myaddr.sin_family = AF_INET;
    myaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    myaddr.sin_port = htons(12345);

    /* create a socket */
    int sock;
    if ((sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP)) < 0)
        _PEXIT;

    /* bind */
    if (bind(sock,(struct sockaddr*)&myaddr,socklen) < 0)
        _PEXIT;

    /* listen */
    if (listen(sock, 5) < 0)
        _PEXIT;

    /* accept */
    int s1;
    struct sockaddr_in paddr;
    s1 = accept(sock, (struct sockaddr*)&paddr, &socklen);
    if (s1 < 0)
        _PEXIT;
    printf("[accept from %s:%d]\n",
              inet_ntoa(*(struct in_addr*)&paddr.sin_addr.s_addr),
              ntohs(paddr.sin_port));

    /* send welcome */
    sleep(1);
    char msg[] = "welcome socket in c!";
    printf("server send: %s\n", msg);
    send(s1, msg, strlen(msg), 0);

    /* recv */
    char rbuf[64] = {0};
    recv(s1, rbuf, 64, 0);
    printf("server recv: %s\n", rbuf);

    sleep(1);
    close(s1);
    close(sock);
    return 0;
}

设置SO_REUSEADDR

int optval = 1;
int sockfd = socket(AF_INET, SOCK_STREAM, 0);

// Set the SO_REUSEADDR option on the socket
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));

setsockopt接口可能会返回错误,以上代码省略了。

设置SO_REUSEPORT

int optval = 1;
int sockfd = socket(AF_INET, SOCK_STREAM, 0);

// Set the SO_REUSEPORT option on the socket
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval)) == -1) {
    perror("setsockopt");
    close(sockfd);
    exit(EXIT_FAILURE);
}

本文链接:https://cs.pynote.net/net/tcp/202204151/

-- EOF --

-- MORE --