UNIX网络编程:socket & fork()多进程 实现clients/server通信

这篇具有很好参考价值的文章主要介绍了UNIX网络编程:socket & fork()多进程 实现clients/server通信。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、问题引入

UNIX网络编程:socket实现client/server通信 随笔简单介绍了TCP Server服务单客户端的socket通信,但是并未涉及多客户端通信。

对于网络编程肯定涉及到多客户端通信和并发编程 (指在同时有大量的客户链接到同一服务器),故本随笔补充这部分知识。

而且并发并发编程涉及到多进程、多线程,其中 fork()函数是Unix中派生新进程的唯一方法。

二、解决过程

2-1 server 代码

#include <stdlib.h>
#include <pthread.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

#include <stdlib.h>
#include <errno.h>
#include <signal.h>
#include <sys/wait.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>

#define IP "10.8.198.227"
#define PORT 8887

static int string_toupper(const char *src, int str_len, char *dst)
{
    int count = 0;
    for (int i = 0; i < str_len; i++)
    {
        dst[i] = toupper(src[i]);
        count++;
    }
    return count;
}

static int handle(int connect_fd, const char *socket)
{
    int recv_len, send_len;
    char read_buf[1024], send_buf[1024];

    for (;;)
    {
        memset(read_buf, 0, sizeof(read_buf));
        recv_len = read(connect_fd, read_buf, sizeof(read_buf));
        if (recv_len < 0)
        {
            printf("read error \n");
            break;
        }
        else if (recv_len == 0)
        {
            printf("%s close \n", socket);
            break;
        }
        printf("%s:%s(%d Byte)\n", socket, read_buf, recv_len);
        send_len = string_toupper(read_buf, strlen(read_buf), send_buf);
        write(connect_fd, send_buf, send_len);
        if (strcmp("exit", read_buf) == 0)
        {
            printf("%s close \n", socket);
            break;
        }
    }

    return 0;
}

static void sighandler(int signum)
{
    pid_t pid;
    while (1)
    {
        pid = waitpid(-1, NULL, WNOHANG);
        if (pid > 0)
        {
            printf("child %d terminated\n", pid);
        }
        if (pid == -1 || pid == 0)
            break;
    }
}

int main(void)
{
    int listenfd, connfd;
    struct sockaddr_in server_sockaddr;
    struct sockaddr_in client_addr;
    char buf[1024];
    char client_socket[128];
    socklen_t length;
    int pid;

    server_sockaddr.sin_family = AF_INET;
    server_sockaddr.sin_port = htons(PORT);
    server_sockaddr.sin_addr.s_addr = inet_addr(IP);
    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd < 0)
    {
        perror("socket error");
        exit(1);
    }
    if (bind(listenfd, (struct sockaddr *)&server_sockaddr, sizeof(struct sockaddr)) < 0)
    {
        perror("bind error");
        exit(1);
    }
    if (listen(listenfd, 5) < 0)
    {
        perror("listen error");
        exit(1);
    }
    // 注册信号捕捉函数
    signal(SIGCHLD, sighandler);
    for (;;)
    {
        
        // 接受来自客户端的信息
        printf("accept start \n");
        memset(&client_addr, 0, sizeof(client_addr));
        length = sizeof(client_addr);
        if ((connfd = accept(listenfd, (struct sockaddr *)&client_addr, &length)) < 0)
        {
            if (errno == EINTR)
                continue;
            else
            {
                perror("accept error");
                exit(1);
            }
        }
        printf("client addr:%s por:%d\n",
               inet_ntop(AF_INET, &client_addr.sin_addr, buf, sizeof(buf)),
               ntohs(client_addr.sin_port));
        snprintf(client_socket, sizeof(client_socket), "client socket (%s:%d)",
                 inet_ntop(AF_INET, &client_addr.sin_addr, buf, sizeof(buf)),
                 ntohs(client_addr.sin_port));
        pid = fork();
        if (pid == 0) // 子进程
        {
            close(listenfd);
            handle(connfd, client_socket);
            close(connfd);
            exit(1);
        }
        else if (pid < 0) // error
        {
            perror("fork error");
            close(connfd);
            exit(1);
        }
        else // 父进程
        {
            close(connfd);
        }
    }
    close(listenfd);
    return EXIT_SUCCESS;
}

2-2 client 代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <unistd.h>

#define IP "10.8.198.227"
#define PORT 8887

static int handle(int connect_fd, const char *socket)
{
    char send_buf[1024], recv_buf[1024];
    int recv_len;
    for (;;)
    {
        memset(send_buf, 0, sizeof(send_buf));
        memset(recv_buf, 0, sizeof(recv_buf));
        fgets(send_buf, sizeof(send_buf), stdin);
        if (strlen(send_buf) <= 1)
            continue;
        if (send_buf[strlen(send_buf) - 1] == '\n')
            send_buf[strlen(send_buf) - 1] = '\0';
        write(connect_fd, send_buf, strlen(send_buf));
        if (strcmp("exit", send_buf) == 0)
            break;
        recv_len = read(connect_fd, recv_buf, sizeof(recv_buf));
        if (recv_len <= 0)
        {
            printf("read error or server closed, n==[%d] \n", recv_len);
            break;
        }
        printf("%s:%s(%d Byte)\n", socket, recv_buf, recv_len);
    }
    return 0;
}

int main(void)
{
    int sockfd;
    char buf[1024];
    struct sockaddr_in server_addr;
    char server_socket[128];

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    server_addr.sin_addr.s_addr = inet_addr(IP);
    if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) < 0)
    {
        printf("connect error \n");
        return -1;
    }
    printf("server addr:%s por:%d\n",
           inet_ntop(AF_INET, &server_addr.sin_addr, buf, sizeof(buf)),
             ntohs(server_addr.sin_port));
    snprintf(server_socket, sizeof(server_socket), "server socket (%s:%d)",
             inet_ntop(AF_INET, &server_addr.sin_addr, buf, sizeof(buf)),
             ntohs(server_addr.sin_port));
    handle(sockfd, server_socket);
    close(sockfd);

    return EXIT_SUCCESS;
}

2-3 运行测试

1、client 1 连接 server

UNIX网络编程:socket & fork()多进程 实现clients/server通信

💡 注意:server后台查看TCP进程时,client的端口号可能不一致(原因是图片是后补的)

UNIX网络编程:socket & fork()多进程 实现clients/server通信

2、client 2 连接 server

UNIX网络编程:socket & fork()多进程 实现clients/server通信

💡 注意:server后台查看TCP进程时,client的端口号可能不一致(原因是图片是后补的)

UNIX网络编程:socket & fork()多进程 实现clients/server通信

3、多客户端与服务器通信

UNIX网络编程:socket & fork()多进程 实现clients/server通信

UNIX网络编程:socket & fork()多进程 实现clients/server通信

4、client 2断开与 server的连接

UNIX网络编程:socket & fork()多进程 实现clients/server通信

UNIX网络编程:socket & fork()多进程 实现clients/server通信

client 2后台查看TCP进程,发现TCP对应的套接字状态:TIME_WAIT,一段时间后,TCP对应的套接字进程才消失。

2-4 程序解读

  • 客户端

客户端程序的主要功能是发送消息给服务器,并接受来自服务器的消息。

  • 服务器

服务器程序的主要功能是:
1)接受多个客户端的连接,并为每个客户端派生子进程负责通信,父进程负责接受客户端的连接

2)接受来自不同客户端的消息,并将消息加工发送给对应的客户端

  • fork()
// pid_t 数据类型声明在如下头文件中
#include <sys/types.h>
// fork()原型声明在如下头文件中
#include <unistd.h>

/*
 * fork() 通过复制调用进程来创建一个新进程,子进程是父进程的一个副本
 * @return 若成功,返回值有效范围:0~32767;否则失败,返回值-1
 */
pid_t fork(void);

👉 任何子进程只有一个父进程,而且子进程总是可以通过 getppid() 获取父进程的 pid。但是一个父进程可以拥有n (n >= 0) 个子进程

fork() 调用一次,它却返回两次。原因是子进程对父进程一个(逻辑)拷贝,它在调用进程(即父进程)中返回一次,返回值是新派生进程(即子进程)的pid,在子进程中又返回一次,返回值是0。

代码中可以看到:

  pid = fork();
  if (pid == 0) // 子进程
  {
      close(listenfd);
      handle(connfd, client_socket);
      exit(1);
  }
  else if (pid < 0) // error
  {
      perror("fork error");
      close(connfd);
      exit(1);
  }
  else // 父进程
  {
      close(connfd);
  }

新的客户端由子进程提供服务,同时关闭父进程的监听套接字listenfd,父进程需要关闭已连接套接字connfd

question

父进程对connfd 调用close 没有终止它与客户的连接呢?
为了便于理解,我们必须知道每个文件或套接字都有一个引用计数。引用计数在文件表项中维护(APUE第58~59页),它是当前打开着的引用该文件或套接字的描述符的个数。socket 返回后与listenfd 关联的文件表项的引用计数值为1。accept 返回后与connfd 关联的文件表项的引用计数值也为1。然而fork 返回后,这两个描述符就在父进程与子进程间共享(也就是被复制),因此与这两个套接字相关联的文件表项各自的访问计数值均为2。这么一来,当父进程关闭connfd时,它只是把相应的引用计数值从2减为1,当子进程关闭listenfd时,它只是把相应的引用计数值从2减为1。该套接字真正的清理和资源释放要等到其引用计数值到达0时才发生。

fork() 的两个典型用法:

1)一个进程创建一个自身的副本,这样每个副本都可以在另一个副本执行其他任务的同时处理各自的某个操作。例如网络服务器

2)一个进程创建一个自身的副本,然后其中一个副本(通常为子进程)调用exec() 把自身替换为新的程序。例如shell程序

  • signal()
// signal()原型声明在如下头文件中
#include <signal.h>

typedef void (*sighandler_t)(int);

/*
 * @param signum 信号,可以根据对应信号,函数指针对信号进行处理
 * @param handler 函数指针
 * @return 返回值是一个函数指针
 */
sighandler_t signal(int signum, sighandler_t handler);

信号机制是进程之间相互传递消息的一种方法,信号全称为软中断信号,也有人称作软中断。从它的命名可以看出,它的实质和使用很象中断。所以,信号可以说是进程控制的一部分

随笔中:signal(SIGCHLD, sighandler); ,其中 SIGCHLD 表示:子进程状态发生变化。通过signal函数,一旦服务器和客户端通信异常,即可捕捉服务器中和客户端通信的子进程。

UNIX网络编程:socket & fork()多进程 实现clients/server通信

三、反思总结

服务器与多客户端通信,涉及到多进程的处理,子进程结束,如何监控子进程的pid。

👉 问题思考:随笔例子中,若服务器子进程结束,客户端是无感知的,仅当客户端往服务器再次发送数据才能得知服务器子进程结束。那么如何在服务器子进程一结束,客户端进程立马能知道呢?(待解决。。。)

UNIX网络编程:socket & fork()多进程 实现clients/server通信

UNIX网络编程:socket & fork()多进程 实现clients/server通信

UNIX网络编程:socket & fork()多进程 实现clients/server通信

UNIX网络编程:socket & fork()多进程 实现clients/server通信

四、参考引用

UNIX网络编程 卷1:套接字联网API 第3版

Unix网络编程学习笔记文章来源地址https://www.toymoban.com/news/detail-455817.html

到了这里,关于UNIX网络编程:socket & fork()多进程 实现clients/server通信的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Linux网络编程:socket实现client/server通信

    阅读 UNIX网络编程 卷1:套接字联网API 第3版 的前4个章节,觉得有必要对书籍上的源码案例进行复现,并推敲TCP的C/S通信过程。 📌 测试环境:CentOS7.6 x64 编译server.c 和 client.c gcc server.c -g -std=gnu99 -o server 和 gcc client.c -g -std=gnu99 -o client 运行测试: 📌 server.c仅仅实现对单个客户

    2024年02月03日
    浏览(27)
  • Unix 网络编程:Socket 状态图&编程参数

        Flags (9 bits) (aka Control bits) . Contains 9 1-bit flags NS (1 bit): ECN-nonce - concealment protection (experimental: see RFC 3540). CWR (1 bit): Congestion Window Reduced (CWR) flag is set by the sending host to indicate that it received a TCP segment with the ECE flag set and had responded in congestion control mechanism (added to header by RFC 31

    2024年02月02日
    浏览(25)
  • Linux网络编程:socket & pthread_create()多线程 实现clients/server通信

    UNIX网络编程:socket fork()多进程 实现clients/server通信 随笔介绍了通过fork()多进程实现了服务器与多客户端通信。但除了多进程能实现之外,多线程也是一种实现方式。 重要的是,多进程和多线程是涉及操作系统层次。随笔不仅要利用pthread_create()实现多线程编程,也要理解线

    2024年02月05日
    浏览(30)
  • Linux网络编程:Socket套接字编程(Server服务器 Client客户端)

    文章目录: 一:定义和流程分析 1.定义 2.流程分析  3.网络字节序 二:相关函数  IP地址转换函数inet_pton inet_ntop(本地字节序 网络字节序) socket函数(创建一个套接字) bind函数(给socket绑定一个服务器地址结构(IP+port)) listen函数(设置最大连接数或者说能同时进行三次握手的最

    2024年02月12日
    浏览(61)
  • 网络编程day1——进程间通信-socket套接字

            基本特征:socket是一种接口技术,被抽象了一种文件操作,可以让同一计算机中的不同进程之间通信,也可以让不同计算机中的进程之间通信(网络通信)         进程A                                                        进程B     创建socket对象

    2024年02月10日
    浏览(45)
  • 「网络编程」第二讲:socket套接字(四 - 完结)_ Linux任务管理与守护进程 | TCP协议通讯流程

    「前言」文章是关于网络编程的socket套接字方面的,上一篇是网络编程socket套接字(三),这篇续上篇文章的内容,下面开始讲解!  「归属专栏」网络编程 「主页链接」个人主页 「笔者」枫叶先生(fy) 「枫叶先生有点文青病」「句子分享」 Time goes on and on, never to an 

    2024年02月10日
    浏览(44)
  • 使用Netty实现Socket网络编程

    ** ** Netty支持多种网络通信模型,包括传统的阻塞I/O、非阻塞I/O、多路复用I/O和异步I/O。其中,非阻塞I/O和多路复用I/O是Netty的核心特性。 非阻塞I/O :Netty通过使用Java的NIO(New I/O)库,实现了非阻塞的I/O操作。这意味着当一个操作正在进行时,不会阻塞线程,线程可以继续处

    2024年01月16日
    浏览(30)
  • Socket编程接口API并实现简单的TCP网络编程

    #include sys/types.h #include sys/socket.h socket()创建套接字,成功返回套接字的文件描述符,失败返回-1 domain: 设置套接字的协议簇, AF_UNIX AF_INET AF_INET6 type: 设置套接字的服务类型 SOCK_STREAM SOCK_DGRAM protocol: 一般设置为 0,表示使用默认协议 int socket(int domain, int type, int protocol); bind()将

    2024年02月13日
    浏览(26)
  • Java网络编程-Socket实现数据通信

    本文主要是为下一篇Websockt做铺垫,大家了解socket的一些实现。 网络编程是指利用计算机网络进行程序设计、开发的技术。网络编程主要包含三个要素,分别是: IP地址和端口号 传输协议 Socket 在计算机网络中,每台计算机都有一个IP地址,用于唯一标识该计算机在网络中的

    2024年02月10日
    浏览(28)
  • 「网络编程」第二讲:网络编程socket套接字(三)_ 简单TCP网络通信程序的实现

    「前言」文章是关于网络编程的socket套接字方面的,上一篇是网络编程socket套接字(二),下面开始讲解!  「归属专栏」网络编程 「主页链接」个人主页 「笔者」枫叶先生(fy) 「枫叶先生有点文青病」「每篇一句」 I do not know where to go,but I have been on the road. 我不知

    2024年02月11日
    浏览(35)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包