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

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


实验目的

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

实验内容

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

基本概念

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

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

#include  
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 
void pthread_exit(void *retval);

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

#include 
int pthread_cancel(pthread_t  th);

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

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

#include  
int pthread_join(pthread_t  th, void **thread_return);

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

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

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

#include 
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
#include
#include
#include
#include
#ifndef INADDR_NONE
#define INADDR_NONE 0xFFFFFFFF
#endif
int 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 
#include 
#include 
#include 
#include 
#include 

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   
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 
 #include 
 #include 
 #include 
 #include 
 #include 
 #include 
 #include   
 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))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 
  #include 
  #include 
  #include 
  #include 
  #include 
  #include   
  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运行客户端。
三个客户端能互相交互,则实验成功。(端口号可以自行设定)

你可能感兴趣的:(网络编程实验3-并发多线程服务器设计)