导航

网络编程

Posted on 2023-06-16 21:07  koodu  阅读(33)  评论(0)    收藏  举报

网络编程概要

网络层协议

传输层:

ip层:

socket套接字

socket创建

字节序

字节序转换

因特网通用地址结构

通用地址结构:struct sockaddr

详细地址结构:struct sockaddr_in

对地址端口信息等,先保存在sockaddr_in 中,在转为通用地址结构

地址转换

tcp模型

tcp简单编程实例

获取系统时间
long t=time(0);
char* s=ctime(&t);
实例
服务端的serveraddr.sin_addr.s_addr=INADDR_ANY表示其绑定本机所有的IP地址
accept()定义因特网地址结构体存储客户端信息
客户端connect()中的因特网地址是要访问的服务器地址

服务端:

//服务端
#include<netdb.h>
#include<sys/socket.h>
#include<stdio.h>
#include<stdlib.h>
#include<memory.h>
#include<unistd.h>
#include<string.h>
#include<signal.h>
#include<time.h>
#include<arpa/inet.h>

int sockfd;//socket套接字的描述符

void sig_handler(int signo)//服务端检测到ctrl+\信号后,信号处理函数要关闭socket,关闭描述符
{
	if(signo==SIGINT)
	{
		puts("server close");
		close(sockfd);
		exit(1);
	}
}
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 error");
	}
	return;
}
void out_addr(struct sockaddr_in* clientaddr)//输出的客户端信息
{
	//将端口的网络字节序转换成主机字节序
	int port=ntohs(clientaddr->sin_port);
	//将ip地址转换成点分十进制
	char ip[16];
	memset(ip,0,sizeof(ip));
	inet_ntop(AF_INET,&clientaddr->sin_addr.s_addr,ip,sizeof(ip));
	printf("client:%s(%d) connected\n",ip,port);
	return;
}
int main(int argc,char* argv[])
{
	if(argc<2)
	{
		puts("argc error");
		exit(1);
	}
	if(signal(SIGINT,sig_handler)==SIG_ERR)
	{
		perror("signal error");
		exit(1);
	}
	/*
	 *1.创建socket(套接字)socket创建在内核中,是一个结构体
	 */
	sockfd=socket(AF_INET,SOCK_STREAM,0);//服务端的socket套接字
    //2、调用bind函数将socket和地址(ip,port)进行绑定.先定义inter专用地址结构体,在转成通用
	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]));//设置端口号    short类型
    serveraddr.sin_addr.s_addr=INADDR_ANY;//ip地址,响应所有ip地址
	if(bind(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr))<0)//服务端socketfd套接字
	{
		perror("bind error");
		exit(1);
	}
	//调用listen()监听(指定端口)通知系统接受客户端的请求,将请求放到对应的队列中
    if(listen(sockfd,10)<0)//队列长度为10
	{
		perror("listen error");
		exit(1);
	}
	//accept()
	struct sockaddr_in clientaddr;//internet结构体存客户端信息
	socklen_t clientaddr_len=sizeof(clientaddr);

	//一直接受客户端请求
	while(1)
	{
		//fd是服务端从队列里取出某一客户端,与其建立连接后,服务端与其通信的fd  socket描述符
		//因为服务端可能要同时响应多个客户端的请求,每个fd代表服务端分别与连接的客户端进行通信
		//同样客户端可能要同时对多个服务器请求,在队列里的sockfd客户端也不是客户端套接字本体
		
		//若没有请求连接,则阻塞,直到有连接
		int fd=accept(sockfd,(struct sockaddr*)&clientaddr,&clientaddr_len);//获得连接,返回新的socket描述符
		if(fd<0)
		{
			perror("accept error");
			continue;
		}
        //IO读写
		out_addr(&clientaddr);//打印获取的客户端信息
		do_service(fd);//与客户端互动
		//关闭socket与客户端的连接
		close(fd);
	}
	return 0;
}
#include<stdio.h>
#include<stdlib.h>
#include<netdb.h>
#include<sys/socket.h>
#include<string.h>
#include<memory.h>
#include<arpa/inet.h>
#include<unistd.h>

int main(int argc,char* argv[])
{
	if(argc<3)
	{
		printf("usage:%s ip port\n",argv[0]);
		exit(1);
	}
	//创建socket
	int sockfd=socket(AF_INET,SOCK_STREAM,0);
	if(sockfd<0)
	{
		perror("socket error");
		exit(1);
	}
	//调用connect()连接服务端
	//先用结构体存下客户端要访问的服务端的数据(ip,port)等
	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);
	
	if(connect(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr))<0)
	{
		perror("connect error");
		exit(1);
	}
	//与服务端交互
	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("write error");
		exit(1);
	}
	close(sockfd);
	return 0;
}

自定义协议

#ifndef __MSG_H__
#define __MSG_H__
#include<sys/types.h>
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"tcp_self.h"
#include<unistd.h>
#include<sys/types.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<memory.h>

/*
 *发送一个基于自定义协议的message
 *要发送的数据存放在buff中
 */
//计算校验码
static unsigned char msg_check(Msg*message)
{
	unsigned char s=0;
	int i;
	for(i=0;i<sizeof(message->head);i++) s+=message->head[i];
	for(i=0;i<sizeof(message->buff);i++) s+=message->buff[i];
	return s;
}
int write_msg(int sockfd,char* buff,size_t len)//要发送的buff数据存到自定义的协议结构体发送
{
   Msg message;
   memset(&message,0,sizeof(message));
   strcpy(message.head,"iotek2023");//协议头部
   memcpy(message.buff,buff,len);//协议体部,写入要发送的数据
   message.checknum=msg_check(&message);
   if(write(sockfd,&message,sizeof(message))!=sizeof(message))
   {
	   perror("write error");
	   return -1;
   }
   return sizeof(message);
}
/*
 *读取一个基于自定义协议的message
 *读取的数据存放在buff中
 */
int read_msg(int sockfd,char* buff,size_t len)//读取的数据存到buff,长度为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 s=msg_check(&message);//获取message的校验码
  if((s==(unsigned char)message.checknum)&&(!strcmp("iotek2023",message.head)))
  {
	  memcpy(buff,message.buff,len);
	  return sizeof(message);
  }
  return -1;
}

并发的服务端

  1. 多进程处理连接请求,子进程在结束后产生SIGCHLD信号,父进程捕获处理,wait()回收子进程资源
  2. 多线程处理连接请求,子线程以分离状态启动
  3. fcntl()让read() write()函数非阻塞
  4. IO多路复用 select poll epoll

udp

发送数据

接收数据

设置套接字选项setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)
//设置套接字选项
	if((ret=setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)))<0)
	{//SOL_SOCKET:udp可以多次绑定相同的端口号,后绑定的会覆盖前面绑定的
		perror("setsockopt error");
		exit(1);
	}
udp实例
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netdb.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<stdio.h>
#include<signal.h>
#include<time.h>

int sockfd;

void sig_handler(int signo)//信号处理函数
{
	if(signo==SIGINT)
	{
		puts("server close");
		close(sockfd);
		exit(1);
	}
}

void out_addr(struct sockaddr_in* clientaddr)//输出客户端信息
{
	char ip[16];
	memset(ip,0,sizeof(ip));
	inet_ntop(AF_INET,&clientaddr->sin_addr.s_addr,ip,sizeof(ip));
	int port=ntohs(clientaddr->sin_port);
	printf("client :%s(%d)\n",ip,port);

}
void do_service()//与客户端交互
{
    struct sockaddr_in clientaddr;//存客户端信息
	socklen_t len=sizeof(clientaddr);
	char buffer[1024];
	memset(buffer,0,sizeof(buffer));
	//等待接收客户端数据报
	if(recvfrom(sockfd,buffer,sizeof(buffer),0,(struct sockaddr*)&clientaddr,&len)<0)
		perror("recvfrom error");
	else//接收成功
	{
		out_addr(&clientaddr);//输出客户端信息
		printf("client send into: %s\n",buffer);

		//向客户端发送数据
		long int t=time(0);
		char *ptr=ctime(&t);
		size_t size=strlen(ptr)*sizeof(char);
		if(sendto(sockfd,ptr,size,0,(struct sockaddr*)&clientaddr,len)<0)
		{
			perror("sendto error");
		}
	}
}
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)
	{
		perror("signal sigint error");
		exit(1);
	}
	//创建socket
	sockfd=socket(AF_INET,SOCK_DGRAM,0);
	if(sockfd<0)
	{
		perror("socket error");
		exit(1);
	}
    
	int ret;
	int opt=1;
	//设置套接字选项
	if((ret=setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)))<0)
	{//SOL_SOCKET:udp可以多次绑定相同的端口号,后绑定的会覆盖前面绑定的
		perror("setsockopt error");
		exit(1);
	}

	//2:bind函数对socket和地址绑定
	struct sockaddr_in serveraddr;
	memset(&serveraddr,0,sizeof(serveraddr));
	serveraddr.sin_family=AF_INET;
	serveraddr.sin_port=htons(atoi(argv[1]));//端口号
	serveraddr.sin_addr.s_addr=INADDR_ANY;//任何ip地址访问
	if(bind(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr))<0)
	{
		perror("bind error");
		exit(1);
	}
    //和客户端双向通信
	while(1)
	{
		do_service();
	}
	return 0;
}
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<netdb.h>
#include<memory.h>
#include<unistd.h>
#include<arpa/inet.h>

int is_host(struct hostent* host,char* name)//对域名与域名结构体里的别名,正名进行比对
{
	if(!strcmp(host->h_name,name)) return 1;//正名与域名相同
	while(host->h_aliases[i]!=NULL) //遍历别名
		if(!strcmp(host->h_aliases[i],name)) return 1;//别名与域名相同
	return 0;
}
unsigned int get_ip_by_name(char* name)
{
	unsigned int ip=0;
	struct hostent* host;
	while((host=gethostent())!=NULL)//获取域名信息结构体
	{
		if(is_host(host,name))//比对传入的域名与结构体里的是否相符
		{
			memcpy(&ip,host->h_addr_list[0],4);//相符则将结构体量网络字节序复制给ip
			break;
		}
	}
	endhostent();
	return ip;
}
int main(int argc,char* argv[])
{
	if(argc<3)
	{
		printf("usage: %s ip port\n",argv[0]);
		exit(1);
	}
	//创建socket
	int sockfd=socket(AF_INET,SOCK_DGRAM,0);
	if(sockfd<0)
	{
		perror("socket error");
		exit(1);
	}
	//调用recvfrom()和sendto()和服务器通信
	struct sockaddr_in serveraddr;
	memset(&serveraddr,0,sizeof(serveraddr));
	serveraddr.sin_family=AF_INET;//ipv4
	serveraddr.sin_port=htons(atoi(argv[2]));//port

	//inet_pton(AF_INET,argv[1],&serveraddr.sin_addr.s_addr);//ip
	//对域名地址解析
	unsigned int ip=get_ip_by_nam(argv[1]);//返回ip为网络字节序
	if(ip!=0)
	{
		serveraddr.sin_addr.s_addr=ip;
	}else
	{
		inet_pton(AF_INET,argv[1],&serveraddr.sin_addr.s_addr);
	}

	char buffer[1024]="hello iotek";
	//向服务器发送数据报
	if(sendto(sockfd,buffer,sizeof(buffer),0,(struct sockaddr*)&serveraddr,sizeof(serveraddr))<0)
	{
		perror("sendto error");
		exit(1);
	}else
	{
       //发送成功。接收
		memset(buffer,0,sizeof(buffer));
		if(recv(sockfd,buffer,sizeof(buffer),0)<0)//接收服务器数据
		{
			perror("recv error");
			exit(1);
		}else puts(buffer);
	}
	close(sockfd);
	return 0;
}

域名解析DNS

广播

套接字选项

IO多路复用

fcntl()

接上面并发服务器,通过fcntl()将阻塞函数改为非阻塞

服务端用线程还是进程处理与客户端的连接,服务端在等待读取客户端信息时,使用read()会阻塞,若是客户端一直不发送数据,服务端会一直阻塞等待,导致资源浪费。
改变服务端与客户端socket的描述符fd的属性为非阻塞,当没有客户端资源发送时,线程不会阻塞。
用fcntl()改变fd的属性

具体:

主控线程不断接收客户端请求,将连接的fd放到动态数组

子线程不断循环遍历动态数组,取出动态数组里每个fd进行通信在do_service里通信

子线程不断循环fd数组,非阻塞,快速循环;在do_service()函数里,只有读失败或写失败,才会把fd从动态数组里删除,成功则会不断循环fd数组

存放fd的动态数组
#ifndef __VECTOR_H__
#define __VECTOR_H__
typedef struct
{
	int *fd;
	int counter;
	int max_counter;
}VectorFD;
extern VectorFD* create_vector_fd(void);//创建数组
extern void destroy_vector_fd(VectorFD*);//销毁数组
extern int get_fd(VectorFD*,int index);//获取fd
extern void remove_fd(VectorFD*,int fd);//删除fd
extern void add_fd(VectorFD*,int fd);//增加fd
#endif
#include<stdio.h>
#include<memory.h>
#include"vector_fd.h"
/*
typedef struct
{
	int *fd;//指针指向存储fd的数组
	int counter;//大小
	int max_counter;//容量
}VectorFD;
*/

static void encapacity(VectorFD* vfd)//扩展动态数组
{
	if(vfd->counter>=vfd->max_counter)//扩展
	{
		int* fds=(int*)calloc(vfd->counter+5,sizeof(int));//扩展空间
		assert(fds!=NULL);
		memcpy(fds,vfd->fd,sizeof(int)*vfd->counter);//拷贝
		free(vfd->fd);
		vfd->fd=fds;
		vfd->max_counter+=5;
	}
}
static int indexof(VectorFD* vfd,int fd)//在内部运行,static
{
	int i=0;
	for(;i<vfd->counter;i++)
		if(vfd->fd[i]==fd) return i;
	return -1;
}
VectorFD* create_vector_fd(void)//创建数组
{
	VectorFD* vfd=(VectorFD*)calloc(1,sizeof(VectorFD));//在堆中开辟结构体内存
	assert(vfd!=NULL);
	vfd->fd=(int*)calloc(5,sizeof(int));//先存5个fd,开辟5个fd空间
	assert(vfd->fd!=NULL);
	vfd->counter=0;
	vfd->max_counter=0;
	return vfd;
}
void destroy_vector_fd(VectorFD* vfd)//销毁数组
{
	assert(vfd!=NULL);
	free(vfd->fd);
	free(vfd);
}
int get_fd(VectorFD* vfd,int index)//获取fd
{
	assert(vfd!=NULL);
	if(index<0||index>vfd->counter-1) return 0;
	return vfd->fd[index];
}
void remove_fd(VectorFD* vfd,int fd)//删除fd
{
	assert(vfd!=NULL);
	int index=indexof(vfd,fd);//找到fd在vfd里fd数组的位置
	if(index==-1) return;
	int i=index;
	for(;i<vfd->counter-1;i++)//往前复制
	{
		vfd->fd[i]=vfd->fd[i+1];
	}
	vfd->counter--;
	return;
}
void add_fd(VectorFD* vfd,int fd)//增加fd进动态数组
{
	assert(vfd!=NULL);
	encapacity(vfd);//扩展动态数组
	vfd->fd[vfd->counter++]=fd;
}
//服务端
#include<netdb.h>
#include<sys/socket.h>
#include<pthread.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
#include<memory.h>
#include<unistd.h>
#include<string.h>
#include<signal.h>
#include<errno.h>
#include<time.h>
#include<arpa/inet.h>
#include"vector_fd.h"
#include<fcntl.h>

VectorFD* vfd;//动态数组,服务器主控线程将fd放到动态数组里
int sockfd;//socket套接字的描述符

void sig_handler(int signo)//服务端检测到ctrl+\信号后,信号处理函数要关闭socket,关闭描述符
{
	if(signo==SIGINT)
	{
		puts("server close");
		close(sockfd);
		//销毁动态数组
		destroy_vector_fd(vfd);
		exit(1);
	}
}
void do_service(int fd)//fd从数组中取出,对应于某个客户端,与某个客户端进行一次交互通信(非阻塞)
{
	//和客户端进行双向通信
	char buff[512];
	memset(buff,0,sizeof(buff));
	size_t size=read(fd,buff,sizeof(buff));//采用非阻塞读取方式,读不到数据直接返回,直接服务于下一个客户端
    if(size==0)//客户端已关闭
	{
   
		char info[]="client closed";
		write(STDOUT_FILENO,info,sizeof(info));
		//从动态数组中删除fd
		remove_fd(vfd,fd);
		close(fd);
	}else if(size>0)
	{
       write(STDOUT_FILENO,buff,sizeof(buff));
	   if(write(fd,buff,size)<0)
	   {
		   if(errno==EPIPE)//客户端关闭
		   {
			   perror("client closed");
			   remove_fd(vfd,fd);
			   close(fd);
		   }
	   }
	}
	return;
}
void out_addr(struct sockaddr_in* clientaddr)//输出的客户端信息
{
	//将端口的网络字节序转换成主机字节序
	int port=ntohs(clientaddr->sin_port);
	//将ip地址转换成点分十进制
	char ip[16];
	memset(ip,0,sizeof(ip));
	inet_ntop(AF_INET,&clientaddr->sin_addr.s_addr,ip,sizeof(ip));
	printf("client:%s(%d) connected\n",ip,port);
	return;
}

void* th_fn(void* arg)
{
	int i;
	while(1)
	{
		i=0;
		for(;i<vfd->counter;i++)//子线程不断遍历动态数组,执行do_service函数
		{
			do_service(get_fd(vfd,i));//在动态数组中取出下标是i的fd
		}
	}
	return (void*)0;
}
int main(int argc,char* argv[])
{
	if(argc<2)
	{
		puts("argc error");
		exit(1);
	}
	if(signal(SIGINT,sig_handler)==SIG_ERR)
	{
		perror("signal error");
		exit(1);
	}

	/*
	 *1.创建socket(套接字)socket创建在内核中,是一个结构体
	 */
	sockfd=socket(AF_INET,SOCK_STREAM,0);//服务端的socket套接字
    //2、调用bind函数将socket和地址(ip,port)进行绑定.先定义inter专用地址结构体,在转成通用
	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]));//设置端口号    short类型
    serveraddr.sin_addr.s_addr=INADDR_ANY;//ip地址,响应所有ip地址
	if(bind(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr))<0)//服务端socketfd套接字
	{
		perror("bind error");
		exit(1);
	}
	//调用listen()监听(指定端口)通知系统接受客户端的请求,将请求放到对应的队列中
    if(listen(sockfd,10)<0)//队列长度为10
	{
		perror("listen error");
		exit(1);
	}
	//accept()

	//创建放置套接字描述符fd的动态数组
	vfd=create_vector_fd();

	//设置线程的分离属性
	pthread_attr_t attr;
	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);

	//创建子线程
	int err;
	pthread_t th;//只需创建一个子线程,子线程不断循环遍历动态数组
	if((err=pthread_create(&th,&attr,th_fn,(void*)0))!=0)//子线程运行,在子线程函数里,子线程不断遍历动态数组
	{
		perror("pthread error");
		exit(1);
	}
	pthread_attr_destroy(&attr);//销毁属性

	/*
	 *主控线程获得客户端连接,将新的socket描述符放到动态数组中
	 *启动的子线程负责遍历动态数组中的socket描述符,和对应的客户端进行通信
	 */

	struct sockaddr_in clientaddr;//internet结构体存客户端信息
    socklen_t clientaddr_len=sizeof(clientaddr);
	while(1)//主控线程接收客户端连接
	{
		//fd是服务端从队列里取出某一客户端,与其建立连接后,服务端与其通信的fd  socket描述符
		//因为服务端可能要同时响应多个客户端的请求,每个fd代表服务端分别与连接的客户端进行通信
		//同样客户端可能要同时对多个服务器请求,在队列里的sockfd客户端也不是客户端套接字本体
		
		//若没有请求连接,则阻塞,直到有连接
		int fd=accept(sockfd,(struct sockaddr*)&clientaddr,&clientaddr_len);//获得连接,返回新的socket描述符,clientaddr存客户端数据
		if(fd<0)
		{
			perror("accept error");
			continue;
		}
		out_addr(&clientaddr);
		//将fd读写改为非阻塞方式
		int val;
		fcntl(fd,F_GETFL,&val);
		val|=O_NONBLOCK;
		fcntl(fd,F_SETFL,val);

		//将返回的新的socket描述符加入到动态数组里
		add_fd(vfd,fd);
		//加入后主控线程继续接收客户端连接
	}
	return 0;
}
select

用上面的动态数组存fd

//服务端
#include<netdb.h>
#include<sys/socket.h>
#include<pthread.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
#include<memory.h>
#include<unistd.h>
#include<string.h>
#include<signal.h>
#include<errno.h>
#include<time.h>
#include<arpa/inet.h>
#include"vector_fd.h"
#include<sys/types.h>
#include<sys/time.h>

VectorFD* vfd;//动态数组,服务器主控线程将fd放到动态数组里
int sockfd;//socket套接字的描述符

void sig_handler(int signo)//服务端检测到ctrl+\信号后,信号处理函数要关闭socket,关闭描述符
{
	if(signo==SIGINT)
	{
		puts("server close");
		close(sockfd);
		//销毁动态数组
		destroy_vector_fd(vfd);
		exit(1);
	}
}
void do_service(int fd)//fd从数组中取出,对应于某个客户端,与某个客户端进行一次交互通信(非阻塞)
{
	//和客户端进行双向通信
	char buff[512];
	memset(buff,0,sizeof(buff));
	size_t size=read(fd,buff,sizeof(buff));//采用非阻塞读取方式,读不到数据直接返回,直接服务于下一个客户端
    if(size==0)//客户端已关闭
	{
   
		printf("client close");
		//从动态数组中删除fd
		remove_fd(vfd,fd);
		close(fd);
	}else if(size>0)
	{
	   write(STDOUT_FILENO,buff,sizeof(buff));
	   if(write(fd,buff,size)<0)
	   {
		   if(errno==EPIPE)//客户端关闭
		   {
			   perror("client closed");
			   remove_fd(vfd,fd);
			   close(fd);
		   }
	   }
	}
	return;
}
void out_addr(struct sockaddr_in* clientaddr)//输出的客户端信息
{
	//将端口的网络字节序转换成主机字节序
	int port=ntohs(clientaddr->sin_port);
	//将ip地址转换成点分十进制
	char ip[16];
	memset(ip,0,sizeof(ip));
	inet_ntop(AF_INET,&clientaddr->sin_addr.s_addr,ip,sizeof(ip));
	printf("client:%s(%d) connected\n",ip,port);
	return;
}

//遍历动态数组中所有的描述符并加入到描述符集set中
//同时返回数组中最大的描述符
int add_set(fd_set* set)
{
	FD_ZERO(set);//清空描述符集
	int max_fd=vfd->fd[0];
	int i=0;
	for(;i<vfd->counter;i++)
	{
		int fd=get_fd(vfd,i);
		if(max_fd<fd) max_fd=fd;
		FD_SET(fd,set);//将描述符加到set里
	}
	return max_fd;
}
void* th_fn(void* arg)
{
	//定义时间结构体
	struct timeval t;
	t.tv_sec=2;
	t.tv_usec=0;

	int n=0;//准备好的描述符数量
	int maxfd;//描述符集最大的描述符
	fd_set set;//读 描述符集 
	maxfd=add_set(&set);

	//调用select函数阻塞,委托内核检查传入的描述符集
	//返回准备好的描述符数;超时返回0
	while((n=select(maxfd+1,&set,NULL,NULL,&t))>=0)
	{
		if(n>0)
		{
			//检查那些描述符是准备好的
			int i=0;
			for(;i<vfd->counter;i++)
			{
				int fd=get_fd(vfd,fd);
				if(FD_ISSET(fd,&set)) do_service(fd);//准备好的进行通信  FD_ISSET()检查状态是否发生变化,发生变化则为准备好的?
			}
		}
		//超时重新设置时间,继续等待
		t.tv_sec=2;
		t.tv_usec=0;
		maxfd=add_set(&set);//重新将描述符传到set,获得最大描述符
	}
	return (void*)0;
}
int main(int argc,char* argv[])
{
	if(argc<2)
	{
		puts("argc error");
		exit(1);
	}
	if(signal(SIGINT,sig_handler)==SIG_ERR)
	{
		perror("signal error");
		exit(1);
	}

	/*
	 *1.创建socket(套接字)socket创建在内核中,是一个结构体
	 */
	sockfd=socket(AF_INET,SOCK_STREAM,0);//服务端的socket套接字
    //2、调用bind函数将socket和地址(ip,port)进行绑定.先定义inter专用地址结构体,在转成通用
	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]));//设置端口号    short类型
    serveraddr.sin_addr.s_addr=INADDR_ANY;//ip地址,响应所有ip地址
	if(bind(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr))<0)//服务端socketfd套接字
	{
		perror("bind error");
		exit(1);
	}
	//调用listen()监听(指定端口)通知系统接受客户端的请求,将请求放到对应的队列中
    if(listen(sockfd,10)<0)//队列长度为10
	{
		perror("listen error");
		exit(1);
	}
	//accept()

	//创建放置套接字描述符fd的动态数组
	vfd=create_vector_fd();

	//设置线程的分离属性
	pthread_attr_t attr;
	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);

	//创建子线程
	int err;
	pthread_t th;//只需创建一个子线程,子线程不断循环遍历动态数组
	if((err=pthread_create(&th,&attr,th_fn,(void*)0))!=0)//子线程运行,在子线程函数里,子线程不断遍历动态数组
	{
		perror("pthread error");
		exit(1);
	}
	pthread_attr_destroy(&attr);//销毁属性

	/*
	 *主控线程获得客户端连接,将新的socket描述符放到动态数组中
	 *启动的子线程负责调用select()函数委托内核检查传入到select中的描述符是否准备好,利用FD_ISSET找出准备好的描述符,和对应的客户端进行通信
	 *非阻塞读写
	 */

	struct sockaddr_in clientaddr;//internet结构体存客户端信息
    socklen_t clientaddr_len=sizeof(clientaddr);
	while(1)//主控线程接收客户端连接
	{
		//fd是服务端从队列里取出某一客户端,与其建立连接后,服务端与其通信的fd  socket描述符
		//因为服务端可能要同时响应多个客户端的请求,每个fd代表服务端分别与连接的客户端进行通信
		//同样客户端可能要同时对多个服务器请求,在队列里的sockfd客户端也不是客户端套接字本体
		
		//若没有请求连接,则阻塞,直到有连接
		int fd=accept(sockfd,(struct sockaddr*)&clientaddr,&clientaddr_len);//获得连接,返回新的socket描述符,clientaddr存客户端数据
		if(fd<0)
		{
			perror("accept error");
			continue;
		}
		out_addr(&clientaddr);

		//将返回的新的socket描述符加入到动态数组里
		add_fd(vfd,fd);
		//加入后主控线程继续接收客户端连接
	}
	return 0;
}

守护进程

创建守护进程步骤:

将守护进程的错误信息输入到系统日志文件

系统日志

守护进程,日志的实例
#include<netdb.h>
#include<sys/socket.h>
#include<pthread.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/wait.h>
#include<stdlib.h>
#include<memory.h>
#include<unistd.h>
#include<string.h>
#include<signal.h>
#include<errno.h>
#include<time.h>
#include<arpa/inet.h>
#include"vector_fd.h"
#include<sys/types.h>
#include<sys/time.h>
#include<syslog.h>

VectorFD* vfd;//动态数组,服务器主控线程将fd放到动态数组里
int sockfd;//socket套接字的描述符


void do_service(int fd)//fd从数组中取出,对应于某个客户端,与某个客户端进行一次交互通信(非阻塞)
{
	//和客户端进行双向通信
	char buff[512];
	memset(buff,0,sizeof(buff));
	size_t size=read(fd,buff,sizeof(buff));//采用非阻塞读取方式,读不到数据直接返回,直接服务于下一个客户端
    if(size==0)//客户端已关闭
	{
   
		syslog(LOG_DEBUG,"client closed");
		//从动态数组中删除fd
		remove_fd(vfd,fd);
		close(fd);
	}else if(size>0)
	{
	   syslog(LOG_DEBUG,"%s\n",buff);
	   if(write(fd,buff,size)<0)
	   {
		   if(errno==EPIPE)//客户端关闭
		   {
		       syslog(LOG_DEBUG,"client closed");
			   remove_fd(vfd,fd);
			   close(fd);
		   }
	   }
	}
	return;
}
void out_addr(struct sockaddr_in* clientaddr)//输出的客户端信息
{
	//将端口的网络字节序转换成主机字节序
	int port=ntohs(clientaddr->sin_port);
	//将ip地址转换成点分十进制
	char ip[16];
	memset(ip,0,sizeof(ip));
	inet_ntop(AF_INET,&clientaddr->sin_addr.s_addr,ip,sizeof(ip));
	syslog(LOG_DEBUG,"client:%s(%d) connected\n",ip,port);
	return;
}

//遍历动态数组中所有的描述符并加入到描述符集set中
//同时返回数组中最大的描述符
int add_set(fd_set* set)
{
	FD_ZERO(set);//清空描述符集
	int max_fd=vfd->fd[0];
	int i=0;
	for(;i<vfd->counter;i++)
	{
		int fd=get_fd(vfd,i);
		if(max_fd<fd) max_fd=fd;
		FD_SET(fd,set);//将描述符加到set里
	}
	return max_fd;
}
void* th_fn(void* arg)
{
	//定义时间结构体
	struct timeval t;
	t.tv_sec=2;
	t.tv_usec=0;

	int n=0;//准备好的描述符数量
	int maxfd;//描述符集最大的描述符
	fd_set set;//读 描述符集 
	maxfd=add_set(&set);

	//调用select函数阻塞,委托内核检查传入的描述符集
	//返回准备好的描述符数;超时返回0
	while((n=select(maxfd+1,&set,NULL,NULL,&t))>=0)
	{
		if(n>0)
		{
			//检查那些描述符是准备好的
			int i=0;
			for(;i<vfd->counter;i++)
			{
				int fd=get_fd(vfd,fd);
				if(FD_ISSET(fd,&set)) do_service(fd);//准备好的进行通信  FD_ISSET()检查状态是否发生变化,发生变化则为准备好的?
			}
		}
		//超时重新设置时间,继续等待
		t.tv_sec=2;
		t.tv_usec=0;
		maxfd=add_set(&set);//重新将描述符传到set,获得最大描述符
	}
	return (void*)0;
}
int main(int argc,char* argv[])
{
	if(argc<2)
	{
		puts("argc error");
		exit(1);
	}
	//守护进程
	//1、创建屏蔽字为0
	umask(0);
	//2、调用fork创建子进程,父进程退出
	pid_t pid=fork();
	if(pid>0) exit(0);//父进程退出
	//3、调用setsid创建一个新的会话
	setsid();
	//4、将当前工作目录更改为根目录
	chdir("/");
	//5、关闭不需要的文件描述符
	close(STDIN_FILENO);
	close(STDOUT_FILENO);
	close(STDERR_FILENO);

	//打开系统日志服务的一个连接
    openlog(argv[0],LOG_PID,LOG_SYSLOG);

	/*
	 *1.创建socket(套接字)socket创建在内核中,是一个结构体
	 */
	sockfd=socket(AF_INET,SOCK_STREAM,0);//服务端的socket套接字
    //2、调用bind函数将socket和地址(ip,port)进行绑定.先定义inter专用地址结构体,在转成通用
	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]));//设置端口号    short类型
    serveraddr.sin_addr.s_addr=INADDR_ANY;//ip地址,响应所有ip地址
	if(bind(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr))<0)//服务端socketfd套接字
	{
		syslog(LOG_DEBUG,"bind:%s\n",strerror(errno));//将日志信息写入到系统日志文件中(/var/log/syslog)
		exit(1);
	}
	//调用listen()监听(指定端口)通知系统接受客户端的请求,将请求放到对应的队列中
    if(listen(sockfd,10)<0)//队列长度为10
	{
		syslog(LOG_DEBUG,"listen:%s\n",strerror(errno));//将日志信息写入到系统日志文件中(/var/log/syslog)
		exit(1);
	}
	//accept()

	//创建放置套接字描述符fd的动态数组
	vfd=create_vector_fd();

	//设置线程的分离属性
	pthread_attr_t attr;
	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);

	//创建子线程
	int err;
	pthread_t th;//只需创建一个子线程,子线程不断循环遍历动态数组
	if((err=pthread_create(&th,&attr,th_fn,(void*)0))!=0)//子线程运行,在子线程函数里,子线程不断遍历动态数组
	{
		syslog(LOG_DEBUG,"pthread_create:%s\n",strerror(errno));//将日志信息写入到系统日志文件中(/var/log/syslog)
		exit(1);
	}
	pthread_attr_destroy(&attr);//销毁属性

	/*
	 *主控线程获得客户端连接,将新的socket描述符放到动态数组中
	 *启动的子线程负责调用select()函数委托内核检查传入到select中的描述符是否准备好,利用FD_ISSET找出准备好的描述符,和对应的客户端进行通信
	 *非阻塞读写
	 */

	struct sockaddr_in clientaddr;//internet结构体存客户端信息
    socklen_t clientaddr_len=sizeof(clientaddr);
	while(1)//主控线程接收客户端连接
	{
		//fd是服务端从队列里取出某一客户端,与其建立连接后,服务端与其通信的fd  socket描述符
		//因为服务端可能要同时响应多个客户端的请求,每个fd代表服务端分别与连接的客户端进行通信
		//同样客户端可能要同时对多个服务器请求,在队列里的sockfd客户端也不是客户端套接字本体
		
		//若没有请求连接,则阻塞,直到有连接
		int fd=accept(sockfd,(struct sockaddr*)&clientaddr,&clientaddr_len);//获得连接,返回新的socket描述符,clientaddr存客户端数据
		if(fd<0)
		{
		    syslog(LOG_DEBUG,"fd:%s\n",strerror(errno));//将日志信息写入到系统日志文件中(/var/log/syslog)
			continue;
		}
		out_addr(&clientaddr);

		//将返回的新的socket描述符加入到动态数组里
		add_fd(vfd,fd);
		//加入后主控线程继续接收客户端连接
	}
	return 0;
}