linux多线程socket通信与互斥量

什么是线程
每个正在系统上运行的程序都是一个进程。每个进程包含一到多个线程,可以把线程称作程序执行流的最小单位。每个线程都有自己的专有寄存器(栈指针、程序计数器等),但代码区是共享的,即不同的线程可以执行同样的函数。可以用下面的图表示,更加直观,协程是更加轻量级的线程,这里就不叙述了。

单线程与多线程
单线程:单线程在程序执行时,所走的程序路径按照连续顺序排下来,前面的必须处理好,后面的才会执行。单线程就是一个进程只有一个线程。

多线程:多线程就是在一个进程内,多个线程并发执行,但并非同时执行,其中存在线程的切换,CPU通过给每个线程分配CPU时间片来实现这个机制。时间片是CPU分配给各个线程的时间,因为时间片非常短,所以CPU通过不停地切换线程执行,让我们感觉多个线程时同时执行的,时间片一般是几十毫秒(ms)。但是,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再次加载这个任务的状态,从任务保存到再加载的过程就是一次上下文切换。
了解了多线程的概念,还需要知道多个线程是共享一个进程内的 代码段、堆、全局变量、静态变量、文件等共用资源的,而每个线程又拥有自己的栈区和寄存器。如下图

线程安全
在多线程环境下,需要保证线程安全,也就是内存的安全。我们知道,多线程是共享进程内的堆区的,也就是任何一个线程都可以访问同一块堆区的内存,试想: 某个线程把堆内某个数据处理到一半就转去干别的事,等到回来重新处理这个数据的时候,可能这个数据已经变化了(因为其他进程也可以同时访问这块区域)。即堆内存空间在没有保护机制的情况下,对多线程来说是不安全的地方,因为你放进去的数据,可能被别的线程“破坏”。
解决这个问题的方法有很多,这里用的是互斥锁
什么是锁呢,顾名思义,比如:一个线程在操作一个全局变量val时,不想要其他线程进来捣乱,那就可以在操作这个变量时加锁,等到访问完了再放锁,这个时候其他线程才可以拿锁,再进去操作这个全局变量,操作完了再放锁,以此类推。
这样子就可以保证数据的安全性,谁先拿到锁,就可以进去操作共享数据,完了再放锁。
就是类似一群人上卫生间,一个人进去把门锁了,上完后再开门,接着再进去一个人,等等。
线程创建函数为

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);

第一个参数thread为指向线程标识符的地址。(可以定义一个pthread_t  pthid,传入其地址);
第二个参数attr用于设置线程属性,一般为空,表示使用默认属性。
第三个参数start_routine是线程运行函数的地址。(自己写一个线程执行函数传入)
最后一个参数是线程执行函数的参数。

下面程序是多客户端与服务端的socket通信,为每一个线程客户端与服务端的数据交互 上锁,也就是多线程下同一时段只能有一个客户端和服务端通信。

socket服务端程序(部分)

#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>
#include <signal.h>
#include <pthread.h>

pthread_mutex_t g_lock;   //定义锁变量(锁初始化函数的第一个参数)
void* pthmain(void *arg);  //线程执行函数的声明

class TCPserver{
public:
       TCPserver(){
           listen_sock = 0;
           Toclient_sock = 0;
       }
       ~TCPserver(){
           if(listen_sock!=0){
              close(listen_sock);
           }
           if(Toclient_sock!=0){
              close(Toclient_sock);
           }
       }
public:
       bool initserver(int port);
       bool Accept();
       ssize_t Send(const void *buf, int len);
       ssize_t Recv(void *buf, int len);
       void closeListen_sock(){
               if(listen_sock > 0){
                  close(listen_sock);
                  listen_sock=0;
               }
      }
       void closeClient_sock(){
               if(Toclient_sock > 0){
                  close(Toclient_sock);
                  Toclient_sock=0;
               }
       }
public:
       int listen_sock;
       int Toclient_sock;
 };
 /*
 这里忽略服务端初始化、数据收发函数等函数的实现,因为形式是固定的。主要实现多线程。
 注意编译的时候要在末尾加上-lpthread
 如:g++ server.cpp -g -o server.out -lpthread
 */
 TCPserver test02;              // 全局共享对象(多线程共享监听一个socket,连接socket会随着accept更新)
 int main(){
     if(test02.initserver(5000) == false){
        printf("服务端初始化失败\n");
        return -1;
     }
 
     pthread_t pthid;      //线程创建函数第一个参数
     pthread_mutex_init(&g_lock,0);  //创建线程之前初始化锁
     while(1){
     if(test02.Accept() == false){   //一旦有新的客户端连接成功,就会启动一个新的线程
        printf("服务端接受客户端的连接请求失败\n");  
        pthread_mutex_destroy(&g_lock);   //如果连接失败,要把锁摧毁,防止资源浪费
        return -1;
     }
     pthread_create(&pthid,NULL,pthmain,(void*)test02.Toclient_sock);  // 创建一个线程
   }
   
 void* pthmain(void *arg){
     int m_clientf = (long)arg;     //客户端的连接socket
     //与客户端通信,接收客户端发过来的报文后 回复"我收到了"
     char strbuffer[1024];
 
     while(1){
        pthread_mutex_lock(&g_lock);    //加锁 
        if(recv(m_clientf,strbuffer,sizeof(strbuffer),0)<=0){  //为什么不用test02.Recv呢
                                                              //因为每个线程是独立的,将连接socket传给该进程后
                                                              //该进程调用recv自己处理数据
           pthread_mutex_unlock(&g_lock);
           close(m_clientf);
           pthread_exit(0);  //不能用exit退出线程,因为exit会直接把整个进程退了,不只是一个线程
        }    
        printf("接收: %s\n",strbuffer);
        strcpy(strbuffer,"我收到了."); 
        
        if(send(m_clientf,strbuffer,strlen(strbuffer),0)<=0)
        {
           pthread_mutex_unlock(&g_lock);  //防止线程在这里终止,需要解锁
           close(m_clientf);
           pthread_exit(0);  //不能用exit退出线程,因为exit会直接把整个进程退了,不只是一个线程
        }
        printf("发送: %s\n",strbuffer);
        printf("\n");
        pthread_mutex_unlock(&g_lock);
     }
        close(m_clientf);
        pthread_exit(0);
 }
 

客户端程序

#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>
  
class TCPclient{
public:
        TCPclient(){
            sock_clientfp=0;
        }
        ~TCPclient(){
            if(sock_clientfp > 0 ){
               close(sock_clientfp);   //在析构函数中关闭socket
            }
        }
public:
        bool connect_Server(const char *serverIP,int port);  //连接服务端需要做的事情,也就是一些函数的使用和初始化
        ssize_t Send(const void *buf, size_t len);    //发送数据
        ssize_t Recv(void *buf, size_t len);          //接收数据
                                             //这两个函数都会等待服务端的回复
private:
        int sock_clientfp;
};
bool TCPclient::connect_Server(const char *serverIP,int port){
         sock_clientfp = socket(AF_INET,SOCK_STREAM,0); //创建客户端的socket
 
          struct hostent *h; //IP地址的结构体
          if((h = gethostbyname(serverIP)) == 0){ //将传进来的域名转换为相应的IP地址
             close(sock_clientfp);
             sock_clientfp = 0;
             return false;
          }
  
          //把服务器的地址和端口转换为数据结构体
          struct sockaddr_in servaddr;
          memset(&servaddr,0,sizeof(servaddr));
          servaddr.sin_family = AF_INET;  //协议族
          servaddr.sin_port = htons(port); //绑定通讯端口
          memcpy(&servaddr.sin_addr,h->h_addr,h->h_length);
   
          //向服务端发起连接请求
          if(connect(sock_clientfp,(struct sockaddr *)&servaddr,sizeof(servaddr)) != 0){
             close(sock_clientfp);
             sock_clientfp = 0;  //这里手动置零比较严谨
             return false;
          }
          return true;
}

ssize_t TCPclient::Send(const void *buf, size_t len){
            return send(Toclient_sock,buf,len,0);
}
ssize_t TCPclient::Recv(void *buf, size_t len){
            memset(buf,0,len);
            return recv(Toclient_sock,buf,len,0);
}

int main(){
          TCPclient tcptest01;  //构建一个对象
          if(tcptest01.connect_Server("xxx.xxx.xx.xxx",5000)==false){    //xxx这里写你要连接的服务器IP地址
              printf("连接服务器(xxx.xxx.xx.xxx) 失败!\n");
              return -1;
          }
  
          //与服务端通信,发送一个报文后等待回复,然后等待下一个报文
          char strbuffer[1024];
          for(int i = 0; i < 5; i++){
          memset(strbuffer,0,sizeof(strbuffer));
          sprintf(strbuffer,"这是第%d个报文.",i);
   
          if((tcptest01.Send(strbuffer,strlen(strbuffer))) <= 0){       //发送失败
               break;
          }
          printf("发送: %s\n",strbuffer);
    
          if(tcptest01.Recv(strbuffer,strlen(strbuffer)) <= 0){          //接收失败
              break;
          }
          printf("接收: %s\n",strbuffer);
          printf("\n");
}
          return 0;
}

可以看到,从左到右依次为服务端,客户端1,客户端2,客户端3,同一时段只能有一个客户端进去数据交互,另一些客户端都会阻塞,等待放锁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值