udp通信(收发短信)
创建socket
绑定地址
通信
关闭socket
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
//Udp通信 被动接收 服务器#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h> /*memset*/
#include <unistd.h> /*close*/#define IP "127.0.0.1" //本机测试地址
#define CPORT 10010 //客户端端口
#define SPORT 10086 //服务器端口#define SIZE 100int main()
{//1.设置变量int sockfd = 0;struct sockaddr_in addr;int addrlen = 0;int newID = 0;char buf[SIZE] = {'\0'};int ret = 0;//2.创建socket套接字//参数1:地址族--->使用网络协议族//参数2:套接字类型---->使用数据报套接字//参数3:通常设置为0sockfd = socket(PF_INET, SOCK_DGRAM, 0);if(0 > sockfd){perror("socket error");return -1;}printf("socket ok\n");//3.绑定地址(自己)ip+port端口addrlen = sizeof(addr);memset(&addr,0,addrlen);addr.sin_family = PF_INET; //地址族--->使用网络协议族addr.sin_port = htons(SPORT); //htons(主机字节序转换网络字节序函数),short短整型---->设置端口addr.sin_addr.s_addr = inet_addr(IP); //inet_addr(IP地址的转换),转换成32位的网络字节序二进制值if(0 > bind(sockfd,(struct sockaddr *)&addr,addrlen)){perror("bind error");close(sockfd); //关闭套接字return -1;}printf("bind ok\n");//4.1通信----->接收消息//参数3:通常设置为0memset(&addr,0,addrlen);memset(buf,0,SIZE);ret = recvfrom(sockfd,buf,SIZE-1,0,(struct sockaddr *)&addr,&addrlen);if(ret > 0){//inet_ntoa---->将32位的网络字节序二进制地址转换成点分十进制的字符串printf("ip=%s,port=%d\n",(char *)inet_ntoa(addr.sin_addr),ntohs(addr.sin_port));printf("said:%s\n",buf);}else{printf("recvfrom return %d\n",ret);}//4.2通信----->发送消息memset(buf,0,SIZE);strncpy(buf,"真好!!!",SIZE-1);ret = sendto(sockfd,buf, strlen(buf),0,(struct sockaddr *)&addr,addrlen);if(0 < ret){printf("sendto ok\n");}//5.关闭socketclose(sockfd);return 0;
}
//Udp通信 主动发送 客户端#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h> /*memset*/
#include <unistd.h> /*close*/#define IP "127.0.0.1" //本机测试地址
#define CPORT 10010 //客户端端口
#define SPORT 10086 //服务器端口#define SIZE 100int main()
{//1.设置变量int sockfd = 0;struct sockaddr_in addr;int addrlen = 0;int newID = 0;char buf[SIZE] = {'\0'};int ret = 0;//2.创建socket套接字//参数1:地址族--->使用网络协议族//参数2:套接字类型---->使用数据报套接字//参数3:通常设置为0sockfd = socket(PF_INET, SOCK_DGRAM,0);if(0 > sockfd){perror("socket error");return -1;}printf("socket ok\n");//3.绑定地址(自己)ip+port端口(可以忽略,默认系统设置)addrlen = sizeof(addr);memset(&addr,0,addrlen);addr.sin_family = PF_INET; //地址族--->使用网络协议族addr.sin_port = htons(CPORT); //htons(主机字节序转换网络字节序函数),short短整型---->设置端口addr.sin_addr.s_addr = inet_addr(IP); //inet_addr(IP地址的转换),转换成32位的网络字节序二进制值if(0 > bind(sockfd,(struct sockaddr *)&addr,addrlen)){perror("bind error");close(sockfd); //关闭套接字return -1;}printf("bind ok\n");//4.1通信----->发送消息 //设置服务器的地址memset(&addr,0,addrlen);addr.sin_family = PF_INET; //地址族--->使用网络协议族addr.sin_port = htons(SPORT); //htons(主机字节序转换网络字节序函数),short短整型---->设置端口addr.sin_addr.s_addr = inet_addr(IP); //inet_addr(IP地址的转换),转换成32位的网络字节序二//输入准备发送的消息 strncpy(buf,"下课了!!!",SIZE-1);ret = sendto(sockfd,buf,strlen(buf),0,(struct sockaddr *)&addr,addrlen);if(ret > 0){printf("sendto ok\n");}//4.2通信----->接收消息 //参数3:通常设置为0memset(&addr,0,addrlen);memset(buf,0,SIZE);ret = recvfrom(sockfd,buf,SIZE-1,0,(struct sockaddr *)&addr,&addrlen);if(0 < ret) {//inet_ntoa---->将32位的网络字节序二进制地址转换成点分十进制的字符串printf("ip=%s,port=%d\n",(char *)inet_ntoa(addr.sin_addr),ntohs(addr.sin_port));printf("said:%s\n",buf);}else{printf("recvfrom return:%d\n",ret);}//5.关闭socketclose(sockfd);return 0;
}
IO模型 多路复用
阻塞模型
没有数据,读阻塞-->当前进程会被移到阻塞队列 --> 不占用cpu
非阻塞模型
没有数据,立即返回-->当前进程需要循环读数据 --> 占用cpu
int fcntl( int 文件描述符, int 命令, int agr );
命令:F_GETFL,F_SETFL
信号驱动
文件状态改变,内核发送信号给应用程序,应用程序收到信号后,处理信号。异步的方式。
多路复用
一个进程中有多个阻塞IO的情况。
在tcp循环服务器中,每接受一个客户端的连接,就会产生一个已连接的套接字。
这个已连接的套接字就是一个阻塞描述符。
select原理:
1. 创建一张存放描述符的表
2. 将所有准备监听的阻塞描述符放在表中
3. 调用select
4. //将第一步的表复制一份给内核,内核轮询表中的描述符,看哪些可以进行IO操作。
5. //如果所有描述符都不能进行IO操作,内核会阻塞select.
6. //如果有描述符能进行IO操作,内核会修改表,保留可以进行IO操作描述符,然后通过select将表返回
7. 应用程序轮询新表,查看具体哪个描述符可以进行IO操作,然后再去操作。
8. 重复2~7直到服务器关闭为止。
//TCP通信 多路复用服务器#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h> /*memset*/
#include <unistd.h> /*close*/
#include <sys/time.h>
#include <fcntl.h>#define IP "127.0.0.1" //本机测试地址
#define SPORT 10086
#define CPORT 10010#define SIZE 100int main()
{//1.设置变量int sockfd = 0;struct sockaddr_in addr;int addrlen = 0;int newID = 0;char buf[SIZE] = {'\0'};//设置select变量int maxfd = 0;fd_set readfds; fd_set tmpfds;//2.创建socket套接字//参数1:地址族--->使用网络协议族//参数2:套接字类型---->使用流式套接字//参数3:通常设置为0sockfd = socket(PF_INET, SOCK_STREAM, 0);if(0 > sockfd){perror("socket error");return -1;}printf("socket ok\n");//3.绑定地址(自己)ip+port端口addrlen = sizeof(addr);memset(&addr,0,addrlen);addr.sin_family = PF_INET; //地址族--->使用网络协议族addr.sin_port = htons(SPORT); //htons(主机字节序转换网络字节序函数),short短整型---->设置端口addr.sin_addr.s_addr = inet_addr(IP); //inet_addr(IP地址的转换),转换成32位的网络字节序二进制值if(0 > bind(sockfd,(struct sockaddr *)&addr,addrlen)){perror("bind error");close(sockfd); //关闭套接字return -1;}printf("bind ok\n");//4.建立监听----->socket变为被动,只能用于三次握手//参数2:监听队列存放三次握手还没有结束的客户端if(0 > listen(sockfd,5)){perror("listen error");close(sockfd); //关闭套接字return -1;}printf("listen ok\n");//清空存放描述符的表readfdsFD_ZERO(&readfds);//将等连接的套接字放入readfdsFD_SET(sockfd,&readfds);tmpfds = readfds;//maxfd是select所有监听描述符中的最大值maxfd = sockfd;while(1){//tmpfds保存最完整的被监听的描述符readfds = tmpfds;//调用select函数(阻塞函数),select将readfds传给内核//参数2:要读的文件描述符的集合//参数3:要写的文件描述符的集合//参数4:其他的文件描述符//参数5:超时设置,NULL表示阻塞,直到有描述符或者出错select(maxfd + 1,&readfds,NULL,NULL,NULL);int i = 0;//select返回后,应用程序需要轮询内核返回的readfdsfor(i = 0;i < maxfd + 1;i++){//判断i在不在readfds中if(FD_ISSET(i,&readfds)){//判断i是不是等连接socketif(i == sockfd){memset(&addr,0,addrlen);//5.接受连接------>若成功,返回已经连接的套接字newID=accept(sockfd, (struct sockaddr *)&addr,&addrlen);if(0 > newID){perror("accept error");close(sockfd); //关闭套接字return -1;}printf("accept ok %d\r\n", newID);printf("client ip=%s, port=%d\r\n", (char *)inet_ntoa(addr.sin_addr), ntohs(addr.sin_port) );//将newID放到tmpfdsFD_SET(newID,&tmpfds);//更新maxfdif(maxfd < newID){maxfd = newID;}}else{//6.通信----->接收 发送 使用已经连接的套接字//参数3:通常设置为0//参数2:为什么用不了strlen测buf有效长度----->因为strlen测有效字符,buf前面已经置空为0memset(buf,0,SIZE);if(0 < recv(i, buf, sizeof(buf)-1,0)){printf("服务器已收到:%s\n",buf);if(0 == strncmp(buf,"quit",4)){break; }}else{printf("nothing,客户端已经关闭\n");close(i); //关闭套接字FD_CLR(i,&tmpfds);}if ( 0 < send(newID, buf, strlen(buf),0)){printf("回答完毕\n");}}}} }//8.关闭socketclose(sockfd);return 0;
}
服务器
循环服务器
tcp循环服务器
需要将"accept+通信"放在while(1)里
优点:可以接收多个客户端的连接,并且处理通信
缺点:前一个客户端在连接成功后的通信会影响下一个客户端的连接
//TCP通信 服务器#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h> /*memset*/
#include <unistd.h> /*close*/#define IP "127.0.0.1" //本机测试地址
#define SPORT 10086
#define CPORT 10010#define SIZE 100int main()
{//1.设置变量int sockfd = 0;struct sockaddr_in addr;int addrlen = 0;int newID = 0;char buf[SIZE] = {'\0'};//2.创建socket套接字//参数1:地址族--->使用网络协议族//参数2:套接字类型---->使用流式套接字//参数3:通常设置为0sockfd = socket(PF_INET, SOCK_STREAM, 0);if(0 > sockfd){perror("socket error");return -1;}printf("socket ok\n");//3.绑定地址(自己)ip+port端口addrlen = sizeof(addr);memset(&addr,0,addrlen);addr.sin_family = PF_INET; //地址族--->使用网络协议族addr.sin_port = htons(SPORT); //htons(主机字节序转换网络字节序函数),short短整型---->设置端口addr.sin_addr.s_addr = inet_addr(IP); //inet_addr(IP地址的转换),转换成32位的网络字节序二进制值if(0 > bind(sockfd,(struct sockaddr *)&addr,addrlen)){perror("bind error");close(sockfd); //关闭套接字return -1;}printf("bind ok\n");//4.建立监听----->socket变为被动,只能用于三次握手//参数2:监听队列存放三次握手还没有结束的客户端if(0 > listen(sockfd,5)){perror("listen error");close(sockfd); //关闭套接字return -1;}printf("listen ok\n");while(1){memset(&addr,0,addrlen);//5.接受连接------>若成功,返回已经连接的套接字newID=accept(sockfd, (struct sockaddr *)&addr,&addrlen);if(0 > newID){perror("accept error");close(sockfd); //关闭套接字return -1;}printf("accept ok\n");while(1) { //6.通信----->接收 发送 使用已经连接的套接字//参数3:通常设置为0//参数2:为什么用不了strlen测buf有效长度----->因为strlen测有效字符,buf前面已经置空为0memset(buf,0,SIZE);if(0 < recv(newID, buf, sizeof(buf)-1,0)){printf("服务器已收到:%s\n",buf);if(0 == strncmp(buf,"quit",4)){break; }}else{printf("nothing\n");break;}if ( 0 < send(newID, buf, strlen(buf),0)){printf("回答完毕\n");}}//7.关闭已经连接的套接字close(newID);}//8.关闭socketclose(sockfd);return 0;
}
??//TCP通信 客户端 #include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>#define IP "127.0.0.1" //本机测试地址
#define SPORT 10086
#define CPORT 10010
#define SIZE 100
int main()
{//定义变量int sockfd = 0;struct sockaddr_in addr;int addrLength = 0;char buf[SIZE] = {"终于写完了!"};//创建socketsockfd = socket( AF_INET, SOCK_STREAM, 0);if ( sockfd < 0 ){perror("socket error");return -1;}printf("socket ok \r\n");//绑定自己的地址addrLength = sizeof(addr);/*memset(&addr, 0, addrLength );addr.sin_family = AF_INET;addr.sin_port = htons(CPORT);addr.sin_addr.s_addr = INADDR_ANY;if (0 > bind( sockfd, (struct sockaddr *)&addr, addrLength) ){perror("bind error");close(sockfd);return -1;}printf("bind ok \r\n");*/printf("操作系统默认bind ok \r\n");//设置服务器的地址,并且发送连接请求memset(&addr, 0, addrLength );addr.sin_family = AF_INET;addr.sin_port = htons(SPORT);addr.sin_addr.s_addr = inet_addr( IP );if (0 > connect( sockfd,( struct sockaddr *)&addr, addrLength )){perror("connect error");close(sockfd);return -1;}printf("connect ok \r\n");while(1){//通信fgets( buf, SIZE - 1, stdin );send( sockfd, buf, strlen(buf),0);if ( 0 == strncmp( buf, "quit", 4 ) ){break;}memset(buf, 0, SIZE);recv( sockfd, buf, SIZE - 1, 0 );printf("客户端收到:%s\r\n",buf); }//关闭套接字close( sockfd );return 0;
}
udp循环服务器
需要将"通信"放在while(1)里
优点:可以处理多个客户端的通信
缺点:前一个客户端接收消息后,处理消息和回复消息,会影响下一个客户端消息的接收。
---------------------------------------------------------------------------------------------------------------------------------
int open( char * fileName, int flags );
默认都是阻塞方式打开
open( "1.txt", O_RDONLY | O_NDELAY); //非阻塞打开
或者open( "1.txt", O_RDONLY | O_NONBLOCK); //非阻塞打开