多个client的交互式请求(Linux,C)

1. 多进程并发处理–fork

// wrap.h
#include <stdlib.h>
#include <errno.h>
#include <sys/socket.h>

void perr_exit(const char *);
int Accept(int, struct sockaddr *, socklen_t *);
void Bind(int, const struct sockaddr *, socklen_t);
void Connect(int, const struct sockaddr *, socklen_t);
void Listen(int, int);
int Socket(int, int, int);
ssize_t Read(int, void *, size_t);
ssize_t Write(int, const void *, size_t);
void Close(int);
ssize_t Readn(int, void *, size_t);
ssize_t Writen(int, const void *, size_t);
static ssize_t my_read(int, char *);
ssize_t Readline(int, void *, size_t);
//wrap.c
#include <stdlib.h>
#include <errno.h>
#include <sys/socket.h>

void perr_exit(const char *s){
	perror(s);
	exit(1);
}

int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr){
	int n;
	again:
		if ( (n = accept(fd, sa, salenptr)) < 0) {
			if ((errno == ECONNABORTED) || (errno == EINTR))
				goto again;
			else
				perr_exit("accept error");
		}
	return n;
}

void Bind(int fd, const struct sockaddr *sa, socklen_t salen){
	if (bind(fd, sa, salen) < 0)
		perr_exit("bind error");
}

void Connect(int fd, const struct sockaddr *sa, socklen_t salen){
	if (connect(fd, sa, salen) < 0)
		perr_exit("connect error");
}

void Listen(int fd, int backlog){
	if (listen(fd, backlog) < 0)
		perr_exit("listen error");
}

int Socket(int family, int type, int protocol){
	int n;
	if ((n = socket(family, type, protocol)) < 0)
		perr_exit("socket error");
	return n;
}

ssize_t Read(int fd, void *ptr, size_t nbytes){
	ssize_t n;
again:
	if ((n = read(fd, ptr, nbytes)) == -1) {
		if (errno == EINTR)
			goto again;
		else
			return -1;
	}
	return n;
}

ssize_t Write(int fd, const void *ptr, size_t nbytes){
	ssize_t n;
again:
	if ((n = write(fd, ptr, nbytes)) == -1) {
		if (errno == EINTR)
			goto again;
		else
			return -1;
	}
	return n;
}

void Close(int fd){
	if (close(fd) == -1)
	perr_exit("close error");
}

ssize_t Readn(int fd, void *vptr, size_t n){
	size_t nleft;
	ssize_t nread;
	char *ptr;
	ptr = vptr;
	nleft = n; // 还需要读取n个
	while (nleft > 0) {	// 还有需要读取的
		if ((nread = read(fd, ptr, nleft)) < 0) { // 读取异常
			if (errno == EINTR) // 错误码为EINTR(换行)
				nread = 0; // 本次读取0个字节(换行)
			else			// 错误码不为EINTR
				return -1; // 读取失败
		} 
		else if (nread == 0) // 如果本次读取0个字节(结束)
			break; 			// 退出循环
		nleft -= nread; // 还需要读取个数
		ptr += nread;
	}
	return n - nleft;
}

ssize_t Writen(int fd, const void *vptr, size_t n){
	size_t nleft;
	ssize_t nwritten;
	const char *ptr;
	ptr = vptr;
	nleft = n; // 还需要写出多少个
	while (nleft > 0) { // 还有需要写出的
		if ((nwritten = write(fd, ptr, nleft)) <= 0) { // 写出异常
			if (nwritten < 0 && errno == EINTR) // 换行
				nwritten = 0; // 本次写出0个字节
			else
				return -1; // 写出失败
		}
	
		nleft -= nwritten; // 还有需要写出的
		ptr += nwritten;
	}
	return n;
}


static ssize_t my_read(int fd, char *ptr){

	static int read_cnt;
	static char *read_ptr;

	char read_buf[100];
	if (read_cnt <= 0) {
	again:
		if ((read_cnt = read(fd, read_buf,sizeof(read_buf))) < 0) {
			if (errno == EINTR)
				goto again;
			return -1;
		} 
		else if (read_cnt == 0)
			return 0;
		read_ptr = read_buf;
	}
	read_cnt--;
	*ptr = *read_ptr++;
	return 1;
}

ssize_t Readline(int fd, void *vptr, size_t maxlen){
	ssize_t n, rc;
	char c, *ptr;
	ptr = vptr;
	for (n = 1; n < maxlen; n++) {
		if ( (rc = my_read(fd, &c)) == 1) {
				*ptr++ = c;
				if (c == '\n')
					break;
		} 
		else if (rc == 0) {
				*ptr = 0;
				return n - 1;
		} 
		else
			return -1;
	}	
	*ptr = 0;
	return n;
}
// server_ie.c
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include "wrap.h"
#define MAXLINE 80
#define SERV_PORT 8000

int main(void){
	struct sockaddr_in servaddr, cliaddr;
	socklen_t cliaddr_len;
	int listenfd, connfd;
	char buf[MAXLINE];
	char str[INET_ADDRSTRLEN];
	int i, n;
	listenfd = Socket(AF_INET, SOCK_STREAM, 0);
	//socket()打开一个网络通讯端口,返回监听文件描述符listenfd
	//AF_INET -> IPV4
	//SOCK_STREAM -> TCP协议, SOCK_DGRAM -> UDP协议
	
	int opt = 1;
	setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
	//设置socket描述符的选项SO_REUSEADDR为1,
	//表示允许创建端口号相同但IP地址不同的多个socket描述符
	
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port = htons(SERV_PORT);
	//servaddr整个结构体清零
	//设置地址类型为AF_INET
	//网络地址为INADDR_ANY,这个宏表示本地的任意IP地址
	//端口号为SERV_PORT,8000,从主机序转换成网络序,并赋值给服务器地址
	
	Bind(listenfd, (struct sockaddr *)&servaddr,sizeof(servaddr));
	//bind绑定一个固定的网络地址和端口号
	//listenfd, 服务器地址
	
	Listen(listenfd, 20);//最多允许20个客户端处于连接待状态
	
	printf("Accepting connections ...\n");
	while (1) {
		cliaddr_len = sizeof(cliaddr);
		connfd = Accept(listenfd,(struct sockaddr *)&cliaddr,&cliaddr_len);
		//阻塞等待直到有客户端连接上来,返回客户端文件描述符
		n = fork(); // fork并发处理多个client的请求
		if (n == -1){
			perror("call to fork");
			exit(1);
		}
		else if (n == 0){
			close(listenfd);
			while (1){
				n = Read(connfd, buf, MAXLINE); //读取接收到的数据
				if (n == 0) {
					printf("the other side has been closed.\n");
					break;
				}
				printf("received from %d at PORT %d\n",inet_ntop(AF_INET,&cliaddr.sin_addr, str, sizeof(str)),ntohs(cliaddr.sin_port));
				for (i = 0; i < n; i++)
					buf[i] = toupper(buf[i]);
			
				Write(connfd, buf, n);//向客户端(文件描述符)传(写)信息
			}
			Close(connfd);
			exit(0);
		}
		else
			Close(connfd);
	}
}
// client_ie.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include "wrap.h"
#define MAXLINE 80
#define SERV_PORT 8000

int main(int argc, char *argv[]){
	
	struct sockaddr_in servaddr;
	char buf[MAXLINE];
	int sockfd, n;
	sockfd = Socket(AF_INET, SOCK_STREAM, 0);
	//socket()打开一个网络通讯端口,返回对方文件描述符sockfd
	//AF_INET -> IPV4
	//SOCK_STREAM -> TCP协议, SOCK_DGRAM -> UDP协议
	
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
	//将"127.0.0.1",转换成AF_INET(IPV4)地址,存入servaddr.sin_addr
	
	servaddr.sin_port = htons(SERV_PORT);
	//端口号从主机序转换成网络序,并赋值给服务器地址
	
	Connect(sockfd, (struct sockaddr *)&servaddr,sizeof(servaddr));
	//调用connect()连接服务器
	//服务器地址和端口servaddr SERV_PORT
	
	while (fgets(buf, MAXLINE, stdin) != NULL) {
		Write(sockfd, buf, strlen(buf));
		//向对方的文件描述符sockfd写一行
		n = Read(sockfd, buf, MAXLINE);
		//从对方的文件描述符sockfd读取
		if (n == 0)
			printf("the other side has been closed.\n");
		else
			Write(STDOUT_FILENO, buf, n);//读到的内容输出终端
	}
	Close(sockfd);//关闭连接
	return 0;
}
gcc server_ie.c wrap.c -o server_ie
gcc client_ie.c wrap.c -o client_ie
./server_ie
./client_ie

2. 系统调用–select

//server_ie2.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include "wrap.h"
#define MAXLINE 80
#define SERV_PORT 8000

int main(int argc, char **argv){

	int i, maxi, maxfd, listenfd, connfd, sockfd;
	int nready, client[FD_SETSIZE];
	ssize_t n;
	fd_set rset, allset;
	char buf[MAXLINE];
	char str[INET_ADDRSTRLEN];
	socklen_t cliaddr_len;
	struct sockaddr_in cliaddr, servaddr;
	listenfd = Socket(AF_INET, SOCK_STREAM, 0);
	//socket()打开一个网络通讯端口,返回监听文件描述符listenfd
	//AF_INET -> IPV4
	//SOCK_STREAM -> TCP协议, SOCK_DGRAM -> UDP协议
	
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port = htons(SERV_PORT);
	//servaddr整个结构体清零
	//设置地址类型为AF_INET
	//网络地址为INADDR_ANY,这个宏表示本地的任意IP地址
	//端口号为SERV_PORT,8000,从主机序转换成网络序,并赋值给服务器地址
	
	Bind(listenfd, (struct sockaddr *)&servaddr,sizeof(servaddr));
	//bind绑定一个固定的网络地址和端口号
	//listenfd, 服务器地址
	
	Listen(listenfd, 20); //最多允许20个客户端处于连接待状态
	maxfd = listenfd; //监听编号初始化
	maxi = -1; // 客户端数组索引
	
	for (i = 0; i < FD_SETSIZE; i++)
		client[i] = -1; //可用的客户端索引标记为-1
	FD_ZERO(&allset);
	FD_SET(listenfd, &allset);
	for ( ; ; ) {
		rset = allset; //构件赋值
		nready = select(maxfd+1, &rset, NULL, NULL, NULL);//选择客户端
		if (nready < 0)
			perr_exit("select error");
			
		if (FD_ISSET(listenfd, &rset)) { //新的客户端连接
			cliaddr_len = sizeof(cliaddr);
			connfd = Accept(listenfd, (struct sockaddr*)&cliaddr, &cliaddr_len);
			printf("received from %d at PORT %d\n",inet_ntop(AF_INET,&cliaddr.sin_addr, str, sizeof(str)),ntohs(cliaddr.sin_port));
			for (i = 0; i < FD_SETSIZE; i++)
				if (client[i] < 0) {
					client[i] = connfd; //保存文件描述符
					break;
				}
			if (i == FD_SETSIZE) {
				fputs("too many clients\n",stderr);
				exit(1);
			}
			FD_SET(connfd, &allset); //增加新的文件描述符
			if (connfd > maxfd)
				maxfd = connfd; //选择
			if (i > maxi)
				maxi = i; // 可用的客户端
			if (--nready == 0)
				continue; //没有可读的文件描述符
		}
		
		for (i = 0; i <= maxi; i++) { //检查所有客户端的数据
			if ((sockfd = client[i]) < 0)
				continue;
			if (FD_ISSET(sockfd, &rset)) {
				if ((n = Read(sockfd, buf,MAXLINE)) == 0) { //处理已经关闭的客户端
					Close(sockfd);
					FD_CLR(sockfd, &allset);
					client[i] = -1;
				} 
				else {
					int j;
					for (j = 0; j < n; j++)
						buf[j] = toupper(buf[j]);
					Write(sockfd, buf, n);
				}
				if (--nready == 0)
					break; //没有可读的文件描述符
			}
		}
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值