多线程socket通信,信号量控制发送多线程发送顺序,测试通信延时

这是我工作的第一个任务,为了熟悉套接字,刚开始做这个任务几乎是崩溃的,因为对C语言,对套接字,锁的原理毫无了解,编了很久,出了很多问题
任务:在linux上,做一个1个client,1个service,自己做makefile,client建立5个线程建立5个套接字按顺序向service发包,service收到包以后返回timestamp,计算延时。
需求分析:
1.一个client实现5个线程
2.5个线程按顺序向sevice发包
3.server端发送test ready to client,client接到命令后,向service发送测试程序
4.service可以实现和多个套接字的通信,分别返回多个套接字所用的信息
5.做makefile,可以在linux环境下编译


client端:

/* 
* 客户端代码 
*/  
#include "confile.h"

/*定义传送包结构体*/
struct DataForm
	{
	char 	DataForm_content[1024];
	int 	DataForm_i;
	long    DataForm_send_sec;
	long 	DataForm_send_usec;
	};
/*定义时间戳结构体*/
struct timeval t_begin,t_end;
struct DataForm p1,p3;
long latecy,Recvtime;
/*定义互斥量,条件变量*/
pthread_mutex_t mutex;
pthread_cond_t cond1_2 = PTHREAD_COND_INITIALIZER;  
pthread_cond_t cond2_3 = PTHREAD_COND_INITIALIZER;  
pthread_cond_t cond3_4 = PTHREAD_COND_INITIALIZER; 
pthread_cond_t cond4_5 = PTHREAD_COND_INITIALIZER;  
pthread_cond_t cond5_1 = PTHREAD_COND_INITIALIZER;  
//标记
int flag1_2,flag2_3,flag3_4,flag4_5,flag5_1;
char buffer[MAX_LINE],msg[MAX_LINE];
int  iSent,length;
//声明延迟时间,收包时间
long latecy,Recvtime_sec,Recvtime_usec;
/*声明套接字和链接服务器地址*/
int  sockfd,sockfd_1,sockfd_2,sockfd_3,sockfd_4,sockfd_5;
struct sockaddr_in servaddr;
//标记检查的线程等待  
void th_condflag_wait(int *flag,pthread_cond_t *cond,pthread_mutex_t *mutex)  
{  
    (*flag) = 1;  
    pthread_cond_wait(cond,mutex);  
    (*flag) = 0;  
}  
  
//标记检查的线程通知  
void th_condflag_broadcast(int *flag,pthread_cond_t *cond,pthread_mutex_t *mutex)  
{  
    while(!(*flag))  
    {  
        pthread_mutex_unlock(mutex);  
        usleep(50);  
        pthread_mutex_lock(mutex);  
    }  
    pthread_cond_broadcast(cond);  
}  

//*线程1向服务器发送test包 序列号_收服务器test包 计算延时并输出 *//
void *send_sn1()
{	
	while(1) {
		pthread_mutex_lock(&mutex);
		th_condflag_wait(&flag5_1,&cond5_1,&mutex); 
		p1.DataForm_i++;
		gettimeofday(&t_begin, NULL);//get send time
		p1.DataForm_send_sec=t_begin.tv_sec;
		p1.DataForm_send_usec=t_begin.tv_usec;
		strcpy(p1.DataForm_content,"test");
		bzero(msg, sizeof(msg));
		memcpy(msg,&p1,sizeof(p1));
		iSent=send(sockfd_1,msg,sizeof(msg),0);
		
		if (iSent<0) {  
			printf("Client Send Data Failed!\n");  
			exit(1);  
		}
		
		bzero(buffer, sizeof(buffer));
		
		length=recv(sockfd_1, buffer, sizeof(buffer), 0);  
		
		if (length < 0) {  
			printf("Server Recieve Data Failed!\n");  
			exit(1);  
    	        }
  
		memcpy(&p3,buffer,sizeof(p3));
		if (strcmp(p3.DataForm_content, "test") == 0) {	
			Recvtime_sec=p3.DataForm_send_sec;
			Recvtime_usec=p3.DataForm_send_usec;
			gettimeofday(&t_end, NULL);//get recive time
			latecy=(1000000*(t_end.tv_sec-Recvtime_sec)+(t_end.tv_usec-Recvtime_usec))/2;
			printf("延时是%ld微秒\n",latecy);
		}
		sleep(1);
		th_condflag_broadcast(&flag1_2,&cond1_2,&mutex); 
		pthread_mutex_unlock(&mutex);
	}
}
//*线程2向服务器发送test包 序列号_收服务器test包 计算延时并输出 *//
void *send_sn2()
{	
	while(1) {
		pthread_mutex_lock(&mutex);
		th_condflag_wait(&flag1_2,&cond1_2,&mutex); 
		p1.DataForm_i++;
		gettimeofday(&t_begin, NULL);
		p1.DataForm_send_sec=t_begin.tv_sec;
		p1.DataForm_send_usec=t_begin.tv_usec;
		strcpy(p1.DataForm_content,"test");
		bzero(msg, sizeof(msg));
		memcpy(msg,&p1,sizeof(p1));
		iSent=send(sockfd_2,msg,sizeof(msg),0);
		if (iSent<0) {  
			printf("Client Send Data Failed!\n");  
			exit(1);  
		}
		bzero(buffer, sizeof(buffer));
		
		length=recv(sockfd_2, buffer, sizeof(buffer), 0);  
		
		if (length < 0) {  
			printf("Recieve Data Failed!\n");  
			exit(1);  
    		}  
		
		memcpy(&p3,buffer,sizeof(p3));
		
		if (strcmp(p3.DataForm_content, "test") == 0) {	
			Recvtime_sec=p3.DataForm_send_sec;
			Recvtime_usec=p3.DataForm_send_usec;
			gettimeofday(&t_end, NULL);
			latecy=(1000000*(t_end.tv_sec-Recvtime_sec)+(t_end.tv_usec-Recvtime_usec))/2;
			printf("延时是%ld微秒\n",latecy);
		}
		sleep(1);
		th_condflag_broadcast(&flag2_3,&cond2_3,&mutex);
		pthread_mutex_unlock(&mutex);
	}
}

//*线程3向服务器发送test包 序列号_收服务器test包 计算延时并输出 *//
void *send_sn3()
{	
	while(1) {
		pthread_mutex_lock(&mutex);
		th_condflag_wait(&flag2_3,&cond2_3,&mutex); 
		p1.DataForm_i++;
		gettimeofday(&t_begin, NULL);
		p1.DataForm_send_sec=t_begin.tv_sec;
		p1.DataForm_send_usec=t_begin.tv_usec;
		strcpy(p1.DataForm_content,"test");
		bzero(msg, sizeof(msg));
		memcpy(msg,&p1,sizeof(p1));
		iSent=send(sockfd_3,msg,sizeof(msg),0);
		if (iSent<0) {  
			printf("Client Send Data Failed!\n");  
			exit(1);  
		}
		bzero(buffer, sizeof(buffer));
	
		length=recv(sockfd_3, buffer, sizeof(buffer), 0);  
	
		if (length < 0) {  
			printf("Server Recieve Data Failed!\n");  
			exit(1);  
    	        }
	  
		memcpy(&p3,buffer,sizeof(p3));
		if (strcmp(p3.DataForm_content, "test") == 0) {	
			Recvtime_sec=p3.DataForm_send_sec;
			Recvtime_usec=p3.DataForm_send_usec;
			gettimeofday(&t_end, NULL);
			latecy=(1000000*(t_end.tv_sec-Recvtime_sec)+(t_end.tv_usec-Recvtime_usec))/2;
			printf("延时是%ld微秒\n",latecy);
		}
		sleep(1);
		th_condflag_broadcast(&flag3_4,&cond3_4,&mutex);
		pthread_mutex_unlock(&mutex);
	}
}
	
//*线程4向服务器发送test包 序列号_收服务器test包 计算延时并输出 *//
void *send_sn4()
{	
	while(1) {
		pthread_mutex_lock(&mutex);
		th_condflag_wait(&flag3_4,&cond3_4,&mutex);
		p1.DataForm_i++;
		gettimeofday(&t_begin, NULL);
		p1.DataForm_send_sec=t_begin.tv_sec;
		p1.DataForm_send_usec=t_begin.tv_usec;
		strcpy(p1.DataForm_content,"test");
		bzero(msg, sizeof(msg));
		memcpy(msg,&p1,sizeof(p1));
		iSent=send(sockfd_4,msg,sizeof(msg),0);
		if (iSent<0) {  
			printf("Client Send Data Failed!\n");  
			exit(1);  
		}
		bzero(buffer, sizeof(buffer));
	
		length=recv(sockfd_4, buffer, sizeof(buffer), 0);  
	
		if (length < 0) {  
			printf("Server Recieve Data Failed!\n");  
			exit(1);  
    		}  
		
		memcpy(&p3,buffer,sizeof(p3));
		
		if (strcmp(p3.DataForm_content, "test") == 0) {	
			Recvtime_sec=p3.DataForm_send_sec;
			Recvtime_usec=p3.DataForm_send_usec;
			gettimeofday(&t_end, NULL);
			latecy=(1000000*(t_end.tv_sec-Recvtime_sec)+(t_end.tv_usec-Recvtime_usec))/2;
			printf("延时是%ld微秒\n",latecy);
		}
		sleep(1);
		th_condflag_broadcast(&flag4_5,&cond4_5,&mutex);
		pthread_mutex_unlock(&mutex);
	}
}
//*线程5向服务器发送test包 序列号_收服务器test包 计算延时并输出 *//
void *send_sn5()
{	
	while(1) {
		pthread_mutex_lock(&mutex);
		th_condflag_wait(&flag4_5,&cond4_5,&mutex);
		p1.DataForm_i++;
		gettimeofday(&t_begin, NULL);
		p1.DataForm_send_sec=t_begin.tv_sec;
		p1.DataForm_send_usec=t_begin.tv_usec;
		strcpy(p1.DataForm_content,"test");
		bzero(msg, sizeof(msg));
		memcpy(msg,&p1,sizeof(p1));
		iSent=send(sockfd_5,msg,sizeof(msg),0);
		if (iSent<0) {  
			printf("Client Send Data Failed!\n");  
			exit(1);  
		}
		bzero(buffer, sizeof(buffer));
		length=recv(sockfd_5, buffer, sizeof(buffer), 0);  
		if (length < 0) {  
			printf("Server Recieve Data Failed!\n");  
			exit(1);  
    	}  
		memcpy(&p3,buffer,sizeof(p3));
		if (strcmp(p3.DataForm_content, "test") == 0) {	
			Recvtime_sec=p3.DataForm_send_sec;
			Recvtime_usec=p3.DataForm_send_usec;
			gettimeofday(&t_end, NULL);
			latecy=(1000000*(t_end.tv_sec-Recvtime_sec)+(t_end.tv_usec-Recvtime_usec))/2;
			printf("延时是%ld微秒\n",latecy);
		}
		sleep(1);
		th_condflag_broadcast(&flag5_1,&cond5_1,&mutex);
		pthread_mutex_unlock(&mutex);
	}
}
int main(int argc, char **argv)
{
    /*申明线程ID*/
	pthread_t send_tid1,send_tid2,send_tid3,send_tid4,send_tid5; 
	p1.DataForm_i=0;
 	/*(1) 设置链接服务器地址结构*/ 
	bzero(&servaddr , sizeof(servaddr));  
	servaddr.sin_family = AF_INET;  
	servaddr.sin_port = htons(PORT);
	servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); 
	/*(2) 创建套接字,连接套接字,接受test请求*/
	sockfd= socket(AF_INET,SOCK_STREAM , 0);
	if(sockfd==-1) {  
	perror("socket error");  
	exit(1);  
	}
	if (connect(sockfd, (struct sockaddr *)&servaddr , sizeof(servaddr)) < 0) {  
		perror("connect error");  
		exit(1);
	};
	length=recv(sockfd, buffer, sizeof(buffer), 0);  
	if (length < 0) {  
		printf("Recieve Data Failed!\n");  
		exit(1);  
    } 
	if (strcmp(buffer, "test ready") == 0) {
		printf("test begin\n");
		sleep(1);
	/*(3)创建5个线程的套接字并连接*/
		sockfd_1= socket(AF_INET,SOCK_STREAM , 0);
		if(sockfd_1==-1) {  
			perror("socket_1 error");  
			exit(1);  
		}//创建套接字1
		if (connect(sockfd_1, (struct sockaddr *)&servaddr , sizeof(servaddr)) < 0) {  
			perror("connect error");  
			exit(1);  
		};//连接套接字1
		sockfd_2= socket(AF_INET,SOCK_STREAM , 0);
		if(sockfd_2==-1) {  
        	perror("socket_2 error");  
        	exit(1);  
		}//创建套接字2
		if (connect(sockfd_2, (struct sockaddr *)&servaddr , sizeof(servaddr)) < 0) {  
			perror("connect error");  
			exit(1);  
		}; //连接套接字2
		sockfd_3= socket(AF_INET,SOCK_STREAM , 0);
		if(sockfd_3==-1) {  
			perror("socket error");  
			exit(1);  
		}//创建套接字3
		if (connect(sockfd_3, (struct sockaddr *)&servaddr , sizeof(servaddr)) < 0) {  
			perror("connect error");  
			exit(1);  
		} //连接套接字3
		sockfd_4= socket(AF_INET,SOCK_STREAM , 0);
		if(sockfd_4==-1) {  
			perror("socket error");  
			exit(1);  
		}//创建套接字4
		if (connect(sockfd_4, (struct sockaddr *)&servaddr , sizeof(servaddr)) < 0) {  
			perror("connect error");  
			exit(1);
		};//连接套接字4
		sockfd_5= socket(AF_INET,SOCK_STREAM , 0);
		if(sockfd_5==-1) {  
			perror("socket error");  
			exit(1);  
		}//创建套接字5
		if (connect(sockfd_5, (struct sockaddr *)&servaddr , sizeof(servaddr)) < 0) {  
			perror("connect error");  
			exit(1);  
		};//连接套接字5
		/*创建5个线程向服务器发送序列号*/
		pthread_mutex_init(&mutex,NULL);
  		if (pthread_create(&send_tid1, NULL , send_sn1, NULL) == -1) {  
			perror("pthread create error.\n");  
			exit(1);  
		} 
		if (pthread_create(&send_tid2, NULL , send_sn2, NULL) == -1) {  
			perror("pthread create error.\n");  
			exit(1);  
		} 
		if (pthread_create(&send_tid3, NULL , send_sn3, NULL) == -1) {  
			perror("pthread create error.\n");  
			exit(1);  
		}
		if (pthread_create(&send_tid4, NULL , send_sn4, NULL) == -1) {  
			perror("pthread create error.\n");  
			exit(1);  
		}
		if (pthread_create(&send_tid5, NULL , send_sn5, NULL) == -1) {  
			perror("pthread create error.\n");  
			exit(1);  
		}
		//给起始线程发信号,防止1和5死锁
		pthread_mutex_lock(&mutex);  
    	        th_condflag_broadcast(&flag5_1,&cond5_1,&mutex);  
    	        pthread_mutex_unlock(&mutex);  
		pthread_join(send_tid1,NULL);
		pthread_join(send_tid2,NULL);
		pthread_join(send_tid3,NULL);
		pthread_join(send_tid4,NULL);
		pthread_join(send_tid5,NULL);	  
		pthread_mutex_destroy(&mutex);
		}
	return 0;  
}





 
 
client端分析:
1.基本实现了需求的功能,用pthread_cond_wait实现了按顺序发送的功能
2.封装性较差,5个函数send_sn,实现了几乎相同的功能。可以将cond,flag,sockfd作为参数传递进去
修改方法如下:
typedef  struct{
	int cond_wait;
	int flag_tell;
	int cond_tell;
	int socketfd;
   }send_t

void *send_sn(void *arg)
{
	send_t  *send = (send_t  *)arg
	while(1) {
		pthread_mutex_lock(&mutex);
		th_condflag_wait(&(send->flag_wait),&(send->cond_wait),&mutex); 
		p1.DataForm_i++;
		gettimeofday(&t_begin, NULL);//get send time
		p1.DataForm_send_sec=t_begin.tv_sec;
		p1.DataForm_send_usec=t_begin.tv_usec;
		strcpy(p1.DataForm_content,"test");
		bzero(msg, sizeof(msg));
		memcpy(msg,&p1,sizeof(p1));
		iSent=send(send->socketfd,msg,sizeof(msg),0);
		If (iSent<0) {  
			printf("Client Send Data Failed!\n");  
			exit(1);  
		}
		bzero(buffer, sizeof(buffer));
		length=recv(send->socketfd, buffer, sizeof(buffer), 0);  
		if (length < 0) {  
			printf("Server Recieve Data Failed!\n");  
			exit(1);  
           	}
  
		memcpy(&p3,buffer,sizeof(p3));
		if (strcmp(p3.DataForm_content, "test") == 0) {
			Recvtime_sec=p3.DataForm_send_sec;
			Recvtime_usec=p3.DataForm_send_usec;
			gettimeofday(&t_end, NULL);//get recive time
			latecy=(1000000*(t_end.tv_sec-Recvtime_sec)+(t_end.tv_usec-Recvtime_usec))/2;
			printf("延时是%ld微秒\n",latecy);
		}
		sleep(1);
		th_condflag_broadcast(&(send->flag_tell),&(send->cond_tell),&mutex); 
		pthread_mutex_unlock(&mutex);
	}
}

	send_t send_1;
	send_1.flag_wait = flag5_1;
	send_1. flag_tell = flag1_2;
	send_1.cond_wait = cond5_1;
	send_1.cond_tell = cond1_2;
 
	if (pthread_create(&send_tid1, NULL , send_sn,  (void *)&sent_t) == -1) {  
		perror("pthread create error.\n");  
		exit(1);  
	} 


/* 
*  服务器端代码实现 
*/  

#include "confile.h"
//定义接收数据包结构体
	struct DataForm
	{
	char 	DataForm_content[1024];
	int 	DataForm_i;
	long    DataForm_send_sec;
	long 	DataForm_send_usec;
	};
	
int main(int argc , char **argv)  
{  
	struct DataForm p2;//接受包的结构体
	int i,p,maxi, maxfd, listenfd, connfd, sockfd, testfd;//创建套接字
	int nready , client[BACKLOG];//select的返回值    
	ssize_t n;// 接收字节	  
	fd_set rset , allset;//定义套接字的文件描述符
	char buffer[MAX_LINE];//接受信息的缓存区 
	socklen_t clilen;
	//设置套接字地址结构
	struct sockaddr_in servaddr,cliaddr;
	clilen = sizeof(cliaddr);
	bzero(&servaddr , sizeof(servaddr));  
	servaddr.sin_family = AF_INET;	
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);  
	servaddr.sin_port = htons(PORT);  
	/*(1) 得到监听描述符*/	
	listenfd = socket(AF_INET , SOCK_STREAM , 0);  
	/*(2) 绑定套接字*/	
	bind(listenfd , (struct sockaddr *)&servaddr , sizeof(servaddr));  	  
	/*(3) 监听*/  
	listen(listenfd , LISTENQ);
	/*(4) 向服务器发送test信号*/
	if((testfd= accept(listenfd , (struct sockaddr *)&cliaddr , &clilen)) < 0) {  
		perror("accept error.\n");	
		exit(1);
	}
	bzero(buffer,sizeof(buffer));
	strcpy(buffer,"test ready");
	send(testfd,buffer,sizeof(buffer),0);
	/*(5) 设置select*/	
	maxfd = listenfd;  
	maxi = -1;	
	p=0;
	for(i=0 ; i<BACKLOG; ++i)  
	{  
		client[i] = -1;  
		}//for	
	FD_ZERO(&allset);  
	FD_SET(listenfd , &allset);    
	/*(6) 进入服务器接收请求死循环*/  
	while(1) {  
		p++;//计数器
		rset = allset;//向文件描述符赋值	
		nready = select(maxfd+1 , &rset , NULL , NULL , NULL);	  
		if(FD_ISSET(listenfd , &rset)) {//判断是否有新的套接字进入     
			printf("\naccpet connection~\n");  
			if((connfd = accept(listenfd , (struct sockaddr *)&cliaddr , &clilen)) < 0) { //连接
				perror("accept error.\n");	
				exit(1);  
			}//if		  
			printf("accpet a new client: %s:%d\n", inet_ntoa(cliaddr.sin_addr) , cliaddr.sin_port);
		/*将客户链接套接字描述符添加到数组*/  
			for(i=0 ; i<FD_SETSIZE ; ++i) {  
				if(client[i] < 0) {  
				client[i] = connfd;  
				break;	
				}  
			}	
			if(BACKLOG == i) {//判断是否达到最大监听数
				perror("too many connection.\n");  
				exit(1);  
			}  
			FD_SET(connfd , &allset);//把新连接加入到allset中
			if(connfd > maxfd) {	
				maxfd = connfd;
			}
			if(i > maxi) {  
				maxi = i;
			}
			if(--nready < 0) {//说明没有新连接 
				continue;
			}
		}//if    
		for(i=0; i<=maxi ; ++i) {  
			if((sockfd = client[i]) < 0)//  
				continue;  
			if(FD_ISSET(sockfd , &rset)) {  
				/*处理客户请求*/  
				bzero(buffer , MAX_LINE);
				if((n = recv(sockfd , buffer, sizeof(buffer), 0)) <= 0) {  
					close(sockfd);	
					FD_CLR(sockfd , &allset);  
					client[i] = -1;  
				}//if  
				else {  
					memcpy(&p2,buffer,sizeof(p2));
					printf("clint[%d] send message: %d\n", i , p2.DataForm_i);
					if(strcmp(buffer, "\0") == 0) {
						printf("传递序列号有空值\n"); 	
						continue;
					}
					if (p2.DataForm_i==p) {
						printf("传递序列号顺序乱\n");
						continue;
					}
					if (strcmp(p2.DataForm_content, "test") == 0) {
					send(sockfd,buffer,sizeof(buffer),0);
					bzero(buffer,sizeof(buffer));
					}//if 
				}//else  
			if(--nready <= 0)  
				break;	
			}//if  
		}//for	
	} 
} 

	

service端分析:
1.利用select监听多个线程,收到信息后,check包序列号是否正确,立刻返回。
2.对于一个新手来说,selec模式监听的套接字server端理解相对较为困难。



include:

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <sys/socket.h>  
#include <sys/types.h>  
#include <unistd.h>  
#include <errno.h>  
#include <netinet/in.h>  
#include <netdb.h>  
#include <arpa/inet.h>  
#include <pthread.h> 
#include <time.h> 
#include <sys/time.h>


#define MAX_LINE  2048  
#define PORT	  8000  
#define BACKLOG     10  
#define LISTENQ   6666  
#define MAX_CONNECT 20

makefile:

all:server1 client1

server1:server1.o
	arm-linux-gcc -o server1 server1.o
	
client1:client1.o
	arm-linux-gcc -o client1 client1.o -lpthread
	
server1.o:server1.c confile.h
	arm-linux-gcc -c -lpthread -Wall -Werror server1.c 
	
client1.o:client1.c confile.h
	arm-linux-gcc	-c -lpthread -Wall -Werror client1.c 

clean:
	rm *.o
	rm server1
	rm client1
一个标准的linux开发人员,应该基本写makefile,写shell脚本的能力,这一期的程序为一个简单的测试程序,不做过多的详细描述,希望各位看官提出宝贵的意见,希望初学者们可以仔细理解server端select模式代码处理逻辑,对于今后的开发会有很大的帮助


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值