Multi-Thread TCP Server & Client

发布于:2024-05-01 ⋅ 阅读:(35) ⋅ 点赞:(0)

prerequisite knowledge:
Basic TCP Server & Client: URL

Server

#include <stdio.h>
#include <string.h>
#include <unistd.h> // read and write (TCP); sendto and recvfrom (UDP)
#include <arpa/inet.h> // 包含#include <sys/socket.h>
#include <pthread.h>

// 想要将两份参数通过一个位置传递给working 得定义一个信息结构体
struct SockInfo {
    struct sockaddr_in addr;
    int fd;
};

// 由于有多个客户端 所以有多份信息 搞个数组存一下
struct SockInfo infos[512]; // 意味着我们最多只能和512个客户端*同时*通信
// 注意线程池是多个工作线程组成的队列,与此处不同

/* infos需要上锁吗?
   不需要,每个子线程都使用了数组的一个位置,即多个线程没有操控同一内存空间*/

void* working(void* arg) {
    struct SockInfo* pinfo = (struct SockInfo*)arg;
    printf("client socket %d, Address: %s:%d\n", pinfo->fd, inet_ntoa(pinfo->addr.sin_addr), ntohs(pinfo->addr.sin_port));
    // 5. 通信
    while (1) {
        char buf[1024];
        int len = recv(pinfo->fd, buf, sizeof(buf), 0);
        if (len > 0) {
            printf("client say: %s\n", buf);
            send(pinfo->fd, buf, len, 0); // 长度指定为len 不要传多了
        } else if (len == 0) {
            printf("客户端已经断开连接...\n");
            break;
        } else if (len == -1) {
            perror("recv");
            break;
        }
    } // 跳出后说明通信结束
    pinfo->fd = -1; // 回收
    close(pinfo->fd);
    return NULL;
}

int main(int argc, char* argv[]) {
    // 1. 创建监听fd
    int fd = socket(PF_INET, SOCK_STREAM, 0);
    if (fd == -1) {
        perror("socket");
        return -1;
    }
    // 2. 绑定监听fd
    struct sockaddr_in saddr;
    memset(&saddr, 0, sizeof(saddr)); 
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(9999);
    saddr.sin_addr.s_addr = INADDR_ANY; // 宏INADDR_ANY(可以绑定本地)实际值是0=0.0.0.0;由于大小端没区别,因此无需htonl
    int ret = bind(fd, (struct sockaddr*)&saddr, sizeof(saddr));
    if (ret == -1) {
        perror("bind");
        return -1;
    }
    // 3. 设置监听
    ret = listen(fd, SOMAXCONN); // #define SOMAXCONN 128 // 最大监听队列长度 内部定义过来
    if (ret == -1) {
        perror("listen");
        return -1;
    }

    // 初始化信息数组
    int max = sizeof(infos) / sizeof(infos[0]);
    for (int i=0; i<max; ++i) {
        memset(&infos[i], 0, sizeof(infos[i]));
        infos[i].fd = -1; // -1表示该数组元素未被占用
    }

    // 4. 阻塞并等待客户端连接
    // struct sockaddr_in caddr; // 这个就不需要了
    // memset(&caddr, 0, sizeof(caddr));
    socklen_t caddr_len = sizeof(struct sockaddr_in);

    // 主线程不断接收:
    while (1) {
        // 创建子线程
        pthread_t tid;
        struct SockInfo* pinfo;
        for (int i=0; i<max; ++i) {
            if (infos[i].fd == -1) {
                pinfo = &infos[i];
                break;
            }
        }
        int cfd = accept(fd, (struct sockaddr*)&pinfo->addr, &caddr_len); // 直接传入信息数组中空闲元素的addr
        pinfo->fd = cfd; // 传递cfd
        if (cfd == -1) {
            perror("accept");
            break;
        }
        pthread_create(&tid, NULL, working, pinfo);
        // 此处不能使用主线程回收子线程资源 因为pthread_join是阻塞函数 与我们设想的不断accept矛盾
        pthread_detach(tid);
    }

    close(fd);
    return 0;
}

Client

#include <stdio.h>
#include <string.h>
#include <unistd.h> // read and write (TCP); sendto and recvfrom (UDP)
#include <arpa/inet.h> // 包含#include <sys/socket.h>

int main(int argc, char* argv[]) {
    // 1. 创建通信fd
    int fd = socket(PF_INET, SOCK_STREAM, 0); // AF_*和PF_*值完全相同,通常混用
    if (fd == -1) {
        perror("socket");
        return -1;
    }
    // 2. 连接服务器
    struct sockaddr_in saddr;
    memset(&saddr, 0, sizeof(saddr)); 
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(9999);
    inet_pton(AF_INET, "127.0.0.1", &saddr.sin_addr.s_addr);
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 或者直接指定ip: 172.31.78.11
    int ret = connect(fd, (struct sockaddr*)&saddr, sizeof(saddr));
    if (ret == -1) {
        perror("connect");
        return -1;
    }
    printf("socket connect successful!\n");
    // 3. 通信
    int number = 0;
    while (1) {
        char buf[1024];
        sprintf(buf, "hello, message number #%d...\n", number++); // sprintf将数据写入字符串 而非输出到标准输出流
        send(fd, buf, strlen(buf)+1, 0); // 注意这里不要发送sizeof(buf),发送实际字符数+'\0'
        memset(buf, 0, sizeof(buf)); // 有必要清空buf的
        int len = recv(fd, buf, sizeof(buf), 0);
        if (len > 0) {
            printf("server say: %s\n", buf);
        } else if (len == 0) {
            printf("服务器已经断开连接...\n");
            break;
        } else if (len == -1) {
            perror("recv");
            break;
        }
        sleep(1); // 让客户端1秒发一条
    } // 跳出后说明通信结束
    close(fd);
    return 0;
}

makefile

server:
	gcc -o server server.c -lpthread &
	gcc -o client client.c

clean:
	rm -f server client

网站公告

今日签到

点亮在社区的每一天
去签到