8. 网络编程
网络的基本概念
TCP/IP协议概述
OSI和TCP/IP模型
socket(套接字)
创建socket
字节序
字节序转换函数
通用地址结构
因特网地址结构
IPV4地址族和字符地址间的转换(点分十进制->网络字节序)
填写IPV4地址族结构案例
掌握TCP协议网络基础编程
相关函数
特殊bind地址
案例1:time
-
先启动一个服务端,客户链接上来之后,服务端返回一个系统时间
-
time_tcp_server.c
#include <stdio.h>
#include<stdlib.h>
#include<netdb.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<memory.h>
#include<signal.h>
#include<time.h>
int sockfd;
void sig_handler(int signo)
{
if(signo==SIGINT)
{
printf("server close\n");
/*步骤6:
* 关闭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));
// 网络字节序-> 点分十进制
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 error");
}
}
int main(int argc,char *argv[])
{
// 参数:服务器端绑定的端口
if(argc<2)
{
printf("usage :%s #port\n",argv[0]);
exit(0);
}
// 绑定一个信号,ctrl+c 终止服务端
if(signal(SIGINT,sig_handler)==SIG_ERR)
{
perror("signal sigint error");
exit(1);
}
/*
* 步骤1. 创建socket,套接字socket创建在内核,是一个结构体
* AF_INET : IPV4
* SOCK_STREAM:TCP协议
*/
sockfd = socket(AF_INET,SOCK_STREAM,0);
/*
* 步骤2:调用bind函数将socket和地址(ip和port)进行绑定
* */
// 专用地址,绑定的时候在强转为通用地址
struct sockaddr_in serveraddr;
memset(&serveraddr,0,sizeof(serveraddr));
serveraddr.sin_family = AF_INET; // IPV4
serveraddr.sin_port = htons(atoi(argv[1]));// 字符串-> 整型,主机字节序->网络字节序
serveraddr.sin_addr.s_addr = INADDR_ANY;//响应所有请求
if(bind(sockfd,(struct sockaddr*)&serveraddr,
sizeof(serveraddr))<0)
{
perror("bind error");
exit(1);
}
/*
*步骤3:调用listen函数启动监听(指定port监听)
通知系统去接受来自客户端的连接请求
将接受到的客户端连接请求放置到对应的队列中
第二个参数:
10:队列的长度
* */
if(listen(sockfd,10)<0)
{
perror("listen error");
exit(1);
}
/*
*步骤4:
调用accept函数,从队列中获得一个客户端的请求连接
* 并返回一个新的socket描述符
*注意:
若没有客户端连接,调用此函数后会阻塞
直到获得一个客户端的连接
* */
struct sockaddr_in clientaddr;
socklen_t clientaddr_len = sizeof(clientaddr);
while(1)
{
// 第二个参数可以设置为NULL,如果不想知道客户端的信息的话
int fd = accept(sockfd,
(struct sockaddr*)&clientaddr,
&clientaddr_len);
if(fd<0)
{
perror("accept error");
continue;
}
/*
*步骤5:
调用IO函数和客户端进行双向的通信
*/
out_addr(&clientaddr);
do_service(fd);
/*步骤5:
* 关闭 socket
* */
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/d5dcdb3be95a4bbdb7566cb9994daf40.png)
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/24b23c1c7771411493b1380af83b23af.png)
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/910db7d627b54bada7cd3b98047f0cf8.png)
- time_tcp_client.c
```c
#include<stdio.h>
#include<stdlib.h>
#include<netdb.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<memory.h>
#include<signal.h>
#include<time.h>
int main(int argc,char *argv[])
{
// 参数:服务器端绑定的端口
if(argc<2)
{
printf("usage :%s #port\n",argv[0]);
exit(0);
}
/*
* 步骤1. 创建socket,套接字socket创建在内核,是一个结构体
* AF_INET : IPV4
* SOCK_STREAM:TCP协议
*/
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0)
{
perror("sockek error");
exit(1);
}
/*
* 步骤2:创建socket
* */
struct sockaddr_in serveraddr;
memset(&serveraddr,0,sizeof(serveraddr));
serveraddr.sin_family = AF_INET; // IPV4
serveraddr.sin_port = htons(atoi(argv[2]));// 字符串-> 整型,主机字节序->网络字节序
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);
}
// 步骤3:调用IO函数,和服务器进行双向通讯
char buffer[1024];
memset(buffer,0,sizeof(buffer));
size_t size;
if((size=read(sockfd,buffer,sizeof(buffer)))<0)
{
perror("read error");
}
if(write(STDOUT_FILENO,buffer,size)!=size)
{
perror("write error");
}
// 步骤4:关闭socket
close(sockfd);
return 0;
}
自定义协议
- msg.h
#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
- msg.c
#include<unistd.h>
#include<string.h>
#include<memory.h>
#include<sys/types.h>
#include"msg.h"
// 计算校验码
static unsigned char msg_check(Msg *message)
{
unsigned char s = 0;
for(int i=0;i<sizeof(message->head);i++)
{
s+=message->head[i];
}
for(int i=0;i<sizeof(message->buff);i++)
{
s+=message->buff[i];
}
return s;
}
/*
* 发送一个基于自定义协议的message,发送的数据存放在buff中
* */
int write_msg(int sockfd,char *buff,size_t len)
{
Msg message;
memset(&message,0,sizeof(message));
strcpy(message.head,"iotek2025");
memcpy(message.buff,buff,len);
message.checknum = msg_check(&message);
if(write(sockfd,&message,sizeof(message))!=sizeof(message))
{
return -1;
}
}
/*读取一个基于自定义协议的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;
}
// j进行校验码的验证
unsigned char s =msg_check(&message);
if((s==(unsigned char)message.checknum) && (!strcmp("iotek2025",message.head)))
{
memcpy(buff,message.buff,len);
return sizeof(message);
}
return -1;
}
- 在服务端的do_service中增加
void do_service(int fd)
{
//----------------------------------------
size_t len;
char buff[20];
if((len=read(fd,buff,20))<0) // 注意由于客户端没有给服务端写数据,所以会在此阻塞
{
perror("do_service server read error");
}
//----------------------------------------
// 获得系统时间
long t = time(0);
char *s = ctime(&t);
size_t size = strlen(s)*sizeof(char);
// 将服务器端获得的系统时间写到客户端
if(write(fd,s,size)!=size)
{
perror("write error");
}
}
服务器并发性处理
多进程模型
- echo_tcp_server
#include<stdio.h>
#include<stdlib.h>
#include<netdb.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<memory.h>
#include<signal.h>
#include<time.h>
#include<errno.h>
#include"msg.h"
int sockfd;
void sig_handler(int signo)
{
if(signo==SIGINT)
{
printf("server close\n");
/*步骤6:
* 关闭socket
* */
close(sockfd);
exit(1);
}
if(signo==SIGCHLD)
{
printf("child process dead.....\n");
wait(0);
}
}
/*输出连接上来的客户端相关信息
* */
void out_addr(struct sockaddr_in *clientaddr)
{
// 将端口从网络字节序转换成主机字节序
int port = ntohs(clientaddr->sin_port);
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);
}
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)
{
break; // 写端突然挂了
}
else
{
printf("%s\n",buff);
if(write_msg(fd,buff,sizeof(buff))<0)
{
if(errno==EPIPE)// 读端突然关闭时,类似于管道通信
{
break;
}
perror("protocal error");
}
}
}
}
int main(int argc,char *argv[])
{
// 参数:服务器端绑定的端口
if(argc<2)
{
printf("usage :%s #port\n",argv[0]);
exit(0);
}
// 绑定一个信号,ctrl+c 终止服务端
if(signal(SIGINT,sig_handler)==SIG_ERR)
{
perror("signal sigint error");
exit(1);
}
if(signal(SIGCHLD,sig_handler)==SIG_ERR)// 等子进程终止发送的信号
{
perror("signal sigchild error");
exit(1);
}
/*
* 步骤1. 创建socket,套接字socket创建在内核,是一个结构体
* AF_INET : IPV4
* SOCK_STREAM:TCP协议
*/
sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0)
{
perror("sockek error");
exit(1);
}
/*
* 步骤2:调用bind函数将socket和地址(ip和port)进行绑定
* */
// 专用地址,绑定的时候在强转为通用地址
struct sockaddr_in serveraddr;
memset(&serveraddr,0,sizeof(serveraddr));
serveraddr.sin_family = AF_INET; // IPV4
serveraddr.sin_port = htons(atoi(argv[1]));// 字符串-> 整型,主机字节序->网络字节序
serveraddr.sin_addr.s_addr = INADDR_ANY;//响应所有请求
if(bind(sockfd,(struct sockaddr*)&serveraddr,
sizeof(serveraddr))<0)
{
perror("bind error");
exit(1);
}
/*
*步骤3:调用listen函数启动监听(指定port监听)
通知系统去接受来自客户端的连接请求
将接受到的客户端连接请求放置到对应的队列中
第二个参数:
10:队列的长度
* */
if(listen(sockfd,10)<0)
{
perror("listen error");
exit(1);
}
/*
*步骤4:
调用accept函数,从队列中获得一个客户端的请求连接
* 并返回一个新的socket描述符
*注意:
若没有客户端连接,调用此函数后会阻塞
直到获得一个客户端的连接
* */
struct sockaddr_in clientaddr;
socklen_t clientaddr_len = sizeof(clientaddr);
while(1)
{
// 第二个参数可以设置为NULL,如果不想知道客户端的信息的话
int fd = accept(sockfd,
(struct sockaddr*)&clientaddr,
&clientaddr_len);
if(fd<0)
{
perror("accept error");
continue;
}
/*
*步骤5:
启动子进程调用IO函数
*/
pid_t pid = fork();
if(pid<0)
{
continue;
}
else if(pid==0)
{
out_addr(&clientaddr);
do_service(fd);
/*步骤6:
* 关闭 socket
* */
close(fd); /// 子进程会复制父进程的fd
}
else
{
close(fd);
}
}
return 0;
}
- echo_tcp_client.c
#include<stdio.h>
#include<stdlib.h>
#include<netdb.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<memory.h>
#include<signal.h>
#include<time.h>
#include<errno.h>
#include"msg.h"
int main(int argc,char *argv[])
{
// 参数:服务器端绑定的端口
if(argc<2)
{
printf("usage :%s #port\n",argv[0]);
exit(0);
}
/*
* 步骤1. 创建socket,套接字socket创建在内核,是一个结构体
* AF_INET : IPV4
* SOCK_STREAM:TCP协议
*/
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0)
{
perror("sockek error");
exit(1);
}
/*
* 步骤2:创建socket
* */
struct sockaddr_in serveraddr;
memset(&serveraddr,0,sizeof(serveraddr));
serveraddr.sin_family = AF_INET; // IPV4
serveraddr.sin_port = htons(atoi(argv[2]));// 字符串-> 整型,主机字节序->网络字节序
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);
}
// 步骤3:调用IO函数,和服务器进行双向通讯
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));
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;
}
- 下面这个没有出现
多线程模型
- ehco_tcp_server_th.c
#include<stdio.h>
#include<stdlib.h>
#include<netdb.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<memory.h>
#include<signal.h>
#include<time.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<pthread.h>
#include"msg.h"
int sockfd;
void sig_handler(int signo)
{
if(signo==SIGINT)
{
printf("server close\n");
/*步骤6:
* 关闭socket
* */
close(sockfd);
exit(1);
}
}
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)
{
break; // 写端突然挂了
}
else
{
printf("%s\n",buff);
if(write_msg(fd,buff,sizeof(buff))<0)
{
if(errno==EPIPE)// 读端突然关闭时,类似于管道通信
{
break;
}
perror("protocal error");
}
}
}
}
void out_fd(int fd)
{
struct sockaddr_in addr;
socklen_t len = sizeof(addr);
// 从fd中获得连接的客户端的相关信息
if(getpeername(fd,(struct sockaddr*)&addr,&len)<0)
{
perror("getpeername error");
return;
}
char ip[16];
memset(ip,0,sizeof(ip));
int port = ntohs(addr.sin_port);
inet_ntop(AF_INET,&addr.sin_addr.s_addr,ip,sizeof(ip));
printf("%16s(%5d) 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(0);
}
// 绑定一个信号,ctrl+c 终止服务端
if(signal(SIGINT,sig_handler)==SIG_ERR)
{
perror("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("sockek error");
exit(1);
}
/*
* 步骤2:调用bind函数将socket和地址(ip和port)进行绑定
* */
// 专用地址,绑定的时候在强转为通用地址
struct sockaddr_in serveraddr;
memset(&serveraddr,0,sizeof(serveraddr));
serveraddr.sin_family = AF_INET; // IPV4
serveraddr.sin_port = htons(atoi(argv[1]));// 字符串-> 整型,主机字节序->网络字节序
serveraddr.sin_addr.s_addr = INADDR_ANY;//响应所有请求
if(bind(sockfd,(struct sockaddr*)&serveraddr,
sizeof(serveraddr))<0)
{
perror("bind error");
exit(1);
}
/*
*步骤3:调用listen函数启动监听(指定port监听)
通知系统去接受来自客户端的连接请求
将接受到的客户端连接请求放置到对应的队列中
第二个参数:
10:队列的长度
* */
if(listen(sockfd,10)<0)
{
perror("listen error");
exit(1);
}
/*
*步骤4:
调用accept函数,从队列中获得一个客户端的请求连接
* 并返回一个新的socket描述符
*注意:
若没有客户端连接,调用此函数后会阻塞
直到获得一个客户端的连接
* */
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)
{
// 第二个参数可以设置为NULL,如果不想知道客户端的信息的话
// 主控线程负责调用accept去获得客户端的连接
int fd = accept(sockfd,NULL,NULL);
if(fd<0)
{
perror("accept error");
continue;
}
/*
*步骤5:
启动子线程调用IO函数
*/
pthread_t th;
int err;
// 以分离状态启动子线程
if((err=pthread_create(&th,&attr,
th_fn,(void*)fd))!=0)
{
perror("pthread create error");
}
pthread_attr_destroy(&attr);
}
return 0;
}
I/O多路转换(select)
掌握UDP协议网络基础编程
发生数据
接收数据
- time_udp_server
#include <stdio.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<signal.h>
#include<time.h>
#include<netdb.h>
int sockfd;
void sig_handler(int signo)
{
if(signo==SIGINT)
{
printf("server close\n");
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<0)
{
printf("usage : %s port \n",argv[0]);
exit(1);
}
if(signal(SIGINT,sig_handler)==SIG_ERR)
{
perror("signal sigint error");
exit(1);
}
/*步骤1:
*
* */
sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(sockfd<0)
{
perror("sockfd error");
exit(1);
}
int res;
int opt = 1;
// 设置套接字选项:让刚才实现的端口立即启动
if((res=setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)))<0)
{
perror("setsockopt error");
exit(1);
}
/*步骤2:调用bind函数对socket和地址进行绑定
* */
struct sockaddr_in serveraddr;
memset(&serveraddr,0,sizeof(serveraddr));
serveraddr.sin_family = AF_INET; // IPV4
serveraddr.sin_port = htons(atoi(argv[1])); // port
serveraddr.sin_addr.s_addr = INADDR_ANY; //ip
if(bind(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr))<0)
{
perror("bind error");
exit(1);
}
/*步骤3:和客户端进行双向的数据通信
* */
while(1)
{
do_service();
}
return 0;
}
- time_udp_client
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include<netdb.h>
#include<sys/socket.h>
#include<memory.h>
#include<unistd.h>
int main(int argc,char *argv[])
{
if(argc<3)
{
printf("usgae : %s ip port \n",argv[0]);
exit(1);
}
/*步骤1:创建socket
* */
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(sockfd<0)
{
perror("socket error");
exit(1);
}
/*步骤2:调用recvfrom和sendto等函数
* 和服务器进行双向通信
* */
struct sockaddr_in serveraddr;
memset(&serveraddr,0,sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[2]));
inet_pton(AF_INET,argv[1],&serveraddr.sin_addr.s_addr); //ip
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
{
printf("%s",buffer);
}
close(sockfd);
}
return 0;
}
- 在客户端可以使用connect进行连接
这里的connect并没有和服务端进行三次握手,只是在内核中记录了服务端的地址信息和端口,所以可以直接使用send不用指出服务端的地址等信息了。而且调用connect可以保证只是接受来自服务端发送的数据,不接受其他的信息
- 多次绑定同一个端口
第二个端口起作用
域名
- gethostent获取所有
- gethostbuname获取某一个
-
在Linux中的,位于
/etc/hosts
-
gethost.c
#include <stdio.h>
#include<netdb.h>
#include<stdlib.h>
#include<memory.h>
void out_addr(struct hostent *h)
{
printf("hostbyname: %s\n",h->h_name);
printf("addrtype: %s\n",h->h_addrtype==AF_INET ? "IPV4" : "IPV6");
char ip[16];
memset(ip,0,sizeof(ip));
inet_ntop(h->h_addrtype,h->h_addr_list[0],ip,sizeof(ip));
printf("ip addrress : %s\n",ip);
int i = 0;
while(h->h_aliases[i] != NULL)
{
printf("alias : %s \n",h->h_aliases[i]);
i++;
}
}
int main(int argc,char *argv[])
{
if(argc<2)
{
printf("usage %s host \n",argv[0]);
exit(1);
}
struct hostent *h;
h = gethostbyname(argv[1]);
if(h!=NULL)
{
out_addr(h);
}
else
{
printf("no %s exits\n",argv[1]);
}
return 0;
endhostent();
}
注意gethostbyname应该避免在多线程下使用。
- 使用gethostent
#include <stdio.h>
#include<netdb.h>
#include<stdlib.h>
#include<memory.h>
void out_addr(struct hostent *h)
{
printf("hostbyname: %s\n",h->h_name);
printf("addrtype: %s\n",h->h_addrtype==AF_INET ? "IPV4" : "IPV6");
char ip[16];
memset(ip,0,sizeof(ip));
inet_ntop(h->h_addrtype,h->h_addr_list[0],ip,sizeof(ip));
printf("ip addrress : %s\n",ip);
int i = 0;
while(h->h_aliases[i] != NULL)
{
printf("alias : %s \n",h->h_aliases[i]);
i++;
}
}
int main(int argc,char *argv[])
{
if(argc<2)
{
printf("usage %s host \n",argv[0]);
exit(1);
}
struct hostent *h;
while((h=gethostent()) != NULL)
{
if(!strcmp(argv[1],h->h_name))
{
out_addr(h);
exit(0);
}
else
{
int i = 0;
while(h->h_aliases[i] != NULL)
{
if(!strcmp(argv[1],h->h_aliases[i]))
{
out_addr(h);
exit(0);
}
i++;
}
}
}
endhostent();
return 0;
}
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include<netdb.h>
#include<sys/socket.h>
#include<memory.h>
#include<unistd.h>
int is_host(struct hostent *host,char *name)
{
if(!strcmp(host->h_name,name))
return 1;
int i =0;
while(host->h_aliases[i] != NULL)
{
if(!strcmp(host->h_aliases[i],name))
return 1;
i++;
}
}
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);
break;
}
}
endhostent();
return ip;
}
int main(int argc,char *argv[])
{
if(argc<3)
{
printf("usgae : %s ip port \n",argv[0]);
exit(1);
}
/*步骤1:创建socket
* */
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(sockfd<0)
{
perror("socket error");
exit(1);
}
/*步骤2:调用recvfrom和sendto等函数
* 和服务器进行双向通信
* */
struct sockaddr_in serveraddr;
memset(&serveraddr,0,sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[2]));
unsigned int ip = get_ip_by_name(argv[1]);
if(ip!=0)
{
serveraddr.sin_addr.s_addr = ip;
}
else
{
inet_pton(AF_INET,argv[1],&serveraddr.sin_addr.s_addr);
}
//inet_pton(AF_INET,argv[1],&serveraddr.sin_addr.s_addr); //ip
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
{
printf("%s",buffer);
}
close(sockfd);
}
return 0;
}
网络高级编程
广播
套接字选项
TCP不支持广播,只有UDP才能支持广播
缓存区
- receiver.c
#include <stdio.h>
#include<netdb.h>
#include<sys/socket.h>
#include<string.h>
#include<signal.h>
#include<stdlib.h>
int sockfd;
void sig_handler(int signo)
{
if(signo == SIGINT)
{
printf("receiver will exited");
close(sockfd);
exit(1);
}
}
int main(int argc,char *argv[])
{
if(argc<2)
{
fprintf(stderr,"usage : %s port \n",argv[0]);
exit(1);
}
if(signal(SIGINT,sig_handler)==SIG_ERR)
{
perror("signal sigint errro");
exit(1);
}
sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(sockfd<0)
{
perror("socket error");
exit(1);
}
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;
if(bind(sockfd,(struct sockadd*)&serveraddr,sizeof(serveraddr))<0)
{
perror("bind error");
exit(1);
}
char buffer[1024];
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
while(1)
{
memset(buffer,0,sizeof(buffer));
memset(&clientaddr,0,sizeof(clientaddr));
if(recvfrom(sockfd,buffer,sizeof(buffer),0,(struct sockaddr*)&clientaddr,&len)<0)
{
perror("recvfrom error");
exit(1);
}
else
{
char ip[16];
inet_ntop(AF_INET,&clientaddr.sin_addr.s_addr,ip,sizeof(ip));
int port = ntohs(clientaddr.sin_port);
printf("%s{%d}:%s\n",ip,port,buffer);
}
}
return 0;
}
- broadcast.c
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include<netdb.h>
#include<sys/socket.h>
int main(int argc,char * argv[])
{
if(argc<3)
{
fprintf(stderr,"usage: %s ip port\n",argv[0]);
exit(1);
}
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(sockfd<0)
{
perror("sockdt error");
exit(1);
}
int opt = 1;
// 采用广播地址发送
setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&opt,sizeof(opt));
struct sockaddr_in serveraddr;
memset(&serveraddr,0,sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[2]));
inet_pton(AF_INET,argv[1],&serveraddr.sin_addr.s_addr);
printf("I will broadcast....\n");
char *info = "helo yangpipi...";
size_t size = strlen(info)*sizeof(char);
if(sendto(sockfd,info,size,0,(struct sockaddr*)&serveraddr,sizeof(serveraddr))<0)
{
perror("sendto error");
exit(1);
}
else
{
printf("boradcast success\n");
}
close(sockfd);
return 0;
多路复用之fcntl
- server.c
#include<stdio.h>
#include<stdlib.h>
#include<netdb.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<memory.h>
#include<signal.h>
#include<time.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<pthread.h>
#include<fcntl.h>
#include"vector_fd.h"
VectorFD *vfd;
int sockfd;
void sig_handler(int signo)
{
if(signo==SIGINT)
{
printf("server close----\n");
/*步骤6:
* 关闭socket
* */
close(sockfd);
// 销毁动态数组
destroy_vector_fd(vfd);
exit(1);
}
}
/*
*fd对应于某个连接的客户端,和某个连接的客户端进行双向通信(非阻塞方式)
* */
void do_service(int fd)
{
// 和客户端进行读写操作(双向通信)
char buff[512];
while(1)
{
memset(buff,0,sizeof(buff));
// 以非阻塞方式读,读不到数据就返回了,直接服务于下一个客户端,因此不需要判断size<0的情况
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);
// 关闭对应的客户端的socket
close(fd);
}
else if(size > 0)
{
write(STDOUT_FILENO,buff,sizeof(buff));
if(write(fd,buff,size)<size)
{
if(errno==EPIPE)// 客户端关闭连接
{
perror("write error");
remove_fd(vfd,fd);
close(fd);
}
}
}
}
}
void * th_fn(void *arg)
{
int i;
while(1)
{
// 遍历动态数中的socket描述符
for(int i = 0;i<vfd->counter;i++)
{
do_service(get_fd(vfd,i));
}
}
return (void*)0;
}
void out_addr(struct sockaddr_in *clientaddr)
{
char ip[16];
memset(ip,0,sizeof(ip));
int port = ntohs(clientaddr->sin_port);
inet_ntop(AF_INET,&clientaddr->sin_addr.s_addr,ip,sizeof(ip));
printf("%s(%d) connected!\n",ip,port);
}
int main(int argc,char *argv[])
{
// 参数:服务器端绑定的端口
if(argc<2)
{
printf("usage :%s #port\n",argv[0]);
exit(0);
}
// 绑定一个信号,ctrl+c 终止服务端
if(signal(SIGINT,sig_handler)==SIG_ERR)
{
perror("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("sockek error");
exit(1);
}
/*
* 步骤2:调用bind函数将socket和地址(ip和port)进行绑定
* */
// 专用地址,绑定的时候在强转为通用地址
struct sockaddr_in serveraddr;
memset(&serveraddr,0,sizeof(serveraddr));
serveraddr.sin_family = AF_INET; // IPV4
serveraddr.sin_port = htons(atoi(argv[1]));// 字符串-> 整型,主机字节序->网络字节序
serveraddr.sin_addr.s_addr = INADDR_ANY;//响应所有请求
if(bind(sockfd,(struct sockaddr*)&serveraddr,
sizeof(serveraddr))<0)
{
perror("bind error");
exit(1);
}
/*
*步骤3:调用listen函数启动监听(指定port监听)
通知系统去接受来自客户端的连接请求
将接受到的客户端连接请求放置到对应的队列中
第二个参数:
10:队列的长度
* */
if(listen(sockfd,10)<0)
{
perror("listen error");
exit(1);
}
/*
*步骤4:
调用accept函数,从队列中获得一个客户端的请求连接
* 并返回一个新的socket描述符
*注意:
若没有客户端连接,调用此函数后会阻塞
直到获得一个客户端的连接
* */
// 创建放置套接字描述符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 create error");
exit(1);
}
pthread_attr_destroy(&attr);
/*
* 1) 主控线程获得客户端的连接,将新的socket描述符放置到动态数组中
* 2) 启动的子线程负责遍历动态数组中socket描述符并和对应的客户端进行双向通信(采用非阻塞方式读写)
*
* */
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;
}
out_addr(&clientaddr);
// 将读写方式修改为非阻塞方式
int val;
fcntl(fd,F_GETFL,&val);
val |= O_NONBLOCK;
fcntl(fd,F_SETFL,val);
// 将返回新的socket描述符加入到动态数组中
add_fd(vfd,fd);
}
return 0;
- client.c
#include<stdio.h>
#include<stdlib.h>
#include<netdb.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<memory.h>
#include<signal.h>
#include<time.h>
#include<errno.h>
int main(int argc,char *argv[])
{
// 参数:服务器端绑定的端口
if(argc<2)
{
printf("usage :%s #port\n",argv[0]);
exit(0);
}
/*
* 步骤1. 创建socket,套接字socket创建在内核,是一个结构体
* AF_INET : IPV4
* SOCK_STREAM:TCP协议
*/
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0)
{
perror("sockek error");
exit(1);
}
/*
* 步骤2:创建socket
* */
struct sockaddr_in serveraddr;
memset(&serveraddr,0,sizeof(serveraddr));
serveraddr.sin_family = AF_INET; // IPV4
serveraddr.sin_port = htons(atoi(argv[2]));// 字符串-> 整型,主机字节序->网络字节序
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);
}
// 步骤3:调用IO函数,和服务器进行双向通讯
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));
if(size<0)
{
continue;
}
buff[size-1] = '\0';
if(write(sockfd,buff,sizeof(buff))<0)
{
perror("write msg error");
continue;
}
else
{
if(read(sockfd,buff,sizeof(buff))<0)
{
perror("read msg error");
continue;
}
else
{
printf("%s\n",buff);
}
}
}
// 步骤4:关闭socket
close(sockfd);
return 0;
}
多路复用之select
- echo_tcp_sever_select.c
#include<stdio.h>
#include<stdlib.h>
#include<netdb.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<memory.h>
#include<signal.h>
#include<time.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<pthread.h>
#include<fcntl.h>
#include"vector_fd.h"
VectorFD *vfd;
int sockfd;
void sig_handler(int signo)
{
if(signo==SIGINT)
{
printf("server close----\n");
/*步骤6:
* 关闭socket
* */
close(sockfd);
// 销毁动态数组
destroy_vector_fd(vfd);
exit(1);
}
}
/*
*fd对应于某个连接的客户端,和某个连接的客户端进行双向通信(非阻塞方式)
* */
void do_service(int fd)
{
// 和客户端进行读写操作(双向通信)
char buff[512];
while(1)
{
memset(buff,0,sizeof(buff));
// 以非阻塞方式读,读不到数据就返回了,直接服务于下一个客户端,因此不需要判断size<0的情况
size_t size=read(fd,buff,sizeof(buff));
if(size==0)
{
//char info[] = "client closed";
//write(STDOUT_FILENO,info,sizeof(info));
printf("client closed\n"); // 这里由于内核设置,所以可以使用带缓存的IO
// 从动态数组中删除对应的fd
remove_fd(vfd,fd);
// 关闭对应的客户端的socket
close(fd);
}
else if(size > 0)
{
//write(STDOUT_FILENO,buff,sizeof(buff));
printf("%s\n",buff);
if(write(fd,buff,size)<size)
{
if(errno==EPIPE)// 客户端关闭连接
{
perror("write error");
remove_fd(vfd,fd);
close(fd);
}
}
}
}
}
/*遍历出动态数组中所有的描述符并加入到描述符集set中
* 同时此函数返回动态数组中最大的那个描述符
* */
int add_set(fd_set *set)
{
FD_ZERO(set); // 清空描述符
int max_fd = vfd->fd[0];
for(int i = 0;i < vfd->fd[0];i++)
{
int fd = get_fd(vfd,i);
if(fd>max_fd)
{
max_fd = fd;
}
FD_SET(fd,set);//将fd加入到描述符集中
}
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
* 第一个参数为描述符集中描述符的范围(最大描述符+1)
* */
while((n=select(maxfd+1,&set,NULL,NULL,&t))>=0)
{
if(n>0)
{
/*检测那些描述符准备好,并和这些准备好的描述符对应的客户端进行数据的双向通信
* */
for(int i=0;i<vfd->counter;i++)
{
int fd = get_fd(vfd,i);
if(FD_ISSET(fd,&set))
{
do_service(fd);
}
}
}
//超时
// 重新设置时间和清空描述符集
t.tv_sec = 2;
t.tv_usec = 0;
// 重新遍历动态数组中最新的描述符放置到描述符集中
maxfd = add_set(&set);
}
}
void out_addr(struct sockaddr_in *clientaddr)
{
char ip[16];
memset(ip,0,sizeof(ip));
int port = ntohs(clientaddr->sin_port);
inet_ntop(AF_INET,&clientaddr->sin_addr.s_addr,ip,sizeof(ip));
printf("%s(%d) connected!\n",ip,port);
}
int main(int argc,char *argv[])
{
// 参数:服务器端绑定的端口
if(argc<2)
{
printf("usage :%s #port\n",argv[0]);
exit(0);
}
// 绑定一个信号,ctrl+c 终止服务端
if(signal(SIGINT,sig_handler)==SIG_ERR)
{
perror("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("sockek error");
exit(1);
}
/*
* 步骤2:调用bind函数将socket和地址(ip和port)进行绑定
* */
// 专用地址,绑定的时候在强转为通用地址
struct sockaddr_in serveraddr;
memset(&serveraddr,0,sizeof(serveraddr));
serveraddr.sin_family = AF_INET; // IPV4
serveraddr.sin_port = htons(atoi(argv[1]));// 字符串-> 整型,主机字节序->网络字节序
serveraddr.sin_addr.s_addr = INADDR_ANY;//响应所有请求
if(bind(sockfd,(struct sockaddr*)&serveraddr,
sizeof(serveraddr))<0)
{
perror("bind error");
exit(1);
}
/*
*步骤3:调用listen函数启动监听(指定port监听)
通知系统去接受来自客户端的连接请求
将接受到的客户端连接请求放置到对应的队列中
第二个参数:
10:队列的长度
* */
if(listen(sockfd,10)<0)
{
perror("listen error");
exit(1);
}
/*
*步骤4:
调用accept函数,从队列中获得一个客户端的请求连接
* 并返回一个新的socket描述符
*注意:
若没有客户端连接,调用此函数后会阻塞
直到获得一个客户端的连接
* */
// 创建放置套接字描述符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 create error");
exit(1);
}
pthread_attr_destroy(&attr);
/*
* 1) 主控线程获得客户端的连接,将新的socket描述符放置到动态数组中
* 2)
* a) 启动的子线程调用select函数委托内核去检查传入到select中的描述符是否准备好
* b) 利用FD_ISSET来找出准备好的那些描述符并和对应的客户端进行双向通信(非阻塞)
*
* */
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;
}
out_addr(&clientaddr);
// 将返回新的socket描述符加入到动态数组中
add_fd(vfd,fd);
}
return 0;
}
守护进程
- 编程步骤
- 出错处理
#include<stdio.h>
#include<stdlib.h>
#include<netdb.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<memory.h>
#include<signal.h>
#include<time.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<pthread.h>
#include<fcntl.h>
#include<syslog.h>
#include<sys/stat.h>
#include"vector_fd.h"
VectorFD *vfd;
int sockfd;
/*
*fd对应于某个连接的客户端,和某个连接的客户端进行双向通信(非阻塞方式)
* */
void do_service(int fd)
{
// 和客户端进行读写操作(双向通信)
char buff[512];
while(1)
{
memset(buff,0,sizeof(buff));
// 以非阻塞方式读,读不到数据就返回了,直接服务于下一个客户端,因此不需要判断size<0的情况
size_t size=read(fd,buff,sizeof(buff));
if(size==0)
{
char info[] = "client closed\n";
syslog(LOG_DEBUG,"client closed");
//printf("client closed\n"); // 这里由于内核设置,所以可以使用带缓存的IO
// 从动态数组中删除对应的fd
remove_fd(vfd,fd);
// 关闭对应的客户端的socket
close(fd);
}
else if(size > 0)
{
//write(STDOUT_FILENO,buff,sizeof(buff));
// printf("%s\n",buff);
syslog(LOG_DEBUG,"%s\n",buff);
if(write(fd,buff,size)<size)
{
if(errno==EPIPE)// 客户端关闭连接
{
syslog(LOG_DEBUG,"write:%s\n",strerror(errno));
remove_fd(vfd,fd);
close(fd);
}
}
}
}
}
/*遍历出动态数组中所有的描述符并加入到描述符集set中
* 同时此函数返回动态数组中最大的那个描述符
* */
int add_set(fd_set *set)
{
FD_ZERO(set); // 清空描述符
int max_fd = vfd->fd[0];
for(int i = 0;i < vfd->fd[0];i++)
{
int fd = get_fd(vfd,i);
if(fd>max_fd)
{
max_fd = fd;
}
FD_SET(fd,set);//将fd加入到描述符集中
}
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
* 第一个参数为描述符集中描述符的范围(最大描述符+1)
* */
while((n=select(maxfd+1,&set,NULL,NULL,&t))>=0)
{
if(n>0)
{
/*检测那些描述符准备好,并和这些准备好的描述符对应的客户端进行数据的双向通信
* */
for(int i=0;i<vfd->counter;i++)
{
int fd = get_fd(vfd,i);
if(FD_ISSET(fd,&set))
{
do_service(fd);
}
}
}
//超时
// 重新设置时间和清空描述符集
t.tv_sec = 2;
t.tv_usec = 0;
// 重新遍历动态数组中最新的描述符放置到描述符集中
maxfd = add_set(&set);
}
}
void out_addr(struct sockaddr_in *clientaddr)
{
char ip[16];
memset(ip,0,sizeof(ip));
int port = ntohs(clientaddr->sin_port);
inet_ntop(AF_INET,&clientaddr->sin_addr.s_addr,ip,sizeof(ip));
syslog(LOG_DEBUG,"%s(%d) connected!\n",ip,port);
}
int main(int argc,char *argv[])
{
// 参数:服务器端绑定的端口
if(argc<2)
{
printf("usage :%s #port\n",argv[0]);
exit(1);
}
// 守护进程编程的5个步骤
// 步骤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创建在内核,是一个结构体
* AF_INET : IPV4
* SOCK_STREAM:TCP协议
*/
sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0)
{
syslog(LOG_DEBUG,"socket:%s\n",strerror(errno));
exit(1);
}
/*
* 步骤2:调用bind函数将socket和地址(ip和port)进行绑定
* */
// 专用地址,绑定的时候在强转为通用地址
struct sockaddr_in serveraddr;
memset(&serveraddr,0,sizeof(serveraddr));
serveraddr.sin_family = AF_INET; // IPV4
serveraddr.sin_port = htons(atoi(argv[1]));// 字符串-> 整型,主机字节序->网络字节序
serveraddr.sin_addr.s_addr = INADDR_ANY;//响应所有请求
if(bind(sockfd,(struct sockaddr*)&serveraddr,
sizeof(serveraddr))<0)
{
// 将日志信息写入到系统日志文件中(/var/log/syslog)
syslog(LOG_DEBUG,"bind:%s\n",strerror(errno));
exit(1);
}
/*
*步骤3:调用listen函数启动监听(指定port监听)
通知系统去接受来自客户端的连接请求
将接受到的客户端连接请求放置到对应的队列中
第二个参数:
10:队列的长度
* */
if(listen(sockfd,10)<0)
{
syslog(LOG_DEBUG,"listen:%s\n",strerror(errno));
exit(1);
}
/*
*步骤4:
调用accept函数,从队列中获得一个客户端的请求连接
* 并返回一个新的socket描述符
*注意:
若没有客户端连接,调用此函数后会阻塞
直到获得一个客户端的连接
* */
// 创建放置套接字描述符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:%s\n",strerror(errno));
exit(1);
}
pthread_attr_destroy(&attr);
/*
* 1) 主控线程获得客户端的连接,将新的socket描述符放置到动态数组中
* 2)
* a) 启动的子线程调用select函数委托内核去检查传入到select中的描述符是否准备好
* b) 利用FD_ISSET来找出准备好的那些描述符并和对应的客户端进行双向通信(非阻塞)
*
* */
struct sockaddr_in clientaddr;
socklen_t clientaddr_len = sizeof(clientaddr);
while(1)
{
int fd = accept(sockfd,(struct sockaddr*)&clientaddr,&clientaddr_len);
if(fd<0)
{
syslog(LOG_DEBUG,"accept:%s\n",strerror(errno));
continue;
}
out_addr(&clientaddr);
// 将返回新的socket描述符加入到动态数组中
add_fd(vfd,fd);
}
return 0;
}