【网络编程】并发服务器 — — fork、thread ## fork()

pid_t fork(void);//创建一个进程,返回一个文件描述符
//文件描述符<0(或者==-1),进程创建失败
//文件描述符==0,表示为子进程
//文件描述符>0,表示为父进程
int main ()
{
    pid_t fpid; //fpid表示fork函数返回的值
    int count = 0;
    fpid = fork();
    if (fpid < 0)
        printf("进程创建失败");
    else if (fpid == 0){//子进程
        printf("我是子进程/n");
        count++;
    }
    else{//fpid>0,父进程
        printf("我是父进程/n");
        count++;
    }
    printf("统计结果是: %d/n", count);
    return 0;
}
//运行结果:
我是子进程
统计结果是:1
我是父进程
统计结果是:1
/*注意哦
    两个进程变量不共用(count最后结果为1),相当于完全复制了一份*/

在语句fpid=fork()之前,只有一个进程在执行这段代码,但在这条语句之后,就变成两个进程在执行了,这两个进程的几乎完全相同,将要执行的下一条语句都是if(fpid<0)……

为什么两个进程的fpid不同呢,这与fork函数的特性有关。

fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
1)在父进程中,fork返回新创建子进程的进程ID;对应父进程中fork返回值>0
2)在子进程中,fork返回0;对应子进程中fork返回值==0
3)如果出现错误,fork返回一个负值;

在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID。

利用fork创建多个进程,实现并发服务器。父进程用于客户端的请求连接,所以关闭父进程的connfd,子进程用于服务器客户端的通信,因此子进程关闭listenfd

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2PU0AA0y-1646448994253)(img\fork创建进程.png)]

server.cpp

//server.cpp
#include<iostream>
#include<string>
#include<string.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<netdb.h>
#include<arpa/inet.h>
#include<netinet/in.h>
using namespace std;

int main(int argc, char *argv[]){
	//1.创建套接字socket
	int listenfd;
	if((listenfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))<0)
		cout<<"make socket error!"<<endl;

	//2.创建ipv4地址,并和socket绑定 
	struct sockaddr_in servaddr;
	memset(&servaddr,0,sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(5188);
	servaddr.sin_addr.s_addr = inet_addr("192.168.177.128");//my(seirver) addr
	//servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//本机的任意地址
	int on = 1;
	if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0)
		cout<<"setsockopt reuseaddr error!"<<endl;
	if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
		cout<<"bind error!"<<endl;
	
	//3.监听
	if(listen(listenfd,10)<0)
		cout<<"listen error!"<<endl;
	
	//4.accept,等待连接(阻塞)
	struct sockaddr_in clientaddr;
	socklen_t clientlen =sizeof(clientaddr);
		//socklen_t是一种数据类型,和int差不多
		//32位下,size_t和int的长度相同,都是32 bits,但64位下,size_t(32bits)和int(64 bits)长度不同
		//socket编程中的accept函数的第三个参数的长度必须和int的长度相同。于是有了socklen_t类型。
	int connfd;
	pid_t pid;
	while(1){
		if((connfd=accept(listenfd,(struct sockaddr*)&clientaddr,&clientlen))<0)
                	cout<<"accept error!"<<endl;
        cout<<"New Connect"<<"\tClient ip:"<<inet_ntoa(clientaddr.sin_addr);
		cout<<"\tClient port:"<<ntohs(clientaddr.sin_port)<<endl;
		pid = fork();
		if(pid==0){//子进程
			close(listenfd);
			//5.data transfer
        	char rec[1024];
        	int iconnect = 0;
        	while(1){
                memset(rec,0,sizeof(rec));
                int readlen = read(connfd,rec,sizeof(rec));//set 接收字符串的长度
				//cout<<"接收的数据长度:"<<set<<endl;
                if(readlen==0){//接收的数据长度为0时,退出子进程
					cout<<"Close"<<"\tClient ip:"<<inet_ntoa(clientaddr.sin_addr);
                	cout<<"\tClient port:"<<ntohs(clientaddr.sin_port)<<endl;
					close(connfd);
					exit(0);//退出子进程
				}	
				cout<<"From ip:"<<inet_ntoa(clientaddr.sin_addr);
            	cout<<" port:"<<ntohs(clientaddr.sin_port)<<"  ";
				cout<<rec<<endl;
            	write(connfd,rec,readlen);
        	}
		}
		else if(pid>0){//父进程
			close(connfd);
		}
		else cout<<"fork error!"<<endl;//pid<0,进程创建失败
	}
	close(listenfd);
	close(connfd);
	cout<<"over"<<endl;
	return 0;
}

client.cpp

//client.cpp
#include<iostream>
#include<string>
#include<string.h>
#include<stdio.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<netdb.h>
#include<arpa/inet.h>
#include<netinet/in.h>
using namespace std;

int main(int argc,char* argv[]){
	//1.make socket
	int sockfd;
	if((sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))<0)
		cout<<"make socket error!"<<endl;
	
	//2.make ip addr and connect
	struct sockaddr_in servaddr;
	memset(&servaddr,0,sizeof(servaddr));
	//void *memset(void *s,int c,size_t n)
	//总的作用:将已开辟内存空间 s 的首 n 个字节的值设为值 c。
	servaddr.sin_family= AF_INET;
	servaddr.sin_port = htons(5188);
	servaddr.sin_addr.s_addr = inet_addr("192.168.177.128");

	if(connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
		cout<<"connect error!"<<endl;
	
	//3.after connect	
	char sendbuf[1024]= {0};
	char recvbuf[1024]= {0};
	while(1){
		cin>>sendbuf;
		cout<<strlen(sendbuf)<<endl;
		write(sockfd,sendbuf,strlen(sendbuf));
		read(sockfd,recvbuf,sizeof(recvbuf));
		cout<<recvbuf<<endl;
		memset(sendbuf,0,sizeof(sendbuf));
		memset(recvbuf,0,sizeof(recvbuf));
	}
	close(sockfd);
	return 0;
}

thread()

多进程并发服务器:主线程处理listenfd事件,每有一个新的连接创建一个子进程去处理。

多线程并发服务器:主线程处理listenfd事件,每有一个新的连接创建一个子线程去处理。

区别:

每个进程都有自己的资源空间(不共用),在创建子进程时会先自动把父进程的资源全拷贝过来。

子线程和主线程共用一个资源空间,每一次新来的连接都会覆盖客户端信息,所以每个子线程因该自己开辟一个空间存储对应的用户信息(在被覆盖前,从主线程拷贝过来)

//pthread_create是UNIX环境创建线程的函数
#include <pthread.h>
int pthread_create(pthread_t* tid,const pthread_attr_t* attr,void* (*start_rtn)(void*),void* arg);
    tidp:事先创建好的pthread_t类型的参数。成功时tid指向的内存单元被设置为新创建线程的线程ID
    attr:用于定制各种不同的线程属性,通常直接设为NULL
    start_rtn:新创建线程从此函数开始运行,无参数是arg设为NULL即可
    arg:start_rtn函数的参数。无参数时设为NULL即可。有参数时输入参数的地址。当多于一个参数时应当使用结构体传入。
    返回值:成功返回0,否则返回错误码。
//例子
pthread_t tid;
void do_work();//函数
pthread_create(&tid, NULL, do_work, (void*)&ts);
//线程号tid,调用do_work()函数,将数据ts传给do_work()函数
//针对pthread_create()中第二个参数设置线程分离属性,这样在线程结束时,回收线程资源
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
//设置线程分离属性,将设置好的attr传入,在线程结束时,回收线程资源
pthread_create(&tid,&attr,do_work,&cliInfo);

由于pthread库不是Linux系统默认的库,连接时需要使用库libpthread.a,所以在使用pthread_create创建线程时,在编译中要加-lpthread参数:

对‘pthread_create’未定义的引用
g++ -g mser_thread.cpp wrap.c -lpthread -o mser

server.cpp

#include<iostream>
#include<string>
#include<string.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<netdb.h>
#include<pthread.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include"wrap.h"
using namespace std;
//高并发--多线程实现
struct cli_info{//pthread_create()只能传一个参数,所以报要传的数据写出一个结构体
	int connfd;
	struct sockaddr_in clientaddr;
};

void* do_work(void* arg){
	struct cli_info *info = (struct cli_info*)arg;
	int connfd=info->connfd;
	struct sockaddr_in clientaddr=info->clientaddr;
	char rec[256]={0};
	while(1){
        int readlen=read(connfd,rec,sizeof(rec));
        if(readlen==0){//接收的数据长度为0时,退出
            cout<<"Close"<<"\tClient ip:"<<inet_ntoa(clientaddr.sin_addr);
            cout<<"\tClient port:"<<ntohs(clientaddr.sin_port)<<endl;
            close(connfd);
            break;
        }
		cout<<"From ip:"<<inet_ntoa(clientaddr.sin_addr);
		cout<<" port:"<<ntohs(clientaddr.sin_port)<<"  ";
		cout<<rec<<endl;
		write(connfd,rec,readlen);
	}
}

int main(int argc, char *argv[]){
	//1.创建套接字socket
	int listenfd;
	listenfd = Socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);

	//2.创建ipv4地址,并和socket绑定 
	struct sockaddr_in servaddr;
	memset(&servaddr,0,sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(5188);
	servaddr.sin_addr.s_addr = inet_addr("192.168.109.128");//my(seirver) addr
	//servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//本机的任意地址
	int on = 1;
	if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0)
		cout<<"setsockopt reuseaddr error!"<<endl;
	Bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
	
	//3.监听
	Listen(listenfd,10);
	
	//4.accept,等待连接(阻塞)
	struct sockaddr_in clientaddr;
	socklen_t clientlen =sizeof(clientaddr);
	int connfd;
	cli_info cliInfo;
	pthread_attr_t attr;
	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
	while(1){
		connfd=Accept(listenfd,(struct sockaddr*)&clientaddr,&clientlen);
        cout<<"New Connect"<<"\tClient ip:"<<inet_ntoa(clientaddr.sin_addr);
		cout<<"\tClient port:"<<ntohs(clientaddr.sin_port)<<endl;
		cliInfo.connfd=connfd;
		cliInfo.clientaddr=clientaddr;
		pthread_t tid;
		pthread_create(&tid,&attr,do_work,&cliInfo);
	}
	close(listenfd);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值