Socket编程之TCP并发服务器

目录

一、服务器模型

二、TCP并发服务器

2.1使用多线程实现TCP并发服务器

2.2使用多进程实现TCP并发服务器

2.3使用多路IO复用实现TCP并发服务器

三、select、setsockopt/getsockopt函数说明

3.1 selsct函数说明:

3.2关于selset函数的几个问题

3.3setsockopt/getsockopt函数说明


一、服务器模型

        服务器模型分为两种:循环服务器并发服务器

        循环服务器:在同一个时刻只能响应一个客户端的请求。

        并发服务器:在同一时刻可以响应多个客户端的请求。

TCP服务器默认就是一个循环服务器,受两类阻塞函数acceptrecv之间的影响。

UDP服务器默认就是一个并发服务器,因为只有一个会造成阻塞的函数(recvfrom)。

二、TCP并发服务器

        在有些场景下我们即需要保证数据可靠,又需要支持并发,这样就需要用到TCP并发服务器了。

2.1使用多线程实现TCP并发服务器

思路:主线程负责accept等待客户端连接,一旦有客户端连接就创建一个子线程专门用于和当前的客户端通信。

服务器代码:

#include <arpa/inet.h>
#include <errno.h>
#include <error.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#define PRINT_ERR(msg)                                      \
    do {                                                    \
        printf("%s %s:%d\n", __FILE__, __func__, __LINE__); \
        perror(msg);                                        \
        return -1;                                          \
    } while (0)
typedef struct MSG {
    char txt[128];
    int acceptfd;
    struct sockaddr_in clientaddr;
} msg_t;

// 线程体
void* deal_read_write(void* arg)
{
    pthread_detach(pthread_self()); // 将线程设置成被动分离状态
    msg_t buff = *(msg_t*)arg;
    // 定义结构体保存客户端信息
    int nbytes = 0; // 记录recv函数的返回值
    int acceptfd;
    struct sockaddr_in clientaddr;
    memset(&clientaddr, 0, sizeof(clientaddr));
    socklen_t clientaddr_len = sizeof(clientaddr);
    acceptfd = buff.acceptfd;
    clientaddr = buff.clientaddr;
    while (1) {
        // 收发数据
        memset(&buff, 0, sizeof(buff));
        if (-1 == (nbytes = recv(acceptfd, &buff, sizeof(buff), 0))) {
            perror("recv error");
            printf("%s %s:%d\n", __FILE__, __func__, __LINE__);
            pthread_exit(EXIT_FAILURE);
        } else if (0 == nbytes) {
            printf("客户端[%s:%d]断开了连接..\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
            break;
        }
        if (!strcmp(buff.txt, "quit")) {
            printf("客户端[%s:%d]退出了..\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
            break;
        }
        printf("[%s:%d]:[%s]\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port), buff.txt);

        // 组装应答回复客户端
        strcat(buff.txt, "--server");
        if (-1 == send(acceptfd, &buff, sizeof(buff), 0)) {
            perror("send error");
            printf("%s %s:%d\n", __FILE__, __func__, __LINE__);
            pthread_exit(EXIT_FAILURE);
        }
    }

    close(acceptfd);
    pthread_exit(EXIT_SUCCESS);
}

int main(int argc, const char* argv[])
{
    // 入参合理性检查
    if (3 != argc) {
        printf("Usage : %s <ip> <port>\n", argv[0]);
        exit(-1);
    }

    // 1.创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == sockfd) {
        PRINT_ERR("socket error");
    }

    // 2.填充服务器网络信息结构体
    struct sockaddr_in serveraddr;
    memset(&serveraddr, 0, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argv[2]));
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    socklen_t serveraddr_len = sizeof(serveraddr);

    // 设置端口复用
    int flag = 1;
    if (-1 == setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag))) {
        printf("setsockopt error");
    }

    // 3.将套接字与服务器的网络信息结构体绑定
    if (-1 == bind(sockfd, (struct sockaddr*)&serveraddr, serveraddr_len)) {
        PRINT_ERR("bind error");
    }

    // 4.将套接字设置成被动监听状态
    if (-1 == listen(sockfd, 5)) {
        PRINT_ERR("listen error");
    }

    // 定义结构体保存客户端信息
    struct sockaddr_in clientaddr;
    memset(&clientaddr, 0, sizeof(clientaddr));
    socklen_t clientaddr_len = sizeof(clientaddr);

    int nbytes = 0;
    msg_t buff;
    // 5.阻塞等待客户端连接
    pthread_t tid;
    int acceptfd = 0;
    int fd = 0;
    char file[128] = { 0 };
    while (1) {
        // 主线程收发数据
        printf("正在等待客户端连接..\n");
        if (-1 == (acceptfd = accept(sockfd, (struct sockaddr*)&clientaddr, &clientaddr_len))) {
            PRINT_ERR("accept error");
        }
        printf("客户端[%s:%d]连接到服务器..\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
        buff.acceptfd = acceptfd;
        buff.clientaddr = clientaddr;
        // 创建子线程
        if ((errno = pthread_create(&tid, NULL, deal_read_write, (void*)&buff)) != 0)
            PRINT_ERR("pthread create error");
    }
    // 关闭套接字
    close(sockfd);

    return 0;
}

客户端代码:

#include <arpa/inet.h>
#include <errno.h>
#include <error.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <netinet/in.h>
#include <net
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

枸杞桑葚菊花茶

创作不易,鼓励==>动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值