网络编程实验3-并发多线程服务器设计

来源:互联网 发布:淘宝网机械水压开关 编辑:程序博客网 时间:2024/06/05 17:23

网络编程实验3-并发多线程服务器设计


实验目的

本次实验的主要目的是使用多线程实现并发的,面向连接的服务器设计。

实验内容

1、设计多线程的,面向连接的并发服务器
2、改造客户端为面向连接的多线程客户端

基本概念

1、将线程用于并发的、面向连接服务器的算法
主1 创建套接字并将其绑定到所提供服务的熟知地址上。让该套接字保持非连接。
主2 将该端口设置为被动模式,使其准备为服务器所用。
主3 反复调用accept以便接受来自客户的下一个连接请求,并创建新的从线程来处理响应。
从1 由主线程传递来的连接请求(即针对连接的套接字)开始。
从2 用该连接与客户进行交互:读取请求并发回响应。
从3 关闭连接并退出。在处理完来自客户的所有请求后,从线程就退出。

2、线程
线程是一个进程内部的一个控制序列,是一次独立的计算。
(1)创建线程

#include  <pthread.h>int  pthread_create(pthread_t   thread,   pthread_attr_t   *attr,                  void *(*start_routine)(void*), void *arg);

返回值: 0 成功 错误号表示失败。
thread: 指向pthread_t类型的变量,新创建的线程标识符;
attr:指向pthread_attr_t线程属性类型的变量,
start_routine: 指向线程函数的指针,线程要执行的代码。
arg: 指向线程参数的指针。

(2)终止线程
线程退出方式:
① 从线程函数中返回,返回值为线程的退出码;
return(retu_val);
② 被同一进程的其他线程终止,即被取消;
pthread_cancel;
③ 执行线程退出调用;
pthread_exit;
exit
线程退出:

#include <pthread.h>void pthread_exit(void *retval);

*retval: void类型的指针。
线程终止:

#include <pthread.h>int pthread_cancel(pthread_t  th);

返回值: 0 成功 错误号表示失败

(3)等待线程
等待线程的终止。

#include  <pthread.h>int pthread_join(pthread_t  th, void **thread_return);

返回值:0 成功 错误码表示失败

(4)线程协调和同步
Linux的同步机制:
① 互斥( mutex)
② 信号量( semaphore)
③ 条件变量( condition variable)

(5)线程的属性
属性: 脱离线程(detached thread)
初始化属性对象

#include <pthread.h>int  pthread_attr_init(pthread_attr_t  *attr);

返回码:成功 0
失败 错误代码
回收函数
Pthread_attr_destroy(pthread_attr_t *attr);

实验步骤

1.编写头文件sockutil.h

#ifndef SOCKUTIL_H#define SOCKUTIL_H#include<sys/types.h>#include<sys/socket.h>#include<arpa/inet.h>#include<netinet/in.h>#include<netdb.h>#ifndef INADDR_NONE#define INADDR_NONE 0xFFFFFFFF#endifint connectSock(char* host,char* service,char* protocol);int passiveSock(char* service,char* protocol,int qlen);void errexit(char* fmt,...);#endif

2.创建sockutil.c文件,编写通用过程connectSock及passiveSock代码

#include "sockutil.h"#include <stdio.h>#include <stdlib.h>#include <string.h>#include <stdarg.h>#include <unistd.h>#include <errno.h>int connectSock(char* host,char* service,char* protocol){        struct hostent* pHost;        struct servent* pServ;        struct protoent* pProto;        struct sockaddr_in addr;        int s,type;        memset(&addr,0,sizeof(addr));        addr.sin_family=AF_INET;        if((pHost=gethostbyname(host))!=NULL)                memcpy(&addr.sin_addr,pHost->h_addr,pHost->h_length);        else if((addr.sin_addr.s_addr=inet_addr(host))==INADDR_NONE)                errexit("can't get \"%s\" host entry",host);        if((pServ=getservbyname(service,protocol))!=NULL)                addr.sin_port=pServ->s_port;        else if((addr.sin_port=htons((unsigned short)atoi(service)))==0)                errexit("can't get \"%s\" service entry",service);        if((pProto=getprotobyname(protocol))==0)                errexit("can't get \"%s\" protocol entry",protocol);        if(strcmp(protocol,"tcp")==0)                type=SOCK_STREAM;        else                type=SOCK_DGRAM;        s=socket(PF_INET,type,pProto->p_proto);        if(s<0)                errexit("can't create socket");        if(connect(s,(struct sockaddr*)&addr,sizeof(addr))<0)                errexit("can't connect to %s:%s",host,service);        return s;}int passiveSock(char* service,char* protocol,int qlen){        struct servent* pServ;        struct protoent* pProto;        struct sockaddr_in addr;        int s,type,reuse=1;        memset(&addr,0,sizeof(addr));        addr.sin_family=AF_INET;        addr.sin_addr.s_addr=INADDR_ANY;        if((pServ=getservbyname(service,protocol))!=NULL)                addr.sin_port=pServ->s_port;        else if((addr.sin_port=htons((unsigned short)atoi(service)))==0)                errexit("can't get \"%s\" service entry",service);        if((pProto=getprotobyname(protocol))==0)                errexit("can't get \"%s\" protocol entry",protocol);        if(strcmp(protocol,"tcp")==0)                type=SOCK_STREAM;        else                type=SOCK_DGRAM;        s=socket(PF_INET,type,pProto->p_proto);        if(s<0)                errexit("can't create socket");        if(setsockopt(s,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(int))<0)                errexit("setsockopt err");        if(bind(s,(struct sockaddr*)&addr,sizeof(addr))<0)                errexit("can't bind to %s port",service);        if(type==SOCK_STREAM&&listen(s,qlen)<0)                errexit("can't to listen on %s port",service);        return s;}void errexit(char* fmt,...){        va_list arg_ptr;        va_start(arg_ptr,fmt);        vfprintf(stderr,fmt,arg_ptr);        fprintf(stderr,":%s.\n",strerror(errno));        va_end(arg_ptr);        exit(errno);}

3.编写头文件socklink.h

#ifndef SOCKLINK_H #define SOCKLINK_H#include<pthread.h>   typedef struct SockEle{      void* next;      int sock;      pthread_t tid;  }*pSockEle;    extern pSockEle linkHead,linkTail; void linkInsert(int sock,pthread_t tid); void linkDelete(int sock); void linkPrint(void); int linkGetSize(void); #endif//SOCKLINK_H

4.编写socklink.c文件,用来存储发送的消息内容。

#include"socklink.h" #include"sockutil.h" #include<stdio.h> #include<stdlib.h>   pSockEle linkHead,linkTail; static int      linkSize;  static pSockEle Linkmalloc(int sock,pthread_t tid){      pSockEle p=(pSockEle)malloc(sizeof(struct SockEle));      p->sock=sock;      p->tid=tid;      p->next=NULL;    }void linkInsert(int sock,pthread_t tid){      pSockEle p;      if(sock<0)            errexit("linkInsert err");     p=Linkmalloc(sock,tid);      if(linkHead==NULL)           linkHead=linkTail=p;      else{           linkTail->next=p;           linkTail=p;       }       linkSize++;    } void linkDelete(int sock){      pSockEle p,it;      if(sock<0)           errexit("linkDelete err");      for(p=linkHead;p!=NULL;p=p->next){           if(p->sock==sock){                if(linkHead==linkTail)                    linkHead=linkTail=NULL;               else if(p==linkHead)                     linkHead=linkHead->next;                else if(p==linkTail){                     //找出尾部元素的前一个对象                       for(it=linkHead;it->next!=linkTail;it=it->next);                      it->next=NULL;                     linkTail=it;                 }                  else{                     //找出待删除元素的前一个对象                       for(it=linkHead;it->next!=p;it=it->next);                      it->next=p->next;                  }                  break;        }       }       if(p!=NULL){           free(p);           linkSize--;        }    }void linkPrint(void) {      pSockEle p;       for(p=linkHead;p!=NULL;p=p->next)           printf("%d ",p->sock);      printf("\n");    } int linkGetSize(void) {     return linkSize;  }

5.编写头文件server.h

#ifndef SERVER_H #define SERVER_H   #define QLEN 20 #define BUFSIZE 100  #endif

6.编写server.c文件,实现TCP多线程并发服务器

#include"server.h" #include"sockutil.h"  #include"socklink.h"  #include<stdio.h>  #include<string.h>  #include<stdlib.h>  #include<unistd.h>  #include<pthread.h>  #include<errno.h>  #include<sys/signal.h>  #include<sys/select.h>    int sockFd;   pthread_mutex_t linkLock=PTHREAD_MUTEX_INITIALIZER;  void sigint_handler(int sig){      close(sockFd);       printf("\nConnection has closed.\n");      exit(0);   }   void* commWithClient(void* arg){      char buf[BUFSIZE]={0};       char* strWelcome="Welcome To ChatRoom\n";      int n,ret,sock=(int)arg;      pSockEle p,tmp=0;       //向客户端发送欢迎消息    send(sock,strWelcome,strlen(strWelcome),0);      while(1){           while((n=recv(sock,buf,BUFSIZE,0))>0){              //线程加锁            pthread_mutex_lock(&linkLock);                //循环处理消息发送任务            for(p=linkHead;p!=NULL;p=p->next){                     printf("send to %d\n",p->sock);                     if((ret=send(p->sock,buf,n,0))<n)                             tmp=p;                 }                 if(tmp!=0){                     printf("error:%s.\n",strerror(errno));                     linkDelete(tmp->sock);                     pthread_cancel(tmp->tid);                     tmp=0;                    }            //线程取消枷锁            pthread_mutex_unlock(&linkLock);           }               if(n<0)                      errexit("recv err");       }  }  int main(int argc,char* argv[]) {      int newFd,err;      unsigned int alen;      struct sockaddr_in addr;     pthread_t tid;      char IPBuf[16];      if(argc!=2)            errexit("Usage:%s port.\n",argv[0]);      sockFd=passiveSock(argv[1],"tcp",QLEN);      signal(SIGPIPE,SIG_IGN);      signal(SIGINT,sigint_handler);      while(1)  {           //多线程处理客户端请求        newFd=accept(sockFd,(struct sockaddr*)&addr,&alen);          err=pthread_create(&tid,NULL,commWithClient,(void*)newFd);          pthread_mutex_lock(&linkLock);           linkInsert(newFd,tid);            pthread_mutex_unlock(&linkLock);           if(err!=0)                 errexit("pthread create err");           printf("New Connection  to %s\n",inet_ntop(AF_INET,&(addr.sin_addr.s_addr),IPBuf,16));       }   }

7.编写头文件client.h

#ifndef CLIENT_H #define CLIENT_H   #define BUFSIZE 100#endif

8.编写client.c文件,设计面向连接的多线程客户端

 #include"client.h"   #include"sockutil.h"   #include<stdio.h>   #include<string.h>   #include<stdlib.h>   #include<unistd.h>   #include<errno.h>   #include<sys/signal.h>   #include<sys/select.h>     int sockFd;     void sigint_handler(int sig) {      close(sockFd);       printf("\nConnection has closed.\n");     exit(0);    }     int main(int argc,char* argv[]) {      int n;       fd_set oset,nset;      char buf[BUFSIZE];     if(argc!=3)            errexit("Usage:%s hostname port.\n",argv[0]);      sockFd=connectSock(argv[1],argv[2],"tcp");      FD_ZERO(&oset);      FD_SET(STDIN_FILENO,&oset);      FD_SET(sockFd,&oset);     nset=oset;      signal(SIGPIPE,SIG_IGN);     signal(SIGINT,sigint_handler);      while(1)  {          //利用select进行数据收发        if(select(4,&nset,NULL,NULL,0)<0)                  errexit("select err");            if(FD_ISSET(sockFd,&nset)){                n=recv(sockFd,buf,BUFSIZE,0);               if(n<0)                     errexit("recv err");              if(n>0 && write(STDOUT_FILENO,buf,n)!=n)                         errexit("write err");            }           if(FD_ISSET(STDIN_FILENO,&nset)){                fgets(buf,BUFSIZE,stdin);                if((n=send(sockFd,buf,strlen(buf),0))<0)                     errexit("send err");          }           nset=oset;      }    }

9.编写makefile文件,对已编写完成的代码进行编译运行。

SERVOBJ=sockutil.o server.o socklink.o CLITOBJ=sockutil.o client.o CFLAGS=-lpthread   server:${SERVOBJ}       gcc  -o $@ ${SERVOBJ} ${CFLAGS} client:${CLITOBJ}       gcc  -o $@ ${CLITOBJ} ${CFLAGS} clean:       rm -rf ${CLITOBJ} ${SERVOBJ} 

简要说明:Makefile 文件描述了整个工程的编译、连接等规则。为工程编写Makefile 的好处是能够使用一行命令来完成“自动化编译”。SERVEROBJ、CLIENTOBJ为定义的一个变量,gcc命令为编译变量中的文件,-o用来设置编译后的输出文件名称。
rm命令用来删除指定的文件或目录 -r表明同时删除目录下的所有子目录,-f表明强行删除文件或目录,不提示任何信息。

10.显示实验结果

开启终端,执行以下命令对文件进行编译和运行。
执行命令:make clean server client 对文件进行编译。
执行命令:./server 7777运行服务器端。
重新开启三个新终端,执行命令:./client localhost 7777运行客户端。
三个客户端能互相交互,则实验成功。(端口号可以自行设定)

原创粉丝点击