Linux网络编程:socket & fork实现clients/server通信

这篇具有很好参考价值的文章主要介绍了Linux网络编程: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

Linux网络编程:socket & fork实现clients/server通信

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

Linux网络编程:socket & fork实现clients/server通信

2、client 2 连接 server

Linux网络编程:socket & fork实现clients/server通信

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

Linux网络编程:socket & fork实现clients/server通信

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

Linux网络编程:socket & fork实现clients/server通信

Linux网络编程:socket & fork实现clients/server通信

4、client 2断开与 server的连接

Linux网络编程:socket & fork实现clients/server通信

Linux网络编程: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函数,一旦服务器和客户端通信异常,即可捕捉服务器中和客户端通信的子进程。

Linux网络编程:socket & fork实现clients/server通信

三、反思总结

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

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

Linux网络编程:socket & fork实现clients/server通信

Linux网络编程:socket & fork实现clients/server通信

Linux网络编程:socket & fork实现clients/server通信

Linux网络编程:socket & fork实现clients/server通信

四、参考引用

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

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

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

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

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

相关文章

  • UNIX网络编程: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月06日
    浏览(49)
  • Linux网络编程:Socket套接字编程(Server服务器 Client客户端)

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

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

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

    2024年02月06日
    浏览(55)
  • Linux socket网络编程实战(tcp)实现双方聊天

    在上节已经系统介绍了大致的流程和相关的API,这节就开始写代码! 回顾上节的流程: 创建一个NET文件夹 来存放网络编程相关的代码: 这部分先实现服务器的连接部分的代码并进行验证 server1.c: 代码验证: 先编译并运行这部分代码: 可见,此时没有客户端进行连接,程

    2024年02月03日
    浏览(51)
  • 【Linux网络】网络编程套接字 -- 基于socket实现一个简单UDP网络程序

    我们把数据从A主机发送到B主机,是目的吗?不是,真正通信的不是这两个机器!其实是这两台机器上面的软件(人) 数据有 IP(公网) 标识一台唯一的主机 ,用谁来标识各自主机上客户或者服务进程的唯一性呢? 为了更好的表示一台主机上服务进程的唯一性,我们采用 端口号

    2024年02月12日
    浏览(159)
  • Linux网络编程:Socket服务器和客户端实现双方通信

    目录 一,什么是网络编程 二,为什么使用端口号 三,TCP协议与UDP协议 ①TCP(传输控制协议) ②UDP(用户数据报协议,User Data Protocol) ③总结归纳 四,Socket服务器和客户端的开发流程 五,服务器和客户端相关API说明 ①socket()函数 ②bind()函数 ③listen()函数 ④accept()函数 ⑤客户端

    2024年02月11日
    浏览(71)
  • 【实战项目】网络编程:在Linux环境下基于opencv和socket的人脸识别系统--C++实现

    这里我们会实现一个项目:在linux操作系统下基于OpenCV和Socket的人脸识别系统。 目录 🌞前言 🌞一、项目介绍 🌞二、项目分工 🌞三、项目难题 🌞四、实现细节 🌼4.1 关键程序 🌼4.2 运行结果 🌞五、程序分析 🌷5.1 wkcv.link 🌷5.2 客户端client.cpp 🌷5.3 服务端server.cpp 项目

    2024年03月12日
    浏览(60)
  • 【socket】从计算机网络基础到socket编程——Windows && Linux C语言 + Python实现(TCP+UDP)

    简单讲一下基础知识,便于后面代码的理解,建议大概浏览一下这一小节内容。这里讲的只是冰山一角,建议大家学习计算机网络相关知识,推荐几本书: 《计算机网络》(谢希仁) 《计算机网络 自顶向下方法》 《计算机网络技术》 《计算机网络基础及应用》 《Linux C从入

    2024年02月08日
    浏览(60)
  • Linux网络编程- 原始套接字(Raw Socket)

    原始套接字(Raw Socket)提供了一种机制,允许应用程序直接访问底层传输协议,绕过操作系统提供的传输层接口。这种套接字通常用于实现新的协议或对现有协议进行低级别的操作。 以下是对原始套接字的详细介绍: 定义与用途 : 原始套接字是直接基于网络层(如IP)的。

    2024年02月07日
    浏览(56)
  • 使用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日
    浏览(48)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包