这是我工作的第一个任务,为了熟悉套接字,刚开始做这个任务几乎是崩溃的,因为对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模式代码处理逻辑,对于今后的开发会有很大的帮助