【计算机网络】TCP并发处理多客户端

目录

TCP相关概念

TCP并发处理多客户端的方法

多线程模型

多进程模型

IO多路复用

异步IO模型

边缘触发与水平触发

性能优化建议


TCP相关概念

TCP是一个面向连接的、可靠的、流式服务。

  • 面向链接指我们需要通过三次握手来进行链接的建立,通过四次挥手来进行链接断开。握手不可以两次,挥手可以三次(具体原因上节已经讲过)
  • 可靠性怎末保证?我们通过应答确认超时重传,或者去重机制、乱序重排、滑动窗口进行流量控制。
  • 流式服务本身的特点会出现一个粘包现象,因为我们每次发送一个数据,并不是独立的包,属于整数流的一部分,因此在TCP在进行发送数据的时候,经过两次或多次发送数据有可能会被对方一次收到,即发送次数和链接次数不对应或者不固定。

TCP并发处理多客户端的方法

多线程模型

为每个客户端连接创建一个独立的线程处理I/O操作。线程池可优化资源消耗,避免频繁创建销毁线程。

示例代码(C):

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include<pthread.h>
//多线程
int socket_init();
void* fun(void *arg){
    int *p=(int*)arg;
    int c=*p;
    free(p);
    while(1){//循环结束客户端的连接
        char buff[128]={0};
        int n=recv(c,buff,127,0);
        if(n<=0){//客户端关闭,退出循环
            break;
        }
        printf("buff=%s\n",buff);
        send(c,"ok",2,0);
    }
    printf("client close\n");
}
int main(){
    int sockfd = socket_init();
    if(sockfd==-1){
        exit(1);
    }
    while( 1 ){
        struct sockaddr_in caddr;      // 记录客户端地址
        socklen_t len = sizeof(caddr); // 计算大小
        // 从已完成握手的监听队列处理一个链接,队列空,阻塞,
        int c = accept(sockfd, (struct sockaddr *)&caddr, &len); // 接受客户端连接
        if (c < 0){
            continue;
        }
        printf("c=%d,ip:%s,port=%d\n", c, inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));
        // ip转化为点分十进制的字符串,端口将网络字节序列转化为主机字节序列
        pthread_t id;
        int *p = (int *)malloc(sizeof(int));
        if (p == NULL){
            close(c);
            continue;
        }
        *p=c;
        pthread_create(&id, NULL, fun, (void *)p);
    }
}
int socket_init(){//创建套接字
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if( sockfd == -1 ){
        return -1;
    }
    struct sockaddr_in saddr;
    memset(&saddr,0,sizeof(saddr));
    saddr.sin_family = AF_INET;//填充地址图
    saddr.sin_port = htons(6000);//填充端口,大端,主机字节序列转为网络字节序列,
    //1024以内知名端口,root;1024-4096保留端口一般不用;4096以上为临时端口,随便用
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1");//ip地址,转为无符号整型
    int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
    if( res == -1){
        printf("bind err\n");
        return -1;
    }
    res = listen(sockfd,5);
    if( res == -1){
        return -1;
    }
    return sockfd;
}

多进程模型

每个客户端连接由独立进程处理,隔离性更好但资源消耗更高。适用于CPU密集型场景。

注意:如果子进程结束而父进程没有结束,容易出现僵死进程,因此我们应采用wait去防止出现这种状态。

示例代码(C语言):

c 复制代码
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>
//多进程
int socket_init();
void sig_fun(int sig){//信号处理
    int val = 0;
    wait(&val);
}
int main(){
    int sockfd = socket_init();
    if( sockfd == -1 ){
        exit(1);
    }
    signal(SIGCHLD,sig_fun);
    while( 1 ){
        struct sockaddr_in caddr;//客户端的ip,port
        socklen_t len = sizeof(caddr);
        int c = accept(sockfd,(struct sockaddr*)&caddr,&len);//阻塞,有客户端连接解除阻塞
        if( c < 0 ){
            continue;
        }
        printf("accept c=%d,client port=%d\n",c,ntohs(caddr.sin_port));
        pid_t pid = fork();//子进程
        if( pid == -1){//无法创建新的进程,就关闭服务器
            close(c);
            continue;
        }
        if( pid == 0 ){//如果创建子进程成功,
            while( 1 ){
                char buff[128] = {0};
                int n = recv(c,buff,127,0);
                if( n <= 0 ){
                    break;
                }
                printf("buff=%s\n",buff);
                send(c,"ok",2,0);
            }
            close(c);
            printf("client close\n");
            exit(0);
        }
        close(c); //父进程关闭c
    }
}
int socket_init(){
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if( sockfd == -1 ){
        return -1;
    }
    struct sockaddr_in saddr;
    memset(&saddr,0,sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(6000);
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
    if( res == -1){
        printf("bind err\n");
        return -1;
    }
    res = listen(sockfd,5);
    if( res == -1 ){
        return -1;
    }
    return sockfd;
}

IO多路复用

使用select/poll/epoll监控多个文件描述符,单线程即可处理高并发连接。

epoll示例(Linux):

c 复制代码
struct epoll_event ev, events[MAX_EVENTS];
int epoll_fd = epoll_create1(0);
ev.events = EPOLLIN;
ev.data.fd = server_fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &ev);

while(1) {
    int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
    for(int i = 0; i < nfds; i++) {
        if(events[i].data.fd == server_fd) {
            // 处理新连接
        } else {
            // 处理客户端数据
        }
    }
}

异步IO模型

通过回调机制实现非阻塞处理,如libevent、boost.asio等库。

Python asyncio示例:

python 复制代码
import asyncio

async def handle_client(reader, writer):
    while True:
        data = await reader.read(100)
        if not data: break
        writer.write(data)
        await writer.drain()
    writer.close()

async def main():
    server = await asyncio.start_server(handle_client, '0.0.0.0', 8888)
    async with server: await server.serve_forever()

asyncio.run(main())

边缘触发与水平触发

epoll支持两种模式:

  • 边缘触发(ET):仅在状态变化时通知,需一次性处理完所有数据
  • 水平触发(LT):只要满足条件就会持续通知,编程更简单

性能优化建议

  • 设置SO_REUSEADDR选项避免TIME_WAIT状态影响
  • 使用非阻塞socket配合IO多路复用
  • 针对短连接场景调整TCP快速回收参数
  • 负载过高时考虑添加应用层协议头标识包边界
相关推荐
梅见十柒2 小时前
UNIX网络编程笔记:共享内存区和远程过程调用
linux·服务器·网络·笔记·tcp/ip·udp·unix
ajassi20003 小时前
开源 C++ QT Widget 开发(八)网络--Http文件下载
网络·c++·开源
云望无线图传模块5 小时前
突破视界的边界:16公里远距离无人机图传模块全面解析
网络·物联网·无人机
key_Go7 小时前
05.《ARP协议基础知识探秘》
运维·服务器·网络·华为·arp
好名字更能让你们记住我8 小时前
Linux网络基础1(一)之计算机网络背景
linux·服务器·网络·windows·计算机网络·算法·centos
神一样的老师9 小时前
面向 6G 网络的 LLM 赋能物联网:架构、挑战与解决方案
网络·物联网·架构
Prejudices9 小时前
Linux查看有线网卡和无线网卡详解
linux·网络
LabVIEW开发10 小时前
LabVIEW测斜设备承压试验台
网络·自动化·labview开发案例
雷工笔记10 小时前
Linux命令学习:make,make install,modprobe,lsmod
linux·网络·学习