TCP并发服务器程序

多线程TCP服务器程序

主线程负责监听有无新的连接,当新的连接到来时,就创建一个新线程服务客户(与客户端进行通信)。

void *working(void *arg)
{
    int cfd = *((int *)arg);

    while(1)
    {
        // 与客户端进行通信
    }

    close(cfd);  // 关闭通信socket。与多进程实现不同,主线程中不用关闭cfd,因为线程之间文件描述符不共享。
}

int main()
{
    // 创建socket,绑定bind,监听listen

    while(1)
    {
        int cfd = accept(lfd, NULL, NULL);
        pthread_t tid;
        pthread_create(&tid, NULL, working, &cfd);
        pthread_detach(tid);  // 调用join会使主线程一直阻塞在这,监听不了,可以调用detach
    }

    close(lfd); // 新线程中不用关闭lfd,原因同上

    return 0;
}

这个程序在一些情况下会出错,因为每次调用accept都会向cfd覆盖写入一个新值,可能出现如下情况。

  • 主线程调用accept并将结果存入cfd,并创建新线程1,新线程1还没来得及执行。
  • 主线程又调用accept并将结果存入cfd,原先的值被覆盖,又创建一个新线程2。
  • 此时线程1和线程2参数中的cfd指向同一块数据,即同一个socket,发生错误。

改进后:

int main()
{
    // 创建socket,绑定bind,监听listen

    while(1)
    {
        int *p = (int *)malloc(sizeof(int));
        *p = accept(lfd, NULL, NULL);
        pthread_t tid;
        pthread_create(&tid, NULL, working, p);
        pthread_detach(tid);  // 调用join会使主线程一直阻塞在这,监听不了,可以调用detach
    }

    close(lfd); // 新线程中不用关闭lfd,原因同上

    return 0;
}

每次调用accept时,我们首先调用malloc分配一个整数变量的内存空间,用于存放有待accept返回的已连接描述符。这使得每个线程都有各自的已连接描述符副本。(即参数中传的地址指向不同的数据)

多进程TCP服务器程序

父进程负责监听有无新的连接,当新的连接到来时,就fork一个子进程服务客户(与客户端进行通信)。

int main()
{
    // 创建socket,绑定bind,监听listen

    while(1)
    {
        int cfd = accept(lfd, NULL, NULL);
        if(cfd == -1)
        {
            perror("accept");
            exit(1);
        }

        pid_t pid = fork();
        if(pid == 0) // 子进程
        {
            close(lfd);  // 关闭监听描述符
            
            while(1)
            {
                // 与客户端进行通信
            }
            
            close(cfd);   // 关闭通信描述符
            exit(0);
        }
        else if(pid > 0)  // 父进程
        {
            close(cfd);  // 关闭刚创建出来的通信描述符
        }
        else if(pid < 0)
        {
            perror("fork");
            exit(1);
        }
    }

    close(lfd);
    return 0;
}

代码分析:

子进程与父进程共享打开的文件(socket也看作文件),所以父进程fork后,监听socket和通信socket的引用计数均为2,当父进程或子进程结束后,对应的文件描述符的引用计数由2->1,套接字不会被清理或释放,所以需要如下操作:

  • 父进程中需要close(cfd)关闭刚创建出来的通信描述符。
  • 子进程中需要close(lfd)关闭监听描述符。

源码

多线程
// MultiThreadServer.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>


void *working(void *arg)
{
    int cfd = *((int *)arg);
    free(arg);

    while(1)
    {
        // 接收数据
        char buf[1024];
        memset(buf, 0, sizeof buf);
        int len = read(cfd, buf, sizeof buf);
        if(len > 0)
        {
            printf("客户端say: %s\n", buf);
            write(cfd, buf, len);
        }
        else if(len == 0)
        {
            printf("客户端断开了连接...\n");
            break;
        }
        else
        {
            perror("read");
            break;
        }
    }

    close(cfd);
}

int main()
{
    const char *ip = "192.168.154.137";

    // 1、创建监听的套接字
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    if(lfd == -1)
    {
        perror("socket");
        return 1;
    }

    // 2、将socket()返回值和本地的IP端口绑定到一起
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(10000);
    inet_pton(AF_INET, ip, &server.sin_addr.s_addr);

    int ret = bind(lfd, (struct sockaddr *)&server, sizeof server);
    if(ret == -1)
    {
        perror("bind");
        return 1;
    }

    // 3、设置监听
    ret = listen(lfd, 128);
    if(ret == -1)
    {
        perror("listen");
        return 1;
    }

    while(1)
    {
        int *p= malloc(sizeof(int));  // 使得每个线程都有自己的已连接描述符副本
        *p = accept(lfd, NULL, NULL);
        
        pthread_t tid;
        pthread_create(&tid, NULL, working, p);
        pthread_detach(tid);
    }


    close(lfd);

    return 0;
}
多进程
// MultiProcessServer.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>

int main()
{
    const char *ip = "192.168.154.137";

    // 1、创建监听的套接字
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    if(lfd == -1)
    {
        perror("socket");
        return 1;
    }

    // 2、将socket()返回值和本地的IP端口绑定到一起
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(10000);
    inet_pton(AF_INET, ip, &server.sin_addr.s_addr);

    int ret = bind(lfd, (struct sockaddr *)&server, sizeof server);
    if(ret == -1)
    {
        perror("bind");
        return 1;
    }

    // 3、设置监听
    ret = listen(lfd, 128);
    if(ret == -1)
    {
        perror("listen");
        return 1;
    }

    // 和客户端通信
    while(1)
    {
        // 4、阻塞并接受客户端连接
        int cfd = accept(lfd, NULL, NULL);
        if(cfd == -1)
        {
            perror("accept");
            return 1;
        }

        pid_t pid = fork();
        if(pid == 0)
        {
            close(lfd);

            while(1)
            {
                // 接收数据
                char buf[1024];
                memset(buf, 0, sizeof buf);
                int len = read(cfd, buf, sizeof buf);
                if(len > 0)
                {
                    printf("客户端say: %s\n", buf);
                    write(cfd, buf, len);
                }
                else if(len == 0)
                {
                    printf("客户端断开了连接...\n");
                    break;
                }
                else
                {
                    perror("read");
                    break;
                }
            }

            close(cfd);
            exit(0);
        }
        else if(pid > 0)
        {
            close(cfd);
        }
        else if(pid < 0)
        {
            perror("fork");
            exit(1);
        }
    }

    close(lfd);
    return 0;
}
客户端
// client.c
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

const char *ip = "192.168.154.137";

int main()
{
    // 1、创建用于通信的套接字
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if(fd == -1)
    {
        perror("socket");
        return 1;
    }

    // 2、连接服务器
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(10000);
    inet_pton(AF_INET, ip, &addr.sin_addr.s_addr);
    int ret = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
    if(ret == -1)
    {
        perror("connect");
        return 1;
    }

    // 通信
    while(1)
    {
        char recvbuf[1024];
        fgets(recvbuf, sizeof(recvbuf), stdin);
        write(fd, recvbuf, strlen(recvbuf) + 1);
        read(fd, recvbuf, sizeof(recvbuf));
        printf("recv buf: %s", recvbuf);
        sleep(1);
    }

    close(fd);


    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值