ABCD特殊 类网络介绍
A类网路:第一个字节是网络地址,其最高位必须为‘0’
B类网络:第一、二个字节是网络地址,其从‘10’开始
C类网络:第一、二、三个字节是网络地址,其从’110‘开始
D类网络:D 类 IP 地址在历史上被叫做多播地址(multicast address),即组播地址。在以太网中,多播地址命名了一组应该在这个网络中应用接收到一个分组的站点。多播地址的最高位必须是 “1110”,范围从224.0.0.0 - 239.255.255.255。
特殊网络:每一个字节都为 0 的地址( “0.0.0.0” )对应于当前主机;
IP 地址中的每一个字节都为 1 的 IP 地址( “255.255.255.255” )是当前子网的广播地址;
IP 地址中凡是以 “11110” 开头的 E 类 IP 地址都保留用于将来和实验使用。
IP地址中不能以十进制 “127” 作为开头,该类地址中数字 127.0.0.1 到 127.255.255.255 用于回路测
试,如:127.0.0.1可以代表本机IP地址。
子网掩码subnet mask又叫网络掩码、地址掩码、子网络遮罩,它是一种用来指明一个 IP 地
址的哪些位标识的是主机所在的子网,以及哪些位标识的是主机的位掩码。不能单独存在。
注:在传输数据时都是转成大端字节序,若接收数据的主机是小端字节序则会通过API转换为小端字节序。
#include
// 转换端口
uint16_t htons(uint16_t hostshort);
// 主机字节序 - 网络字节序
uint16_t ntohs(uint16_t netshort);
// 主机字节序 - 网络字节序
// 转IP
uint32_t htonl(uint32_t hostlong);
// 主机字节序 - 网络字节序
uint32_t ntohl(uint32_t netlong);
// 主机字节序 - 网络字节序
#include
struct sockaddr_storage {
sa_family_t sa_family;
unsigned long int __ss_align;
char __ss_padding[ 128 - sizeof(__ss_align) ];
};
typedef unsigned short int sa_family_t;
很多网络编程函数诞生早于 IPv4 协议,那时候都使用的是 struct sockaddr 结构体,为了向前兼容,现在sockaddr 退化成了(void *)的作用,传递一个地址给函数,至于这个函数是 sockaddr_in 还是
sockaddr_in6,由地址族确定,然后函数内部再强制类型转化为所需的地址类型。
所有专用 socket 地址(以及 sockaddr_storage)类型的变量在实际使用时都需要转化为通用 socket 地址类型 sockaddr(强制转化即可),因为所有 socket 编程接口使用的地址参数类型都是 sockaddr。
#include
p:点分十进制的IP字符串,n:表示network,网络字节序的整数
int inet_pton(int af, const char *src, void *dst);
#include
将网络字节序的整数,转换成点分十进制的IP地址字符串
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
TCP 提供了一种可靠、面向连接、字节流、传输层的服务,采用三次握手建立一个连接。采用 四次挥手
来关闭一个连接。
write(cfd,revbuf,strlen(revbuf)+1);
//加1是为了把 字符串终止符发送过去 以免产生bug(该bug跟字符串长短相关)//多进程服务端TCP通信
#include
//字节序
#include
//socket通信
#include
#include
//exit
#include
#include
#include
//信号捕捉,子进程回收
#include
#include
#include
void recyleChild(int arg) {
while(1) {
int ret = waitpid(-1, NULL, WNOHANG);
if(ret == -1) {
// 所有的子进程都回收了
break;
}else if(ret == 0) {
// 还有子进程活着
break;
} else if(ret > 0){
// 被回收了
printf("子进程 %d 被回收了\n", ret);
}
}
}
int main() {
//定义act相关参数
struct sigaction act;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
act.sa_handler = recyleChild;
//注册信号捕捉
sigaction(SIGCHLD,&act,NULL);
//创建socket
int lfd = socket(AF_INET,SOCK_STREAM,0);
if(lfd == -1) {
perror("socket");
exit(-1);
}
//绑定本机ip地址和端口
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(9999);
saddr.sin_addr.s_addr = INADDR_ANY;
int ret = bind(lfd,(struct sockaddr*)&saddr,sizeof(saddr));
if(ret == -1) {
perror("bind");
exit(-1);
}
//监听连接
ret = listen(lfd,8);
if(ret == -1) {
perror("listen");
exit(-1);
}
//循环接收客户端连接
while(1) {
struct sockaddr_in caddr;
int len = sizeof(caddr);
int cfd = accept(lfd,(struct sockaddr*)&caddr,&len);
if(cfd == -1) {
if(errno == EINTR) continue;
perror("accept");
exit(-1);
}
//创建子进程,输出客户端信息并进行通信
pid_t spid = fork();
if(spid == 0) {
//子进程
//输出客户端ip 和端口号
char cip[16];
inet_ntop(AF_INET,&caddr.sin_addr.s_addr,cip,strlen(cip));
unsigned short cport = ntohs(caddr.sin_port);
printf("Client ip is %s and port is %d\n",cip,cport);
//创建接收缓冲区
char revbuf[1024];
while(1) {
//接收客户端信息
int rlen = read(cfd,revbuf,sizeof(revbuf));
if(rlen == -1) {
perror("read");
exit(-1);
} else if(rlen > 0) {
printf("Sever have recieved :%s\n",revbuf);
} else if(rlen == 0) {
printf("client have closed..\n");
break;
}
sleep(1);
//发送信息给客户端
write(cfd,revbuf,strlen(revbuf)+1);//加1是为了把 字符串终止符发送过去 以免产生bug(该bug跟字符串长短相关)
}
//关闭客户端文件描述符
close(cfd);
//退出当前子进程
exit(0);
}
}
//关闭监听描述符
close(lfd);
return 0;
}
write(cfd,revbuf,strlen(revbuf)+1);
//加1是为了把 字符串终止符发送过去 以免产生bug(该bug跟字符串长短相关)//TCP通信的客户端(无多进程)
#include
#include
#include
#include
#include
#include
#include
int main() {
//1创建socket
int cfd = socket(AF_INET,SOCK_STREAM,0);
if(cfd == -1) {
perror("socket");
exit(-1);
}
//2与服务端连接
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(9999);
inet_pton(AF_INET,"172.26.4.132",&saddr.sin_addr.s_addr);
int ret = connect(cfd,(struct sockaddr *)&saddr,sizeof(saddr));
if(ret == -1) {
perror("connect");
exit(-1);
}
//3通信
char revbuf[1024];
int i = 0;
while(1) {
//发送信息给服务端
sprintf(revbuf,"hello ! I am Client:%d\n",i++);
//sprintf(revbuf, "data : %d\n", i++);
write(cfd,revbuf,strlen(revbuf)+1);//加1是为了把 字符串终止符发送过去 以免产生bug(该bug跟字符串长短相关)
//i++;
//接收服务端信息
int len = read(cfd,revbuf,sizeof(revbuf));
if(len == -1) {
perror("read");
exit(-1);
} else if(len > 0) {
printf("Client have recieved :%s\n",revbuf);
} else if(len == 0) {
printf("Sever have closed..");
break;
}
}
//4关闭
close(cfd);
return 0;
}
//多线程服务端TCP通信
#include
//字节序
#include
//socket通信
#include
#include
//exit
#include
#include
#include
#include
#include
//创建结构体的原因: 线程处理需要获取多个参数,那么pthread_creat 的第四个参数可以作为传入,
// 但只能传入一个,所以只需传入一个结构体指针即可获得三个参数。
struct sockInfo {
int fd; // 通信的文件描述符
struct sockaddr_in addr;
pthread_t tid; // 线程号
};
struct sockInfo sockinfos[128];
//——————————————————————创建线程后执行的区域——————————————————————————————//
void * working(void* arg) {
// 子线程和客户端通信 cfd 客户端的信息 线程号
struct sockInfo * pinfo = (struct sockInfo *)arg;
//输出客户端ip 和端口号
char cip[16];
inet_ntop(AF_INET,&pinfo->addr.sin_addr.s_addr,cip,strlen(cip));
unsigned short cport = ntohs(pinfo->addr.sin_port);
printf("Client ip is %s and port is %d\n",cip,cport);
//创建接收缓冲区
char revbuf[1024];
while(1) {
//接收客户端信息
int rlen = read(pinfo->fd,revbuf,sizeof(revbuf));
if(rlen == -1) {
perror("read");
exit(-1);
} else if(rlen > 0) {
printf("Sever have recieved :%s\n",revbuf);
} else if(rlen == 0) {
printf("client have closed..\n");
break;
}
sleep(1);
//发送信息给客户端
write(pinfo->fd,revbuf,strlen(revbuf)+1);//加1是为了把 字符串终止符发送过去 以免产生bug(该bug跟字符串长短相关)
}
//关闭客户端文件描述符
close(pinfo->fd);
return NULL;
}
//——————————————————————创建线程后执行的区域——————————————————————————————//
int main() {
//创建socket
int lfd = socket(AF_INET,SOCK_STREAM,0);
if(lfd == -1) {
perror("socket");
exit(-1);
}
//绑定本机ip地址和端口
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(9999);
saddr.sin_addr.s_addr = INADDR_ANY;
int ret = bind(lfd,(struct sockaddr*)&saddr,sizeof(saddr));
if(ret == -1) {
perror("bind");
exit(-1);
}
//监听连接
ret = listen(lfd,8);
if(ret == -1) {
perror("listen");
exit(-1);
}
//初始化
int max = sizeof(sockinfos)/sizeof(sockinfos[0]);
for(int i = 0; i < max; i++) {
bzero(&sockinfos[i], sizeof(sockinfos[i]));//让多个字节为零
sockinfos[i].fd = -1;
sockinfos[i].tid = -1;
}
//循环接收客户端连接
while(1) {
//子线程
struct sockaddr_in caddr;
int len = sizeof(caddr);
int cfd = accept(lfd,(struct sockaddr*)&caddr,&len);
if(cfd == -1) {
if(errno == EINTR) continue;
perror("accept");
exit(-1);
}
struct sockInfo * pinfo;
//查找空闲的子进程
for(int i = 0;i<max;i++) {
if(sockinfos[i].fd == -1) {
pinfo = &sockinfos[i];
break;
}
if(i == max - 1) {//当没有空闲的 让客户端等待一秒。
sleep(1);
i--;
}
}
pinfo->fd = cfd;
memcpy(&pinfo->addr, &caddr, len);//注意这里拷贝结构体的方式!
//创建子线程
pthread_create(&pinfo->tid,NULL, working, pinfo);
//子线程自动回收,不用父进程回收
//不能用另一个pthread_join的原因是该函数阻塞
pthread_detach(pinfo->tid);
}
//关闭lfd (cfd在线程中已经关闭)
close(lfd);
return 0;
}
端口复用最常用的用途:
#include
#include
// 设置套接字的属性(不仅仅能设置端口复用)
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
参数:
//形式参数省略
setsockopt();
bind();
fd_set 是一个文件描述符表 ,有1024位 。前0 1 2三位默认占用。
重点看:IO多路复用部分
#include
#include
#include
#include
#include
#include
int main() {
// 创建socket
int lfd = socket(PF_INET, SOCK_STREAM, 0);
struct sockaddr_in saddr;
saddr.sin_port = htons(9999);
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY;
// 绑定
bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));
// 监听
listen(lfd, 8);
//----------------------------------IO多路复用------------------------------//
// 创建一个fd_set的集合,存放的是需要检测的文件描述符
fd_set rdset, tmp;//tmp 的目的是防止本地的fd_set 文件被内核改变 /
FD_ZERO(&rdset);
FD_SET(lfd, &rdset);
int maxfd = lfd;
while(1) {
tmp = rdset;
// 调用select系统函数,让内核帮检测哪些文件描述符有数据
int ret = select(maxfd + 1, &tmp, NULL, NULL, NULL);
if(ret == -1) {
perror("select");
exit(-1);
} else if(ret == 0) {
continue;
} else if(ret > 0) {
// 说明检测到了有文件描述符的对应的缓冲区的数据发生了改变
//判断fd对应的标志位是0还是1, 返回值 : fd对应的标志位的值,0,返回0, 1,返回1
if(FD_ISSET(lfd, &tmp)) {
// 表示有新的客户端连接进来了
struct sockaddr_in cliaddr;
int len = sizeof(cliaddr);
int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len);
// 将新的文件描述符加入到集合中
FD_SET(cfd, &rdset);
// 更新最大的文件描述符
maxfd = maxfd > cfd ? maxfd : cfd;
}
//遍历内核发回来的 fd_set 接收有数据的文件描述符
for(int i = lfd + 1; i <= maxfd; i++) {//注意等于号!!!
//判断fd对应的标志位是0还是1, 返回值 : fd对应的标志位的值,0,返回0, 1,返回1
if(FD_ISSET(i, &tmp)) {
// 说明这个文件描述符对应的客户端发来了数据
char buf[1024] = {0};
int len = read(i, buf, sizeof(buf));
if(len == -1) {
perror("read");
exit(-1);
} else if(len == 0) {
printf("client closed...\n");
close(i);
FD_CLR(i, &rdset);
} else if(len > 0) {
printf("read buf = %s\n", buf);
write(i, buf, strlen(buf) + 1);
}
}
}
}
}
//----------------------------------IO多路复用------------------------------//
close(lfd);
return 0;
}
if(fds[i].revents & POLLIN) {
//注意是用"&",而不用"==",因为 revents 可能 有 “|” 进行拼接多个事件,双等号不能判断。#include
#include
#include
#include
#include
#include
int main() {
// 创建socket
int lfd = socket(PF_INET, SOCK_STREAM, 0);
struct sockaddr_in saddr;
saddr.sin_port = htons(9999);
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY;
// 绑定
bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));
// 监听
listen(lfd, 8);
//----------------------IO多路复用--------------------------------------------//
// 初始化检测的文件描述符数组
struct pollfd fds[1024];
for(int i = 0; i < 1024; i++) {
fds[i].fd = -1;
fds[i].events = POLLIN;
}
fds[0].fd = lfd;
int nfds = 0;
while(1) {
// 调用poll系统函数,让内核帮检测哪些文件描述符有数据
int ret = poll(fds, nfds + 1, -1);//-1表示阻塞(当有客户端接入进来才不阻塞)
if(ret == -1) {
perror("poll");
exit(-1);
} else if(ret == 0) {
continue;
} else if(ret > 0) {
// 说明检测到了有文件描述符的对应的缓冲区的数据发生了改变
if(fds[0].revents & POLLIN) {//注意是用"&",而不用"==",因为 revents 可能 有 "|" 进行拼接多个事件,双等号不能判断。
// 表示有新的客户端连接进来了
struct sockaddr_in cliaddr;
int len = sizeof(cliaddr);
int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len);
// 将新的文件描述符加入到集合中
for(int i = 1; i < 1024; i++) {
if(fds[i].fd == -1) {
fds[i].fd = cfd;
fds[i].events = POLLIN;
break;
}
}
// 更新最大的文件描述符的索引
nfds = nfds > cfd ? nfds : cfd;
}
for(int i = 1; i <= nfds; i++) {
if(fds[i].revents & POLLIN) {//注意是用"&",而不用"==",因为 revents 可能 有 "|" 进行拼接多个事件,双等号不能判断。
// 说明这个文件描述符对应的客户端发来了数据
char buf[1024] = {0};
int len = read(fds[i].fd, buf, sizeof(buf));
if(len == -1) {
perror("read");
exit(-1);
} else if(len == 0) {
printf("client closed...\n");
close(fds[i].fd);
fds[i].fd = -1;
} else if(len > 0) {
printf("read buf = %s\n", buf);
write(fds[i].fd, buf, strlen(buf) + 1);
}
}
}
}
}
//----------------------IO多路复用--------------------------------------//
close(lfd);
return 0;
}
struct rb_root rbr 是红黑树,查找效率高 告诉内核需要监听的文件描述符
struct list_head rdlist 是双向链表 用于记录 文件描述符变化
相较于 select 和 poll 节省了cpu算力,提高工作效率。
#include
#include
#include
#include
#include
#include
int main() {
//创建socket
int lfd = socket(AF_INET,SOCK_STREAM,0);
if(lfd == -1) {
perror("socket");
exit(-1);
}
//绑定
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(9999);
saddr.sin_addr.s_addr = INADDR_ANY;
int len = sizeof(saddr);
int ret = bind(lfd,(struct sockaddr*)&saddr,len);
if(ret == -1) {
perror("bind");
exit(-1);
}
//监听
ret = listen(lfd,8);
if(ret == -1) {
perror("listen");
exit(-1);
}
//-----------------------------------------------------IO多路复用-----------------------------------------------------------------------//
// 调用epoll_create()创建一个epoll实例
int epfd = epoll_create(100); // 参数:size : 目前没有意义了。随便写一个数,必须大于0 - 返回值: -1 : 失败 > 0 : 文件描述符,操作epoll实例的
if(epfd == -1) {
perror("epollCreat");
exit(-1);
}
struct epoll_event epev;
epev.events = EPOLLIN;
epev.data.fd = lfd;
int ret_epc = epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &epev);
if(ret_epc== -1) {
perror("epoll_ctl");
exit(-1);
}
struct epoll_event epevs[1024];//保存了发送了变化的文件描述符的信息
while(1) {
int ret_wat = epoll_wait(epfd,epevs,1024,-1);
if(ret_wat== -1) {
perror("epoll_wait");
exit(-1);
}
printf("ret_wat = %d\n", ret_wat);//输出当前正在操作的客户端数
//遍历查找有变化的文件描述符
for(int i = 0 ;i < ret_wat;i++) {
int cur_fd = epevs[i].data.fd;
if(cur_fd == lfd) {
//检测到客户端连接进来;
struct sockaddr_in cliaddr;
int len = sizeof(cliaddr);
int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len);
if(cfd== -1) {
perror("accept");
exit(-1);
}
//设置对应的客户端信息
epev.events = EPOLLIN;
epev.data.fd = cfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &epev);//将新的文件描述符添加到epev。
} else {
if(epevs[i].events & EPOLLOUT) {//不同事件不同的处理 此处略;EPOLLOUT是输出事件,服务端发送给客户端。
continue;
}
// 有数据到达,需要通信
char buf[1024] = {0};
int len = read(cur_fd, buf, sizeof(buf));
if(len == -1) {
perror("read");
exit(-1);
} else if(len == 0) {
printf("client closed...\n");
//需要在内核先删除当前文件描述符 再关闭,最后一个参数可以是NULL
epoll_ctl(epfd, EPOLL_CTL_DEL, cur_fd, NULL);
close(cur_fd);
} else if(len > 0) {
printf("read buf = %s\n", buf);
write(cur_fd, buf, strlen(buf) + 1);
}
}
}
}
//-----------------------------------------------------IO多路复用-----------------------------------------------------------------------//
close(epfd);
close(lfd);
return 0;
}
#include
#include
#include
#include
#include
#include
int main() {
// 创建socket
int lfd = socket(PF_INET, SOCK_STREAM, 0);
struct sockaddr_in saddr;
saddr.sin_port = htons(9999);
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY;
// 绑定
bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));
// 监听
listen(lfd, 8);
// 调用epoll_create()创建一个epoll实例
int epfd = epoll_create(100);
// 将监听的文件描述符相关的检测信息添加到epoll实例中
struct epoll_event epev;
epev.events = EPOLLIN;
epev.data.fd = lfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &epev);
struct epoll_event epevs[1024];
while(1) {
int ret = epoll_wait(epfd, epevs, 1024, -1);
if(ret == -1) {
perror("epoll_wait");
exit(-1);
}
printf("ret = %d\n", ret);
for(int i = 0; i < ret; i++) {
int curfd = epevs[i].data.fd;
if(curfd == lfd) {
// 监听的文件描述符有数据达到,有客户端连接
struct sockaddr_in cliaddr;
int len = sizeof(cliaddr);
int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len);
epev.events = EPOLLIN;
epev.data.fd = cfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &epev);
} else {
if(epevs[i].events & EPOLLOUT) {
continue;
}
// 有数据到达,需要通信
char buf[5] = {0};
int len = read(curfd, buf, sizeof(buf));
if(len == -1) {
perror("read");
exit(-1);
} else if(len == 0) {
printf("client closed...\n");
epoll_ctl(epfd, EPOLL_CTL_DEL, curfd, NULL);
close(curfd);
} else if(len > 0) {
printf("read buf = %s\n", buf);
write(curfd, buf, strlen(buf) + 1);
}
}
}
}
close(lfd);
close(epfd);
return 0;
}
#include
#include
#include
#include
#include
#include
#include
#include
int main() {
// 创建socket
int lfd = socket(PF_INET, SOCK_STREAM, 0);
struct sockaddr_in saddr;
saddr.sin_port = htons(9999);
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY;
// 绑定
bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));
// 监听
listen(lfd, 8);
// 调用epoll_create()创建一个epoll实例
int epfd = epoll_create(100);
// 将监听的文件描述符相关的检测信息添加到epoll实例中
struct epoll_event epev;
epev.events = EPOLLIN;
epev.data.fd = lfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &epev);
struct epoll_event epevs[1024];
while(1) {
int ret = epoll_wait(epfd, epevs, 1024, -1);
if(ret == -1) {
perror("epoll_wait");
exit(-1);
}
printf("ret = %d\n", ret);
for(int i = 0; i < ret; i++) {
int curfd = epevs[i].data.fd;
if(curfd == lfd) {
// 监听的文件描述符有数据达到,有客户端连接
struct sockaddr_in cliaddr;
int len = sizeof(cliaddr);
int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len);
// 设置cfd属性非阻塞
int flag = fcntl(cfd, F_GETFL);
flag |= O_NONBLOCK;
fcntl(cfd, F_SETFL, flag);
epev.events = EPOLLIN | EPOLLET; // 设置边沿触发
epev.data.fd = cfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &epev);
} else {
if(epevs[i].events & EPOLLOUT) {
continue;
}
// 循环读取出所有数据
char buf[5];
int len = 0;
while( (len = read(curfd, buf, sizeof(buf))) > 0) {
// 打印数据
// printf("recv data : %s\n", buf);
write(STDOUT_FILENO, buf, len);
write(curfd, buf, len);
}
if(len == 0) {
printf("client closed....");
}else if(len == -1) {
if(errno == EAGAIN) {
printf("data over.....");
}else {
perror("read");
exit(-1);
}
}
}
}
}
close(lfd);
close(epfd);
return 0;
}
int fd = socket(PF_INET, SOCK_DGRAM, 0);
这里是SOCK_DGRAM
数据报格式,与tcp通信不同!#include
#include
#include
#include
#include
int main() {
// 1.创建一个通信的socket(这里是SOCK_DGRAM数据报格式!!!)
int fd = socket(PF_INET, SOCK_DGRAM, 0);
if(fd == -1) {
perror("socket");
exit(-1);
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(9999);
addr.sin_addr.s_addr = INADDR_ANY;
// 2.绑定
int ret = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
if(ret == -1) {
perror("bind");
exit(-1);
}
// 3.通信
while(1) {
char recvbuf[128];
char ipbuf[16];
struct sockaddr_in cliaddr;
int len = sizeof(cliaddr);
// 接收数据
int num = recvfrom(fd, recvbuf, sizeof(recvbuf), 0, (struct sockaddr *)&cliaddr, &len);
printf("client IP : %s, Port : %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ipbuf, sizeof(ipbuf)),
ntohs(cliaddr.sin_port));
printf("client say : %s\n", recvbuf);
// 发送数据
sendto(fd, recvbuf, strlen(recvbuf) + 1, 0, (struct sockaddr *)&cliaddr, sizeof(cliaddr));
}
close(fd);
return 0;
}
int fd = socket(PF_INET, SOCK_DGRAM, 0);
这里是SOCK_DGRAM
数据报格式,与tcp通信不同!#include
#include
#include
#include
#include
int main() {
// 1.创建一个通信的socket
int fd = socket(PF_INET, SOCK_DGRAM, 0);
if(fd == -1) {
perror("socket");
exit(-1);
}
// 服务器的地址信息
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(9999);
inet_pton(AF_INET, "127.0.0.1", &saddr.sin_addr.s_addr);
int num = 0;
// 3.通信
while(1) {
// 发送数据
char sendBuf[128];
sprintf(sendBuf, "hello , i am client %d \n", num++);
sendto(fd, sendBuf, strlen(sendBuf) + 1, 0, (struct sockaddr *)&saddr, sizeof(saddr));
// 接收数据
int num = recvfrom(fd, sendBuf, sizeof(sendBuf), 0, NULL, NULL);
printf("server say : %s\n", sendBuf);
sleep(1);
}
close(fd);
return 0;
}
int fd = socket(PF_INET, SOCK_DGRAM, 0);
这里是SOCK_DGRAM
数据报格式。没有设置监听 、绑定#include
#include
#include
#include
#include
int main() {
//创建socket 注意参数
int lfd = socket(PF_INET,SOCK_DGRAM,0);
if(lfd == -1) {
perror("socket");
exit(-1);
}
//设置广播属性
int op;
setsockopt(lfd,SOL_SOCKET,SO_BROADCAST,&op,sizeof(op));
//创建一个广播地址(不用绑定ip)
struct sockaddr_in addr;
//x.x.x.255为广播地址
inet_pton(AF_INET,"172.26.4.255",&addr.sin_addr.s_addr);
addr.sin_family = AF_INET;
addr.sin_port = htons(9999);
int num = 0;
while (1) {
//发送数据
char sendbuf[128];
sprintf(sendbuf,"Hello!Client..%d\n",num++);
sendto(lfd,sendbuf,strlen(sendbuf) + 1,0,(struct sockaddr*)&addr,sizeof(addr));
printf("广播数据:%s", sendbuf);
sleep(1);
}
close(lfd);
return 0;
}
int fd = socket(PF_INET, SOCK_DGRAM, 0);
这里是SOCK_DGRAM
数据报格式。#include
#include
#include
#include
#include
int main() {
//创建socket 注意参数
int lfd = socket(PF_INET,SOCK_DGRAM,0);
if(lfd == -1) {
perror("socket");
exit(-1);
}
//绑定广播地址
struct sockaddr_in addr;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_family = AF_INET;
addr.sin_port = htons(9999);
int ret = bind(lfd,(struct sockaddr*)&addr,sizeof(addr));
if(ret == -1) {
perror("bind");
exit(-1);
}
while (1) {
//接收广播数据
char revbuf[128];
recvfrom(lfd,revbuf,sizeof(revbuf),0,NULL,NULL);
printf("server send:%s",revbuf);
}
close(lfd);
return 0;
}
#include
#include
#include
#include
#include
int main() {
// 1.创建一个通信的socket
int fd = socket(PF_INET, SOCK_DGRAM, 0);
if(fd == -1) {
perror("socket");
exit(-1);
}
// 2.设置多播的属性,设置外出接口
struct in_addr imr_multiaddr;
// 初始化多播地址
inet_pton(AF_INET, "239.0.0.10", &imr_multiaddr.s_addr);
setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &imr_multiaddr, sizeof(imr_multiaddr));
// 3.初始化客户端的地址信息
struct sockaddr_in cliaddr;
cliaddr.sin_family = AF_INET;
cliaddr.sin_port = htons(9999);
inet_pton(AF_INET, "239.0.0.10", &cliaddr.sin_addr.s_addr);
// 3.通信
int num = 0;
while(1) {
char sendBuf[128];
sprintf(sendBuf, "hello, client....%d\n", num++);
// 发送数据
sendto(fd, sendBuf, strlen(sendBuf) + 1, 0, (struct sockaddr *)&cliaddr, sizeof(cliaddr));
printf("组播的数据:%s\n", sendBuf);
sleep(1);
}
close(fd);
return 0;
}
#include
#include
#include
#include
#include
int main() {
// 1.创建一个通信的socket
int fd = socket(PF_INET, SOCK_DGRAM, 0);
if(fd == -1) {
perror("socket");
exit(-1);
}
struct in_addr in;
// 2.客户端绑定本地的IP和端口
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(9999);
addr.sin_addr.s_addr = INADDR_ANY;
int ret = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
if(ret == -1) {
perror("bind");
exit(-1);
}
//设置多播地址属性
struct ip_mreq op;
inet_pton(AF_INET, "239.0.0.10", &op.imr_multiaddr.s_addr);
op.imr_interface.s_addr = INADDR_ANY;
// 加入到多播组
setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &op, sizeof(op));
// 3.通信
while(1) {
char buf[128];
// 接收数据
int num = recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL);
printf("server say : %s\n", buf);
}
close(fd);
return 0;
}
AF_LOCAL 、 SOCK_STREAM
strcpy(addr.sun_path, "server.sock")
。因为数组名是指针常量,是不能被修改的。#include
#include
#include
#include
#include
#include
int main() {
unlink("server.sock");
// 1.创建监听的套接字
int lfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if(lfd == -1) {
perror("socket");
exit(-1);
}
// 2.绑定本地套接字文件
struct sockaddr_un addr;
addr.sun_family = AF_LOCAL;
strcpy(addr.sun_path, "server.sock");
int ret = bind(lfd, (struct sockaddr *)&addr, sizeof(addr));
if(ret == -1) {
perror("bind");
exit(-1);
}
// 3.监听
ret = listen(lfd, 100);
if(ret == -1) {
perror("listen");
exit(-1);
}
// 4.等待客户端连接
struct sockaddr_un cliaddr;
int len = sizeof(cliaddr);
int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len);
if(cfd == -1) {
perror("accept");
exit(-1);
}
printf("client socket filename: %s\n", cliaddr.sun_path);
// 5.通信
while(1) {
char buf[128];
int len = recv(cfd, buf, sizeof(buf), 0);
if(len == -1) {
perror("recv");
exit(-1);
} else if(len == 0) {
printf("client closed....\n");
break;
} else if(len > 0) {
printf("client say : %s\n", buf);
send(cfd, buf, len, 0);
}
}
close(cfd);
close(lfd);
return 0;
}
#include
#include
#include
#include
#include
#include
int main() {
unlink("client.sock");
// 1.创建套接字
int cfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if(cfd == -1) {
perror("socket");
exit(-1);
}
// 2.绑定本地套接字文件
struct sockaddr_un addr;
addr.sun_family = AF_LOCAL;
strcpy(addr.sun_path, "client.sock");
int ret = bind(cfd, (struct sockaddr *)&addr, sizeof(addr));
if(ret == -1) {
perror("bind");
exit(-1);
}
// 3.连接服务器
struct sockaddr_un seraddr;
seraddr.sun_family = AF_LOCAL;
strcpy(seraddr.sun_path, "server.sock");
ret = connect(cfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
if(ret == -1) {
perror("connect");
exit(-1);
}
// 4.通信
int num = 0;
while(1) {
// 发送数据
char buf[128];
sprintf(buf, "hello, i am client %d\n", num++);
send(cfd, buf, strlen(buf) + 1, 0);
printf("client say : %s\n", buf);
// 接收数据
int len = recv(cfd, buf, sizeof(buf), 0);
if(len == -1) {
perror("recv");
exit(-1);
} else if(len == 0) {
printf("server closed....\n");
break;
} else if(len > 0) {
printf("server say : %s\n", buf);
}
sleep(1);
}
close(cfd);
return 0;
}
典型的一次IO的两个阶段是什么?数据就绪 和 数据读写
数据就绪 :根据系统IO操作的就绪状态,分为阻塞、非阻塞。如(read函数调用,非阻塞状态要通过返回值去判断)
数据读写:根据应用程序和内核交互的方式,分为同步、异步 。(异步api:aio_read(), aio_write())
如何区分同步和异步?
同步:表示A向B请求调用一个网络IO接口时(或者调用某个业务逻辑API接口时),数据的读写都是
由请求方A自己来完成的(不管是阻塞还是非阻塞);
异步:表示A向B请求调用一个网络IO接口时(或者调用某个业务逻辑API接口时),向B传入请求的事件以及事件发生时通知的方式,A就可以处理其它逻辑了,当B监听到事件处理完成后,会用事先约定好的通知方式,通知A处理结果。
阻塞、非阻塞模型、IO复用、信号驱动、异步IO模型
调用者调用了某个函数,等待这个函数返回,期间什么也不做,不停的去检查这个函数有没有返回,必
须等这个函数返回才能进行下一步动作。
非阻塞等待,每隔一段时间就去检测IO事件是否就绪。没有就绪就可以做其他事。非阻塞I/O执行系统调
用总是立即返回,不管事件是否已经发生,若事件没有发生,则返回-1,此时可以根据 errno 区分这两种情况,对于accept,recv 和 send,事件未发生时,errno 通常被设置成 EAGAIN。
Linux 用 select/poll/epoll 函数实现 IO 复用模型,这些函数也会使进程阻塞,但是和阻塞IO所不同的是
这些函数可以同时阻塞多个IO操作。而且可以同时对多个读操作、写操作的IO函数进行检测。直到有数
据可读或可写时,才真正调用IO操作函数。
Linux 用套接口进行信号驱动 IO,安装一个信号处理函数,进程继续运行并不阻塞,当IO事件就绪,进
程收到SIGIO 信号,然后处理 IO 事件。
内核在第一个阶段是异步,在第二个阶段是同步;与非阻塞IO的区别在于它提供了消息通知机制,不需
要用户进程不断的轮询检查,减少了系统API的调用次数,提高了效率。
Linux中,可以调用 aio_read 函数告诉内核描述字缓冲区指针和缓冲区的大小、文件偏移及通知的方
式,然后立即返回,当内核将数据拷贝到缓冲区后,再通知应用程序。