Linux网络编程:socket & pthread_create()多线程 实现clients/server通信

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

一、问题引入

UNIX网络编程:socket & fork()多进程 实现clients/server通信 随笔介绍了通过fork()多进程实现了服务器与多客户端通信。但除了多进程能实现之外,多线程也是一种实现方式。

重要的是,多进程和多线程是涉及操作系统层次。随笔不仅要利用pthread_create()实现多线程编程,也要理解线程和进程的区别。

二、解决过程

client 代码无需修改,请参考 Linux网络编程:socket & fork()多进程 实现clients/server通信

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 <errno.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <sys/syscall.h>

#define IP "10.8.198.227"
#define PORT 8887
#define gettid() syscall(__NR_gettid)
#define PTHREAD_MAX_SIZE 3    // 允许最大客户端连接数

typedef struct PTHREAD_DATA_ST
{
    pthread_t pthread_id;
    int connfd;
    char socket[128];
    struct sockaddr_in cliaddr;
}PTHREAD_DATA_ST;

//static int g_pthread_num = 3; // 允许最大客户端连接数
static struct PTHREAD_DATA_ST g_pthread_data[PTHREAD_MAX_SIZE];

static void pthread_data_index_init(void)
{
    for (int i = 0; i < PTHREAD_MAX_SIZE; i++)
    {
        memset(&g_pthread_data[i], 0 , sizeof(struct PTHREAD_DATA_ST));
        g_pthread_data[i].connfd = -1;
    }
}

static int pthread_data_index_find(void)
{
    int i;
    for (i = 0; i < PTHREAD_MAX_SIZE; i++)
    {
        if (g_pthread_data[i].connfd == -1)
            break;
    }
    return i;
}

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;
}

void *pthread_handle(void *arg)
{
    struct PTHREAD_DATA_ST *pthread = (struct PTHREAD_DATA_ST *)arg;
    int connfd = pthread->connfd;
    int recv_len, send_len;
    pid_t tid = gettid();
    char read_buf[1024], write_buf[1024];
    while (1)
    {
        memset(read_buf, 0, sizeof(read_buf));
        memset(write_buf, 0, sizeof(write_buf));
        recv_len = read(connfd, read_buf, sizeof(read_buf));
        if (recv_len <= 0)
        {
            printf("%s close, child %d terminated\n", pthread->socket, tid);
            close(connfd);
            pthread->connfd = -1;
            pthread_exit(NULL);
        }
        printf("%s:%s(%d Byte)\n", pthread->socket, read_buf, recv_len);
        send_len = string_toupper(read_buf, strlen(read_buf), write_buf);
        write(connfd, write_buf, send_len);
        if (strcmp("exit", read_buf) == 0)
        {
            printf("%s exit, child %d terminated\n", pthread->socket, tid);
            close(connfd);
            pthread->connfd = -1;
            pthread_exit(NULL);
        }
    }
}

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 idx;
    int opt = 1;

    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);
    }
    // 设置端口复用
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); 
    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);
    }

    pthread_data_index_init();
    while (1)
    {
        // 接受来自客户端的信息
        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);
            }
        }

        idx = pthread_data_index_find();
        if (idx == PTHREAD_MAX_SIZE)
        {
            printf("client connected upper limit, refused connect\n");
            close(connfd);
            continue;
        }

        memset(&client_socket, 0, sizeof(client_socket));
        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));
        g_pthread_data[idx].connfd = connfd;
        g_pthread_data[idx].cliaddr = client_addr;
        strcpy(g_pthread_data[idx].socket, client_socket);
        pthread_create(&(g_pthread_data[idx].pthread_id), NULL, pthread_handle, &(g_pthread_data[idx]));
        pthread_detach(g_pthread_data[idx].pthread_id);
    }
    close(listenfd);
    return EXIT_SUCCESS;
}

2-2 编译运行

📌 编译server.c时,出现 对‘pthread_create’未定义的引用

原因:pthread.h库不是linux系统的默认库,添加编译参数:-lpthread 即可 (或 pthread 也行)

  • 编译源文件

gcc server.c -g -std=gnu99 -lpthread -o server

Linux网络编程:socket & pthread_create()多线程 实现clients/server通信

  • client 1 和client 2连接server

Linux网络编程:socket & pthread_create()多线程 实现clients/server通信

  • client 1 断开连接

Linux网络编程:socket & pthread_create()多线程 实现clients/server通信

  • client 2 断开连接

Linux网络编程:socket & pthread_create()多线程 实现clients/server通信

👉 在server未设置端口复用,server主动断开,重启报错: bind error: Adress already in use

Linux网络编程:socket & pthread_create()多线程 实现clients/server通信

解决方法:

int opt = 1;
// 设置端口复用
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

2-3 多线程处理

  • 线程id

👉 同一进程下的进程号pid相同,各个线程的线程号tid不相同

在linux中无法直接使用gettid()获取线程id,正确解决办法在源文件中添加如下宏:

#include <sys/syscall.h>
#define gettid() syscall(__NR_gettid)
  • 多线程

通过宏 PTHREAD_MAX_SIZE 定义线程最大数量,毕竟计算机资源是有限的。

accept()成功返回,此时某一个client已经完成TCP三次握手,接下来可以准备创建线程通信。但不能无脑创建线程,应检测当前已创建的线程数量,只有未超越线程最大数量才能创建线程。

1、父线程负责监听新的客户端连接,并创建新的线程

2、子线程负责和客户端进行通信

👉 注意:使用多线程要将子线程设置为分离属性, 让线程在退出之后自己回收资源

// pthread_create(), pthread_detach(), pthread_exit()原型声明在如下头文件中
#include <pthread.h>

/*
 * @param *thread 新线程创建成功后的线程指针
 * @param *attr 创建线程时,设置属性。默认可设置为NULL
 * @param *(*start_routine) (void *) 线程运行函数指针
 * @param *arg 线程运行函数指针的形参
 * @return 若成功,返回值为0,否则失败
 */
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                    void *(*start_routine) (void *), void *arg);

/* pthread_detact()在线程结束后,回收资源。
 * @param thread 新线程创建成功后的线程指针
 * @return 若成功,返回值为0,否则失败
 */
int pthread_detach(pthread_t thread);

//Compile and link with -pthread.

线程调用 描述
pthread_create 创建一个新线程
pthread_exit 结束调用的线程
pthread_join 等待一个特定的线程退出
pthread_detach 线程进入脱离状态
pthread_self 获取自身的线程ID
pthread_yield 释放CPU来运行另外一个线程
pthread_attr_init 创建并初始化一个线程的属性结构
pthread_attr_destroy 删除(销毁)一个线程的属性结构

三、反思总结

3-1 进程

在进程模型中,计算机上所有可运行的软件,通常也包括操作系统,被组织成若干顺序进程,简称进程(process)。

进程是资源(CPU、内存等)分配的基本单位,它是程序执行时的一个实例。

3-2 线程

线程是程序执行时的最小单位,它是进程的一个执行流,是CPU调度和分派的基本单位,一个进程可以由一个或多个线程组成(一般有且只有一个主线程),线程间共享进程的所有资源,每个线程有自己的堆栈和局部变量。

四、参考引用

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

现代操作系统(第四版)

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

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

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

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

相关文章

  • 【Linux网络】网络编程套接字 -- 基于socket实现一个简单UDP网络程序

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

    2024年02月12日
    浏览(38)
  • Linux pthread_create源码分析

    本文介绍pthread_create函数的使用和源码分析。 /include/pthread.h bionic/libc/bionic/pthread_create.cpp bionic/libc/bionic/pthread_attr.cpp Android中的绝大部分线程,最后都是通过pthread_create创建的。 pthread_t *thread: 传递一个 pthread_t 类型的指针变量,也可以直接传递某个 pthread_t 类型变量的地址。

    2024年02月07日
    浏览(25)
  • Linux网络编程:socket、客户端服务器端使用socket通信(TCP)

    socket(套接字),用于网络中不同主机间进程的通信。 socket是一个伪文件,包含读缓冲区、写缓冲区。 socket必须成对出现。 socket可以建立主机进程间的通信,但需要协议(IPV4、IPV6等)、port端口、IP地址。          (1)创建流式socket套接字。                 a)此s

    2024年02月11日
    浏览(43)
  • 【探索Linux】—— 强大的命令行工具 P.26(网络编程套接字基本概念—— socket编程接口 | socket编程接口相关函数详细介绍 )

    本文将深入探讨使用套接字进行网络通信的基本步骤,包括创建套接字、绑定地址、监听连接(对于服务器端)、连接远程主机(对于客户端)、以及发送和接收数据等操作。套接字编程涉及一系列系统调用和函数,如 socket() 、 bind() 、 listen() 、 connect() 、 send() 、 recv() 等。

    2024年03月10日
    浏览(59)
  • Linux网络编程:socket & fork实现clients/server通信

    UNIX网络编程:socket实现client/server通信 随笔简单介绍了TCP Server服务单客户端的socket通信,但是并未涉及多客户端通信。 对于网络编程肯定涉及到多客户端通信和并发编程 (指在同时有大量的客户链接到同一服务器),故本随笔补充这部分知识。 而且并发并发编程涉及到多进程

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

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

    2024年02月12日
    浏览(62)
  • Linux学习之网络编程2(socket,简单C/S模型)

    Linux网络编程我是看视频学的,Linux网络编程,看完这个视频大概网络编程的基础差不多就掌握了。这个系列是我看这个Linux网络编程视频写的笔记总结。 小端法:pc本地存储,高位存高地址,低位存低地址。 大端法:网络存储,高位存低地址,低位存高地址。 由此我们看到

    2024年02月01日
    浏览(35)
  • C/C++ Linux Socket网络编程 TCP 与 UDP

    之前已经学习了QT的socket编程 和 C/C++在window环境的socket编程,现在再来学习一波C/C++在Linux环境下的socket编程,为以后学习C++ Linux 服务器开发做准备。 目录 一、Socket简介 二、Socket编程基础 1. 网络字节序 2. sockaddr数据结构 3. IP地址转换函数 三、TCP编程函数 1. socket函数 2. bi

    2024年02月02日
    浏览(42)
  • Linux下网络编程(3)——socket编程实战,如何构建一个服务器和客户端连接

            经过前几篇的介绍,本文我们将进行编程实战,实现一个简单地服务器和客户端应用程序。 编写服务器程序          编写服务器应用程序的流程如下:         ①、调用 socket()函数打开套接字,得到套接字描述符;         ②、调用 bind()函数将套接字

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

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

    2024年02月11日
    浏览(52)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包