TCP多进程并发服务器(c)

其他关联文章@丶4ut15m:

TCP网络编程(c)

UDP网络编程(c)

多线程并发服务器(c)

IO复用(c)

多进程并发服务器

多进程并发服务器整个流程和单进程差别不太大,主要区别在交互部分.

服务器

->创建套接字

->绑定地址结构

->监听套接字

->等待并接受连接请求

->主进程创建子进程后执行上一步,子进程与客户端进行交互

->关闭套接字

父进程和子进程都会执行fork(创建子进程)后面的代码,为了区别父进程和子进程通过使用if语句和pid来控制想要父进程和子进程分别执行的代码.

详细内容见我的作业三和代码

多进程编程流程

下面给个实例

服务器:

	服务器等待接收客户的连接请求,连接成功则显示客户地址;
	接收客户端的名称并显示;
	然后接收来自该客户的字符串,对接收的字符串按分组进行加密(分组长度为个人学号,密钥为个人序号,分组不够补0);
	将加密后的字符串发送给客户端;
	继续等待接收该客户的信息,直到客户关闭连接;
	要求服务器具有同时处理多个客户请求的能力。

客户端:

	客户首先与相应的服务器建立连接;
	接着接收用户输入的客户端名称,并将其发送给服务器;
	接收用户输入的字符串并发送给服务器;
	接收服务器发回的加密后的字符串并显示。
	继续等待用户输入字符串;
	若用户输入的是quit,则关闭连接并退出。

	使用多进程实现

服务器代码如下

#include <stdio.h>		//基本输入输出
#include <stdlib.h>		//exit函数所在头文件
#include <sys/socket.h>	//套接字函数所在头文件
#include <string.h>		//memset函数所在头文件
#include <arpa/inet.h>	//inet_ntoa函数所在头文件
#include <unistd.h>		//close等函数所在头文件

#define PORT 3333

char miwen[100];			//定义全局变量,用以存放密文

int response(int connectfd,struct sockaddr_in client);
void encode(char data[],int snum[]);

int main(){
	int listenfd,connectfd;		//定义监听套接字和已连接套接字
	int miyao[] = {2,0,1,7,1,2,2,1,1,9};
	char data[100];
	pid_t chld_pid;				//存放子进程pid
	struct sockaddr_in server,client;		//定义地址结构
	socklen_t clientlen;

	if((listenfd = socket(AF_INET,SOCK_STREAM,0)) == -1){
		//创建套接字
		perror("Sockfd() error!\n");	
		exit(0);
	}

	//地址重用
	int opt = SO_REUSEADDR;	
	setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));	

	//bind操作前的预处理
	memset(&server,'\0',sizeof(server));
	server.sin_family = AF_INET;		//配置协议族为IPv4
	server.sin_port = htons(PORT);		//配置端口
	server.sin_addr.s_addr = htonl(INADDR_ANY);		//设置监听地址
	if(bind(listenfd,(struct sockaddr *)&server,sizeof(server)) == -1){
		//绑定套接字与地址结构
		perror("Bind error!\n");
		exit(0);
	}
	
	if(listen(listenfd,5) == -1){
		//监听,并且设置最大连接数为5
		perror("Listen error!\n");
		exit(0);
	}
	
	while(1){
		//while循环接收连接
		clientlen = sizeof(client);

		if((connectfd = accept(listenfd,(struct sockaddr *)&client,&clientlen)) == -1){
			perror("Connect error!\n");
			exit(0);
		}

		printf("Got a connect from %s:%d\n",inet_ntoa(client.sin_addr),htons(client.sin_port));
		//调用响应函数
		response(connectfd,client);			//接收用户输入的名字并打印
		
		/*
		*fork会分别给父进程和子进程返回一个值
		*对父进程会返回子进程的pid(一个大于0的整数)
		*对子进程会返回0
		*所以可以通过if判断pid的值来分辨子进程与父进程
		*/
		chld_pid = fork();	//创建子进程

		if(chld_pid == 0){
			//说明是子进程
			close(listenfd);								//子进程不需要监听套接字,直接关闭防止后方主进程关闭套接字失败
			while(1){
				recv(connectfd,data,100,0);
				if(!strcmp(data,"quit")){
					break;
				}
				memset(&miwen,'\0',sizeof(miwen));
				encode(data,miyao);
				send(connectfd,miwen,strlen(miwen),0);
			}
			close(connectfd);	
			exit(0);									//处理完事件之后关闭子进程,记得上方注释所说的关闭已连接套接字connectfd
		}
		else if(chld_pid > 0){
				//说明是父进程
				close(connectfd);		//父进程不需要对客户端的请求做事情,直接关闭已连接套接字
				continue;				//主进程执行下一次循环,即等待新的连接
		}
		else {
				perror("Fork error!\n");
				exit(0);
		}

	}
	close(listenfd);
	return 0;
}

//定义响应函数用以打印客户端名称
int response(int connectfd,struct sockaddr_in client){
	int num;
	char client_name[20];
	
	memset(&client_name,'\0',sizeof(client_name));
	if((num = recv(connectfd,client_name,20,0) == -1)){
		//接收客户端名字
		perror("Recv error!\n");
		exit(0);
	}
	client_name[num-1] ='\0';		//将最后一个字符置为空字符,用以作为字符串结尾
	printf("Client name is %s\n",client_name);
	
	return 0;
}
//根据学号进行字符串加密
void encode(char data[],int snum[]){
	int i,j;
	j = 0;

	//密钥长度为学号长度,能整除该长度就说明不需要在字符串后面补零可以直接加密
	for(i = 0; i<strlen(data); i++){
		if((data[i] >= 'a' && data[i] <='z') || (data[i] >= 'A' && data[i] <='Z')){
			//字符加密与数字加密需要分开,因为字符移位可能会越过字母的区域,需要另做操作
			if(((data[i]+snum[j]) > 'z') && (data[i] >= 'a') && (data[i] <= 'z')){
				//越界情况一
				miwen[i] = data[i] + snum[j]-26;
			}
			else if(((data[i]+snum[j])>'Z') && (data[i] >= 'A') && (data[i] <= 'Z')){
					//越界情况二
					miwen[i] = data[i] + snum[j] -26;
			}
			else{
				//这是正常情况执行的代码
				miwen[i] = data[i] + snum[j];
			}
			//每次加密结束,j增加移至下一个密钥
			j++;
			if(j %10 == 0){
				//如果j%10等于0,就说明已经加密完一轮,该让密钥回到第一个值的位置了
				j = 0;
			}
		}		
	}
	
	if(strlen(data) % 10 != 0){
		//如果不能整除则需要填补。
		
		for(i=0;i<(10-strlen(data)%10);i++){
			
			miwen[strlen(data)+i] += 48;
			miwen[strlen(data)+i] += snum[j];
			j++;
			if(j %10 == 0){
			//如果j%10等于0,就说明已经加密完一轮,该让密钥回到第一个值的位置了
			j = 0;
			}
		}
		
	}
}

客户端代码如下

#include <stdio.h>						//基本输入输出
#include <netinet/in.h>					//sockaddr_in结构体所在头文件
#include <sys/socket.h>					//socket函数所在头文件
#include <netdb.h>						//hostent结构体所在头文件
#include <string.h>						//memset等函数所在头文件
#include <unistd.h>						//close等函数所在头文件
#include <stdlib.h>						//exit等函数所在头文件

#define PORT 3333

int main(int argc,char *args[]){
	int sockfd,num;				//定义套接字描述符
	struct sockaddr_in server;
	struct hostent *he;
	socklen_t server_len;
	char data[100];


	if(argc != 2){
		printf("Usage:%s <IP Address>\n",args[0]);
		exit(0);
	}

	//创建套接字
	if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1){
		perror("Socket error!\n");
		exit(0);
	}
	
	//获取服务器信息
	if((he = gethostbyname(args[1])) == NULL){
		perror("Gethostbyname error!\n");
		exit(0);
	}

	memset(&server,'\0',sizeof(server));
	//配置服务器地址结构
	server.sin_family = AF_INET;							//配置协议族为IPv4
	server.sin_port = htons(PORT);							//配置服务器端口
	server.sin_addr = *((struct in_addr *)he->h_addr);		//服务器地址
	server_len = sizeof(server);
	
	//而后可以进行连接
	if((connect(sockfd,(struct sockaddr *)&server,sizeof(server))) ==-1){
		perror("Connect error!\n");
		exit(0);
	}

	printf("Plz enter your name:");
	memset(&data,'\0',sizeof(data));		//每次输入信息前先清空缓存,防止前次数据影响后面输入的数据
	scanf("%s",&data);
	if(send(sockfd,data,strlen(data),0) ==-1){
		//send函数原型send(int sockfd,const char FAR *datafer,int dataferlen,int flags)
		//第二个参数为要发送的数据,第三个参数为发送的大小也即是长度,第四个参数通常置0代表其功能与write相同
		printf("Send error!\n");
		exit(1);
	}

	//连接之后便可开始数据的发送与接收
	while(1){
		
		memset(&data,'\0',sizeof(data));
		printf("Now,you can enter something what you want to encode.\n");
		scanf("%s",&data);
		send(sockfd,data,sizeof(data),0);

		if(!strcmp(data,"quit")){
			break;
		}
		else{	
		memset(&data,'\0',sizeof(data));
		if((recv(sockfd,data,100,0) ==-1)){
				//recv函数原型recv(int sockfd,char *datafer,int dataferlen,int flags)
				//第二个参数为将接收的数据暂存的位置,第三个参数为接收的数据大小,第四个参数通常置0代表其功能与read相同
				printf("Recv error!\n");
				exit(1);
		}
		printf("The code message:%s\n",data);

	}

}
	printf("Bye~\n");
	close(sockfd);
}

  • 0
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值