0


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

使用多线程实现一对多的TCP服务器

一、前言

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

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

二、新增使用的API

2.1、pthread_create() 函数

pthread_create函数原型:

#include<pthread.h>intpthread_create(pthread_t*restrict tidp,//新创建的线程ID指向的内存单元。constpthread_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>voidpthread_exit(void*retval);

三、实现步骤

使用多线程方案,来一个连接请求则创建一个线程。
在这里插入图片描述

(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)绑定地址。

structsockaddr_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,(structsockaddr*)&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)接收连接。

structsockaddr_in client;memset(&client,0,sizeof(client));socklen_t len=sizeof(client);int clientfd=accept(listenfd,(structsockaddr*)&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>#defineLISTEN_PORT8888#defineBLOCK_SIZE10#defineBUFFER_LENGTH1024enumERROR_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);}intmain(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.structsockaddr_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,(structsockaddr*)&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);structsockaddr_in client;socklen_t len=sizeof(client);int clientfd=-1;while(1){// 4.memset(&client,0,sizeof(client));
        clientfd=accept(listenfd,(structsockaddr*)&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);return0;}

编译:

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>#defineBUFFER_LENGTH1024enumERROR_CODE{
    SOCKET_CREATE_FAILED=-1,
    SOCKET_CONN_FAILED=-2,
    SOCKET_LISTEN_FAILED=-3,
    SOCKET_ACCEPT_FAILED=-4};intmain(int argc,char** argv){if(argc<3){printf("Please enter the server IP and port.");return0;}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;}structsockaddr_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,(structsockaddr*)&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);return0;}

编译:

gcc -o client client.c

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

在这里插入图片描述

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

小结

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

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

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

在这里插入图片描述


本文转载自: https://blog.csdn.net/Long_xu/article/details/135140837
版权归原作者 Lion Long 所有, 如有侵权,请联系我们删除。

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

还没有评论