socket网络编程——两个终端共用一份代码,先启动的做服务器,后启动做客户端

socket网络编程——两个终端共用一份代码,先启动的做服务器,后启动做客户端,第1张

两个终端共用一份代码,先启动的做服务器,后启动做客户端

两个终端,先运行的作为服务器,后运行的作为客户端:这样两个终端的代码基本一致,不同点在于端口号的不同,通过端口号来区分两个终端。其中的实现代码,应考虑先创建两个线程,主线程放服务器程序,子线程放客户端程序。利用connect()的返回值来确定当前是否已开启服务器端,需要将connect()放在子线程中【针对错误以结束线程的方式结束,否则系统会直接退出】在运行过程中,为了保证每个终端只有一个身份(要么是客户端,要么是服务器),因此采用互斥锁来实现。


文章目录
      • 两个终端共用一份代码,先启动的做服务器,后启动做客户端
      • 实现源码
      • 线程同步常用的方方式与区别
      • 线程同步的五种常用资源
      • 互斥锁举例


实现源码
#include 
#include 
#include 
#include 
#include 
#include 
#include   //IP转换函数
#include   //toupper函数头文件
#include 
#include 
#include 
#include 
#include 
#include   //线程头文件
#include 
#include 

#include "wrap.h"  // 

#define MAXLINE 8   //client.c与server.c中的数据传输的buf的size 
#define INET_ADDRSTRLEN 16
#define SERV1_PORT 6666
#define SERV2_PORT 8888


pthread_mutex_t mutex;//建锁

struct s_info{  //定义一个结构体,将客户端的地址与connfd进行捆绑
    struct sockaddr_in cliaddr;
    int connfd;
};

struct s_info_client{  //定义一个结构体,将客户端的地址与connfd进行捆绑
    struct sockaddr_in servaddr;
    int sockfd;
};


//服务器端写数据
void *do_work_back(void* arg_back)
{
        int n,i;
        struct s_info *ts_back=(struct s_info*)arg_back;
        char buf[MAXLINE];
        char str[INET_ADDRSTRLEN];

        while(1)
        {
            fgets(buf,MAXLINE,stdin);
            printf("Data to client ip: %s,port: %d\n",inet_ntop(AF_INET,&ts_back->cliaddr.sin_addr.s_addr,str, sizeof(str)),ntohs(ts_back->cliaddr.sin_port));
           Send(ts_back->connfd,buf,MAXLINE,0);	//发送给客户端
        }

        Close(ts_back->connfd);
        pthread_exit(0);  
}

//服务器端读数据
void *do_work(void* arg)
{
        int n,i;
        struct s_info *ts=(struct s_info*)arg;
        char buf[MAXLINE];
        char str[INET_ADDRSTRLEN];

        while(1)
        {
            n=Recv(ts->connfd,buf,MAXLINE,MSG_WAITALL);
            if(n==0)
            {
                printf("The [client ip: %s,port: %d] has beens closed!\n",inet_ntop(AF_INET,&ts->cliaddr.sin_addr.s_addr,str, sizeof(str)),ntohs(ts->cliaddr.sin_port));
                break;
            }
            //show client IP+PORT
            printf("Data from client ip: %s,port: %d\n",inet_ntop(AF_INET,&ts->cliaddr.sin_addr.s_addr,str, sizeof(str)),ntohs(ts->cliaddr.sin_port));
            printf("Data from client is:%s",buf);
        }
        Close(ts->connfd);
        pthread_exit(0);  
}



void *do_work_client(void *arg)
{
        int n;
        char buf[MAXLINE];
        char str[INET_ADDRSTRLEN];

        struct s_info_client *ts=(struct s_info_client *)arg;
        while(1)
        {
            n=Recv(ts->sockfd,buf,MAXLINE,MSG_WAITALL);
            if(n==0)
            {
                printf("The server has beens closed\n");
                break;
            }
            printf("Data from other side:%s",buf);
            memset(buf,0,sizeof(buf));  
        }
        Close(ts->sockfd);
        pthread_exit(0);
}

void *do_client()
{
        pthread_mutex_lock(&mutex);  //加锁

        struct sockaddr_in servaddr_client;
        char buf[MAXLINE];
        int sockfd;
        pthread_t tid; //定义pthread_t型变量
        struct s_info_client ts;

        sockfd = Socket(AF_INET, SOCK_STREAM, 0);

        bzero(&servaddr_client, sizeof(servaddr_client));
        servaddr_client.sin_family = AF_INET;
        inet_pton(AF_INET, "127.127.0.1", &servaddr_client.sin_addr);
        servaddr_client.sin_port = htons(SERV2_PORT);

        //利用connect()的返回值来确定系统是否开启服务器,返回值-1:系统未开启服务器;0:系统已开启服务器
        if (connect(sockfd, (struct sockaddr *)&servaddr_client, sizeof(servaddr_client)) < 0) 
        {
            pthread_mutex_unlock(&mutex); //解锁
            pthread_exit(0);   //结束此线程
        }

        ts.servaddr=servaddr_client;
        ts.sockfd=sockfd;
        pthread_create(&tid,NULL,do_work_client,(void*)&ts);  //将服务器端的地址及客服端的文件描述符作为参数传递给子线程do_work_client()函数
        pthread_detach(tid);     //将线程设置成分离的,线程运行结束后会自动释放所有资源
        while(1)
        {
            fgets(buf,sizeof(buf),stdin);
            Send(sockfd,buf,MAXLINE,0);
        }

        pthread_mutex_unlock(&mutex);
        Close(sockfd);
        pthread_exit(0);   
}


    
int main(void)
{
        struct sockaddr_in servaddr, cliaddr;
        socklen_t cliaddr_len;
        int listenfd, connfd;
        char buf[MAXLINE];
		char str[MAXLINE];
        int i, n;
		pthread_t tid,tid_back,tid_other;  //定义pthread_t型的变量

		pthread_mutex_init(&mutex,NULL);  //初始化锁

    	struct s_info ts[100]; //创建结构体数组,设置线程上限
    	i=0;


		pthread_create(&tid_other,NULL,do_client,NULL);  //客户端线程
		pthread_detach(tid_other);

		sleep(1);

		pthread_mutex_lock(&mutex);//加锁

		//主线程:服务器
	   	listenfd = Socket(AF_INET, SOCK_STREAM, 0);

        bzero(&servaddr, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        servaddr.sin_port = htons(SERV1_PORT);
        Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
        Listen(listenfd, 20);

        printf("Accepting connections ...\n");
        cliaddr_len=sizeof(cliaddr);

        while(1)
        {
            connfd=Accept(listenfd,(struct sockaddr *)&cliaddr,&cliaddr_len);
            ts[i].cliaddr=cliaddr;
            ts[i].connfd=connfd;

            pthread_create(&tid,NULL,do_work,(void*)&ts[i]);  //将ts[i]作为参数传递给子线程do_work()函数
            pthread_detach(tid);     //将线程设置成分离的,线程运行结束后会自动释放所有资源
            pthread_create(&tid_back,NULL,do_work_back,(void*)&ts[i]);
            pthread_detach(tid_back);

            i++;  //起下一线程
        }

        pthread_mutex_unlock(&mutex);  //解锁
        pthread_mutex_destroy(&mutex); //关闭锁
        pthread_exit(0);
}

线程同步常用的方方式与区别

线程(进程)之间的制约关系?
当线程并发执行时,由于资源共享和线程协作,使用线程之间会存在以下两种制约关系。
(1)间接相互制约(互斥)。一个系统中的多个线程必然要共享某种系统资源,如共享CPU,共享I/O设备,所谓间接相互制约即源于这种资源共享,打印机就是最好的例子,线程A在使用打印机时,其它线程都要等待。
(2)直接相互制约(同步)。这种制约主要是因为线程之间的合作,如有线程A将计算结果提供给线程B作进一步处理,那么线程B在线程A将数据送达之前都将处于阻塞状态。 归纳如下:
1、互斥是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
2、同步是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。
3、同步其实已经实现了互斥,所以同步是一种更为复杂的互斥。
4、互斥是一种特殊的同步。
间接相互制约可以称为互斥,直接相互制约可以称为同步,对于互斥可以这样理解,线程A和线程B互斥访问某个资源则它们之间就会产个顺序问题——要么线程A等待线程B *** 作完毕,要么线程B等待线程 *** 作完毕,这其实就是线程的同步了。因此同步包括互斥,互斥其实是一种特殊的同步。

线程同步的五种常用资源

1.临界资源/关键段

当多个线程访问一个独占性共享资源时,可以使用临界区对象

2.互斥量/互斥锁

互斥量多用于多进程之间的线程互斥,用来确保一个线程独占一个资源的访问。

互斥对象和临界区对象非常相似,只是其允许在进程间使用,而临界区只限制与同一进程的各个线程之间使用,但是更节省资源,更有效率。
相关函数:

int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutex_attr_t *mutexattr); //初始化
int pthread_mutex_lock(pthread_mutex *mutex); //建锁
int pthread_mutex_destroy(pthread_mutex *mutex); //销毁
int pthread_mutex_unlock(pthread_mutex * mutex); //开锁

3.事件(CEvent)/条件变量

事件机制,则允许一个线程在处理完一个任务后,主动唤醒另外一个线程执行任务。或者按照条件变量的说法,提供线程之间的一种通知机制。
每个Cevent对象可以有两种状态:有信号状态和无信号状态。
Cevent类对象有两种类型:人工事件和自动事件。

int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);     
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime);
int pthread_cond_destroy(pthread_cond_t *cond);  
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);  //解除所有线程的阻塞

5.令牌

互斥锁举例

同步机制就是:通过一些机制实现对共享资源的有序访问。即多个线程按一种规则来对共享资源顺序访问(在对共享资源 *** 作时,必须保证没有其他线程 *** 作,若在访问共享数据时有其他线程正在对其 *** 作,则需要等待其 *** 作完成再访问)

互斥锁的数据类型为pthread_mutex_t,是一个结构体,我们可以把它当作值只有0和
1的数来简化看待,为1时状态为加锁状态,0为不加锁状态。

对共享资源 *** 作的代码称之为临界区,一般再进入临界区之前会加锁,离开临界区后会解锁。**若加锁的时候这把锁已经被别的线程占有(即已经有线程加锁了),则该线程会阻塞等待,当加锁的线程 *** 作完成解锁后,会唤醒阻塞在锁上的进程,**这样就实现了线程间的同步。

注:所有的锁机制都是一把建议锁,也就是 *** 作系统给你提供了锁机制,但并不强制要求。比如说一个线程已经加锁了,这个时候另一个线程没有加锁就对共享资源访问也是会成功的, *** 作系统并不会因为你加了锁就会限制其他线程访问共享资源。

使用方法:在全局建一把锁,在主控线程中对互斥锁初始化,在所有临界区前后加锁,解锁。

#include
#include
#include
#include
#include

void *tfn(void *arg)
{   
  srand(time(NULL));
     
   while(1)
    {   
        pthread_mutex_lock((pthread_mutex_t *)arg);//加锁
         
       printf("hello ");
         sleep(rand()%3);//模拟长时间 *** 作共享内存,导致cpu易主,产生与时间有关的错误
        printf("world\n");
       
        pthread_mutex_unlock((pthread_mutex_t *)arg);//解锁
       sleep(rand()%3);
     }
     return NULL;
 }
 int main(void)
 {
     //父线程和子线程的共享资源为STDOUT

     pthread_t tid;
     srand(time(NULL));
 
    pthread_mutex_t mutex;//建锁
     pthread_mutex_init(&mutex,NULL);//初始化锁
    pthread_create(&tid,NULL,tfn,(void*)&mutex);//一般做法是把mutex设为全局变量,这里设成局部变量通过传参也是可以的
     while(1)
      {
         pthread_mutex_lock(&mutex);//加锁
       
         printf("HELLO ");
         sleep(rand()%3);
        printf("WORLD\n");
 
         pthread_mutex_unlock(&mutex);//解锁

        sleep(rand()%3);
    }

    pthread_mutex_destroy(&mutex);//释放锁
    return 0;
 }


还有一种自旋锁的概念,使用spinlock自旋,在线程切换时会一直等待释放锁;mutex互斥,在线程切换的时候可能会干其他事情,不会一直等待。spinlock对于时间较短,比较简单,处理时间小于切换线程代价,使用spinlock。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/786081.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-04
下一篇 2022-05-04

发表评论

登录后才能评论

评论列表(0条)

保存