两个终端,先运行的作为服务器,后运行的作为客户端:这样两个终端的代码基本一致,不同点在于端口号的不同,通过端口号来区分两个终端。其中的实现代码,应考虑先创建两个线程,主线程放服务器程序,子线程放客户端程序。利用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。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)