Preface

Socket In the actual system program development , Very widely used , And very important . In practical applications, the server often needs to support multiple client connections , It is very important to implement the high concurrency server model . High concurrency servers handle a small number of network concurrent requests from a simple circular server model , Evolution to solution C10K,C10M High concurrency server model of the problem .

C/S framework

The server - The client , namely Client-Server(C/S) structure .C/S The structure usually has two layers . The server is responsible for data management , The client is responsible for the interaction with the user .

stay C/S In structure , The application is divided into two parts : The server part and the client part . The server part is the information and function shared by multiple users , Perform background services , Such as controlling the operation of shared database ; The client part is user specific , Responsible for performing front desk functions , In the error prompt 、 Online help and other aspects have powerful functions , And can freely switch between subroutines .
C/S framework
As shown in the figure above : This is based on socket to realize the function calling relationship between client and server ,socket API There is a lot of information , This article will not describe too much .

pthread Thread library :(POSIX)

pthread The thread library is Linux The next more commonly used thread library , About his usage and features, you can search related articles by yourself , The following is a brief introduction to its usage and compilation .

Thread ID

Threads have ID, But it's not the only system , It's the only one in the process environment that works .
The handle to the thread is pthread_t type , The type cannot be treated as an integer , It's a structure .
Here are two functions :

 The header file : <pthread.h> Prototype : int pthread_equal(pthread_t tid1, pthread_t tid2); Return value :  Equal return non 0,  Unequal return 0. explain :  Compare two threads ID Whether it is equal or not . The header file : <pthread.h> Prototype : pthread_t pthread_self(); Return value :  Returns the calling thread's thread ID.

Thread creation

Create a thread in execution , You can assign the thread what it needs to do ( Thread execution function ), This thread shares the resources of the process . Functions that create threads pthread_create()

 The header file : <pthread.h> Prototype : int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(start_rtn)(void), void *restrict arg); Return value :  Return on success 0,  Otherwise, the error number is returned . Parameters :tidp:  Point to the newly created thread ID The variable of ,  As the output of the function .attr:  Used to customize various thread properties , NULL Is the default property ( See below ).start_rtn:  A function pointer ,  The name of the function that starts execution for the thread . This function can return a void * Return value of type ,
The return value can also be of other types , And by the  pthread_join() obtain
arg:  The unique untype of a function (void) Pointer parameter ,  To transfer multiple parameters ,  It can be packaged in a structure .

compile

 because pthread My library is not linux The library of the system , So we need to add      -lpthread# gcc filename -lpthread  // By default gcc Use c library , To use additional libraries, select the library to use in this way 

Common network server models

This paper combines my own understanding , Mainly with TCP For example , This paper summarizes the implementation of several common network server models , And finally a simple command line chat room .

Single process loop

The principle of one line process loop is that the main process does not communicate with the client , Clients have to connect to the server first , The server accepts a client connection and reads data from the client , Then process and return the result to the client , Then accept the connection request from the next client .

advantage
The advantage of the single threaded loop model is simplicity 、 Easy to implement , There is no synchronization 、 Lock up these troubles , And there's no cost .

shortcoming

  1. Blocking model , Network request serial processing ;
  2. No use of multicore cpu The advantages of , Network request serial processing ;
  3. Cannot support multiple client connections at the same time ;
  4. Program serial operation , The server can't send and receive data at the same time .
     Insert picture description here

Single thread IO Reuse

linux Commonly used in high concurrency server epoll As IO Reuse mechanism . The thread will need to handle socket Read and write events are registered to epoll in , When there is a network IO occurs ,epoll_wait return , Threads check and handle incoming socket Request on .

advantage

  1. Implement a simple , Reduce lock overhead , Reduce thread switching overhead .

shortcoming

  1. Only single core can be used cpu,handle Too long will cause the whole service to hang up ;
  2. When the number of clients exceeds a certain number , The performance will degrade significantly ;
  3. Only for high IO、 Low computing ,handle Short processing time scenarios .

 Insert picture description here

Multithreading / Multi process

Multithreading 、 The main feature of multi process model is that each network request is made by one process / threading , Blocking system calls are used inside threads , On the function division of thread , It can be handled by a single thread accept Connect , The remaining threads handle specific network requests ( Receive package , Handle , Contract awarding ); You can also have multiple processes alone listen、accept network connections .

advantage :

1、 Implementation is relatively simple ;
2、 Make use of CPU Multi nuclear resources .

shortcoming :

1、 The thread is still blocked inside , Take an extreme example , If a thread is in handle In the business logic of sleep 了 , This thread also hangs .
 Insert picture description here

Multithreading / Multi process IO Reuse

Multithreading 、 Multi process IO Take the model , Each subprocess listens to the service , And they all use epoll Mechanism to handle the network requests of the process , Subprocesses accept() After that, the connected descriptor... Will be created , Then communicate with the client through the connected descriptor . This mechanism is suitable for high concurrency scenarios .

advantage :

  1. Support high concurrency .

shortcoming :

  1. Asynchronous programming is not intuitive 、 It's easy to make mistakes

 Insert picture description here

Multithreading partition IO role

Multithreading partition IO The main functions of roles are : One accept thread Handle new connection establishment ; One IO thread pool Deal with the network IO; One handle thread pool Process business logic . Use scenarios such as : Telemarketing applications ,thrift TThreadedSelectorServer.

advantage :

  1. Divide threads according to different functions , Each thread handles fixed functions , More efficient
  2. The number of threads can be configured according to the business characteristics to tune the performance

shortcoming :

  1. Inter thread communication needs to introduce lock overhead
  2. The logic is more complicated , It's hard to achieve

 Insert picture description here

Summary

The above describes the common network server model , also AIO、 coroutines , There are even other variants , I won't discuss it here . It's important to understand the problems faced in each scenario and the characteristics of each model , It is a good solution to design a solution that meets the application scenario .

Multithreaded concurrent server model

Next, we mainly discuss the multithreaded concurrent server model .

The code structure

The concurrent server code structure is as follows :

thread_func(){
 while(1) {
 recv(...);
 process(...);
 send(...);}close(...);}main(socket(...); 
bind(...);listen(...);while(1) { 
accept(...);pthread_create();}}

As can be seen from the above , The server is divided into two parts : The main thread 、 Sub thread .

The main thread

main Function is the main thread , Its main tasks are as follows :

  1. socket() Create a listening set of words ;
  2. bind() Bind port number and address ;
  3. listen() Turn on monitoring ;
  4. accept() Waiting for the client to connect ,
  5. When there is a client connection ,accept() A new socket will be created new_fd;
  6. The main thread creates child threads , And will new_fd Passed to a child thread .

Sub thread

  1. The subthread function is thread_func(), He passed new_fd Deal with all the communication tasks with the client .

Detailed steps of client connecting to server

Next, let's take a step-by-step look at the step-by-step instructions for the client to connect to the server .

1. The client connects to the server

  1. The server sets up a listening socket listen_fd, And initialization ;
  2. The client creates the socket fd1;
  3. client client1 Through socket fd1 Connect to the server listen_fd;

 Insert picture description here

2. The main thread creates the child thread thread1

  1. server received client1 After the connection request ,accpet Function returns a new socket newfd1;
  2. Back server And client1 Our communication depends on newfd1, Listening socket listen_fd Will continue to listen to other client connections ;
  3. Main thread pass pthead_create() Create a child thread thread1, And put newfd1 Pass to thread1;
  4. server And client1 Our communication depends on newfd1、fd1.
  5. client1 In order to be able to receive it in real time server Messages sent , And also be able to read data from the keyboard , Both operations are blocked , When there is no data, the process will sleep , So you have to create a subthread read_thread;
  6. client1 The main line of is responsible for reading data from the keyboard and sending it to , Sub thread read_thread In charge of from server Receive information .

 Insert picture description here

3. client2 Connect to server

  1. client client2 Create socket fd2;
  2. adopt connect Function connection server Of listen_fd;
     Insert picture description here

4. The main thread creates the child thread thread2

  1. server received client2 After the connection request ,accpet Function returns a new socket newfd2;
  2. Back server And client2 Our communication depends on newfd2, Listening socket listen_fd Will continue to listen to other client connections ;
  3. Main thread pass pthead_create() Create a child thread thread2, And put newfd2 Pass to thread2;
  4. server And client1 Our communication depends on newfd2、fd2.
  5. Again client2 In order to be able to receive it in real time server Messages sent , At the same time, you need to be able to read data from the keyboard. You must create a sub thread read_thread;
  6. client1 The main line of is responsible for reading data from the keyboard and sending it to , Sub thread read_thread In charge of from server Receive information .

 Insert picture description here

Visible from above , Every client connection server after ,server We need to create a special thread Responsible for communication with the client ; Every client and server They all have a fixed pair of fd Combinations are used to connect .

example

Okay , The theory is over , According to yikoujun's custom , He also inherited the teachings of his grandfather :talk is cheap,show you my code. No code , It's all theory writing .

The main functions of this example are described as follows :

  1. Multiple clients can connect to the server at the same time ;
  2. The client can send and receive data independently ;
  3. After the client sends data to the server , The server will return the data to the client intact .

Server side

/*********************************************
            Server program   TCPServer.c  
            official account : A bite of Linux
*********************************************/#include <stdio.h>#include <sys/types.h>#include <sys/socket.h>#include <arpa/inet.h>#include <errno.h>#include <string.h>#include <pthread.h>#include <stdlib.h>#define RECVBUFSIZE 2048void *rec_func(void *arg){int sockfd,new_fd,nbytes;char buffer[RECVBUFSIZE];int i;
new_fd = *((int *) arg);free(arg);while(1){if((nbytes=recv(new_fd,buffer, RECVBUFSIZE,0))==-1){fprintf(stderr,"Read Error:%s\n",strerror(errno));exit(1);}if(nbytes == -1){// Client error   Return value -1close(new_fd);break;}if(nbytes == 0){// The client actively disconnects , The return value is 0close(new_fd);break;}
buffer[nbytes]='\0';printf("I have received:%s\n",buffer);if(send(new_fd,buffer,strlen(buffer),0)==-1){fprintf(stderr,"Write Error:%s\n",strerror(errno));exit(1);}}}int main(int argc, char *argv[]){char buffer[RECVBUFSIZE];int sockfd,new_fd,nbytes;struct sockaddr_in server_addr;struct sockaddr_in client_addr;int sin_size,portnumber;char hello[]="Hello! Socket communication world!\n";
pthread_t tid;int *pconnsocke = NULL;int ret,i;if(argc!=2){fprintf(stderr,"Usage:%s portnumber\a\n",argv[0]);exit(1);}/* The port number is wrong , sign out */if((portnumber=atoi(argv[1]))<0){fprintf(stderr,"Usage:%s portnumber\a\n",argv[0]);exit(1);}/* The server side starts to set up socket The descriptor   sockfd For monitoring */if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)  
{fprintf(stderr,"Socket error:%s\n\a",strerror(errno));exit(1);}
 
/* Server side padding  sockaddr structure */ 
bzero(&server_addr,sizeof(struct sockaddr_in));
server_addr.sin_family     =AF_INET;/* Auto fill host IP*/
server_addr.sin_addr.s_addr=htonl(INADDR_ANY);// Get the network card address automatically
server_addr.sin_port       =htons(portnumber);
 
/* binding sockfd The descriptor */ 
if(bind(sockfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr))==-1){fprintf(stderr,"Bind error:%s\n\a",strerror(errno));exit(1);}
 
/* monitor sockfd The descriptor */if(listen(sockfd, 10)==-1){fprintf(stderr,"Listen error:%s\n\a",strerror(errno));exit(1);}while(1){/* The server is blocked , Until the client establishes the connection */
sin_size=sizeof(struct sockaddr_in);if((new_fd = accept(sockfd,(struct sockaddr *)&client_addr,&sin_size))==-1){fprintf(stderr,"Accept error:%s\n\a",strerror(errno));exit(1);}
pconnsocke = (int *) malloc(sizeof(int));*pconnsocke = new_fd;
ret = pthread_create(&tid, NULL, rec_func, (void *) pconnsocke);if (ret < 0) 
{perror("pthread_create err");return -1;}}//close(sockfd);exit(0);}

client

/*********************************************
            Server program   TCPServer.c  
            official account : A bite of Linux
*********************************************/#include <stdio.h>#include <sys/types.h>#include <sys/socket.h>#include <arpa/inet.h>#include <errno.h>#include <string.h>#include <pthread.h>#include <stdlib.h>#define RECVBUFSIZE 1024void *func(void *arg){int sockfd,new_fd,nbytes;char buffer[RECVBUFSIZE];
new_fd = *((int *) arg);free(arg);while(1){if((nbytes=recv(new_fd,buffer, RECVBUFSIZE,0))==-1){fprintf(stderr,"Read Error:%s\n",strerror(errno));exit(1);}
buffer[nbytes]='\0';printf("I have received:%s\n",buffer);}}int main(int argc, char *argv[]){int sockfd;char buffer[RECVBUFSIZE];struct sockaddr_in server_addr;struct hostent *host;int portnumber,nbytes;
pthread_t tid;int *pconnsocke = NULL;int ret;// The number of detection parameters if(argc!=3){fprintf(stderr,"Usage:%s hostname portnumber\a\n",argv[0]);exit(1);}//argv2  It's the port number  , Read the port , Convert to integer variables if((portnumber=atoi(argv[2]))<0){fprintf(stderr,"Usage:%s hostname portnumber\a\n",argv[0]);exit(1);}// Create a   A socket if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1){fprintf(stderr,"Socket Error:%s\a\n",strerror(errno));exit(1);}// Fill structure ,ip and port It has to be the server bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(portnumber);
server_addr.sin_addr.s_addr = inet_addr(argv[1]);//argv【1】  yes server ip Address /*¿Í»§³ÌÐò·¢ÆðÁ¬œÓÇëÇó*/ 
if(connect(sockfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr))==-1){fprintf(stderr,"Connect Error:%s\a\n",strerror(errno));exit(1);}// Create thread
pconnsocke = (int *) malloc(sizeof(int));*pconnsocke = sockfd;
ret = pthread_create(&tid, NULL, func, (void *) pconnsocke);if (ret < 0) 
{perror("pthread_create err");return -1;}while(1){#if 1printf("input msg:");scanf("%s",buffer);if(send(sockfd,buffer,strlen(buffer),0)==-1){fprintf(stderr,"Write Error:%s\n",strerror(errno));exit(1);}#endif}close(sockfd);exit(0);}

compile
Compile thread , Need to use pthread library , The compile command is as follows :

  1. gcc s.c -o s -lpthread
  2. gcc cli.c -o c -lpthread
    First, test the machine
  3. Open a terminal ./s 8888
  4. Open another terminal ./cl 127.0.0.1 8888, Enter a string "qqqqqqq"
  5. Open another terminal ./cl 127.0.0.1 8888, Enter a string "yikoulinux"
     Insert picture description here

Some readers may notice that ,server The following code is used to create a subthread :

 pconnsocke = (int *) malloc(sizeof(int));*pconnsocke = new_fd;
ret = pthread_create(&tid, NULL, rec_func, (void *) pconnsocke);if (ret < 0) 
{perror("pthread_create err");return -1;}

Why do we have to malloc A block of memory is dedicated to this new socket ?
This is a very hidden , A lot of novice mistakes . Next chapter , I'll give you a special explanation .

This series is expected to be updated 4-5 piece . The ultimate goal is to write a chat room with login, registration, public chat, private chat and other functions . If you like, please pay attention to .