TCP服务器的演变过程:揭秘使用多线程实现一对多的TCP服务器

这篇具有很好参考价值的文章主要介绍了TCP服务器的演变过程:揭秘使用多线程实现一对多的TCP服务器。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、前言

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

为了避免篇幅过长使读者感到乏味,对【TCP服务器的开发】进行分阶段实现,一步步进行优化升级。本节在上一章节的基础上,添加多线程,为每个新接入的客户端分配线程,实现一个服务器程序处理多个客户端连接。

二、新增使用的API

2.1、pthread_create() 函数

pthread_create函数原型:

#include <pthread.h>
int pthread_create(
                 pthread_t *restrict tidp,   			//新创建的线程ID指向的内存单元。
                 const pthread_attr_t *restrict attr,		//线程属性
                 void *(*start_rtn)(void *), 			//线程函数的地址
                 void *restrict arg 				//线程函数所需的参数
                  );

2.2、pthread_exit()函数

函数pthread_exit()终止调用线程,并通过retval返回一个值,该值(如果线程是可连接的)可用于调用pthread_join()的同一进程中的另一个线程。

pthread_cleanup_push()建立的任何尚未弹出的清理处理程序都会弹出(按与推送顺序相反的顺序)并执行。如果线程有任何特定于线程的数据,那么在执行清理处理程序之后,将以未指定的顺序调用相应的析构函数。

当线程终止时,进程共享资源(例如,互斥锁、条件变量、信号量和文件描述符)不会被释放,并且使用atexit()注册的函数也不会被调用。

在进程中的最后一个线程终止后,进程终止为调用atexit(),出口状态为零;因此,进程共享资源被释放,并且使用atexit()注册的函数被调用。

函数原型:

#include <pthread.h>

void pthread_exit(void *retval);

三、实现步骤

使用多线程方案,来一个连接请求则创建一个线程。
TCP服务器的演变过程:揭秘使用多线程实现一对多的TCP服务器,Linux网络设计,tcp/ip,服务器,网络协议,网络,c语言,开发语言

(1)创建socket。

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

(4)接收连接。

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)为每个连接创建线程。

pthread_t thread;
if(0!=pthread_create(&thread,NULL,routine,&clientfd))
{
     printf("pthread_create failed");

}

(6)在线程函数里面接收数据。

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)在线程函数里面发送数据。

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

(8)关闭文件描述符。

close(listenfd);

四、完整代码

#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(void *arg)
{
    int clientfd=*((int*)arg);
    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);
        pthread_t thread;
        if(0!=pthread_create(&thread,NULL,routine,&clientfd))
        {
            printf("pthread_create failed");
            break;
        }
    }

    
    close(listenfd);

    return 0;
}

编译:

gcc -o server server.c -lpthread

注意,由于使用了多线程,在编译时要加上 -lpthread 连接多线程库。

五、TCP客户端

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

自己实现一个TCP客户端连接TCP服务器的代码,主要是连接服务器,发送数据、接收数据功能:

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

编译:

gcc -o client client.c

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

TCP服务器的演变过程:揭秘使用多线程实现一对多的TCP服务器,Linux网络设计,tcp/ip,服务器,网络协议,网络,c语言,开发语言

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

小结

这里基于上一个章节的内容进行了升级,由原来的一对一连接通信升级为一对多连接通信,可以接受多个客户端同时连接,并为每个连接分配一个线程。

但是,使用多线程会有一个瓶颈,受CPU的核心数和内存大小线程,一台机器可以并发的线程数量是有限的,内存大小也是有限的;这个模式下1024个线程基本就是一台服务器机器的极限。

除了可以使用多线程实现一对多发服务外,还可以使用多进程来实现这个功能。这个我们将在下一个章节进行实现。

TCP服务器的演变过程:揭秘使用多线程实现一对多的TCP服务器,Linux网络设计,tcp/ip,服务器,网络协议,网络,c语言,开发语言文章来源地址https://www.toymoban.com/news/detail-762231.html

到了这里,关于TCP服务器的演变过程:揭秘使用多线程实现一对多的TCP服务器的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • TCP服务器的演变过程:IO多路复用机制select实现TCP服务器

    手把手教你从0开始编写TCP服务器程序,体验开局一块砖,大厦全靠垒。 为了避免篇幅过长使读者感到乏味,对【TCP服务器的开发】进行分阶段实现,一步步进行优化升级。 本节,在上一章节的基础上,将并发的实现改为IO多路复用机制,使用select管理每个新接入的客户端连

    2024年02月03日
    浏览(55)
  • 【TCP服务器的演变过程】编写第一个TCP服务器:实现一对一的连接通信

    手把手教你从0开始编写TCP服务器程序,体验 开局一块砖,大厦全靠垒 。 为了避免篇幅过长使读者感到乏味,对【TCP服务器的开发】进行分阶段实现,一步步进行优化升级。 函数原型: 这个函数建立一个协议族、协议类型、协议编号的socket文件描述符。如果函数调用成功,

    2024年02月03日
    浏览(51)
  • 多线程并发服务器(TCP)

    服务器       客户端     结果    

    2024年02月12日
    浏览(40)
  • TCP服务器实现—多进程版,多线程版,线程池版

    目录 前言 1.存在的问题 2.多进程版 3.多线程版 4.线程池版 总结         在上一篇文章中使用TCP协议实现了一个简单的服务器,可以用来服务端和客户端通信,但是之前的服务器存在一个问题,就是当有多个客户端连接服务器的时候,服务器只能和一个客户端通信,其它的客

    2024年02月12日
    浏览(46)
  • 【Linux后端服务器开发】封装线程池实现TCP多线程通信

    目录 一、线程池模块 Thread.h LockGuard.h ThreadPool.h 二、任务模块模块 Task.h 三、日志模块 Log.h 四、守护进程模块 Deamon.h  五、TCP通信模块 Server.h Client.h server.cpp client.cpp 关于TCP通信协议的封装,此篇博客有详述: 【Linux后端服务器开发】TCP通信设计_命运on-9的博客-CSDN博客 线程池

    2024年02月16日
    浏览(42)
  • 简单的TCP网络程序·线程池(后端服务器)

    目录 版本四:线程池 注意事项 文件:Task.hpp -- 任务单独为一个文件 组件:日志修改 新函数:vprintf() 可变参数的提取逻辑 vfprintf()的工作原理 初始化一个va_list 日志准备 获取时间小知识 日志初版 日志启动测试 TCP通用服务器(守护进程) * 新指令1:jobs -- 查看进程作业 新指令

    2024年02月09日
    浏览(42)
  • Qt多线程TCP服务器客户端传输文件

    TCP是面向连接的运输层协议。应用程序在使用TCP协议之前,必须先建立TCP连接。在传送数据完毕后,必须释放已经建立的TCP连接。 每一条TCP连接只能有两个端点,每一条TCP连接只能是点对点的(一对一)。 TCP提供可靠交付的服务。通过TCP 连接传送的数据,无差错、不丢失、不

    2024年02月13日
    浏览(53)
  • 互联网编程之多线程/线程池TCP服务器端程序设计

    目录 需求 多线程TCP服务器 线程池TCP服务器 测试 日志模块 多线程TCP服务器(30分): 设计编写一个TCP服务器端程序,需使用多线程处理客户端的连接请求。客户端与服务器端之间的通信内容,以及服务器端的处理功能等可自由设计拓展,无特别限制和要求。 线程池TCP服务器

    2024年02月11日
    浏览(40)
  • QT下的多线程TCP客户端和服务器

    qt下的QTcpSocket在同一个线程使用时没有问题的,但是如果进行跨线程,很容易出现问题。那么有什么方法可以跨线程进行使用吗? 答案是肯定的:使用QThread的movetothread可以完成扩线程接收。 首先是基于QTcpSocket的类 头文件tcpsocket.h 然后是cpp文件tcpsocket.cpp 再次基础上,创建

    2024年01月17日
    浏览(46)
  • Qt实现简易的多线程TCP服务器(附源码)

    目录 一.UI界面的设计 二.服务器的启动 三.实现自定义的TcpServer类 1.在widget中声明自定义TcpServer类的成员变量 2.在TcpServer的构造函数中对于我们声明的m_widget进行初始化,m_widget我们用于后续的显示消息等,说白了就是主界面的更新显示等 四.实现自定义的TcpSocket类 1.TcpSocket.

    2024年04月17日
    浏览(77)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包