赞
踩
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地址。
网络数和单个网段数表格
单个网段最大主机数减去2是因为(第一,IP地址中的全0是个保留地址,意思是“本网络”。第二,网络号为127(即01111111)保留作为本地软件环回测试本主机的进程之间的通信之用。)
子网掩码subnet mask又叫网络掩码、地址掩码、子网络遮罩,它是一种用来指明一个 IP 地
址的哪些位标识的是主机所在的子网,以及哪些位标识的是主机的位掩码。不能单独存在。
注:在传输数据时都是转成大端字节序,若接收数据的主机是小端字节序则会通过API转换为小端字节序。
#include <arpa/inet.h>
// 转换端口
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 <bits/socket.h>
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 <arpa/inet.h>
p:点分十进制的IP字符串,n:表示network,网络字节序的整数
int inet_pton(int af, const char *src, void *dst);
#include <arpa/inet.h>
将网络字节序的整数,转换成点分十进制的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<stdio.h> //字节序 #include<arpa/inet.h> //socket通信 #include <sys/types.h> #include <sys/socket.h> //exit #include<unistd.h> #include<stdlib.h> #include<string.h> //信号捕捉,子进程回收 #include<errno.h> #include <signal.h> #include <sys/wait.h> 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<stdio.h> #include<arpa/inet.h> #include <sys/types.h> #include <sys/socket.h> #include<unistd.h> #include<stdlib.h> #include<string.h> 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<stdio.h> //字节序 #include<arpa/inet.h> //socket通信 #include <sys/types.h> #include <sys/socket.h> //exit #include<unistd.h> #include<stdlib.h> #include<string.h> #include<errno.h> #include<pthread.h> //创建结构体的原因: 线程处理需要获取多个参数,那么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 <sys/types.h>
#include <sys/socket.h>
// 设置套接字的属性(不仅仅能设置端口复用)
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
参数:
//形式参数省略
setsockopt();
bind();
fd_set 是一个文件描述符表 ,有1024位 。前0 1 2三位默认占用。
重点看:IO多路复用部分
#include <stdio.h> #include <arpa/inet.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <sys/select.h> 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 <stdio.h> #include <arpa/inet.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <poll.h> 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算力,提高工作效率。
epoll_data_t data
#include <stdio.h> #include <arpa/inet.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <sys/epoll.h> 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 <stdio.h> #include <arpa/inet.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <sys/epoll.h> 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 <stdio.h> #include <arpa/inet.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <sys/epoll.h> #include <fcntl.h> #include <errno.h> 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 <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> 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 <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> 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 <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> 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 <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> 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 <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> 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 <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> 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 <stdio.h> #include <string.h> #include <unistd.h> #include <stdlib.h> #include <arpa/inet.h> #include <sys/un.h> 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 <stdio.h> #include <string.h> #include <unistd.h> #include <stdlib.h> #include <arpa/inet.h> #include <sys/un.h> 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 函数告诉内核描述字缓冲区指针和缓冲区的大小、文件偏移及通知的方
式,然后立即返回,当内核将数据拷贝到缓冲区后,再通知应用程序。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。