c++ network programming (9) Super detailed tutorial on multi-threading under TCP/IP LINUX/windows and multi-threading server implementation

Original author: aircraft

Original link: https://www.cnblogs.com/DOMLX/p/9661012.html

 Let’s talk about Linux first (you can jump directly to the following for windows):

1. Basic concepts of threads

We talked about the multi-process server earlier, but we know that it is very expensive, so we introduced threads. We can think of it as a lightweight process. It has the following advantages compared to processes:

  • Thread creation and context switching are less expensive and faster.
  • No special techniques are required to exchange data between threads.

Process: A unit that constitutes a separate execution flow in the operating system. 
Thread: A unit that forms a separate execution flow in a process. 
Their inclusion relationship is, operating system > process > thread. The specific difference between processes and threads is actually this. Each process has an independent and complete memory space, which includes the global data area, heap area, and stack area. The reason why multi-process servers are expensive is just to distinguish the memory space in the stack area. Different function flows are executed and the data area, heap area, and stack area memory are all copied. Multi-threading is much more efficient. It only separates the stack area, and the data area and heap area in the process are shared. The specific memory structure example is as follows: 
Write picture description here


Write picture description here

2. Create threads

The following program, we can use to create a thread:

#include <pthread.h>
pthread_create (thread, attr, start_routine, arg)

Here, pthread_create  creates a new thread and makes it executable. The following is a description of the parameters:

parameter describe
thread Pointer to the thread identifier.
attr An opaque properties object that can be used to set thread properties. You can specify a thread properties object or use the default value of NULL.
start_routine The starting address of the thread running function, which will be executed once the thread is created.
arg Parameters to run the function. It must be passed by casting the reference as a pointer to void type. If no parameters are passed, NULL is used.

When the thread is successfully created, the function returns 0. If the return value is not 0, the thread creation fails.

Terminate thread

Using the following program, we can use it to terminate a thread:

#include <pthread.h>
pthread_exit (status)

Here, pthread_exit  is used to exit a thread explicitly. Normally, the pthread_exit() function is called when the thread has completed its work and no longer needs to continue to exist.

If main() ends before the thread it created and exits via pthread_exit(), other threads will continue executing. Otherwise, they will be automatically terminated at the end of main().

Example

The following simple example code uses the pthread_create() function to create 5 threads, each thread outputs "Hello Runoob!":

#include <iostream> 
// Required header file 
#include <pthread.h> 
 
using namespace std; 
 
#define NUM_THREADS 5 
 
// Thread running function 
void* say_hello(void* args) 
{ 
    cout << "Hello Runoob!" < < endl; 
    return 0; 
} 
 
int main() 
{ 
    // Define the thread's id variable, multiple variables use the array 
    pthread_t tids[NUM_THREADS]; 
    for(int i = 0; i < NUM_THREADS; ++i) 
    { 
        //Parameters In order: created thread id, thread parameters, called function, passed in function parameters 
        int ret = pthread_create(&tids[i], NULL, say_hello, NULL); 
        if (ret != 0) 
        { 
           cout << "pthread_create error:error_code=" << ret << endl;
        }
    }
    //Wait until each thread exits before the process ends. Otherwise, the process is forcibly ended and the thread may not react yet; 
    pthread_exit(NULL); 
}

The result after compiling and running under Linux is:

Hello Runoob!

Hello Runoob!

Hello Runoob!

Hello Runoob!

Hello Runoob!

The following simple example code uses the pthread_create() function to create 5 threads and receive the passed parameters. Each thread prints a "Hello Runoob!" message and outputs the received parameters, and then calls pthread_exit() to terminate the thread.

//File name: test.cpp 
 
#include <iostream> 
#include <cstdlib> 
#include <pthread.h> 
 
using namespace std; 
 
#define NUM_THREADS 5 
 
void *PrintHello(void *threadid) 
{   
   // Perform on the incoming parameters Forced type conversion, from an untyped pointer to an integer pointer, and then read 
   int tid = *((int*)threadid); 
   cout << "Hello Runoob! Thread ID, " << tid << endl; 
   pthread_exit( NULL); 
} 
 
int main () 
{ 
   pthread_t threads[NUM_THREADS]; 
   int indexes[NUM_THREADS];//Use an array to save the value of i 
   int rc; 
   int i; 
   for( i=0; i < NUM_THREADS; i++ ){       
      cout << "main(): Create thread," << i << endl; 
      indexes[i] = i; //Save the value of i first
      // When passing in, it must be cast to void* type, that is, an untyped pointer         
      rc = pthread_create(&threads[i], NULL, 
                          PrintHello, (void *)&(indexes[i])); 
      if (rc){ 
         cout << "Error: Unable to create thread," << rc << endl; 
         exit(-1); 
      } 
   } 
   pthread_exit(NULL); 
}

The result after compiling and running under Linux is:

main() : Create thread, 0 
main() : Create thread, 1 
Hello Runoob! thread ID, 0 
main() : Create thread, Hello Runoob! thread ID, 21 

main() : Create thread, 3 
Hello Runoob! thread ID , 2 
main(): Create thread, 4 
Hello Runoob! Thread ID, 3
 

Pass parameters to thread

This example demonstrates how to pass multiple parameters through a structure. You can pass any data type in the thread callback since it points to void, as shown in the following example:

#include <iostream>
#include <cstdlib>
#include <pthread.h>
 
using namespace std;
 
#define NUM_THREADS     5
 
struct thread_data{
   int  thread_id;
   char *message;
};
 
void *PrintHello(void *threadarg)
{
   struct thread_data *my_data;
 
   my_data = (struct thread_data *) threadarg;
 
   cout << "Thread ID : " << my_data->thread_id ;
   cout << " Message : " << my_data->message << endl;
 
   pthread_exit(NULL);
}
 
int main ()
{
   pthread_t threads[NUM_THREADS];
   struct thread_data td[NUM_THREADS];
   int rc;
   int i;
 
   for( i=0; i < NUM_THREADS; i++ ){
      cout <<"main() : creating thread, " << i << endl;
      td[i].thread_id = i;
      td[i].message = (char*)"This is message";
      rc = pthread_create(&threads[i], NULL,
                          PrintHello, (void *)&td[i]);
      if (rc){
         cout << "Error:unable to create thread," << rc << endl;
         exit(-1);
      }
   }
   pthread_exit(NULL);
}

The result after compiling and running under Linux is:

main() : creating thread, 0
main() : creating thread, 1
Thread ID : 0 Message : This is message
main() : creating thread, Thread ID : 21
 Message : This is message
main() : creating thread, 3
Thread ID : 2 Message : This is message
main() : creating thread, 4
Thread ID : 3 Message : This is message
Thread ID : 4 Message : This is message
 
 

Connect and detach threads

We can connect or detach threads using the following two functions:

pthread_join (threadid, status) 
pthread_detach (threadid)

The pthread_join() subroutine blocks the calling program until the thread with the specified threadid terminates. When a thread is created, one of its properties defines whether it is joinable or detached. Only threads defined as connectable when created can be connected. If a thread is defined as detachable when it is created, it can never be connected.

Purpose: Some people do not call pthread_exit(NULL); function wait at the end of the main function, but choose sleep. Here you can use pthread_join() instead of sleep which is uncontrollable. Sometimes you want to do something when the thread ends. If you need to know whether the thread has ended, you can also call this function.

This example demonstrates how to use the pthread_join() function to wait for a thread to complete.

#include <iostream>
#include <cstdlib>
#include <pthread.h>
#include <unistd.h>
 
using namespace std;
 
#define NUM_THREADS     5
 
void *wait(void *t)
{
   int i;
   long tid;
 
   tid = (long)t;
 
   sleep(1);
   cout << "Sleeping in thread " << endl;
   cout << "Thread with id : " << tid << "  ...exiting " << endl; 
   pthread_attr_init (&attr);
   // Initialize and set the thread to be joinable (joinable)
   void *status;
   pthread_attr_t attr;
   pthread_t threads[NUM_THREADS];
   int i;
   int rc;
{
int main ()
}
   pthread_exit(NULL);
 
 
   pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
 
   for( i=0; i < NUM_THREADS; i++ ){
      cout << "main() : creating thread, " << i << endl;
      rc = pthread_create(&threads[i], NULL, wait, (void *)&i );
      if (rc){
         cout << "Error:unable to create thread," << rc << endl;
         exit(-1);
      }
   }
 
   // 删除属性,并等待其他线程
   pthread_attr_destroy(&attr);
   for( i=0; i < NUM_THREADS; i++ ){
      rc = pthread_join(threads[i], &status);
      if (rc){
         cout << "Error:unable to join," << rc << endl;
         exit(-1);
      }
      cout << "Main: completed thread id :" << i ;
      cout << "  exiting with status :" << status << endl;
   }
 
   cout << "Main: program exiting." << endl;
   pthread_exit(NULL);
}

Compilation and running results under linux:

main() : creating thread, 0
main() : creating thread, 1
main() : creating thread, 2
main() : creating thread, 3
main() : creating thread, 4
Sleeping in thread 
Thread with id : 4  ...exiting 
Sleeping in thread 
Thread with id : 3  ...exiting 
Sleeping in thread 
Thread with id : 2  ...exiting 
Sleeping in thread 
Thread with id : 1  ...exiting 
Sleeping in thread 
Thread with id : 0  ...exiting 
Main: completed thread id :0  exiting with status :0
Main: completed thread id :1  exiting with status :0
Main: completed thread id :2  exiting with status :0
Main: completed thread id :3  exiting with status :0
Main: completed thread id :4  exiting with status :0
Main: program exiting.

2. Problems in thread operation

Problems with threads and critical sections

We know how to create threads before. Let's take a look at an example of creating 100 threads. They all access the same variable. Half of them add 1 to this variable and half of them subtract 1. Logically speaking, the result will be equal to 0.

#include <stdio.h> 
#include <unistd.h> 
#include <stdlib.h> 
#include <pthread.h> 
#define NUM_THREAD 100 
void * thread_inc(void * arg); 
void * thread_des(void * arg); 
long long num = 0; //The long long type is a 64-bit integer type, and multiple threads can access it together 

int main(int argc, char *argv[]) 
{ 
    pthread_t thread_id[NUM_THREAD]; 
    int i; 

    //Create 100 threads, Half execute thread_inc, half execute thread_des 
    for(i = 0; i < NUM_THREAD; i++) 
    { 
        if(i %2) 
            pthread_create(&(thread_id[i]), NULL, thread_inc, NULL); 
        else 
            pthread_create(&(thread_id[ i]), NULL, thread_des, NULL); 
    }
 
    //Wait for the thread to return
    for (i = 0; i < NUM_THREAD; i++) 
        pthread_join(thread_id[i], NULL); 

    printf("result: %lld \n", num); //+1, -1 logically results in 0 
    return 0 ; 
} 

//Thread entry function 1 
void * thread_inc(void * arg) 
{ 
    for (int i = 0; i < 50000000; i++) 
        num += 1;//Critical section (the statement that caused the problem is the location of the critical section) 
    return NULL; 
} 

//Thread entry function 2 
void * thread_des(void * arg) 
{ 
    for (int i = 0; i < 50000000; i++) 
        num -= 1; //Critical section 
    return NULL; 
}

Judging from the running results, it is not 0, and the results of each run are different. So what causes this? This is because the process of each thread accessing a variable is such a process: first fetch the variable value from the memory to the CPU, then the CPU calculates the changed value, and finally writes the changed value back to the memory. Therefore, we can easily see that multiple threads access the same variable. If a thread has just fetched the data from the memory and has not had time to write it back to the memory, then other threads access the variable again, so the value will be Not correct anymore.

Why does this happen? Here’s an example:

As shown in the figure above: both threads must add 1 to a commonly accessed variable.

As mentioned above, the operation process is: Thread 1 first gets the value and then assigns it back through the CPU operation, and then Thread 2 performs the value operation and puts it back. The above figure realizes the most ideal situation. If at this time, thread 1 I got the value 99, and at the same time, thread 2 also got 99 without any gap. At this time, there will be a problem. Thread one assigns a value of 100 after the operation, and then thread two assigns a value of 100 back after the operation. Pay attention. Both threads here serve Num++. If they do things like this, doesn't it mean that one of them has done useless work? (If I, Fat Tiger, still have the knife, I won’t beat you to death!!!)

After reading this, you should understand why thread synchronization is needed! ! ! ! And the importance of thread synchronization! !

Next, let’s talk about how to solve this problem: Thread synchronization

Thread synchronization

Thread synchronization is used to solve problems caused by thread access sequence, generally in the following two situations:

  1. What happens when the same memory space is accessed simultaneously
  2. When it is necessary to specify the execution order of threads accessing the same memory space

For these two possible situations, the synchronization technologies we use respectively are: mutex and semaphore.

    • Mutex 
      Mutex technology can be understood literally, that is, if a thread accesses the critical section, other threads have to wait in line. Their access is mutually exclusive. The implementation method is to lock and release the lock in the critical section.

#include <pthread.h> 

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr); //Create a mutex 

int pthread_mutex_destroy(pthread_mutex_t *mutex); //Destroy a mutex 

int pthread_mutex_lock(pthread_mutex_t *mutex); // Lock 

int pthread_mutex_unlock(pthread_mutex_t *mutex);//release the lock

In short, the lock and unlock functions are used to surround both ends of the critical section. When a thread calls pthread_mutex_lock to enter the critical section, if it does not call pthread_mutex_unlock to release the lock and exit, then other threads will always be blocked outside the critical section. We call this situation a deadlock. Therefore, the critical area must be surrounded by lock and unlock in one-to-one correspondence.

Let's take a look at the code example:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#define NUM_THREAD 100
void * thread_inc(void * arg);
void * thread_des(void * arg);

long long num = 0;
pthread_mutex_t mutex;

int main(int argc, char *argv[])
{
    pthread_t thread_id[NUM_THREAD];
    int i;

    //互斥量的创建
    pthread_mutex_init(&mutex, NULL);

    for(i = 0; i < NUM_THREAD; i++)
    {
        if(i %2)
            pthread_create(&(thread_id[i]), NULL, thread_inc, NULL);
        else
            pthread_create(&(thread_id[i]), NULL, thread_des, NULL);
    }

    for (i = 0; i < NUM_THREAD; i++) 
        pthread_join(thread_id[i], NULL); 

    printf("result: %lld \n", num); 
    pthread_mutex_destroy(&mutex); //Destruction of mutex 
    return 0 ; 
} 

/*Expand the critical section to reduce the number of lock and release lock calls, but this way the variable must be filled to 50000000 times before other threads can access it. This prolongs the 
 waiting time of the thread, but shortens the lock and release lock functions. There is no final conclusion here about the calling time. Please consider it at your own discretion*/ 
void * thread_inc(void * arg) 
{ 
    pthread_mutex_lock(&mutex); //The mutex is locked 
    for (int i = 0; i < 1000000; i++) 
        num += 1; 
    pthread_mutex_unlock(&mutex); //Mutex release lock 
    return NULL; 
} 

/*Shorten thread waiting time, but loop creation, release lock function calling time increases*/ 
void * thread_des(void * arg) 
{ 
    for (int i = 0; i < 1000000; i++) 
    {
        pthread_mutex_lock(&mutex);
        num -= 1;
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}

Compiling and running can get the result: 0

Semaphores 
are similar to mutexes, except that mutexes use locks to control thread access, while semaphores use binary 0s and 1s to control thread order. The sem_post semaphore is increased by 1, and the sem_wait semaphore is decreased by 1. When the semaphore is 0, sem_wait will be blocked, so by adding 1 and subtracting 1 from the semaphore, the execution order of the thread can be controlled. 
Note: The test semaphore function on Mac fails to return -1. In the future, it will be fixed on Linux. Maybe these interfaces are outdated...

#include <semaphore.h> 

int sem_init(sem_t *sem, int pshared, unsigned int value);//Create semaphore 
int sem_destroy(sem_t *sem);//Destroy semaphore 
int sem_post(sem_t *sem);// Add 1 to the semaphore 
int sem_wait(sem_t *sem);//Decrease the semaphore by 1 and block when it is 0

Example code: Thread A gets the value from user input and stores it in the global variable num. At this time, thread B will take the value and accumulate it. This process is performed a total of 5 times. After completion, the sum is output and the program exits.

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>

void * read(void * arg);
void * accu(void * arg);
static sem_t sem_one;
static sem_t sem_two;
static int num;


int main(int argc, char *argv[])
{
    pthread_t id_t1, id_t2;
    sem_init(&sem_one, 0, 0);
    sem_init(&sem_two, 0, 1);

    pthread_create(&id_t1, NULL, read, NULL);
    pthread_create(&id_t2, NULL, accu, NULL);

    pthread_join(id_t1, NULL);
    pthread_join(id_t2, NULL);

    sem_destroy(&sem_one);
    sem_destroy(&sem_two);

    return 0;
}

void * read(void * arg)
{
    int i;
    for (i = 0; i < 5; i++) {
        fputs("Input num: ", stdout);
        sem_wait(&sem_two);
        scanf("%d", &num);
        sem_post(&sem_one);
    }
    return NULL;
}

void * accu(void * arg)
{
    int sum = 0 , i;
    for (i = 0; i < 5; i++) {
        sem_wait(&sem_one);
        sum+= num;
        sem_post(&sem_two);
    }
    printf("Result: %d \n", sum);
    return NULL;
}

Supplement: Thread destruction, after the thread is created, it will not be automatically destroyed after its entry function returns. It needs to be destroyed manually, otherwise the memory space created by the thread will always exist. Generally, there are two ways of manual destruction: 1. Call the pthread_join function, which will destroy the thread at the same time after returning. It is a blocking function. The server generally does not use it to destroy it, because the main thread of the server should not be blocked, and the client server must be monitored in real time. connect. 2. Calling the pthread_detach function will not block. The thread will be automatically destroyed when the thread returns. However, it should be noted that the pthread_join function cannot be called again after calling it. The main difference between it and pthread_join is that one is a blocking function and the other is not blocking.

4. Implementation of multi-threaded concurrent server

A simple chat program is implemented using multi-threading, and the critical section (clnt_cnt, clnt_socks) is locked and accessed.

  • Server:

//
//  main.cpp
//  hello_server
//
//  Created by app05 on 15-10-22.
//  Copyright (c) 2015年 app05. All rights reserved.
//临界区是:clnt_cnt和clnt_socks访问处

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

#define BUF_SIZE 100
#define MAX_CLNT 256

void * handle_clnt(void * arg);
void send_msg(char *msg, int len);
void error_handling(char * msg);
int clnt_cnt = 0;
int clnt_socks[MAX_CLNT];
pthread_mutex_t mutx;

int main(int argc, char *argv[])
{
    int serv_sock, clnt_sock;
    struct sockaddr_in serv_adr, clnt_adr;
    socklen_t clnt_adr_sz;
    pthread_t t_id;
    if (argc != 2) {
        printf("Usage : %s <port> \n", argv[0]);
        exit(1);
    }

    //创建互斥量
    pthread_mutex_init(&mutx, NULL);
    serv_sock = socket(PF_INET, SOCK_STREAM, 0);

    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(atoi(argv[1]));

    if(bind(serv_sock, (struct sockaddr *) &serv_adr, sizeof(serv_adr)) == -1)
        error_handling("bind() error");
    if(listen(serv_sock, 5) == -1) 
        error_handling("listen() error"); 

    while (1) 
    { 
        clnt_adr_sz = sizeof(clnt_adr); 
        clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz) ; //Block, listen for client connection requests 

        //Critical section 
        pthread_mutex_lock(&mutx); //Lock 
        clnt_socks[clnt_cnt++] = clnt_sock; //Save the newly connected client to the clnt_socks array 
        pthread_mutex_unlock(&mutx); // Release the lock 

        //Create the thread 
        pthread_create(&t_id, NULL, handle_clnt, (void*) &clnt_sock); 
        pthread_detach(t_id); //Destroy the thread, and automatically call the destruction after the thread returns, without blocking 

        printf("Connected client IP: %s \n", inet_ntoa(clnt_adr.sin_addr));
    }

    close(serv_sock);
    return 0;
}

//线程执行
void * handle_clnt(void * arg)
{
    int clnt_sock = *((int *)arg);
    int str_len = 0, i;
    char msg[BUF_SIZE];

    while ((str_len = read(clnt_sock, msg, sizeof(msg))) != 0)
        send_msg(msg, str_len);

    //从数组中移除当前客服端
    pthread_mutex_lock(&mutx);
    for (i = 0; i < clnt_cnt; i++)
    {
        if (clnt_sock == clnt_socks[i])
        {
            while (i++ < clnt_cnt - 1)
                clnt_socks[i] = clnt_socks[i + 1];
            break;
        }
    }
    clnt_cnt--;
    pthread_mutex_unlock(&mutx);
    close(clnt_sock); 
    return NULL; 
} 

//Send messages to all connected clients 
void send_msg(char * msg, int len) 
{ 
    int i; 
    pthread_mutex_lock(&mutx); 
    for (i = 0; i < clnt_cnt; i++) 
        write(clnt_socks[i], msg, len); 
    pthread_mutex_unlock(&mutx); 
} 

void error_handling(char *message) 
{ 
    fputs(message, stderr); 
    fputc('\n', stderr); 
    exit(1); 
}

Client:

//
//  main.cpp
//  hello_client
//
//  Created by app05 on 15-10-22.
//  Copyright (c) 2015年 app05. All rights reserved.
//
//

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

#define BUF_SIZE 100
#define NAME_SIZE 20

void * send_msg(void * arg);
void * recv_msg(void * arg);
void error_handling(char *message);

char name[NAME_SIZE] = "[DEFAULT]";
char msg[BUF_SIZE];

int main(int argc, const char * argv[]) {
    int sock;
    struct sockaddr_in serv_addr;
    pthread_t snd_thread, rcv_thread; 
    void * thread_return; 

    if(argc != 4) 
    { 
        printf("Usage: %s <IP> <port> \n", argv[0]); 
        exit(1); 
    } 

    sprintf(name, "[%s]", argv[3]); //The name of the chatter, configured in the compiler parameters 
    sock = socket(PF_INET, SOCK_STREAM, 0); 
    if(sock == -1) 
        error_handling("socket() error"); 

    memset(&serv_addr, 0, sizeof(serv_addr)); 
    serv_addr.sin_family = AF_INET; 
    serv_addr.sin_addr.s_addr = inet_addr(argv[1]); 
    serv_addr.sin_port = htons(atoi(argv[2]));

    if (connect(sock, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) == -1)
        error_handling("connect() error"); 

    //Multiple threads Separate input and output 
    pthread_create(&snd_thread, NULL, send_msg, (void *)&sock);
    pthread_create(&rcv_thread, NULL, recv_msg, (void *)&sock);
    //阻塞,等待返回
    pthread_join(snd_thread, &thread_return);
    pthread_join(rcv_thread, &thread_return);

    close(sock);
    return 0;
}

//发送消息
void * send_msg(void * arg)
{
    int sock = *((int *)arg);
    char name_msg[NAME_SIZE + BUF_SIZE];
    while (1) {
        fgets(msg, BUF_SIZE, stdin);
        if (!strcmp(msg, "q\n") || !strcmp(msg, "Q \n")) {
            close(sock);
            exit(0);
        }
        sprintf(name_msg, "%s  %s", name, msg);
        write(sock, name_msg, strlen(name_msg));
    }
    return NULL;
}

//接收消息
void * recv_msg(void * arg)
{
    int sock = *((int *)arg);
    char name_msg[NAME_SIZE + BUF_SIZE];
    int str_len;
    while (1) {
        str_len = read(sock, name_msg, NAME_SIZE + BUF_SIZE - 1);
        if(str_len == -1)
            return (void *)-1;
        name_msg[str_len] = 0;
        fputs(name_msg, stdout);
    }
    return NULL;
}

void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

Under windows:

1. Thread overview

Understanding Windows Kernel Objects

Thread is one of the system kernel objects. Before learning about threads, you should first understand kernel objects. The kernel object is a memory block allocated by the system kernel. The memory block describes a data structure, and its members are responsible for maintaining various information about the object. The data of kernel objects can only be accessed by the system kernel. Applications cannot find these data structures in memory and directly change their contents.

Commonly used system kernel objects include event objects, file objects, job objects, mutex objects, pipe objects, process objects, and thread objects. Different types of kernel objects have different data structures.

Understand processes and threads

A process is considered an instance of a running program, which is also a system kernel object. The process can be simply understood as a container, which only provides space, and the code for executing the program is implemented by threads. A thread exists in a process and is responsible for executing code in the process's address space. When a process is created, the system automatically creates a thread for it, which is called the main thread. In the main thread, users can create other threads through code. When the main thread in the process ends, the process also ends.

Thread creation

Under Windows, there are many ways to create threads, which will be introduced one by one below. Note the difference.

Create a thread using the CreateThread function

Windows API functions. This function creates a new thread based on the main thread. Microsoft provides the function CreateThread to create a new thread in the Windows API.

 
  1. HANDLECreateThread(

  2. LPSECURITY_ATTRIBUTES lpThreadAttributes,//线程安全属性

  3. DWORD dwStackSize,//堆栈大小

  4. LPTHREAD_START_ROUTINE lpStartAddress,//线程函数

  5. LPVOID lpParameter,//线程参数

  6. DWORD dwCreationFlags,//线程创建属性

  7. LPDWORD lpThreadId//线程ID

  8. );

 
示例代码:

#include "stdafx.h"
#include<iostream>
#include<Windows.h>
using namespace std;

DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{
    cout << "thread function Fun1Proc!\n";

    return 0;
}

int main()
{
    HANDLE hThread1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
    CloseHandle(hThread1);

    Sleep(1000);
    cout << "main end!\n";
    system("pause");
    return 0;
}

Result graph:

Create a thread using the _beginthreadex function

In addition to using the CreateThread API function to create a thread, you can also use the _beginthreadex function provided by the C++ language to create a thread.

 
  1. uintptr_t _beginthreadex( // NATIVE CODE

  2. void *security, //线程安全属性

  3. unsigned stack_size, //线程的栈大小

  4. unsigned ( *start_address )( void * ),//线程函数

  5. void *arglist, //传递到线程函数中的参数

  6. unsigned initflag, //线程初始化标记

  7. unsigned *thrdaddr //线程ID

  8. );

 示例代码:

#include "stdafx.h"
#include<iostream>
#include<Windows.h>
#include<process.h>
using namespace std;

unsigned int _stdcall ThreadProc(LPVOID lpParameter)
{
    cout << "thread function ThreadProc!\n";
    return 0;
}

int main()
{
    _beginthreadex(NULL, 0, ThreadProc, 0, 0, NULL);

    Sleep(1000);
    cout << "main end!\n";
    system("pause");
    return 0;
}

 

2. Thread synchronization

Why do we need thread synchronization?

  When using multi-threading in a program, it is generally rare for multiple threads to perform completely independent operations during their lifetime. More often than not, some threads perform certain processing operations, and other threads must understand the results of their processing. Under normal circumstances, understanding of the results of this processing should be done after the processing task is completed. 
  If appropriate measures are not taken, other threads will often access the processing results before the thread processing task is completed, which is likely to obtain an incorrect understanding of the processing results. For example, if multiple threads access the same global variable at the same time, there will be no problem if they are all read operations. If one thread is responsible for changing the value of this variable, and other threads are responsible for reading the variable content at the same time, there is no guarantee that the data read has been modified by the writing thread. 
  In order to ensure that the reading thread reads the modified variable, it is necessary to prohibit other threads from accessing the variable when writing data to it, and then lift the access restrictions on other threads until the assignment process is completed. This kind of protective measure to ensure that threads can understand the processing results of other threads after the task processing is completed is thread synchronization.

Code example: 
Two threads add a global variable at the same time, demonstrating multi-threaded resource access conflicts.

#include "stdafx.h"
#include<windows.h>
#include<iostream>
using namespace std;
 
int number = 1;
 
unsigned long __stdcall ThreadProc1(void* lp)
{
    while (number < 100)
    {
        cout << "thread 1 :"<<number << endl;
        ++number;
        _sleep(100);
    }
 
    return 0;
}
 
unsigned long __stdcall ThreadProc2(void* lp)
{
    while (number < 100)
    {
        cout << "thread 2 :"<<number << endl;
        ++number;
        _sleep(100);
    }
 
    return 0;
}
 
int main()
{
    CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);
    CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL);
 
    Sleep(10*1000);
 
    system("pause");
    return 0;
}

It can be seen that sometimes the values ​​calculated by two threads are the same. This is the same as the above failure to create a hundred threads under Linux to add and subtract numbers to 0. It is a conflict when accessing memory.

Why does this happen? Here’s an example:

As shown in the figure above: both threads must add 1 to a commonly accessed variable.

As mentioned above, the operation process is: Thread 1 first gets the value and then assigns it back through the CPU operation, and then Thread 2 performs the value operation and puts it back. The above figure realizes the most ideal situation. If at this time, thread 1 I got the value 99, and at the same time, thread 2 also got 99 without any gap. At this time, there will be a problem. Thread one assigns a value of 100 after the operation, and then thread two assigns a value of 100 back after the operation. Pay attention. Both threads here serve Num++. If they do things like this, doesn't it mean that one of them has done useless work? (If I, Fat Tiger, still have the knife, I won’t beat you to death!!!)

After reading this, you should understand why thread synchronization is needed! ! ! ! And the importance of thread synchronization! !

About thread synchronization

Two basic issues in communication between threads are mutual exclusion and synchronization.

  • Thread synchronization refers to a restriction relationship between threads. The execution of one thread depends on the message of another thread. When it does not get the message of another thread, it should wait until the message arrives before being awakened.
  • Thread mutual exclusion refers to the exclusivity of each thread when accessing shared operating system resources (referring to "resources" in a broad sense, not Windows' .res files, for example, global variables are a shared resource). When several threads want to use a certain shared resource, only one thread is allowed to use it at any time. Other threads that want to use the resource must wait until the resource occupier releases the resource.

Thread mutual exclusion is a special kind of thread synchronization. In fact, mutual exclusion and synchronization correspond to two situations in which communication between threads occurs:

  • When multiple threads access shared resources without causing the resources to be destroyed;
  • When one thread needs to notify another thread or threads that a certain task has been completed.

Broadly speaking, thread synchronization can be divided into two categories: user-mode thread synchronization and kernel object thread synchronization.

  • The synchronization methods of threads in user mode mainly include atomic access and critical sections. Its characteristic is that the synchronization speed is extremely fast, which is suitable for occasions that have strict requirements on thread running speed.
  • Thread synchronization of kernel objects is mainly composed of kernel objects such as events, wait timers, semaphores, and semaphores. Since this synchronization mechanism uses kernel objects, the thread must be switched from user mode to kernel mode when used. This conversion generally consumes nearly a thousand CPU cycles, so the synchronization speed is slower, but the applicability is much higher. Better than user-mode thread synchronization.

In WIN32, the synchronization mechanisms mainly include the following: 
(1) Event; 
(2) Semaphore; 
(3) Mutex; 
(4) Critical section.

Four synchronization methods in Win32

critical section

A critical section is a piece of code that has exclusive access to certain shared resources. Only one thread is allowed to access shared resources at any time. If multiple threads try to access the critical section at the same time, then after one thread enters, all other threads trying to access the critical section will be suspended until the thread entering the critical section leaves. After the critical section is released, other threads can continue to preempt it, thereby achieving the purpose of atomically operating shared resources.

The critical section protects shared resources with CRITICAL_SECTION structure objects when used, and uses the EnterCriticalSection() and LeaveCriticalSection() functions to identify and release a critical section respectively. The CRITICAL_SECTION structure object used must be initialized by InitializeCriticalSection() before it can be used, and it must be ensured that any code in all threads that attempts to access this shared resource is under the protection of this critical section. Otherwise, the critical section will not play its due role, and shared resources may still be damaged.

Code example:

#include "stdafx.h" 
#include<windows.h> 
#include<iostream> 
using namespace std; 
 
int number = 1; //Define global variable 
CRITICAL_SECTION Critical; //Define critical section handle 
 
unsigned long __stdcall ThreadProc1(void* lp ) 
{ 
    while (number < 100) 
    { 
        EnterCriticalSection(&Critical); 
        cout << "thread 1 :"<<number << endl;
        ++number;
        _sleep(100);
        LeaveCriticalSection(&Critical);
    }
 
    return 0;
}
 
unsigned long __stdcall ThreadProc2(void* lp)
{
    while (number < 100)
    {
        EnterCriticalSection(&Critical);
        cout << "thread 2 :"<<number << endl;
        ++number; 
        _sleep(100); 
        LeaveCriticalSection(&Critical); 
    } 
 
    return 0; 
} 
 
int main() 
{ 
    InitializeCriticalSection(&Critical); //Initialize the critical section 
 
    objectCreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL); 
    CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL); 
 
    Sleep(10*1000); 
 
    system("pause"); 
    return 0; 
}

problem solved! ! !

event

Event is the most flexible inter-thread synchronization method provided by WIN32. Events can be in a fired state (signaled or true) or an unfired state (unsignal or false). According to the different ways of state transition, events can be divided into two categories: 
(1) Manual setting: This kind of object can only be set manually by program. When the event is needed or the event occurs, SetEvent and ResetEvent are used to set it. 
(2) Automatic recovery: Once an event occurs and is processed, it will automatically recover to the no-event state and does not need to be set again.

When using the "event" mechanism, you should pay attention to the following matters: 
(1) If you access events across processes, you must name the event. When naming the event, be careful not to conflict with other global named objects in the system namespace; (2) 
Events Whether to automatically restore; 
(3) Initial status setting of the event.

Since the event object belongs to the kernel object, process B can call the OpenEvent function to obtain the handle of the event object in process A through the object's name, and then use this handle for functions such as ResetEvent, SetEvent, and WaitForMultipleObjects. This method allows a thread in one process to control the running of a thread in another process, for example:

HANDLE hEvent=OpenEvent(EVENT_ALL_ACCESS,true,"MyEvent"); 
ResetEvent(hEvent);

Sample code:

#include "stdafx.h" 
#include<windows.h> 
#include<iostream> 
using namespace std; 
 
int number = 1; //Define global variable 
HANDLE hEvent; //Define event handle 
 
unsigned long __stdcall ThreadProc1(void* lp) 
{ 
    while (number < 100) 
    { 
        WaitForSingleObject(hEvent, INFINITE); //Wait for the object to be in signal state 
        cout << "thread 1 :"<<number << endl; 
        ++number; 
        _sleep(100); 
        SetEvent(hEvent ); 
    } 
 
    return 0; 
} 
 
unsigned long __stdcall ThreadProc2(void* lp) 
{ 
    while (number < 100) 
    {  
        WaitForSingleObject(hEvent,INFINITE); //Wait for the object to be in a signal state
        cout << "thread 2 :"<<number << endl;
        ++number;
        _sleep(100);
        SetEvent(hEvent);
    }
 
    return 0;
}
 
int main()
{
    CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);
    CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL);
    hEvent = CreateEvent(NULL, FALSE, TRUE, "event");
 
    Sleep(10*1000);
 
    system("pause");
    return 0;
}

If the running results are the same, they will not be displayed.

Signal amount

A semaphore is a synchronization object that maintains a value between 0 and a specified maximum value. The semaphore state is signaled when its count is greater than 0, and unsignaled when its count is 0. Semaphore objects can be controlled to support access to a limited number of shared resources.

The characteristics and uses of the semaphore can be defined in the following sentences: 
(1) If the current number of resources is greater than 0, the semaphore is valid; ( 
2) If the current number of resources is 0, the semaphore is invalid; 
(3) The system will never The number of current resources is allowed to be negative; 
(4) The current number of resources must not be greater than the maximum number of resources.

Create semaphore

The function prototype is:

HANDLE CreateSemaphore ( 
   PSECURITY_ATTRIBUTE psa, //Security attributes of the 
   semaphoreLONG lInitialCount, //The number of resources available at the beginningLONG 
   lMaximumCount, //The maximum number of resources 
   PCTSTR pszName); //The name of the semaphore

release semaphore

By calling the ReleaseSemaphore function, the thread can increment the current number of resources in the beacon. The function prototype is:

BOOL WINAPI ReleaseSemaphore( 
   HANDLE hSemaphore, //The semaphore handle to be added 
   LONG lReleaseCount, //The current number of resources of the semaphore is increased by lReleaseCount 
   LPLONG lpPreviousCount //The value before the increase is returned 
   );

open semaphore 

Like other core objects, semaphores can also be accessed across processes by name. The API for opening semaphores is:

HANDLE OpenSemaphore ( 
   DWORD fdwAccess, //access 
   BOOL bInherithandle, //If the child process is allowed to inherit the handle, set to TRUE 
   PCTSTR pszName //Specify the name of the object to be opened 
  );

Code example:

#include "stdafx.h" 
#include<windows.h> 
#include<iostream> 
using namespace std; 
 
int number = 1; //Define global variable 
HANDLE hSemaphore; //Define semaphore handle 
 
unsigned long __stdcall ThreadProc1(void* lp ) 
{ 
    long count; 
    while (number < 100) 
    { 
        WaitForSingleObject(hSemaphore, INFINITE); //Wait for the semaphore to be in signal state 
        cout << "thread 1 :"<<number << endl; 
        ++number; 
        _sleep(100 ); 
        ReleaseSemaphore(hSemaphore, 1,&count);
    }
 
    return 0;
}
 
unsigned long __stdcall ThreadProc2(void* lp)
{
    long count;
    while (number < 100)
    {
        WaitForSingleObject(hSemaphore, INFINITE); //Wait for the semaphore to be in signal state 
        cout << "thread 2:"<<number << endl; 
        ++number; 
        _sleep(100); 
        ReleaseSemaphore(hSemaphore, 1, &count); 
    } 
 
    return 0; 
} 
 
int main() 
{ 
    hSemaphore = CreateSemaphore(NULL, 1, 100, "sema"); 
 
    CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL); 
    CreateThread(NULL, 0, ThreadProc2, NULL, 0 , NULL); 
 
    Sleep(10*1000); 
 
    system("pause"); 
    return 0; 
}

The result is the same.

mutex

Use mutually exclusive object mechanism. Only threads that own mutually exclusive objects have permission to access public resources. Because there is only one mutually exclusive object, it is guaranteed that public resources will not be accessed by multiple threads at the same time. Mutual exclusion not only enables safe sharing of public resources within the same application, but also allows safe sharing of public resources between different applications.

Code example:

#include "stdafx.h"
#include<windows.h>
#include<iostream>
using namespace std;
 
int number = 1; //定义全局变量
HANDLE hMutex;  //定义互斥对象句柄
 
unsigned long __stdcall ThreadProc1(void* lp)
{
    while (number < 100)
    {
        WaitForSingleObject(hMutex, INFINITE);
        cout << "thread 1 :"<<number << endl;
        ++number;
        _sleep(100);
        ReleaseMutex(hMutex);
    }
 
    return 0;
}
 
unsigned long __stdcall ThreadProc2(void* lp)
{
    while (number < 100)
    {
        WaitForSingleObject(hMutex, INFINITE);
        cout << "thread 2 :"<<number << endl;
        ++number;
        _sleep(100);
        ReleaseMutex(hMutex);
    }
 
    return 0;
}
 
int main()
{
    hMutex = CreateMutex(NULL, false, "mutex");     //创建互斥对象
 
    CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);
    CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL);
 
    Sleep(10*1000);
 
    system("pause");
    return 0;
}

The result is the same.

3. Multi-threading + IOPC to implement the server

Server code:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <process.h>
#include <winsock2.h>
#include <windows.h>
 
#pragma comment(lib,"ws2_32.lib");//加载ws2_32.dll
 
#define BUF_SIZE 100
#define READ    3
#define    WRITE    5
 
typedef struct    // socket info
{
    SOCKET hClntSock;
    SOCKADDR_IN clntAdr;
} PER_HANDLE_DATA, *LPPER_HANDLE_DATA;
 
typedef struct    // buffer info
{
    OVERLAPPED overlapped;
    WSABUF wsaBuf;
    char buffer[BUF_SIZE];
    int rwMode;    // READ or WRITE 读写模式
} PER_IO_DATA, *LPPER_IO_DATA;
 
unsigned int  WINAPI EchoThreadMain(LPVOID CompletionPortIO);
void ErrorHandling(char *message);
SOCKET ALLCLIENT[100];
int clientcount = 0;
HANDLE hMutex;//互斥量
 
int main(int argc, char* argv[])
{
 
    hMutex = CreateMutex(NULL, FALSE, NULL);//创建互斥量
 
    WSADATA    wsaData;
    HANDLE hComPort;
    SYSTEM_INFO sysInfo;
    LPPER_IO_DATA ioInfo;
    LPPER_HANDLE_DATA handleInfo;
 
    SOCKET hServSock;
    SOCKADDR_IN servAdr;
    int  i;
    DWORD recvBytes = 0,flags = 0;
 
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
        ErrorHandling("WSAStartup() error!");
 
    hComPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);//Create CP object 
    GetSystemInfo(&sysInfo);//Get the current system information 
 
    for (i = 0; i < sysInfo.dwNumberOfProcessors; i++) 
        _beginthreadex(NULL, 0, EchoThreadMain, (LPVOID)hComPort, 0, NULL);//Create the number of threads = number of CPUs 
 
    hServSock = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);//It is not a non-blocking socket, but it overlaps IO socket. 
    memset(&servAdr, 0, sizeof(servAdr)); 
    servAdr.sin_family = AF_INET; 
    servAdr.sin_addr.s_addr = htonl(INADDR_ANY); 
    servAdr.sin_port = htons(1234); 
 
    bind(hServSock, (SOCKADDR*)&servAdr, sizeof( servAdr)); 
    listen(hServSock, 5); 
 
    while (1) 
    {  
        SOCKET hClntSock;
        SOCKADDR_IN clntAdr;
        int addrLen = sizeof(clntAdr); 
 
        hClntSock = accept(hServSock, (SOCKADDR*)&clntAdr, &addrLen); 
 
        handleInfo = (LPPER_HANDLE_DATA)malloc(sizeof(PER_HANDLE_DATA));//Same as overlapped IO 
        handleInfo->hClntSock = hClntSock;/ /Storage client socket 
 
        WaitForSingleObject(hMutex, INFINITE); //Thread synchronization 
 
        ALLCLIENT[clientcount++] = hClntSock; //Save to socket queue 
 
        ReleaseMutex(hMutex); 
 
        memcpy(&(handleInfo->clntAdr), &clntAdr, addrLen); 
 
        CreateIoCompletionPort((HANDLE)hClntSock, hComPort, (DWORD)handleInfo, 0);//Connect the socket and CP object 
        memset (&(ioInfo->overlapped), 0, sizeof(OVERLAPPED)); 
                                                                                //Complete information will be written to the CP object
        ioInfo = (LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA));//Storage received information
        ioInfo->wsaBuf.len = BUF_SIZE; 
        ioInfo->wsaBuf.buf = ioInfo->buffer; //Same as overlapped IO 
        ioInfo->rwMode = READ; //Read and write mode 
 
        WSARecv(handleInfo->hClntSock, &(ioInfo- >wsaBuf),//Non-blocking mode 
            1, &recvBytes, &flags, &(ioInfo->overlapped), NULL); 
    } 
    CloseHandle(hMutex);//Destroy mutex 
    return 0; 
} 
 
unsigned int WINAPI EchoThreadMain(LPVOID pComPort) //Thread execution 
{ 
    HANDLE hComPort = (HANDLE)pComPort; 
    SOCKET sock; 
    DWORD bytesTrans; 
    LPPER_HANDLE_DATA handleInfo; 
    LPPER_IO_DATA ioInfo; 
    DWORD flags = 0; 
    {
 
    while (1)//Large loop 
        GetQueuedCompletionStatus(hComPort, &bytesTrans,//Confirm "completed" I/O!! 
            (LPDWORD)&handleInfo, (LPOVERLAPPED*)&ioInfo, INFINITE);//When INFINITE is used, the program will block , until the completed I/O information is written to the CP object 
        sock = handleInfo->hClntSock; //Client socket 
 
        if (ioInfo->rwMode == READ) //Read and write mode (there is data in the buffer at this time) 
        { 
            puts("message received!"); 
            if (bytesTrans == 0) //Connection ends 
            { 
                WaitForSingleObject(hMutex, INFINITE); //Thread synchronization 
 
                closesocket(sock); 
                int i = 0; 
                while (ALLCLIENT[i] = = sock){ i++; } 
                ALLCLIENT[i] = 0;//Disconnect and set to 0 
 
                ReleaseMutex(hMutex);
 
                free(handleInfo); free(ioInfo); 
                continue; 
            } 
            int i = 0; 
 
            for (; i < clientcount;i++) 
            { 
                if (ALLCLIENT[i] != 0)//Determine whether it is a connected socket 
                { 
                    if (ALLCLIENT[i] != sock) 
                    { 
                        LPPER_IO_DATA newioInfo; 
                        newioInfo = (LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA));//Dynamic allocation of memory 
                        memset(&(newioInfo->overlapped), 0, sizeof(OVERLAPPED));  
                        strcpy(newioInfo->buffer, ioInfo->buffer);//Rebuild new memory to prevent multiple free releases
                        newioInfo->wsaBuf.buf = newioInfo->buffer; 
                        newioInfo->wsaBuf.len = bytesTrans;
                        newioInfo->rwMode = WRITE;
 
                        WSASend(ALLCLIENT[i], &(newioInfo->wsaBuf),//回声
                            1, NULL, 0, &(newioInfo->overlapped), NULL);
                    }
                    else
                    {
                        memset(&(ioInfo->overlapped), 0, sizeof(OVERLAPPED));
                        ioInfo->wsaBuf.len = bytesTrans;
                        ioInfo->rwMode = WRITE;
                        WSASend(ALLCLIENT[i], &(ioInfo->wsaBuf),//回声
                            1, NULL, 0, &(ioInfo->overlapped), NULL);
                    }
                } 
            }
            ioInfo = (LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA));//Dynamically allocate memory 
            memset(&(ioInfo->overlapped), 0, sizeof(OVERLAPPED)); 
            ioInfo->wsaBuf.len = BUF_SIZE; 
            ioInfo->wsaBuf .buf = ioInfo->buffer; 
            ioInfo->rwMode = READ; 
            WSARecv(sock, &(ioInfo->wsaBuf),//receive non-blocking 
                1, NULL, &flags, &(ioInfo->overlapped), NULL) ; 
        } 
        else 
        { 
            puts("message sent!"); 
            free(ioInfo); 
        } 
    } 
    return 0; 
} 
 
void ErrorHandling(char *message) 
{ 
    fputs(message,stderr);
    fputc('\n', stderr);
    exit(1);
}

Client:

#define _CRT_SECURE_NO_WARNINGS 
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <windows.h> 
#include <process.h> 
#define BUF_SIZE 1000 
#define NAME_SIZE 20 
 
#pragma comment( lib, "ws2_32.lib") //Load ws2_32.dll   
 
unsigned WINAPI SendMsg(void * arg); //Send message function 
unsigned WINAPI RecvMsg(void * arg); //Receive message function 
void ErrorHandling(char * msg); //Error return function 
 
int haveread = 0; 
char NAME[50]; //[name] 
char ANAME[50]; 
char msg[BUF_SIZE]; //Information 
 
int main(int argc, char *argv[]) 
{ 
 
    printf ("Please enter your network name:"); 
    scanf("%s",NAME);
    WSADATA wsaData;
    SOCKET hSock;
    SOCKADDR_IN servAdr;
    HANDLE hSndThread, hRcvThread;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
        ErrorHandling("WSAStartup() error!");
 
    hSock = socket(PF_INET, SOCK_STREAM, 0);
    memset(&servAdr, 0, sizeof(servAdr));
    servAdr.sin_family = AF_INET;
    servAdr.sin_addr.s_addr = inet_addr("127.0.0.1");
    servAdr.sin_port = htons(1234);
 
    if (connect(hSock, (SOCKADDR*)&servAdr, sizeof(servAdr)) == SOCKET_ERROR)
        ErrorHandling("connect() error");
 
    int resultsend;
    puts("Welcome to joining our chatting room!\n");
    sprintf(ANAME, "[%s]", NAME);
 
    hSndThread =
        (HANDLE)_beginthreadex(NULL, 0, SendMsg, (void*)&hSock, 0,NULL);//Writing thread 
    0 
        , RecvMsg, (void*)&hSock, 0, NULL); //Read thread 
 
    WaitForSingleObject(hSndThread, INFINITE); //Wait for the thread to end 
    WaitForSingleObject(hRcvThread, INFINITE); 
    closesocket(hSock); 
    WSACleanup(); 
    system("pause"); 
    return 0; 
} 
 
unsigned WINAPI SendMsg(void * arg ) // send thread main 
{ 
    SOCKET sock = *((SOCKET*)arg); 
    char name_msg[NAME_SIZE + BUF_SIZE]; 
    char padd[2]; 
    fgets(padd, 2, stdin);//Extra '\n' 
    printf("\n send message:"); 
    while (1) 
    {
        { 
            fgets(msg, BUF_SIZE, stdin); 
            if (!strcmp(msg, "q\n") || !strcmp(msg, "Q\n")) 
            { 
                closesocket(sock); 
                exit(0); 
            } 
            sprintf (name_msg, "[%s] %s", NAME, msg); 
            char numofmsg = strlen(name_msg) + '0'; 
            char newmsg[100]; newmsg[0] = numofmsg; newmsg[1] = 0;/ /The first character represents the length of the message 
            strcat(newmsg, name_msg); 
            int result = send(sock, newmsg, strlen(newmsg),0); 
            if (result == -1)return -1;//Send error 
        } 
    } 
    return NULL; 
} 
 
unsigned WINAPI RecvMsg(void * arg) // read thread main
{ 
    SOCKET sock = *((SOCKET*)arg); 
    char name_msg[NAME_SIZE + BUF_SIZE]; 
    int str_len = 0; 
    while (1) 
    { 
        {
            char lyfstr[1000] = { 0 }; 
            int totalnum = 0; 
            str_len = recv(sock, name_msg, 1, 0);//Read the first character! Get the length of the message 
            if (str_len == -1)//Reading error 
            { 
                printf("return -1\n"); 
                return -1; 
            } 
            if (str_len == 0)//End of reading 
            { 
                printf(" return 0\n"); 
                return 0;//end of reading 
            } 
            totalnum = name_msg[0] - '0'; 
            int count = 0; 
 
            do 
            { 
                str_len = recv(sock, name_msg, 1, 0); 
 
                name_msg[str_len ] = 0;
  
                if (str_len == -1)//read error 
                { 
                    printf("return -1\n"); 
                    return -1; 
                } 
                if (str_len == 0) 
                { 
                    printf("return 0\n"); 
                    return 0;//End of reading 
                } 
                strcat(lyfstr, name_msg); 
                count = str_len + count; 
 
            } while (count < totalnum); 
 
            lyfstr[count] = '\0' ; 
            printf("\n"); 
            strcat(lyfstr, "\n"); 
            fputs(lyfstr,stdout); 
            printf(" send message:"); 
            fflush(stdout);
            memset(name_msg, 0, sizeof(char));
        }
    }
    return NULL;
}
 
void ErrorHandling(char * msg)
{
    fputs(msg, stderr);
    fputc('\n', stderr);
    exit(1);
}

 One final word. This series of blogs on getting started with network programming is a series of learning series. If you are interested, you can read other articles on my blog. . . .

Reference blog: https://blog.csdn.net/kaida1234/article/details/79465713

Reference blog: http://www.runoob.com/cplusplus/cpp-multithreading.html

Reference blog: https://blog.csdn.net/u010223072/article/details/49335867

Reference blog: https://blog.csdn.net/wxf2012301351/article/details/73504281

Reference book: "TCP/IP Network Programming---Yin Shengyu"

Source: https://www.cnblogs.com/DOMLX/p/9661012.html

Guess you like

Origin blog.csdn.net/i_likechard/article/details/89448974