基于TCP协议的服务器编程

  • 注:以下代码是在观看网易云课堂,职坐标发布的Linux网络编程|人工智能物联网时,跟着视频写的,仅为学习使用。代码出处在这里:https://study.163.com/course/courseMain.htm?share=1&shareId=1020885091&courseId=1002913013&trace_c_p_k2=f61d55ffe1e84f9d8ec0dd31b371723c

1 用户建立连接,服务器发送系统时间

1.1 服务器

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

int sockfd;								//套接字描述符

void sig_handler(int signo){			//信号处理函数
	if(signo==SIGINT){
		printf("serve close\n");
		/*步骤6:关闭socket
		 *这里关闭服务器socket
		*/
		close(sockfd);
		exit(1);
	}
}

void out_addr(struct sockaddr_in *clientaddr){	//输出连接上来的客户端相关信息
	//将端口从网络字节序转化为主机字节序
	int port=ntohs(clientaddr->sin_port);
	char ip[16];
	memset(ip,0,sizeof(ip));
	//将ip地址从网络字节序转化为点分十进制
	inet_ntop(AF_INET,&clientaddr->sin_addr.s_addr,ip,sizeof(ip));
	printf("client: %s(%d) connected\n",ip,port);
}
void do_service(int fd){						//向客户端发送系统时间
	//获得系统时间
	long t=time(0);								//获得系统时间
	char *s=ctime(&t);							//将系统时间转化为字符串
	size_t size=strlen(s)*sizeof(char);			//获得字符串大小
	
	//将服务端获得的系统时间返回到客户端
	if(write(fd,s,size)!=size){
		perror("write errpr");
		//exit(1);
	}
}

int main(int argc,char* argv[]){
	if(argc<2){							//如果没有输入端口号		
		printf("usage: %s #port\n",argv[0]);
		exit(1);
	}
	if(signal(SIGINT,sig_handler)==SIG_ERR){
		printf("signal sigint error");
		exit(1);
	}

	/*步骤1:创建socket(套接字)
	 *注:socket创建在内核中,是一个结构体。
	 AF_INET:IPv4因特域
	 SOCK_STREAM:流式套接字,TCP协议
	*/	
	sockfd=socket(AF_INET,SOCK_STREAM,0);
	if(sockfd<0){
		perror("socket error");
		exit(1);
	}
	/*步骤2:调用bind函数将socket和地址
	 *(包括ip、port)进行绑定 
	*/
	struct sockaddr_in serveraddr;					//因特网专用地址
	memset(&serveraddr,0,sizeof(serveraddr));		//初始化一下
	//往地址中填入ip、port、internet地址族类型
	serveraddr.sin_family=AF_INET;					//IPv4
	serveraddr.sin_port=htons(atoi(argv[1]));		//端口,由命令行传入,需要用htons函数转成网络字节序
	//serveraddr.sin_addr.s_addr="192.168.0.10";	//ip地址这样设置的话,ip地址就绑死了
	serveraddr.sin_addr.s_addr=INADDR_ANY;			//监听服务器上所有网卡的ip
	
	if(bind(sockfd,(struct sockaddr*)&serveraddr,
	sizeof(serveraddr))<0){							//将套接字与地址绑定,地址需要强转成通用地址形式
		perror("bind error");
		exit(1);
	}

	/*步骤3:调用listen函数启用监听(指定port监听)
	 *通知系统去接管来自客户端的连接请求
	 *将接受到的客户端的连接请求放在对应的队列中
	 *listen的第二个参数是制定这个队列的大小
	*/
	if(listen(sockfd,10)<0){					
		perror("listen error");
		exit(1);
	}

	/*步骤4:调用acept函数从队列中获得一个客户端的请求连接
	 *并返回一个新的客户端描述符
	 *若没有客户端连接,则会阻塞,知道有一个客户端连接
	*/
	struct sockaddr_in clientaddr;					//用于保存客户端地址
	socklen_t clientaddr_len=sizeof(clientaddr);	//客户端地址长度
	while(1){
		int fd=accept(sockfd,(struct sockaddr*)&clientaddr,&clientaddr_len);
		if(fd<0){
			perror("accept error");
			continue;
		}
		
		/*步骤5:调用IO函数(read/write)和连接的客户端
		 *进行双向通讯
		*/
		out_addr(&clientaddr);
		do_service(fd);

		/*步骤6:关闭socket
		 *这里关闭客户端socket
		*/
		close(fd);
	}


	return 0;
}


1.2 客户端

#include
#include
#include
#include
#include
#include
#include
#include

int main(int argc,char *argv[]){
	if(argc<3){
		printf("usage: %s ip port\n",argv[0]);
		exit(1);
	}
	/*步骤1:创建socket
	*/
	int sockfd=socket(AF_INET,SOCK_STREAM,0);
	if(sockfd<0){
		perror("socket error");
		exit(1);
	}
	
	//往serveraddr填入ip、port、地址族类型(IPv4)
	struct sockaddr_in serveraddr;
	memset(&serveraddr,0,sizeof(serveraddr));
	serveraddr.sin_family=AF_INET;
	serveraddr.sin_port=htons(atoi(argv[2]));
	//将ip地址转化为网络字节序
	inet_pton(AF_INET,argv[1],&serveraddr.sin_addr.s_addr);

	/*步骤2:客户端调用connect函数连接到服务器端
	*/
	if(connect(sockfd,(struct sockaddr*)&serveraddr,
		sizeof(serveraddr))<0){
		perror("connect error");
		exit(1);
	}
	/*步骤3:调用IO函数(read/write)和服务器
	 *进行双向通信
	*/
	char buffer[1024];
	memset(buffer,0,sizeof(buffer));
	size_t size;
	if((size=read(sockfd,buffer,sizeof(buffer)))<0){		//读取服务器传过来的信息
		perror("read error");
		exit(1);
	}
	if(write(STDOUT_FILENO,buffer,size)!=size){				//将信息输出到屏幕上
		perror("writer error");
	}
	
	/*步骤4:关闭socket套接字
	*/
	close(sockfd);

	return 0;
}

2 用户发送一个消息,服务器将消息返回(基于自定义协议)

2.1 协议

#ifndef __MSG_H__
#define __MSG_H__
#include 

typedef struct{	
	//协议头部
	char head[10];		
	char checknum;			//校验码
	//协议体部
	char buff[512];			//数据
}Msg;

/*
 *发送一个基于自定义协议的message
 *发送的数据存放在buff中
 */
extern int write_msg(int sockfd,char *buff,size_t len);

/*
 *读取一个基于自定义协议的message
 *读取的数据存放在buff中
 */
extern int read_msg(int sockfd,char *buff,size_t len);

#endif

#include "msg.h"
#include
#include
#include
#include

/*
 *校验函数
*/
static unsigned char msg_check(Msg *pMessage)	//只在本文件使用
{
	unsigned char checknum=0;
	for(int i=0;i<sizeof(pMessage->head);++i){
		checknum+=pMessage->head[i];
	}
	for(int i=0;i<sizeof(pMessage->buff);++i){
		checknum+=pMessage->buff[i];
	}
	return checknum;
}
/*
 *发送一个基于自定义协议的message
 *发送的数据存放在buff中
 */
int write_msg(int sockfd,char *buff,size_t len)
{
	Msg message;
	memset(&message,0,sizeof(message));
	strcpy(message.head,"Night00:");				//头部
	memcpy(message.buff,buff,len);					//数据
	message.checknum=msg_check(&message);			//校验码
	
	if(write(sockfd,&message,sizeof(message))!=sizeof(message))
		return -1;

	return 0;
}
/*
 *读取一个基于自定义协议的message
 *读取的数据存放在buff中
 */
int read_msg(int sockfd,char *buff,size_t len)
{
	Msg message;
	memset(&message,0,sizeof(message));
	size_t size;

	if((size=read(sockfd,&message,sizeof(message)))<0)
		return -1;
	else if(size==0)
		return 0;
	
	//进行校验码验证
	unsigned char checknum=msg_check(&message);
	if((checknum==(unsigned char)message.checknum) && (!strcmp("Night00:",message.head))){
		memcpy(buff,message.buff,len);
		return sizeof(message);
	}
	
	return -1;
}


2.2 客户端

#include"msg.h"
#include
#include
#include
#include
#include
#include
#include
#include

int main(int argc,char *argv[]){
	if(argc<3){
		printf("usage: %s ip port\n",argv[0]);
		exit(1);
	}
	/*步骤1:创建socket
	*/
	int sockfd=socket(AF_INET,SOCK_STREAM,0);
	if(sockfd<0){
		perror("socket error");
		exit(1);
	}
	
	//往serveraddr填入ip、port、地址族类型(IPv4)
	struct sockaddr_in serveraddr;
	memset(&serveraddr,0,sizeof(serveraddr));
	serveraddr.sin_family=AF_INET;
	serveraddr.sin_port=htons(atoi(argv[2]));
	//将ip地址转化为网络字节序
	inet_pton(AF_INET,argv[1],&serveraddr.sin_addr.s_addr);

	/*步骤2:客户端调用connect函数连接到服务器端
	*/
	if(connect(sockfd,(struct sockaddr*)&serveraddr,
		sizeof(serveraddr))<0){
		perror("connect error");
		exit(1);
	}
	
	/*步骤3:调用IO函数(read/write)和服务器
	 *进行双向通信
	*/
	char buff[512];
	size_t size;
	char *prompt=">";
	while(1){
		memset(buff,0,sizeof(buff));
		write(STDOUT_FILENO,prompt,1);					//打印到屏幕
		size=read(STDIN_FILENO,buff,sizeof(buff));		//输入字符串,read并不会将最后一位设置为'\0'
		
		char endChar[]="close socket";					//当输入close socket的时候退出循环
		if(size==sizeof(endChar) && !strncmp(buff,endChar,strlen(endChar))){
			break;
		}

		if(size<0)
			continue;
		buff[size-1]='\0';								//将数组最后一位作为结束符
		if(write_msg(sockfd,buff,sizeof(buff))<0){
			perror("write msg error");					//输入出错
			continue;
		}else{											//输入成功,接受服务器端数据
			if(read_msg(sockfd,buff,sizeof(buff))<0){
				perror("read msg error");				//读取出错
				continue;
			}else{
				printf("%s\n",buff);					//读取成功
			}
		}
	}

	/*步骤4:关闭socket套接字
	*/
	close(sockfd);

	return 0;
}

2.3 服务器(多进程)

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include"msg.h"


int sockfd;								//套接字描述符

void sig_handler(int signo){			//信号处理函数
	if(signo==SIGINT){
		printf("serve close\n");
		/*步骤6:关闭socket
		 *这里关闭服务器socket
		 */
		close(sockfd);
		exit(1);
	}
	if(signo==SIGCHLD){
		printf("child process deaded...");
		wait(0);
	}
}

void out_addr(struct sockaddr_in *clientaddr){	//输出连接上来的客户端相关信息
	//将端口从网络字节序转化为主机字节序
	int port=ntohs(clientaddr->sin_port);
	char ip[16];
	memset(ip,0,sizeof(ip));
	//将ip地址从网络字节序转化为点分十进制
	inet_ntop(AF_INET,&clientaddr->sin_addr.s_addr,ip,sizeof(ip));
	printf("client: %s(%d) connected\n",ip,port);
}
void do_service(int fd){						//和客户端进行双向通信
	/*和客户端进行读写操作(双向通信)*/
	char buff[512];
	while(1){
		memset(buff,0,sizeof(buff));
		printf("start read and write...\n");
		size_t size;
		if((size==read_msg(fd,buff,sizeof(buff)))<0){
			perror("protocal error");			//如果数据读取错误,则跳出循环
			break;
		}else if(size==0){
			/*可能有一种情况s,当客户端连接关闭
			 *即写端关闭,只留下读端,当缓冲区里
			 *的数据都被读完后,那么读取的大小就是0
			 *补充:若写端没有关闭,但是写端又不写入
			 *数据,那么读端就会阻塞。
			 */
			break;
		}else{									//正常读取
			if(write_msg(fd,buff,sizeof(buff))<0){
				if(errno==EPIPE){
					/*当服务端想向客户端写入数据时,若客户端断开连接
					 *就类似与管道的读端关闭,只留下写段,这样会产生
					 *一个SIGPIPE信号,同时errno会等于EPIPE
					 */
					break;
				}
				perror("protocal error");
			}
			printf("%s\n",buff);
		}
	}
}

int main(int argc,char* argv[]){
	if(argc<2){							//如果没有输入端口号		
		printf("usage: %s #port\n",argv[0]);
		exit(1);
	}
	if(signal(SIGINT,sig_handler)==SIG_ERR){
		printf("signal sigint error");
		exit(1);
	}
	if(signal(SIGCHLD,sig_handler)==SIG_ERR){
		perror("signal sigchld error");
		exit(1);
	}

	/*步骤1:创建socket(套接字)
	 *注:socket创建在内核中,是一个结构体。
	 AF_INET:IPv4因特域
	 SOCK_STREAM:流式套接字,TCP协议
	 */	
	sockfd=socket(AF_INET,SOCK_STREAM,0);
	if(sockfd<0){
		perror("socket error");
		exit(1);
	}
	/*步骤2:调用bind函数将socket和地址
	 *(包括ip、port)进行绑定 
	 */
	struct sockaddr_in serveraddr;					//因特网专用地址
	memset(&serveraddr,0,sizeof(serveraddr));		//初始化一下
	//往地址中填入ip、port、internet地址族类型
	serveraddr.sin_family=AF_INET;					//IPv4
	serveraddr.sin_port=htons(atoi(argv[1]));		//端口,由命令行传入,需要用htons函数转成网络字节序
	//serveraddr.sin_addr.s_addr="192.168.0.10";	//ip地址这样设置的话,ip地址就绑死了
	serveraddr.sin_addr.s_addr=INADDR_ANY;			//监听服务器上所有网卡的ip
	
	if(bind(sockfd,(struct sockaddr*)&serveraddr,
	sizeof(serveraddr))<0){							//将套接字与地址绑定,地址需要强转成通用地址形式
		perror("bind error");
		exit(1);
	}

	/*步骤3:调用listen函数启用监听(指定port监听)
	 *通知系统去接管来自客户端的连接请求
	 *将接受到的客户端的连接请求放在对应的队列中
	 *listen的第二个参数是制定这个队列的大小
	 */
	if(listen(sockfd,10)<0){					
		perror("listen error");
		exit(1);
	}

	/*步骤4:调用acept函数从队列中获得一个客户端的请求连接
	 *并返回一个新的客户端描述符
	 *若没有客户端连接,则会阻塞,知道有一个客户端连接
	 */
	struct sockaddr_in clientaddr;					//用于保存客户端地址
	socklen_t clientaddr_len=sizeof(clientaddr);	//客户端地址长度
	while(1){
		int fd=accept(sockfd,(struct sockaddr*)&clientaddr,&clientaddr_len);
		if(fd<0){
			perror("accept error");
			continue;
		}
		
		/*步骤5:fork一个子进程
		 *调用IO函数(read/write)和连接的客户端
		 *进行双向通讯
		 */
		pid_t pid=fork();
		if(pid<0){
			continue;
		}else if(pid==0){							//子进程		
			out_addr(&clientaddr);
			do_service(fd);
			/*步骤6:关闭socket
			 *这里关闭客户端socket
		 	 */
			close(fd);
		}else{										//父进程
			/*当fork一个子进程的时候,子进程与父进程的环境相同
			 *因此,套接字的描述符fd也会复制一份,而内核对fd有
			 *一个类似于引用计数的东西,当父进程和子进程都close
			 *掉这个fd时,这个套接字才会真正关闭
			 */
			close(fd);
		}

	}


	return 0;
}


2.4 服务器(多线程)

(有问题,待更新)

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include"msg.h"


int sockfd;								//套接字描述符

void sig_handler(int signo){			//信号处理函数
	if(signo==SIGINT){
		printf("serve close\n");
		/*步骤6:关闭socket
		 *这里关闭服务器socket
		 */
		close(sockfd);
		exit(1);
	}
}

void out_addr(struct sockaddr_in *clientaddr){	//输出连接上来的客户端相关信息
	//将端口从网络字节序转化为主机字节序
	int port=ntohs(clientaddr->sin_port);
	char ip[16];
	memset(ip,0,sizeof(ip));
	//将ip地址从网络字节序转化为点分十进制
	inet_ntop(AF_INET,&clientaddr->sin_addr.s_addr,ip,sizeof(ip));
	printf("client: %s(%d) connected\n",ip,port);
}
void do_service(int fd){						//和客户端进行双向通信
	/*和客户端进行读写操作(双向通信)*/
	char buff[512];
	while(1){
		memset(buff,0,sizeof(buff));
		printf("start read and write...\n");
		size_t size;
		if((size==read_msg(fd,buff,sizeof(buff)))<0){
			perror("protocal error");			//如果数据读取错误,则跳出循环
			break;
		}else if(size==0){
			/*可能有一种情况s,当客户端连接关闭
			 *即写端关闭,只留下读端,当缓冲区里
			 *的数据都被读完后,那么读取的大小就是0
			 *补充:若写端没有关闭,但是写端又不写入
			 *数据,那么读端就会阻塞。
			 */
			break;
		}else{									//正常读取
			if(write_msg(fd,buff,sizeof(buff))<0){
				if(errno==EPIPE){
					/*当服务端想向客户端写入数据时,若客户端断开连接
					 *就类似与管道的读端关闭,只留下写段,这样会产生
					 *一个SIGPIPE信号,同时errno会等于EPIPE
					 */
					break;
				}
				perror("protocal error");
			}
			printf("%s\n",buff);
		}
	}
}

void out_fd(int fd){							//通过套接字输出客户端信息
	struct sockaddr_in clientaddr;
	socklen_t len=sizeof(clientaddr);
	if(getpeername(fd,(struct sockaddr*)&clientaddr,&len)<0){
		perror("getpeername error");
		return;
	}
	//将端口从网络字节序转化为主机字节序
	int port=ntohs(clientaddr.sin_port);
	char ip[16];
	memset(ip,0,sizeof(ip));
	//将ip地址从网络字节序转化为点分十进制
	inet_ntop(AF_INET,&clientaddr.sin_addr.s_addr,ip,sizeof(ip));
	printf("client: %s(%d) closed\n",ip,port);
}

void* th_fn(void *arg){							//线程运行函数
	int fd=*(int*)arg;
	do_service(fd);								//和客户端进行双向通信
	out_fd(fd);									//输出一个客户端相关信息
	close(fd);
	return (void*)0;
}

int main(int argc,char* argv[]){
	if(argc<2){							//如果没有输入端口号		
		printf("usage: %s #port\n",argv[0]);
		exit(1);
	}
	if(signal(SIGINT,sig_handler)==SIG_ERR){
		printf("signal sigint error");
		exit(1);
	}

	/*步骤1:创建socket(套接字)
	 *注:socket创建在内核中,是一个结构体。
	 AF_INET:IPv4因特域
	 SOCK_STREAM:流式套接字,TCP协议
	 */	
	sockfd=socket(AF_INET,SOCK_STREAM,0);
	if(sockfd<0){
		perror("socket error");
		exit(1);
	}
	/*步骤2:调用bind函数将socket和地址
	 *(包括ip、port)进行绑定 
	 */
	struct sockaddr_in serveraddr;					//因特网专用地址
	memset(&serveraddr,0,sizeof(serveraddr));		//初始化一下
	//往地址中填入ip、port、internet地址族类型
	serveraddr.sin_family=AF_INET;					//IPv4
	serveraddr.sin_port=htons(atoi(argv[1]));		//端口,由命令行传入,需要用htons函数转成网络字节序
	//serveraddr.sin_addr.s_addr="192.168.0.10";	//ip地址这样设置的话,ip地址就绑死了
	serveraddr.sin_addr.s_addr=INADDR_ANY;			//监听服务器上所有网卡的ip
	
	if(bind(sockfd,(struct sockaddr*)&serveraddr,
	sizeof(serveraddr))<0){							//将套接字与地址绑定,地址需要强转成通用地址形式
		perror("bind error");
		exit(1);
	}

	/*步骤3:调用listen函数启用监听(指定port监听)
	 *通知系统去接管来自客户端的连接请求
	 *将接受到的客户端的连接请求放在对应的队列中
	 *listen的第二个参数是制定这个队列的大小
	 */
	if(listen(sockfd,10)<0){					
		perror("listen error");
		exit(1);
	}

	/*步骤4:调用acept函数从队列中获得一个客户端的请求连接
	 *并返回一个新的客户端描述符
	 *若没有客户端连接,则会阻塞,知道有一个客户端连接
	 */
	struct sockaddr_in clientaddr;					//用于保存客户端地址
	socklen_t clientaddr_len=sizeof(clientaddr);	//客户端地址长度
	
	//设置线程的分离属性
	pthread_attr_t attr;
	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr,
		PTHREAD_CREATE_DETACHED);

	while(1){
		//主控线程负责accept
		/*这里的clientaddr可以获得客户端的地址
		 *但是也可以不这样做,可以从fd中获得客
		 *户端地址,使用getpeername函数
		 */
		int fd=accept(sockfd,(struct sockaddr*)&clientaddr,&clientaddr_len);
		//int fd=accept(sockfd,NULL,NULL);
		if(fd<0){
			perror("accept error");
			continue;
		}
		out_addr(&clientaddr);
		/*步骤5:启动子线程(以分离状态启动,子线程运行结束
		 *资源自动回收,主线程不用阻塞来等待它回收资源)
		 *调用IO函数(read/write)和连接的客户端
		 *进行双向通讯
		 */
		pthread_t th;
		int err;
		if((err=pthread_create(&th,&attr,th_fn,(void *)&fd))!=0){
			perror("pthread create error");
			
		}
		pthread_attr_destory(&attr);
	}


	return 0;
}


你可能感兴趣的:(Linux网络编程)