linux网络编程-多线程实现TCP并发服务器

客户端跟服务端通信流程

服务端流程步骤

  1. socket函数创建监听套接字lfd;

  1. bind函数将监听套接字绑定ip和端口;

  1. listen函数将服务器设置为被动监听状态,同时创建一条未完成连接队列(没走完tcp三次握手流程的连接),和一条已完成连接队列(已完成tcp三次握手的连接);

  1. accept函数循环的从已完成连接的队列中提取连接,并返回一个新的套接字cfd跟客户端进行通信;

  1. pthread_create函数创建一个子线程,跟客户端进行通信;

  1. 子线程:read函数循环的从r缓冲区读取客户端发送的数据,write函数将要发送给客户端的数据写入w缓冲区;

  1. close函数关闭套接字;

客户端流程步骤

  1. socket函数创建套接字;

  1. connect函数连接服务器;

  1. write函数将要发送给服务端的数据写入w缓冲区,read函数从r缓冲区读取服务器发送给客户端的数据;

  1. close函数关闭套接字;

相关函数


#include <sys/socket.h>
int socket(int domain, int type, int protocol);
功能:创建一个用于网络通信的套接字文件描述符

参数:domain:协议族(AF_INET:ipv4,AF_INET6:ipv6,等等)
     type:套接字类型(SOCK_DGRAM:udp,SOCK_STREAM:tcp,等等)
     protocol:用于制定某个协议的特定类型,即type类型中的某个类型,通常不用管它,设置为0

返回值:成功则返回socket套接字描述符, 失败返回-1,并设置errno
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);
功能:将sockfd绑定ip和端口

参数:sockfd:套接字
     my_addr:存放有协议,ip,端口的结构体信息
     addrlen:my_addr结构体大小

返回值:成功返回0,失败返回-1,并设置errno
#include <sys/socket.h>
int listen(int s, int backlog);

功能:让服务器处于被动监听状态,同时创建了一条未完成三次握手的连接队列和一条已经完成三次握 手的连接队列

参数:s:套接字

backlog:支持未完成连接和已完成连接之和的最大值,一般设置128

返回值:成功返回0,失败返回-1,并设置errno
#include <sys/types.h>
#include <sys/socket.h>
int accept(int s, struct sockaddr *addr, socklen_t *addrlen);

功能:从已完成连接队列中提取客户端连接

参数:s:套接字
     addr:存放成功连接的客户端的ip,端口等信息结构体
     addrlen:存放addr结构体大小的变量地址

返回值:成功则返回一个非负整数标识这个连接套接字,是否返回-1
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);

功能:从文件描述符 fd 中读取 count 字节的数据并放入从 buf 开始的缓冲区中

参数:fd:文件描述符
     buf:缓冲区
     count:读count字节

返回值:成功时返回读取到的字节数,失败返回-1,并设置errno
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);

功能:向文件描述符fd所引用的文件中写入从buf开始的缓冲区中count字节的数据

参数:fd:文件描述符
     buf:缓冲区
     count:写count字节

返回值:功时返回所写入的字节数,失败返回-1,并设置errno
#include <unistd.h>
int close(int fd);

功能:关闭 一个文件描述符

参数:fd:要关闭的文件描述符

返回值:成功返回0,失败返回-1,并设置errno
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

功能:连接服务器

参数:sockfd:套接字
     addr:服务器的ip,端口等结构体信息
     addrlen:addr结构体大小

返回值:成功返回0,失败返回-1,并设置errno
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);

功能:创建子线程

参数:thread:线程id
     attr:线程属性,对属性没要求,默认的话,传NULL即可
     start_routine:线程回调函数
     arg:需要传进线程回调函数的参数

返回值:成功返回0,失败返回错误码
#include <pthread.h>
int pthread_detach(pthread_t thread);

功能:设置线程为分离态,线程退出时自己回收资源

参数:thread:线程id

返回值:成功返回0,失败返回错误码

服务端代码

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/socket.h>
#include <string.h>
#include <errno.h>

#define BUF_SIZE 256

typedef struct _clientinfo
{
    int fd;
    struct sockaddr_in client_addr;
}clientinfo;

void sys_err(const char *str, const int exitno)
{
    perror(str);
    exit(exitno);
}

void sys_errex(const char *str, const int exitno,  const int err)
{
    fprintf(stderr, "%s:%s\n", str, strerror(err));
    exit(exitno);
}

void *t_client(void *arg)
{
    clientinfo *client = (clientinfo *)arg;
    char buf[BUF_SIZE] = "";
    int size;
    char ip[INET_ADDRSTRLEN] = "";

    printf("client: %s, port = %d connect success\n", inet_ntop(AF_INET, &client->client_addr.sin_addr.s_addr, ip, sizeof(ip)), ntohs(client->client_addr.sin_port));

    while(1)
    {
        size = read(client->fd, buf, sizeof(buf));
        if (size < 0)
        {
            if (EINTR == errno) //被信号中断不能退出
                continue;

            sys_err("read error", 1);
        }
        else if (size == 0) //客户端退出
        {
            printf("client: %s, port = %d exit\n", ip, ntohs(client->client_addr.sin_port));
            break;
        }
        
        printf("client: %s, port = %d say: %s\n", ip, ntohs(client->client_addr.sin_port), buf);

        while(write(client->fd, buf, size) == -1)
        {
            if (EINTR == errno) //被信号中断不能退出
                continue;
            
            sys_err("write error", 1);
        }

        memset(buf, 0, sizeof(buf));
    }

    close(client->fd);
    free(client);

    printf("thread %lu exit success\n", pthread_self());
}

int main(int argc, char *argv[])
{
    if (argc < 2)
    {
        printf("%s port\n", argv[0]);
        exit(1);
    }

    //创建监听socket
    int lfd;
    int cfd;
    int ret;
    int optval = 1;
    struct sockaddr_in server_addr;
    struct sockaddr_in client_addr;
    socklen_t len = sizeof(client_addr);
    pthread_t tid;

    lfd= socket(AF_INET, SOCK_STREAM, 0);
    if (lfd < 0)
        sys_err("socket error", 1);

    //设置端口复用,解决 address already user 问题
    ret = setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, (const void *)&optval, sizeof(optval));
    if (ret < 0)
        sys_err("setsockopt error", 1);

    //绑定ip, 端口
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons((unsigned short)atoi(argv[1]));
    server_addr.sin_addr.s_addr = 0; //0表示将本机所有ip都绑定上
    ret = bind(lfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
    if (ret < 0)
        sys_err("bind error", 1);
    
    //监听
    ret = listen(lfd, 128); //指定未完成连接队列的最大长度为128
    if (ret < 0)
        sys_err("listen error", 1);
    
    printf("server start success\n");

    while(1)
    {
        cfd = accept(lfd, (struct sockaddr *)&client_addr, &len);
        if (cfd < 0)
        {
            if (ECONNABORTED == errno || EINTR == errno) //软件引起的连接中止或被信号中断,不能退出 
                continue;

            sys_err("accept error", 1);
        }
        
        clientinfo *p = malloc(sizeof(clientinfo));
        if (!p)
            fprintf(stderr, "malloc error\n");

        p->fd = cfd;
        p->client_addr = client_addr;

        ret = pthread_create(&tid, NULL, t_client, (void *)p);
        if (0 != ret)
            sys_errex("pthread_create error", 1, ret);
        ret = pthread_detach(tid); //设置为分离态,线程结束时自己回收资源
        if (0 != ret)
            sys_errex("pthread_detach error", 1, ret);
    }

    close(lfd);    

    return 0;
}

客户端代码

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

#define BUF_SIZE 256

int main(int argc, char *argv[])
{
    if (argc < 3)
    {
        printf("%s sever_ip server_port\n", argv[0]);
        exit(1);
    }

    //创建流式套接字
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0)
    {
        perror("socket");
        exit(1);
    }

    //连接服务器
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons((unsigned short)atoi(argv[2]));
    addr.sin_addr.s_addr = inet_addr(argv[1]);
    int ret = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
    
    if (ret < 0)
    {
        perror("connect");
        exit(1);
    }

    printf("connect server success\n");
    //读写数据
    char buf_w[BUF_SIZE] = "";
    char buf_r[BUF_SIZE] = "";
    ssize_t size_r = 0;
    while(1)
    {
        fgets(buf_w, sizeof(buf_w), stdin);
        buf_w[strlen(buf_w) - 1] = 0;
        write(fd, buf_w, strlen(buf_w));
        size_r = read(fd, buf_r, sizeof(buf_r));
        if (size_r == 0) //服务器断开
            break;
        else
            printf("%s\n", buf_r);

        memset(buf_w, 0, sizeof(buf_w));
        memset(buf_r, 0, sizeof(buf_r));
    }

    //关闭套接字
    close(fd);

    return 0;
}

结果

服务器启动,同时连上3个客户端


此时,ps -eLf查看LWP,服务端有4条线程(1条监听线程,3条与连接上的客户端通信的线程)


3个客户端分别发一条消息给服务器,服务器接收到,并发回到客户端


3个客户端都退出


此时,ps -eLf查看LWP,服务器只剩1条监听线程

ending😃

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 《Linux多线程服务器编程PDF》是一本关于在Linux系统下进行多线程服务器编程的电子书。它主要讲解了如何利用Linux多线程技术来搭建高性能的服务器应用程序。 这本书首先介绍了多线程编程的基本概念和原理,包括线程的创建与销毁、线程同步与互斥、线程调度等。然后,针对服务器应用开发过程中的常见问题,详细讲解了如何利用多线程解决这些问题,比如如何处理多个客户端的并发请求、如何实现高效的请求处理、如何解决资源竞争和线程安全等。 除了基础知识和问题解决方法,本书还深入探讨了多线程服务器的性能优化技巧。作者通过实例演示了如何利用线程池、异步IO和事件驱动等技术来提高服务器并发性能和响应速度。同时,还介绍了一些常用的性能监测工具和调优方法,帮助读者找出性能瓶颈并进行优化。 阅读这本书可以帮助读者掌握Linux多线程服务器编程的核心技术,从而设计和实现高性能、稳定性好的服务器应用程序。无论是对于专业开发人员还是对于对服务器编程感兴趣的学习者来说,都是一本非常实用的参考书籍。 ### 回答2: 《Linux多线程服务器编程PDF》是一本关于如何在Linux环境下进行多线程服务器编程的书籍。这本书主要介绍了如何利用Linux操作系统的特性来实现高效的多线程服务器程序。 在这本书中,首先会介绍多线程服务器编程的基础知识,包括线程和进程的概念,以及多线程编程的优势和挑战。然后,会详细讲解如何使用Linux提供的系统调用和库函数来创建和管理线程,以及如何利用线程之间的同步和互斥机制来实现高效的并发处理。 此外,书中还会介绍如何利用Linux提供的网络编程接口来实现基于TCP/IP协议的网络通信。它将介绍如何创建和管理套接字,以及如何使用套接字进行客户端和服务器之间的通信。同时,也会介绍如何使用多线程来处理多个客户端的请求,以及如何利用线程池来优化服务器的性能。 除了介绍基本的多线程服务器编程技术之外,这本书还会介绍一些高级的主题,如如何处理并发访问共享资源的问题,如何利用信号来实现进程间的通信,以及如何使用多进程和多线程的混合编程模型。 总之,这本《Linux多线程服务器编程PDF》是一本全面而实用的指南,适合对多线程服务器编程感兴趣的读者学习和参考。通过学习这本书,读者可以了解到在Linux环境下如何编写高效且稳定的多线程服务器程序,提高应对高并发访问的能力,并为实际项目开发提供指导和支持。 ### 回答3: 《Linux多线程服务器编程》是一本介绍在Linux环境下开发多线程服务器程序的PDF电子书。本书主要内容包括多线程编程的基础知识、线程间同步与通信、线程池的设计与实现多线程服务器的模型与架构等。这本书通过详细讲解多线程编程的原理、技巧和注意事项,帮助读者理解多线程服务器的基本概念和工作原理,掌握多线程编程的核心技术。 《Linux多线程服务器编程》首先介绍了多线程编程的基本概念和背景,包括线程的定义、线程的创建与销毁、线程的同步与互斥等。然后,书中详细讲解了多线程编程中常用的同步与通信机制,如互斥量、条件变量、信号量等,以及它们的具体应用场景和用法。 接着,本书介绍了线程池的设计与实现。线程池是一种常用的服务器编程模型,可以提高服务器的性能和可扩展性。本书详细讲解了线程池的设计原理、线程池的管理与调度算法,以及线程池在实际服务器程序中的应用。通过学习线程池的设计与实现,读者可以更好地理解服务器程序的并发模型和性能优化技巧。 最后,本书介绍了多线程服务器的模型与架构。通过实际案例和代码示例,读者可以了解到多线程服务器的常用设计模式和架构思想,如单线程多路复用模型、多线程非阻塞模型等。这些模型和思想可以帮助读者设计和开发高性能、稳定可靠的多线程服务器程序。 综上所述,《Linux多线程服务器编程》是一本非常实用和全面的指南,适合对Linux服务器编程感兴趣的读者学习和参考。无论是初学者还是有一定经验的程序员,都可以通过阅读本书,全面提升自己在多线程服务器编程方面的能力和水平。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值