TCP服务器的演变过程:多进程实现一对多的TCP服务器

使用多进程实现一对多的TCP服务器

一、前言

手把手教你从0开始编写TCP服务器程序,体验开局一块砖,大厦全靠垒。

为了避免篇幅过长使读者感到乏味,对【TCP服务器的开发】进行分阶段实现,一步步进行优化升级。本节在上一章节的基础上,改为多进程方式实现TCP服务器,为每个新接入的客户端分配进程,实现一个服务器程序处理多个客户端连接。主要目的是比较不同方式的利弊关系。

二、新增使用的fork()函数

函数原型:

c 复制代码
#include <unistd.h>
pid_t fork(void);

fork()通过复制调用进程来创建一个新进程。新进程被称为子进程。调用进程被称为父进程。

子进程和父进程在单独的内存空间中运行。在执行fork()时,两个内存空间都具有相同的内容。其中一个进程执行的内存写入、文件映射(mmap)和取消映射(munmap),不会影响另一个进程。

返回值:

  • 返回0,代表子进程。
  • 返回非零,代表是父进程。

三、实现步骤

使用多进程方案,来一个连接请求则克隆一个子进程。

(1)创建socket。

c 复制代码
int listenfd=socket(AF_INET,SOCK_STREAM,0);
if(listenfd==-1){
    printf("errno = %d, %s\n",errno,strerror(errno));
    return SOCKET_CREATE_FAILED;
}

(2)绑定地址。

c 复制代码
struct sockaddr_in server;
memset(&server,0,sizeof(server));

server.sin_family=AF_INET;
server.sin_addr.s_addr=htonl(INADDR_ANY);
server.sin_port=htons(LISTEN_PORT);

if(-1==bind(listenfd,(struct sockaddr*)&server,sizeof(server))){
    printf("errno = %d, %s\n",errno,strerror(errno));
    close(listenfd);
    return SOCKET_BIND_FAILED;
}

(3)设置监听。

c 复制代码
if(-1==listen(listenfd,BLOCK_SIZE)){
   printf("errno = %d, %s\n",errno,strerror(errno));
   close(listenfd);
   return SOCKET_LISTEN_FAILED;
}

(4)接收连接。

c 复制代码
struct sockaddr_in client;
memset(&client,0,sizeof(client));
socklen_t len=sizeof(client);
    
int clientfd=accept(listenfd,(struct sockaddr*)&client,&len);
if(clientfd==-1){
    printf("errno = %d, %s\n",errno,strerror(errno));
    close(listenfd);
    return SOCKET_ACCEPT_FAILED;
}

(5)为每个连接克隆子进程。

c 复制代码
pid_t pid=fork();
if(pid==0)
{
     routine(clientfd);
     break;
}
else
{
     printf("pid = %d\n",pid);
}

(6)在子进程里面接收数据。

c 复制代码
char buf[BUFFER_LENGTH]={0};
ret=recv(clientfd,buf,BUFFER_LENGTH,0);
if(ret==0) {
     printf("connection dropped\n");

}
printf("recv --> %s\n",buf);

(7)在子进程里面发送数据。

c 复制代码
if(-1==send(clientfd,buf,ret,0))
{
    printf("errno = %d, %s\n",errno,strerror(errno));
}

(8)关闭文件描述符。

c 复制代码
close(listenfd);

四、完整代码

c 复制代码
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>

#include <errno.h>
#include <string.h>
#include <unistd.h>

#include <pthread.h>

#define LISTEN_PORT     8888
#define BLOCK_SIZE      10
#define BUFFER_LENGTH   1024

enum ERROR_CODE{
    SOCKET_CREATE_FAILED=-1,
    SOCKET_BIND_FAILED=-2,
    SOCKET_LISTEN_FAILED=-3,
    SOCKET_ACCEPT_FAILED=-4
};

void routine(int clientfd)
{
    while(1)
    {
        // 5.
        char buf[BUFFER_LENGTH]={0};
        int ret=recv(clientfd,buf,BUFFER_LENGTH,0);
        if(ret==0) {
            printf("connection dropped\n");
            break;
        }
        
        printf("fd=%d recv --> %s\n",clientfd,buf);
        send(clientfd,buf,ret,0);
        
    }
    

    close(clientfd);
}

int main(int argc,char **argv)
{
    // 1.
    int listenfd=socket(AF_INET,SOCK_STREAM,0);
    if(listenfd==-1){
        printf("errno = %d, %s\n",errno,strerror(errno));
        return SOCKET_CREATE_FAILED;
    }

    // 2.
    struct sockaddr_in server;
    memset(&server,0,sizeof(server));

    server.sin_family=AF_INET;
    server.sin_addr.s_addr=htonl(INADDR_ANY);
    server.sin_port=htons(LISTEN_PORT);

    if(-1==bind(listenfd,(struct sockaddr*)&server,sizeof(server))){
        printf("errno = %d, %s\n",errno,strerror(errno));
        close(listenfd);
        return SOCKET_BIND_FAILED;
    }

    // 3.
    if(-1==listen(listenfd,BLOCK_SIZE)){
        printf("errno = %d, %s\n",errno,strerror(errno));
        close(listenfd);
        return SOCKET_LISTEN_FAILED;
    }

    printf("listen port: %d\n",LISTEN_PORT);
    struct sockaddr_in client;
    socklen_t len=sizeof(client);
    int clientfd=-1;
    
    while(1)
    {
        // 4.
        memset(&client,0,sizeof(client));
        clientfd=accept(listenfd,(struct sockaddr*)&client,&len);
        if(clientfd==-1){
            printf("errno = %d, %s\n",errno,strerror(errno));
            continue;
        }

        printf("accept successdul, fd = %d\n",clientfd);
        pid_t pid=fork();
        if(pid==0)
        {
            routine(clientfd);
            break;
        }
        else
        {
            printf("pid = %d\n",pid);
        }
    }

    
    close(listenfd);

    return 0;
}

编译:

bash 复制代码
gcc -o server server.c

五、TCP客户端

5.1、自己实现一个TCP客户端

自己实现一个TCP客户端连接TCP服务器的代码:

c 复制代码
#include <stdio.h>
#include <sys/socket.h>

#include <netinet/in.h>
#include <arpa/inet.h>

#include <errno.h>
#include <string.h>

#include <unistd.h>
#include <stdlib.h>

#define BUFFER_LENGTH   1024

enum ERROR_CODE{
    SOCKET_CREATE_FAILED=-1,
    SOCKET_CONN_FAILED=-2,
    SOCKET_LISTEN_FAILED=-3,
    SOCKET_ACCEPT_FAILED=-4
};

int main(int argc,char** argv)
{
    if(argc<3)
    {
        printf("Please enter the server IP and port.");
        return 0;
    }
    printf("connect to %s, port=%s\n",argv[1],argv[2]);

    int connfd=socket(AF_INET,SOCK_STREAM,0);
    if(connfd==-1)
    {
        printf("errno = %d, %s\n",errno,strerror(errno));
        return SOCKET_CREATE_FAILED;

    }
    struct sockaddr_in serv;
    serv.sin_family=AF_INET;
    serv.sin_addr.s_addr=inet_addr(argv[1]);
    serv.sin_port=htons(atoi(argv[2]));
    socklen_t len=sizeof(serv);
    int rwfd=connect(connfd,(struct sockaddr*)&serv,len);
    if(rwfd==-1)
    {
        printf("errno = %d, %s\n",errno,strerror(errno));
        close(rwfd);
        return SOCKET_CONN_FAILED;
    }
    int ret=1;
    while(ret>0)
    {
        char buf[BUFFER_LENGTH]={0};
        printf("Please enter the string to send:\n");
        scanf("%s",buf);
        send(connfd,buf,strlen(buf),0);

        memset(buf,0,BUFFER_LENGTH);
        printf("recv:\n");
        ret=recv(connfd,buf,BUFFER_LENGTH,0);
        printf("%s\n",buf);
        
    }
    close(rwfd);
    return 0;
}

编译:

bash 复制代码
gcc -o client client.c

5.2、Windows下可以使用NetAssist的网络助手工具

下载地址:http://old.tpyboard.com/downloads/NetAssist.exe

小结

这里基于上一个章节的内容进行了升级,可以接受多个客户端同时连接,并为每个连接克隆一个子进程进行通信。

但是,使用多进程会非常消耗资源,特别是高并发的时候,会是系统内存爆满,开销巨大。那么有没有办法在一个线程中就可以完成高并发通信呢?答案是可以的,下一章节将介绍使用select实现一个线程完成多个连接的通信,也就是IO多路复用技术。

相关推荐
chlk1231 天前
Linux文件权限完全图解:读懂 ls -l 和 chmod 755 背后的秘密
linux·操作系统
舒一笑1 天前
Ubuntu系统安装CodeX出现问题
linux·后端
改一下配置文件1 天前
Ubuntu24.04安装NVIDIA驱动完整指南(含Secure Boot解决方案)
linux
深紫色的三北六号1 天前
Linux 服务器磁盘扩容与目录迁移:rsync + bind mount 实现服务无感迁移(无需修改配置)
linux·扩容·服务迁移
SudosuBash2 天前
[CS:APP 3e] 关于对 第 12 章 读/写者的一点思考和题解 (作业 12.19,12.20,12.21)
linux·并发·操作系统(os)
哈基咪怎么可能是AI2 天前
为什么我就想要「线性历史 + Signed Commits」GitHub 却把我当猴耍 🤬🎙️
linux·github
十日十行3 天前
Linux和window共享文件夹
linux
Jony_3 天前
高可用移动网络连接
网络协议
Sinclair3 天前
简单几步,安卓手机秒变服务器,安装 CMS 程序
android·服务器
chilix3 天前
Linux 跨网段路由转发配置
网络协议