网络编程(学习)2024.9.2

目录

服务器模型

1.循环服务器

2.并发服务器

(1)多进程模型

多进程特点

多进程案例

(2)多线程模型

多线程特点

多线程案例

网络超时检测

超时检测的必要性

网络超时检测方法

1.自带超时参数的函数

(1)select设置超时

(2)poll设置超时

2.利用setsockopt属性设置

设置sockfd接收超时

设置端口重用

服务器模型

1.循环服务器

一次只有一个客户端可以连接,但是客户端退出以后,下一个客户端可以继续连接

2.并发服务器

同时一时刻可以连接多个客户端,常见:IO多路复用(select、poll、epoll)、多进程、多线程

(1)多进程模型

多进程特点

1.fork之前的代码被复制,但是不会重新执行一遍;fork之后的代码被复制,并且再被执行一遍。
2.fork之后两个进程相互独立,子进程拷贝了父进程的所有代码,但内存空间独立
3.fork之前打开文件,fork之后拿到的是同一个文件描述符,操作的是同一个文件指针

多进程案例
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/select.h>
#include <signal.h>
#include <wait.h>
#define N 64
char buf[N];
pid_t pid;

void father(int sig)
{
    waitpid(-1, NULL, WNOHANG); // 回收子进程资源
}

int main(int argc, char const *argv[])
{
    if (argc != 2)
    {
        printf("用法:<port>\n");
        return -1;
    }
    // 1.创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    printf("sorkfd:%d\n", sockfd);
    // 2.bind绑定IP和Port端口号
    struct sockaddr_in saddr, caddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[1]));
    saddr.sin_addr.s_addr = INADDR_ANY;
    socklen_t addrlen = sizeof(saddr);
    if (bind(sockfd, (struct sockaddr *)&saddr, addrlen) < 0)
    {
        perror("bind失败");
        close(sockfd);
        return -1;
    }
    printf("bind成功\n");

    // 3.监听listen将主动套接字变为被动套接字
    if (listen(sockfd, 7) < 0)
    {
        perror("lisren失败");
        close(sockfd);
        return -1;
    }
    printf("listen成功\n");

    while (1)
    {
        int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &addrlen);
        if (acceptfd < 0)
        {
            perror("accept失败");
            return -1;
        }
        printf("acceptfd:%d\n", acceptfd);
        printf("客户端ip:%s\t 端口号:%d 已连接\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));
        pid = fork();
        if (pid == 0)
        {
            // close(sockfd);
            while (1)
            {
                memset(buf, 0, N);
                int ret = recv(acceptfd, buf, N, 0);
                if (ret < 0)
                {
                    perror("recv失败\n");
                    break;
                }
                else if (ret == 0)
                {
                    printf("客户端ip:%s退出\n", inet_ntoa(caddr.sin_addr));
                    break;
                }
                else
                {
                    printf("%s客户端: %s\n", inet_ntoa(caddr.sin_addr), buf);
                }
            }
            close(acceptfd);
            exit(0);
        }
        else
        {
            close(acceptfd);
            signal(SIGCHLD, father);
        }
    }
    return 0;
}

(2)多线程模型

多线程特点

每来一个连接创建一个线程,IO多路复用以外,应用最广泛的并发服务器模型

多线程案例
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>

#define N 64

void *mythread(void *arg)
{
    pthread_detach(pthread_self());
    int fd = *((int *)arg);
    free(arg);
    char buf[N];
    while (1)
    {
        memset(buf, 0, N);
        int ret = recv(fd, buf, N, 0);
        if (ret < 0)
        {
            perror("recv失败");
            break;
        }
        else if (ret == 0)
        {
            printf("客户端acceptfd:%d 退出\n", fd);
            close(fd);
            break;
        }
        else
        {
            printf("客户端: %s\n", buf);
        }
    }

    pthread_exit(NULL);
}

int main(int argc, char const *argv[])
{
    if (argc != 2)
    {
        printf("用法:%s <port>\n", argv[0]);
        return -1;
    }

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket失败");
        return -1;
    }
    printf("sockfd:%d\n", sockfd);

    struct sockaddr_in saddr, caddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[1]));
    saddr.sin_addr.s_addr = INADDR_ANY;
    socklen_t addrlen = sizeof(saddr);
    if (bind(sockfd, (struct sockaddr *)&saddr, addrlen) < 0)
    {
        perror("bind失败");
        close(sockfd);
        return -1;
    }
    printf("bind成功\n");

    if (listen(sockfd, 7) < 0)
    {
        perror("listen失败");
        close(sockfd);
        return -1;
    }
    printf("listen成功\n");

    while (1)
    {
        int *acceptfd = malloc(sizeof(int)); // 为每个客户端连接分配内存
        if (acceptfd == NULL)
        {
            perror("内存分配失败");
            close(sockfd);
            return -1;
        }

        *acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &addrlen);
        if (*acceptfd < 0)
        {
            perror("accept失败");
            free(acceptfd);
            continue;
        }
        printf("acceptfd: %d\n", *acceptfd);
        printf("客户端ip:%s 端口号:%d 连接\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));

        pthread_t tid;
        if (pthread_create(&tid, NULL, mythread, acceptfd) != 0)
        {
            perror("创建线程失败");
            close(*acceptfd);
            free(acceptfd);
            continue;
        }
    }

    close(sockfd);
    return 0;
}

网络超时检测

在网络通信中,很多操作会使得进程阻塞:
TCP套接字中的recv/accept
UDP套接字中的recvfrom

超时检测的必要性

○避免进程在没有数据时无限制地阻塞
○实现某些特定协议要求,比如某些设备规定,发送请求数据后,如果多长时间后没有收到来自设备的回复,需要做出一些特殊处理

网络超时检测方法

1.自带超时参数的函数

例如使用select/poll/epoll函数最后一个参数可以设置超时。

(1)select设置超时

struct timeval tm = {2, 0};//设置2s打算阻塞
sret = select(maxfd + 1, &tempfds, NULL, NULL, &tm);
第五个参数:
struct timeval

{
        long    tv_sec;         /*秒*/
        long    tv_usec;        /*微秒*/
};
 注:select的超时检测不会更新,所以每次重新超时等待之前要更新等待时间

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

#define N 64
char buf[N];
#define ERR_MSG(msg)                           \
    do                                         \
    {                                          \
        fprintf(stderr, "line:%d ", __LINE__); \
        perror(msg);                           \
    } while (0)

int main(int argc, char const *argv[])
{
    if (argc != 2)
    {
        printf("用法:<port>\n");
        return -1;
    }
    // 1.创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    printf("sorkfd:%d\n", sockfd);
    // 2.bind绑定IP和Port端口号
    struct sockaddr_in saddr, caddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[1]));
    // saddr.sin_addr.s_addr = inet_addr("192.168.50.213");
    socklen_t addrlen = sizeof(saddr);
#if 0
    saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
#else
    saddr.sin_addr.s_addr = INADDR_ANY;
#endif

    if (bind(sockfd, (struct sockaddr *)&saddr, addrlen) < 0)
    {
        ERR_MSG("bind失败");
        close(sockfd);
        return -1;
    }
    printf("bind成功\n");

    // 3.监听listen将主动套接字变为被动套接字
    if (listen(sockfd, 7) < 0)
    {
        ERR_MSG("lisren失败");
        close(sockfd);
        return -1;
    }
    printf("listen成功\n");

    // 第一步:建表初始化
    fd_set readfds, tempfds;
    FD_ZERO(&readfds);
    FD_SET(sockfd, &readfds);
    FD_SET(0, &readfds);
    int max = sockfd;

    while (1)
    {
        struct timeval tm = {2, 0};
        memset(buf, 0, N);
        tempfds = readfds;
        int ret = select(max + 1, &tempfds, NULL, NULL, &tm);
        if (ret == 0)
        {
            printf("time out\n");
        }
        if (FD_ISSET(sockfd, &tempfds))
        {
            // 4.accept阻塞等待链接
            int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &addrlen);
            if (acceptfd < 0)
            {
                ERR_MSG("accept失败\n");
                return -1;
            }
            printf("acceptfd:%d\n", acceptfd);
            printf("客户端ip:%s\t 端口号:%d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));
            FD_SET(acceptfd, &readfds);
            if (max < acceptfd)
            {
                max = acceptfd;
            }
        }
        // 5.发送
        else if (FD_ISSET(0, &tempfds))
        {
            scanf("%s", buf);
            for (int i = 4; i <= max; i++)
            {
                if (FD_ISSET(i, &readfds))
                {
                    send(i, buf, N, 0);
                }
            }
        }
        // 6.接收
        for (int n = 4; n <= max; n++)
        {
            if (FD_ISSET(n, &tempfds))
            {
                int ret = recv(n, buf, N, 0);
                if (ret < 0)
                {
                    perror("recv失败");
                    close(sockfd);
                    return -1;
                }
                else if (ret > 0)
                {
                    printf("客户端acceptfd%d:%s\n", n, buf);
                }
                else
                {
                    printf("客户端acceptfd:%d退出\n", n);
                    FD_CLR(n, &readfds);
                    close(n);
                    while (!FD_ISSET(max, &readfds))
                    {
                        max--;
                    }
                }
            }
        }
    }
    close(sockfd);
    return 0;
}

(2)poll设置超时

直接在poll(fds, last + 1, 2000);设置,延迟2s;

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

#define N 64
char buf[N];
#define ERR_MSG(msg)                           \
    do                                         \
    {                                          \
        fprintf(stderr, "line:%d ", __LINE__); \
        perror(msg);                           \
    } while (0)

int main(int argc, char const *argv[])
{
    if (argc != 2)
    {
        printf("用法:<port>\n");
        return -1;
    }
    // 1.创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    printf("sorkfd:%d\n", sockfd);
    // 2.bind绑定IP和Port端口号
    struct sockaddr_in saddr, caddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[1]));
    saddr.sin_addr.s_addr = inet_addr("192.168.50.213");
    socklen_t addrlen = sizeof(saddr);
    // #if 0
    //     saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    // #else
    //     saddr.sin_addr.s_addr = INADDR_ANY;
    // #endif

    if (bind(sockfd, (struct sockaddr *)&saddr, addrlen) < 0)
    {
        ERR_MSG("bind失败");
        close(sockfd);
        return -1;
    }
    printf("bind成功\n");

    // 3.监听listen将主动套接字变为被动套接字
    if (listen(sockfd, 7) < 0)
    {
        ERR_MSG("lisren失败");
        close(sockfd);
        return -1;
    }
    printf("listen成功\n");

    // 第一步:建表初始化
    struct pollfd fds[100];
    int last = -1;

    // 第二步:填表
    fds[++last].fd = 0;
    fds[last].events = POLLIN;
    fds[last].revents = 0;
    fds[++last].fd = sockfd;
    fds[last].events = POLLIN;
    fds[last].revents = 0;

    while (1)
    {
        memset(buf, 0, N);
        int po = poll(fds, last + 1, 2000);
        if (po == -1)
        {
            perror("select失败");
            close(sockfd);
            return -1;
        }
        for (int i = 0; i <= last; i++)
        {
            if (fds[i].revents == POLLIN)
            {
                if (fds[i].fd == sockfd)
                {
                    // 4.accept阻塞等待链接
                    int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &addrlen);
                    if (acceptfd < 0)
                    {
                        ERR_MSG("accept失败\n");
                        return -1;
                    }
                    printf("acceptfd:%d\n", acceptfd);
                    printf("客户端ip:%s\t 端口号:%d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));
                    fds[++last].fd = acceptfd;
                    fds[last].events = POLLIN;
                    fds[last].revents = 0;
                }
                else if (fds[i].fd == 0)
                {
                    scanf("%s", buf);
                    for (int j = 2; j <= last; j++)
                    {
                        send(fds[j].fd, buf, N, 0);
                    }
                }
                else
                {
                    int ret = recv(fds[i].fd, buf, N, 0);
                    if (ret < 0)
                    {
                        perror("recv失败");
                        close(sockfd);
                        return -1;
                    }
                    else if (ret > 0)
                    {
                        printf("客户端%s:%s\n", inet_ntoa(caddr.sin_addr), buf);
                    }
                    else
                    {
                        printf("客户端acceptfd:%d退出\n", fds[i].fd);
                        close(fds[i].fd);
                        fds[i] = fds[last];
                        last--;
                    }
                }
            }
        }
    }
    close(sockfd);
    return 0;
}

2.利用setsockopt属性设置

Linux中socket属性

API接口
功能:设置/获取网络属性;
#include <sys/types.h>     
#include <sys/socket.h>

int getsockopt(int sockfd, int level, int optname,void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
参数:
        int sockfd:指定要设置/获取哪个套接字的属性;
        int level:指定要控制的协议层次;
                SOL_SOCKET:应用层 通用套接字选项;  man 7 socket
                IPPROTO_TCP:TCP选项               man 7 TCP
                IPPROTO_UDP:UDP选项               man 7 UDP
                IPPROTO_IP:IP选项;                man 7 IP
        int optname:指定要控制的内容,指定控制方式;
                 --- SOL_SOCKET: man 7 socket -----
                SO_REUSEADDR:允许端口快速重用        optval: int*
                SO_BROADCAST:允许广播        optval: int*   
                SO_RCVBUF/SO_SNDBUF:接收缓冲区 发送缓冲区大小
                SO_RCVTIMEO/SO_SNDTIMEO:接收超时时间,发送超时时间

        void *optval:根据optname不同,该类型不同;
        socklen_t optlen/socklen_t *optlen:真实的optval指针指向的内存空间的大小;
返回值:
        成功:返回0
        失败:返回-1

设置sockfd接收超时

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

#define ERR_MSG(msg)                           \
    do                                         \
    {                                          \
        fprintf(stderr, "line:%d ", __LINE__); \
        perror(msg);                           \
    } while (0)

int main(int argc, char const *argv[])
{
    if (argc != 2)
    {
        printf("用法:<port>\n");
        return -1;
    }
    // 1.创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    printf("sorkfd:%d\n", sockfd);
    // 2.bind绑定IP和Port端口号
    struct sockaddr_in saddr, caddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[1]));
    // saddr.sin_addr.s_addr = inet_addr("192.168.50.213");
    socklen_t addrlen = sizeof(saddr);
#if 0
    saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
#else
    saddr.sin_addr.s_addr = INADDR_ANY;
#endif
    if (bind(sockfd, (struct sockaddr *)&saddr, addrlen) < 0)
    {
        perror("bind失败");
        return -1;
    }
    printf("bind成功\n");

    // 3.监听listen将主动套接字变为被动套接字
    if (listen(sockfd, 7) < 0)
    {
        ERR_MSG("lisren失败");
        return -1;
    }
    printf("listen成功\n");
    struct timeval val = {2, 0};
    setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &val, sizeof(val));
    while (1)
    {
        // 4.accept阻塞等待链接
        int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &addrlen);
        if (acceptfd < 0)
        {
            if (errno == 11)
            {
               char a[10];
               printf("accept超时,是否继续,继续请输入yes,退出请输入no\n");
               scanf(" %s", a);
               if (strcmp(a, "yes") == 0)
               {
                   continue;
               }
               if (strcmp(a, "no") == 0)
               {
                   break;
               }
               else
               {
                   printf("输入错误,请重新输入\n");
                   continue;
               }
            }
            perror("accept失败");
            return -1;
        }
        printf("acceptfd:%d\n", acceptfd);
        printf("客户端ip:%s\t 端口号:%d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));

        // 5.发送
#define N 64
        char buf[N];
        while (1)
        {
            memset(buf, 0, N);
            int ret = recv(acceptfd, buf, N, 0); // MSG_DONTWAIT
            if (ret < 0)
            {
                if (errno == 11)
                {
                    printf("读缓存区内没数据\n");
                    continue;
                }
                else
                {
                    perror("recv失败\n");
                    return -1;
                }
            }
            else if (ret == 0)
            {
                printf("客户端ip:%s退出\n", inet_ntoa(caddr.sin_addr));
                close(acceptfd);
                break;
            }
            else
            {
                printf("客户端%s\n", buf);
            }
        }
    }

    close(sockfd);
    return 0;
}

设置端口重用

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

#define ERR_MSG(msg)                           \
    do                                         \
    {                                          \
        fprintf(stderr, "line:%d ", __LINE__); \
        perror(msg);                           \
    } while (0)

int main(int argc, char const *argv[])
{
    if (argc != 2)
    {
        printf("用法:<port>\n");
        return -1;
    }
    // 1.创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    printf("sorkfd:%d\n", sockfd);
    // 2.bind绑定IP和Port端口号
    struct sockaddr_in saddr, caddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[1]));
    // saddr.sin_addr.s_addr = inet_addr("192.168.50.213");
    socklen_t addrlen = sizeof(saddr);
#if 0
    saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
#else
    saddr.sin_addr.s_addr = INADDR_ANY;
#endif
    int flag;
    socklen_t len = sizeof(flag);
    getsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &flag, &len);
    flag = 1;
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &flag, len);
    printf("flag:%d\n", flag);
    if (bind(sockfd, (struct sockaddr *)&saddr, addrlen) < 0)
    {
        perror("bind失败");
        return -1;
    }
    printf("bind成功\n");

    // 3.监听listen将主动套接字变为被动套接字
    if (listen(sockfd, 7) < 0)
    {
        ERR_MSG("lisren失败");
        return -1;
    }
    printf("listen成功\n");
    while (1)
    {
        // 4.accept阻塞等待链接
        int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &addrlen);
        if (acceptfd < 0)
        {
            perror("accept失败");
            return -1;
        }
        printf("acceptfd:%d\n", acceptfd);
        printf("客户端ip:%s\t 端口号:%d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));

        // 5.发送
#define N 64
        char buf[N];
        while (1)
        {
            memset(buf, 0, N);
            int ret = recv(acceptfd, buf, N, 0); // MSG_DONTWAIT
            if (ret < 0)
            {
                if (errno == 11)
                {
                    printf("读缓存区内没数据\n");
                    continue;
                }
                else
                {
                    perror("recv失败\n");
                    return -1;
                }
            }
            else if (ret == 0)
            {
                printf("客户端ip:%s退出\n", inet_ntoa(caddr.sin_addr));
                close(acceptfd);
                break;
            }
            else
            {
                printf("客户端%s\n", buf);
            }
        }
    }

    close(sockfd);
    return 0;
}
  • 15
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值