Linux网络编程(六)——TCP服务器(循环,fork多线程并发)

TCP服务器

在网络通信中,服务器端通常需要处理多个客户端,由于多个客户端的请求可能会同时到达,服务器端可采用两种模型来实现:循环服务器和并发服务器。


简单的tcp传输数据的编程实例

TCP编程


server.c

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

#define BUFFER_SIZE 128


int main(int argc, char *argv[])
{
	int socketfd,connfd;
	struct sockaddr_in serveraddr, clientaddr;
	socklen_t peerlen;
	char buf[BUFFER_SIZE];
	
	if(argc < 3){
		printf("Usage : %s <ip> <port>\n",argv[0]);
		exit(-1);
	}
	
	/*建立 socket 连接*/
	if((socketfd = socket(AF_INET, SOCK_STREAM, 0)) ==-1){
		perror("socket");
		exit(1);
	}
	printf("server socket succeed,socketfd = %d\n", socketfd);
	
	//清空数组
	bzero(&serveraddr, sizeof(serveraddr));
	
	/*设置socketaddr_in结构体中的相关参数*/
	serveraddr.sin_family = AF_INET;    //Addressfamily一般来说AF_INET(地址族)PF_INET(协议族)
	serveraddr.sin_port   = htons(atoi(argv[2]));  //Portnumber(必须要采用网络数据格式,普通数字可以用htons()函数转换成网络数据格式的数字)
	serveraddr.sin_addr.s_addr   = inet_addr(argv[1]);  //Internetaddress存储IP地址
	
	/*绑定函数bind*/
	if((bind(socketfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr))) < 0){
		perror("bind");
		exit(1);
	}
	printf("bind success\n");
	
	/*设置监听模式,listen*/
	if((listen(socketfd, 10) == -1)){
		perror("listen\n");
		exit(1);
	}
	printf("server is listening.......\n");
	
	/*调用accept,等待客户端的连接*/
	peerlen = sizeof(clientaddr);
	while(1){
		connfd = accept(socketfd, (struct sockaddr *)&clientaddr, &peerlen);
		if(connfd < 0){
			perror("accept");
			exit(1);
		}
		printf("accept success\n");
		/*接收客户端发送的数据*/
		memset(buf, 0, BUFFER_SIZE);
		if((recv(connfd, buf, BUFFER_SIZE, 0)) == -1){
			perror("recv");
			exit(1);
		}
	
		printf("Received message : %s\n",buf);
		strcpy(buf, "welcome to server");
		send(connfd, buf, BUFFER_SIZE,0);
		close(connfd);
	}
	
	close(socketfd);
	return 0;
}

client.c

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

#define BUFFER_SIZE 128


int main(int argc, char *argv[])
{
	int socketfd;
	struct sockaddr_in serveraddr;
	socklen_t peerlen;
	char buf[BUFFER_SIZE]="hello server";
	
	if(argc < 3){
		printf("Usage : %s <ip> <port>\n",argv[0]);
		exit(-1);
	}
	
	/*建立 socket 连接*/
	if((socketfd = socket(AF_INET, SOCK_STREAM, 0)) ==-1){
		perror("socket");
		exit(1);
	}
	printf("client socket succeed,socketfd = %d\n", socketfd);
	
	//清空数组
	bzero(&serveraddr, sizeof(serveraddr));
	
	/*设置socketaddr_in结构体中的相关参数*/
	serveraddr.sin_family = AF_INET;    //Addressfamily一般来说AF_INET(地址族)PF_INET(协议族)
	serveraddr.sin_port   = htons(atoi(argv[2]));  //Portnumber(必须要采用网络数据格式,普通数字可以用htons()函数转换成网络数据格式的数字)
	serveraddr.sin_addr.s_addr   = inet_addr(argv[1]);  //Internetaddress存储IP地址
	
	/*调用connect,向服务器建立TCP连接*/
	if((connect(socketfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr))) == -1){
		perror("connect");
		exit(1);
	}
	printf("connect success\n");
	send(socketfd, buf, BUFFER_SIZE,0);
	if((recv(socketfd, buf, BUFFER_SIZE, 0)) == -1){
		perror("recv");
		exit(1);
	}
	printf("recv from server:%s\n",buf);
	
	close(socketfd);
	return 0;
}

运行结果:
运行时,先运行服务器在运行客户端
在这里插入图片描述
在这里插入图片描述

循环型服务器


循环型服务器是指服务器端一次的处理每个客户端,直到当前客户端的所有请求全部处理结束,再处理下一个客户端的请求。
**优点:**模型简单;
**缺点:**因为要处理完当前客户端的所有请求,会造成其他客户端等待时间过长。
运行介绍:

  1. server从连接请求队列中提取请求,建立连接并返回新的已连接的套接字;
  2. server通过已连接的套接字循环接收数据,处理并发送给client,直到client关闭连接;
  3. server关闭连接套接字,返回步骤1.

循环型服务器的特点:

  1. 服务器采用循环嵌套来实现。外层循环依次提取每个客户端的连接请求,建立TCP连接。内鞥循环接收并处理当前客户端的所有数据,直到客户端关闭连接;
  2. 如果当前客户端的请求没有处理结束,其他客户端就必须等待。

采用这种模型的server不能同时处理多个client的数据请求

编程实例:
下面实现 TCP ECHO 的server和client。
**server:**接收到client数据后,原封不动的发送回去(回射服务);
**client:**用户从键盘输入字符,发送给server并接受返回的数据,直到用户输入quit后UI出。
server.c

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

#define BUFFER_SIZE 128

int main(int argc, char *argv[])
{
	int socketfd, connfd;
	struct sockaddr_in serveraddr, clientaddr;
	socklen_t peerlen;
	char buf[BUFFER_SIZE];
	
	if(argc < 3){
		printf("Usage : %s <ip> <port>\n",argv[0]);
		exit(-1);
	}
	
	/*建立 socket 连接*/
	if((socketfd = socket(AF_INET, SOCK_STREAM, 0)) ==-1){
		perror("socket");
		exit(1);
	}
	printf("server socket succeed,socketfd = %d\n", socketfd);
	
	//清空数组
	bzero(&serveraddr, sizeof(serveraddr));
	
	/*设置socketaddr_in结构体中的相关参数*/
	serveraddr.sin_family = AF_INET;    //Addressfamily一般来说AF_INET(地址族)PF_INET(协议族)
	serveraddr.sin_port   = htons(atoi(argv[2]));  //Portnumber(必须要采用网络数据格式,普通数字可以用htons()函数转换成网络数据格式的数字)
	serveraddr.sin_addr.s_addr   = inet_addr(argv[1]);  //Internetaddress存储IP地址
	
	/*绑定函数bind*/
	if((bind(socketfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr))) < 0){
		perror("bind");
		exit(1);
	}
	printf("bind success\n");
	
	/*设置监听模式,listen*/
	if((listen(socketfd, 10) == -1)){
		perror("listen\n");
		exit(1);
	}
	printf("server is listening.......\n");
	
	/*调用accept,等待客户端的连接*/
	peerlen = sizeof(clientaddr);
	while(1){
		connfd = accept(socketfd, (struct sockaddr *)&clientaddr, &peerlen);
		if(connfd < 0){
			perror("accept");
			exit(1);
		}
		printf("accept success\n");
	
		/*接收客户端发送的数据*/
		memset(buf, 0, BUFFER_SIZE);
		while((recv(connfd, buf, BUFFER_SIZE, 0)) > 0){
			printf("echo : %s\n",buf);
			send(connfd, buf, BUFFER_SIZE,0);
			exit(-1);
		}
		printf("client is closed!\n");
		close(connfd);
	}
	close(socketfd);
	exit(0);
}

client.c

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

#define BUFFER_SIZE 128


int main(int argc, char *argv[])
{
	int socketfd;
	struct sockaddr_in serveraddr;
	socklen_t peerlen;
	char buf[BUFFER_SIZE]="hello server";
	
	if(argc < 3){
		printf("Usage : %s <ip> <port>\n",argv[0]);
		exit(-1);
	}
	
	/*建立 socket 连接*/
	if((socketfd = socket(AF_INET, SOCK_STREAM, 0)) ==-1){
		perror("socket");
		exit(1);
	}
	printf("client socket succeed,socketfd = %d\n", socketfd);
	
	//清空数组
	bzero(&serveraddr, sizeof(serveraddr));
	
	/*设置socketaddr_in结构体中的相关参数*/
	serveraddr.sin_family = AF_INET;    //Addressfamily一般来说AF_INET(地址族)PF_INET(协议族)
	serveraddr.sin_port   = htons(atoi(argv[2]));  //Portnumber(必须要采用网络数据格式,普通数字可以用htons()函数转换成网络数据格式的数字)
	serveraddr.sin_addr.s_addr   = inet_addr(argv[1]);  //Internetaddress存储IP地址
	
	/*调用connect,向服务器建立TCP连接*/
	if((connect(socketfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr))) == -1){
		perror("connect");
		exit(1);
	}
	
	/*循环从键盘键入*/
	while(1){
		printf("input> ");
		fgets(buf, BUFFER_SIZE, stdin);  //从键盘上输入,并存到buf数组中
		
		/*输入 quit 则退出*/
		if((strcmp(buf, "quit\n")) == 0)
			break;
		send(socketfd, buf, BUFFER_SIZE, 0);  //发送消息给server
		
		bzero(buf, BUFFER_SIZE);
		
		recv(socketfd, buf, BUFFER_SIZE, 0);
		printf("recv from server s : %s", buf);
		
	}
	printf("client exit\n");
	close(socketfd);
	return 0;
}

运行结果:
运行时,先运行服务器在运行客户端
在这里插入图片描述
在这里插入图片描述

并发服务器


为了提高副武器的并发处理能力,又引入了并发服务器。
**并发服务器:**在服务器端采用多任务机制(多进程或者多线程),分别为每个客户端创建一个任务来处理,极大地提高了服务器的并发处理能力,提升了处理速度。
运行介绍:

  1. server的父进程从连接请求队列中提取请求,建立连接并返回新的已连接套接字。
  2. server父进程创建子进程为client服务,client关闭时,子进程结束。
  3. server父进程关闭已连接套接字,返回步骤1.

特点:

  1. server父进程一旦接收到client的连接请求,便建立好连接并创建新的子进程。这就意味着每个client在server中都有一个专门的子进程为它服务;
  2. server的多个子进程同时运行(宏观上),处理多个client
  3. server的父进程不具体处理client的数据请求。

采用这种模型的server需要避免僵死进程

**编程实例:**多进程实现并发服务器
server.c

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

#define BUFFER_SIZE 128

int main(int argc, char *argv[])
{
	int socketfd, connfd, n;
	pid_t pid;
	struct sockaddr_in serveraddr, clientaddr;
	socklen_t peerlen;
	char buf[BUFFER_SIZE];
	
	if(argc < 3){
		printf("Usage : %s <ip> <port>\n",argv[0]);
		exit(-1);
	}
	
	/*建立 socket 连接*/
	if((socketfd = socket(AF_INET, SOCK_STREAM, 0)) ==-1){
		perror("socket");
		exit(1);
	}
	printf("server socket succeed,socketfd = %d\n", socketfd);
	
	//清空数组
	bzero(&serveraddr, sizeof(serveraddr));
	
	/*设置socketaddr_in结构体中的相关参数*/
	serveraddr.sin_family = AF_INET;    //Addressfamily一般来说AF_INET(地址族)PF_INET(协议族)
	serveraddr.sin_port   = htons(atoi(argv[2]));  //Portnumber(必须要采用网络数据格式,普通数字可以用htons()函数转换成网络数据格式的数字)
	serveraddr.sin_addr.s_addr   = inet_addr(argv[1]);  //Internetaddress存储IP地址
	
	/*绑定函数bind*/
	if((bind(socketfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr))) < 0){
		perror("bind");
		exit(1);
	}
	printf("bind success\n");
	
	/*设置监听模式,listen*/
	if((listen(socketfd, 10) == -1)){
		perror("listen\n");
		exit(1);
	}
	printf("server is listening.......\n");
	
	/*调用accept,等待客户端的连接*/
	peerlen = sizeof(clientaddr);
	while(1){
		connfd = accept(socketfd, (struct sockaddr *)&clientaddr, &peerlen);
		if(connfd < 0){
			perror("accept");
			exit(1);
		}
		printf("accept success\n");
	
		/*接收客户端发送的数据*/
		memset(buf, 0, BUFFER_SIZE);
		
		//创建子进程
		pid = fork();
		if(pid < 0){
			perror("fork");
			exit(1);
		}
		else if(pid == 0){    //子进程
			//调用recv函数接收数据
			while((n = recv(connfd, buf, BUFFER_SIZE, 0)) > 0){
				printf("echo : %s\n",buf);
				send(connfd, buf, BUFFER_SIZE,0);
				exit(-1);
			}
			printf("client is closed!\n");
			exit(0);  //子进程结束
		}else{
			close(connfd);
		}
	}
	close(socketfd);
	exit(0);
}

client.c

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

#define BUFFER_SIZE 128


int main(int argc, char *argv[])
{
	int socketfd;
	struct sockaddr_in serveraddr;
	socklen_t peerlen;
	char buf[BUFFER_SIZE]="hello server";
	
	if(argc < 3){
		printf("Usage : %s <ip> <port>\n",argv[0]);
		exit(-1);
	}
	
	/*建立 socket 连接*/
	if((socketfd = socket(AF_INET, SOCK_STREAM, 0)) ==-1){
		perror("socket");
		exit(1);
	}
	printf("client socket succeed,socketfd = %d\n", socketfd);
	
	//清空数组
	bzero(&serveraddr, sizeof(serveraddr));
	
	/*设置socketaddr_in结构体中的相关参数*/
	serveraddr.sin_family = AF_INET;    //Addressfamily一般来说AF_INET(地址族)PF_INET(协议族)
	serveraddr.sin_port   = htons(atoi(argv[2]));  //Portnumber(必须要采用网络数据格式,普通数字可以用htons()函数转换成网络数据格式的数字)
	serveraddr.sin_addr.s_addr   = inet_addr(argv[1]);  //Internetaddress存储IP地址
	
	/*调用connect,向服务器建立TCP连接*/
	if((connect(socketfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr))) == -1){
		perror("connect");
		exit(1);
	}
	
	/*循环从键盘键入*/
	while(1){
		printf("input> ");
		fgets(buf, BUFFER_SIZE, stdin);  //从键盘上输入,并存到buf数组中
		
		/*输入 quit 则退出*/
		if((strcmp(buf, "quit\n")) == 0)
			break;
		send(socketfd, buf, BUFFER_SIZE, 0);  //发送消息给server
		
		bzero(buf, BUFFER_SIZE);
		
		recv(socketfd, buf, BUFFER_SIZE, 0);
		printf("recv from server s : %s", buf);
		
	}
	printf("client exit\n");
	close(socketfd);
	return 0;
}

运行结果:
运行时,先运行服务器在运行客户端
在这里插入图片描述
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值