基于TCP通信协议实现的并发通信服务器

文章介绍了如何构建一个能同时处理多个客户端请求的TCP并发服务器,通过创建进程和线程来提高服务器的并发性和吞吐量。服务端的核心在于accept函数检测到新连接时,使用fork创建子进程处理客户端请求,子进程中再创建线程分别处理发送和接收信息。客户端代码保持不变。目前的实现尚无法支持客户端间直接通信。
摘要由CSDN通过智能技术生成

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

基于昨天实现的程序TCP的编程之socket套接字实现两台主机的无线通信今天实现了更进阶的功能
让多个客户端接入服务器,并可以实现同时发送数据给服务器


一、思路

首先并发服务器是能够同时处理多个客户端请求的服务器。既然要同时处理多个客户端的请求,那就应该引入进程\线程来实现,从而提高服务器的并发性和吞吐量。TCP服务器建立的前几步和上一篇文章是一样的。重点是最后怎么处理创建出来的进程,接下来我会贴出代码并且逐行讲解。

二、实现

1.服务端代码

代码如下(示例):

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>

void *admin_send_SIG(void *arg);//线程函数
void *bf_sth(void *arg)//进程函数
{
	int fd=*(int *)arg;
	//植入线程 
	pthread_t  tid;
	pthread_create(&tid,NULL,admin_send_SIG,&fd);
	char buf[20];
	char buff[50]="服务器已接收\xF0\x9F\x98\x81";
	while(1){
		bzero(buf,sizeof(buf));
		recv(fd,buf,sizeof(buf),0);		//读取信息
		printf("%s\n",buf);			
		send(fd,buff,sizeof(buff),0);	//读完以后给主机回复
	}
	
}
void *admin_send_SIG(void *arg)//后台发送信息
{
	int fd=*(int *)arg;
	char buf[20];
	char buff[100]="服务器向你喊话\xF0\x9F\x98\x81";
	while(1){
		bzero(buf,sizeof(buf));
		send(fd,buff,strlen(buff),0);	
		scanf("%s",buf);
		send(fd,buf,sizeof(buf),0);			
	}	
}
int main(){
	//1>建立socket连接
	int sockfd=socket(AF_INET,SOCK_STREAM,0);
	if(sockfd<0){
		perror("socket");
		return -1;
	}
	
	printf("sockfd:%d\n",sockfd);
	
	
	//2>绑定IP和端口号*************!
	struct sockaddr_in server;//声明结构体,并即将给里面成员赋值
	server.sin_family=AF_INET;//表示使用IPv4地址协议
	server.sin_port=htons(10086);//端口号   将12345转化为大端序
	server.sin_addr.s_addr=inet_addr("192.168.60.119");//IP赋值
	if(bind(sockfd,(struct sockaddr *)&server,sizeof(server))){
		perror("bind");
		return -1;
	}
	
	//3>监听--->服务器的保护机制
	listen(sockfd,8);//当前这个服务器,同一时刻连接客户端的最大值为8;
	
	//声明连接主机的IP地址和端口号
	struct sockaddr_in client;
	int len=sizeof(client);
	int fd;
	while(1){
		printf("主机来咯\n");
		fd=accept(sockfd,(struct sockaddr *)&client,&len); //持续检测是否有用户访问服务器 如果没有访问则阻塞
		if(fd<0){
			perror("accept");
			return -1;
		}else{	//有客户端接入会调用frok函数创建父子进程
				//父进程会正常结束
				//子进程留下持续读取信息
			printf("连接的IP为:%s",inet_ntoa(client.sin_addr));	
			if(fork()==0){
				bf_sth(&fd);			//进入持续读取信息函数
				exit(-1);				//当整个程序结束后 回收子进程的资源
			}
		}
	}	
	
	return 0;
}

1.1核心代码块.

代码如下:

struct sockaddr_in client;	//声明一个主机地址结构体准备接受连接主机的地址信息
	int len=sizeof(client); 
	int fd;                
	while(1){
		printf("主机来咯\n");
		fd=accept(sockfd,(struct sockaddr *)&client,&len); //持续检测是否有用户访问服务器 如果没有访问则阻塞
		if(fd<0){
			perror("accept");
			return -1;
		}else{	//有客户端接入会调用frok函数创建父子进程
				//父进程会正常结束
				//子进程留下持续读取信息
			printf("连接的IP为:%s",inet_ntoa(client.sin_addr));	
			if(fork()==0){
				bf_sth(&fd);			//进入持续读取信息函数
				exit(-1);				//当整个程序结束后 回收子进程的资源
			}
		}
	}	
		return 0;
}

该段代码块实现的逻辑功能是当服务器检测到有用户访问服务器后,会获取该用户的地址信息 并且赋值给fd然后代码根据获取到的用户信息开辟进程组,父进程正常死亡 子进程留下调用进程函数。


1.2进程函数

void *bf_sth(void *arg)//进程函数
{
	int fd=*(int *)arg;
	//植入线程 
	pthread_t  tid;
	pthread_create(&tid,NULL,admin_send_SIG,&fd);//创造线程并调用线程函数
	char buf[20];								 //存储要接收数据的中间数组
	char buff[50]="服务器已接收\xF0\x9F\x98\x81";  //当检测到用户发送给服务器的信息返回一句话并附带上emoji
	while(1){
		bzero(buf,sizeof(buf));			//每次读取信息前都清空一次数组
		recv(fd,buf,sizeof(buf),0);		//读取信息
		printf("%s\n",buf);			
		send(fd,buff,sizeof(buff),0);	//读完以后给主机回复
	}	
}

进程函数实现了服务器(读取)与客户端(发送)的单向通信并开辟了一个新的线程 为服务器的发送功能做准备

1.3线程函数

void *admin_send_SIG(void *arg)//后台发送信息
{
	int fd=*(int *)arg;
	char buf[20];
	char buff[100]="服务器向你喊话\xF0\x9F\x98\x81";
	while(1){
		bzero(buf,sizeof(buf));
		send(fd,buff,strlen(buff),0);	
		scanf("%s",buf);
		send(fd,buf,sizeof(buf),0);			
	}	
}

开辟一个线程从而实现服务器的收发阻塞互不干扰

2.客户端

客户端的代码与昨天无异,主要是服务端上的代码做了修改

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/socket.h>
#include <stdlib.h>
/*建立sock套接字
 * 声明服务器的IP端口号
 * 主动连接服务器
 * 收/发
 * */
int main(int argc,char *argv[])
{
    if(argc != 3){
        printf("User:%s <IP><PORT>\n",argv[0]);
        return -1;
    }
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd < 0){
        perror("socket");
        return -1;
    }
    printf("sockfd = %d\n",sockfd);
    //声明IP和端口号
    struct sockaddr_in server;//声明结构体,并即将给里面的成员赋值
    server.sin_family = AF_INET;//表示使用IPv4地址协议
    server.sin_addr.s_addr = inet_addr(argv[1]);//ip赋值
    server.sin_port = htons(atoi(argv[2]));//端口号 将12345转成大端序

    	//3>主动连接服务器
	if(connect(sockfd,(struct sockaddr *)&server,sizeof(server))){
		perror("connect");
		return -1;
	}
	printf("连接成功\n");
    //4>发
    pid_t pid = fork();//开辟2个进程
	char r_buf[50];
	char w_buf[50];
        if(pid > 0){
            while(1){
                printf("我是父进程\n");
		        bzero(w_buf,sizeof(w_buf));
		        scanf("%s",w_buf);
		        send(sockfd,w_buf,strlen(w_buf),0);
            }
        }
        if(pid ==  0){
            while(1){
		        bzero(r_buf,sizeof(r_buf));
                printf("我是子进程\n");
		        recv(sockfd,r_buf,sizeof(r_buf),0);
	    	    printf("%s\n",r_buf);
            }
        }
    return 0;
}

总结

并口通信主要是在用户接入时引入了创建进程的函数,从而实现了每个用户都互不干扰并且能与服务器进行通信。不过该代码目前还不太完善,比如说无法做到在一个服务器中的两个用户单独的通信。

popen重写 /* * ===================================================================================== * * Filename: tcpserver.c * * Description: this program is demostrate to how to write a remote control server * * Version: 1.0 * Created: 2010年09月11日 21时28分21秒 * Revision: none * Compiler: gcc * * Author: Gang Liang (cs.scu.edu.cn/~lianggang), lianggang@scu.edu.cn * Company: Sichuan university * ===================================================================================== */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> #include <sys/types.h> #include <unistd.h> #include <sys/wait.h> #define PORT 8900 #define BUFSIZE 2048 #define SHELL "/bin/sh" FILE *myPopen(char *command,char *type) { int f_des[2]; int pid_t; // pipe(f_des); if((type[0]!='r'&&type[0]!='w')||type[1]!=0) { printf("myPopen()flag error/n"); return NULL; } if(pipe(f_des)==-1) { printf("pipe create error/n"); return NULL; } pid_t=fork(); if(pid_t<0) return NULL; if(pid_t==0) { if(type[0]=='r') { close(f_des[0]); dup2(f_des[1],STDOUT_FILENO); close(f_des[1]); } else{ close(f_des[1]); dup2(f_des[0],STDIN_FILENO); close(f_des[0]); } execl(SHELL,"sh","-c",command,(char*)0); _exit(127); char *argv[] = {command,0}; if(execvp(command,argv)==-1) return NULL; } wait(0); if(type[0]=='r') { close(f_des[1]); return fdopen(f_des[0],"r"); } else{ close(f_des[0]); return fdopen(f_des[1],"w"); } } int execute(char*command,char*buf) { FILE *fp; int count; char commandbuf[2056]; if ((NULL==command)||(NULL==buf)) { perror("command or buf is empty\n"); return -1; } count =0; memset(commandbuf,0,2056); strcat(commandbuf,"sh -c "); strcat(commandbuf,command); fprintf(stderr,"the command is %s\n",commandbuf); if (NULL==(fp=myPopen(commandbuf,"r"))) { perror("create pipe error\n"); return -1; } while ((count<2047) && (EOF!=(buf[count++]=fgetc(fp)))); buf[count-1]='\0'; return count; } int main() { int sockfd; int conn_sock; char sendbuf[BUFSIZE]; char recvbuf[BUFSIZE]; int sendnum; int recvnum; int length; struct sockaddr_in client; struct sockaddr_in server; int opt; int cnt; pid_t pid; /* The first stage:INITILIZE */ memset(&client,0,sizeof(client)); memset(&server,0,sizeof(server)); memset(sendbuf,0,BUFSIZE); memset(recvbuf,0,BUFSIZE); length=0; sockfd=-1; conn_sock=-1; opt=SO_REUSEADDR; /*The second stage:create listen socket */ if (-1==(sockfd=socket(AF_INET,SOCK_STREAM,0))) { perror("create socket error\n"); return -1; } setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)); /* The third stage:bind socket */ server.sin_family=AF_INET; server.sin_addr.s_addr=htonl(INADDR_ANY); server.sin_port=htons(PORT); if (-1==bind(sockfd,(struct sockaddr*)&server,sizeof(server))) { perror("bind socket error\n"); close(sockfd); return -1; } /* The fourth stage:listen socket */ if (-1==listen(sockfd,10)) { perror("listen socket error\n"); close(sockfd); return -1; } /* The fifth stage:creat connect socket */ while(1) { if (-1==(conn_sock=accept(sockfd,(struct sockaddr*)&client,&length))) { perror("three shakehands error\n"); close(sockfd); return -1; } /* the commnication with client */ pid=fork(); if(pid==0) { close(sockfd); while(1) { memset(recvbuf,0,BUFSIZE); memset(sendbuf,0,BUFSIZE); if (0>=(recvnum=read(conn_sock,recvbuf,BUFSIZE))) { perror("the commucation error\n"); close(conn_sock); close(sockfd); return -1; } recvbuf[recvnum]='\0'; fprintf(stderr,"the command is:%s\n",recvbuf); if (0==strcmp(recvbuf,"quit")) { fprintf(stderr,"the client is quit\n"); close(conn_sock); break; } if (1>=(cnt=execute(recvbuf,sendbuf))) { sprintf(sendbuf,"the invalid command,please try again\n"); } fprintf(stderr,"the result is \n%s",sendbuf); if (0>=(sendnum=write(conn_sock,sendbuf,strlen(sendbuf)))) { perror("the commucation error\n"); close(sockfd); close(conn_sock); return -1; } } } else if(pid>0) { close(conn_sock); continue; } } close(sockfd); }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值