网络编程概要


网络层协议

传输层:

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;
}
并发的服务端
- 多进程处理连接请求,子进程在结束后产生SIGCHLD信号,父进程捕获处理,wait()回收子进程资源
- 多线程处理连接请求,子线程以分离状态启动
- fcntl()让read() write()函数非阻塞
- 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;
}
浙公网安备 33010602011771号