本次实验的主要目的是使用多线程实现并发的,面向连接的服务器设计。
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);
#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
#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);
}
#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
#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;
}
#ifndef SERVER_H
#define SERVER_H
#define QLEN 20
#define BUFSIZE 100
#endif
#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));
}
}
#ifndef CLIENT_H
#define CLIENT_H
#define BUFSIZE 100
#endif
#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;
}
}
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表明强行删除文件或目录,不提示任何信息。
开启终端,执行以下命令对文件进行编译和运行。
执行命令:make clean server client 对文件进行编译。
执行命令:./server 7777运行服务器端。
重新开启三个新终端,执行命令:./client localhost 7777运行客户端。
三个客户端能互相交互,则实验成功。(端口号可以自行设定)