UDP通信实现
通信前无三次握手建立连接不需要创建监听套接字直接创建通信的套接字。
send/recv用于tcp sendto/recvfrom用于udp
#include <sys/types.h>#include <sys/socket.h>ssize_t sendto(int sockfd,const void*buf,size_t len,int flags,const struct sockaddr* dest_addr,socklen_t* addrlen);//sockfd UDP通信的fd//buf 要发送的数据//len 要发送的数据长度//flags 一些配置去看man 可以直接给0//dest_addr 目的地址 sockaddr类型填端口号+IP地址//addrlen dest_addr的大小(和recvfrom不一样 那个是要求指针)//成功返回发送的字节数量失败返回-1ssize_t recvfrom(int sockfd,const void*buf,size_t len,int flags,struct sockaddr* src_addr,socklen_t *addrlen);//sockfd UDP通信的fd//buf 接收数据的buffer//len 接收buffer的大小//flags 一些配置去看man 可以直接给0//src_addr 从哪接收 sockaddr类型 是传出参数 可以通过这个参数看到是哪个客户端给我发消息//addrlen src_addr大小的指针 src_addr为NULL 这个长度也要为NULL//成功返回接收的字节数量失败返回-1
#include <unistd.h>#include <arpa/inet.h>#include <string.h>#include <sys/types.h>int main(){//1创建UDP socketint cfd = socket(PF_INET, SOCK_DGRAM, 0); //udp SOCK_DGRAM tcp SOCK_STREAM//2 绑定struct sockaddr_in saddr;saddr.sin_port = htons(9999);saddr.sin_family = AF_INET;saddr.sin_addr.s_addr = INADDR_ANY; //服务器要绑定的地址 服务器任一网卡的ipint ret = bind(cfd, (struct sockaddr *)&saddr, sizeof(saddr));if (ret == -1){perror("bind:");exit(-1);}//3 通信while (1){char recvbuf[128];struct sockaddr_in client_addr;int len = sizeof(client_addr);int recv_num = recvfrom(cfd, recvbuf, sizeof(recvbuf), 0, (struct sockaddr *)&client_addr, &len); //client_addr是传出参数 将传出是从哪个客户端收到数据if (recv_num == -1) //出错{perror("recvfrom:");exit(-1);}else{char ipbuf[16]; //传出参数 将转换后的字符串传出 inet_ntop的返回值也是这个传出参数printf("server recv from ip:%s port:%d %d bytes\n",inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ipbuf, sizeof(ipbuf)),ntohs(client_addr.sin_port),recv_num);printf("server recv data:%s", recvbuf);//发送数据sendto(cfd, recvbuf, strlen(recvbuf) + 1, 0, (struct sockaddr *)&client_addr, sizeof(client_addr)); //回显 将收到的数据原封不动发回去}}close(cfd);return 0;}
广播
向子网(同一局域网)中多台计算机发送消息,并且子网中所有的计算机都可以接收到对方发送的消息,之前有讲过IP 为网络IP段+主机IP段 当主机IP段全为1则是此子网内的广播IP。
完成广播服务器和客户端需要规定统一的广播端口
服务器向机IP段全为1的IP地址 的 广播端口发数据
注意客户端需要绑定广播端口才能接收到广播数据
服务器设置广播属性客户端绑定广播端口
服务器设置广播属性 int setsockopt(int sockfd,int level,int optname,const void* optval,socklen_t optlen);
sockfd 套接字文件描述符
level 指定系统中解释选项的代码或为通用套接字代码,或为某个特定于协议的代码(如IPV4,IPV6,TCP) 可选参数 SOL_SOCKET IPPROTO_IP IPPROTO_IPV6 IPPROTO_TCP…
lecvel和optname是一起使用的 在某个level下某个optname有…的设置作用。参数太多看具体的可以看上面讲的那本书,下面这张图只是其中一部分
设置广播 level :SOL_SOCKET optname:SO_BROADCAST
optval: int 1允许广播 0不允许 传输指针
optlen: int型的大小
服务器
//1创建UDP socketint cfd = socket(PF_INET, SOCK_DGRAM, 0); //udp SOCK_DGRAM tcp SOCK_STREAM//2 绑定通信int opv = 1;setsockopt(cfd, SOL_SOCKET, SO_BROADCAST, &opv, sizeof(opv)); //设置广播属性//绑定用于和其他客户端通信的IP和端口struct sockaddr_in saddr;saddr.sin_port = htons(9999);saddr.sin_family = AF_INET;saddr.sin_addr.s_addr = INADDR_ANY; //服务器要绑定的地址 服务器任一网卡的ipint ret = bind(cfd, (struct sockaddr *)&saddr, sizeof(saddr));if (ret == -1){perror("bind:");exit(-1);}//创建广播地址struct sockaddr_in bro_addr;bro_addr.sin_port = htons(8989); //客户端 需要绑定到8989端口才能接收到广播信息bro_addr.sin_family = AF_INET;//ip地址需要是一个广播地址ip 中主机字段全为1inet_pton(AF_INET, "192.168.91.255", &bro_addr.sin_addr.s_addr);//广播不需要绑定 因为在广播中服务器是发端 就是向这个已知规定的广播地址发数据
客户端
int cfd = socket(PF_INET, SOCK_DGRAM, 0); //udp SOCK_DGRAM tcp SOCK_STREAM//客户端向服务器发数据 下面是服务器的IP 以及 端口struct sockaddr_in saddr;saddr.sin_port = htons(9999);saddr.sin_family = AF_INET;// saddr.sin_addr.s_addr = ; //服务器的地址inet_pton(AF_INET, "127.0.0.1", &saddr.sin_addr.s_addr); //服务器的地址直接传出到s_addr中int len = sizeof(saddr);int num = 0;//客户端绑定IP以及广播端口 用于接收服务器的广播信息struct sockaddr_in bro_addr;bro_addr.sin_port = htons(8989);bro_addr.sin_family = AF_INET;bro_addr.sin_addr.s_addr = INADDR_ANY; //客户端绑定任一网卡的ipint ret = bind(cfd, (struct sockaddr *)&bro_addr, sizeof(bro_addr));if (ret == -1){perror("bind:");exit(-1);}
组播(多播)
服务器会专门向组播地址去发送信息(类比广播时向全1主机IP的地址去发送广播信息),客户端需要加入多播组(监听组播地址发来的信息)才能接受到服务器的多播信息,多播即可用于局域网,也可跨广域网使用。
组播地址
224.0.0.0-239.255.255.255被划分为局部链接多播地址、预留多播地址和管理权限多播地址三类
224.0.0.0-224.0.0.255 局部链接多播地址:路由器不会转发属于此范围的IP(即在这范围内的多播地址 只能在局域网内使用)
224.0.1.0-224.0.1.255 预留多播地址—公用多播地址 可用于广域网(INTERNET),使用前需要申请
224.0.2.0-238.255.255.255 预留多播地址—用户可用多播地址(临时组地址)全网范围内有效
239.0.0.0-239.255.255.255 本地管理多播地址,供组织内部使用,类似于私有IP地址,不能用于INTERNET,限制在私有的多个局域网内使用。
服务端与客户端通信的fd设置多播属性,客户端需开一个端口加入多播组 用于接收客户端的多播信息。
服务端与客户端通信的fd设置多播属性,客户端需开一个端口加入多播组 用于接收客户端的多播信息。
服务器端还是要通过setsockopt设置多播属性 level:IPPROTO_IP optname:IP_MULTICAST_IF指定外出接口 val类型为in_addr
客户端通过setsockopt设置加入多播组 level:IPPROTO_IP optname:IP_ADD_MEMBERSHIP加入多播组 val类型为ip_mreq
struct ip_mreq{//组播组的IP地址struct in_addr imr_multiaddr;//本地某一网络设备接口的IP地址struct in_addr imr_interface;};typedef unint32_t in_addr_t;struct in_addr{in_addr_t s_addr;};
服务器
//1创建UDP socketint cfd = socket(PF_INET, SOCK_DGRAM, 0); //udp SOCK_DGRAM tcp SOCK_STREAM//2 绑定通信struct in_addr opv;//239.0.0.0-239.255.255.255本地管理多播地址,供组织内部使用inet_pton(AF_INET, "239.0.0.10", &opv.s_addr); //设置多播地址setsockopt(cfd, IPPROTO_IP, IP_MULTICAST_IF, &opv, sizeof(opv)); //设置多播属性//绑定用于和其他客户端通信的IP和端口struct sockaddr_in saddr;saddr.sin_port = htons(9999);saddr.sin_family = AF_INET;saddr.sin_addr.s_addr = INADDR_ANY; //服务器要绑定的地址 服务器任一网卡的ipint ret = bind(cfd, (struct sockaddr *)&saddr, sizeof(saddr));if (ret == -1){perror("bind:");exit(-1);}//创建多播地址struct sockaddr_in multi_addr;multi_addr.sin_port = htons(8989); //客户端 需要绑定到8989端口才能接收到组播信息multi_addr.sin_family = AF_INET;inet_pton(AF_INET, "239.0.0.10", &multi_addr.sin_addr.s_addr);//组播不需要绑定 因为在组播服务器是发端 就是向这个已知规定的组播地址发数据
客户端
//1创建UDP socketint cfd = socket(PF_INET, SOCK_DGRAM, 0); //udp SOCK_DGRAM tcp SOCK_STREAM//客户端向服务器发数据 下面是服务器的IP 以及 端口struct sockaddr_in saddr;saddr.sin_port = htons(9999);saddr.sin_family = AF_INET;// saddr.sin_addr.s_addr = ; //服务器的地址inet_pton(AF_INET, "127.0.0.1", &saddr.sin_addr.s_addr); //服务器的地址直接传出到s_addr中int len = sizeof(saddr);int num = 0;//客户端绑定IP以及组播端口 用于接收服务器的组播信息struct sockaddr_in multi_addr;multi_addr.sin_port = htons(8989);multi_addr.sin_family = AF_INET;multi_addr.sin_addr.s_addr = INADDR_ANY; //客户端绑定任一网卡的ipint ret = bind(cfd, (struct sockaddr *)&multi_addr, sizeof(multi_addr));if (ret == -1){perror("bind:");exit(-1);}//将此cfd加入多播组中struct ip_mreq opv;// //组播组的IP地址// struct in_addr imr_multiaddr;// //本地某一网络设备接口的IP地址// struct in_addr imr_interface;//239.0.0.10为服务器端规定的多播地址inet_pton(AF_INET, "239.0.0.10", &opv.imr_multiaddr.s_addr); //设置组播地址opv.imr_interface.s_addr = INADDR_ANY; //设置本地地址 为任一网卡IPsetsockopt(cfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &opv, sizeof(opv)); //设置多播属性
socket本地通信
本地套接字用于进程间(本地的进程间)的通信,本地套接字实现流程和网络套接字通信流程类似(也可看为是服务器端和客户端) 类TCP通信。
上图中的strcut sockaddr_un用于本地套接字通信
#include <sys/un.h>#define UNIX_PATH_MAX 108strcut sockaddr_un{sa_family_t sun_family;//地址协议族 af_localchar sun_path[UNIX_PATH_MAX];//套接字文件的路径 注意这是个伪文件 大小永远为0//其对应的是内存中的一块缓冲区 我们可以通过操作这个文件去操作内核缓冲区};
服务器端
1 服务器端创建监听套接字
int lfd = socket(AF_LOCAL,SOCK_STREAM,0);//AF_LOCAL or AF_UNIX本地通信协议族
2 绑定监听套接字 到 本地套接字文件
struct sockaddr_un addr;
bind(lfd,addr,len);
//绑定成功后 指定的 sun_path套接字文件会自动生成
3 监听
listen(lfd,100);
4 等待客户端连接请求
struct sockaddr_un client_addr;//用于接收本地的客户端地址 accept传出
int cfd = accept(lfd,&client_addr,len);
5 通信
read/recv write/send
客户端
1 客户器端创建通信套接字
int cfd = socket(AF_LOCAL,SOCK_STREAM,0);//AF_LOCAL or AF_UNIX本地通信协议族
2 绑定套接字 到 本地套接字文件(和服务器端的那个文件不是同一个)
struct sockaddr_un addr;
bind(lfd,addr,len);
//绑定成功后 指定的 sun_path套接字文件会自动生成
4 连接服务器
struct sockaddr_un server_addr;
//…输入服务器地址和通信端口
connect(fd,&server_addr);
5 通信
read/recv write/send
服务器端
#include <stdio.h>#include <string.h>#include <unistd.h>#include <stdlib.h>#include <arpa/inet.h>#include <sys/un.h>#include <fcntl.h> //unlink的一些宏//unlink 删除指定名字的文件//当我们生成文件并已经通信成功 第二次启动程序如果不将之前定义的用于通信的socket文件删掉//在bind时会报错address already in use(之前网络通信刚断开1分钟以内再此连接也会报这个错//是因为tcp四次挥手的最后的TIME_WAIT状态还没过完 所以需要再多等)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;// sa_family_t sun_family; //地址协议族 af_local// char sun_path[UNIX_PATH_MAX]; //套接字文件的路径 注意这是个伪文件 大小永远为0// //其对应的是内存中的一块缓冲区 我们可以通过操作这个文件去操作内核缓冲区addr.sun_family = AF_LOCAL;strcpy(addr.sun_path, "server.sock"); //在本.c路径下生成一个本地socket 文件bind(lfd, (struct sockaddr *)&addr, sizeof(addr));//3监听listen(lfd, 8);//4等待客户端连接struct sockaddr_un client_addr;int len = sizeof(client_addr);int cfd = accept(lfd, (struct sockaddr *)&client_addr, &len); //阻塞等待 client_addr为传出参数 传出连接的客户端信息if (cfd == -1){perror("accept:");exit(-1);}printf("client socket filename:%s\n", client_addr.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");close(cfd);break;}else if (len > 0){printf("client say:%s\n", buf);send(cfd, buf, len, 0);}}close(lfd);return 0;}
客户端
#include <stdio.h>#include <string.h>#include <unistd.h>#include <stdlib.h>#include <arpa/inet.h>#include <sys/un.h>#include <fcntl.h> //unlink的一些宏//unlink 删除指定名字的文件//当我们生成文件并已经通信成功 第二次启动程序如果不将之前定义的用于通信的socket文件删掉//在bind时会报错address already in use(之前网络通信刚断开1分钟以内再此连接也会报这个错//是因为tcp四次挥手的最后的TIME_WAIT状态还没过完 所以需要再多等)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;// sa_family_t sun_family; //地址协议族 af_local// char sun_path[UNIX_PATH_MAX]; //套接字文件的路径 注意这是个伪文件 大小永远为0// //其对应的是内存中的一块缓冲区 我们可以通过操作这个文件去操作内核缓冲区addr.sun_family = AF_LOCAL;strcpy(addr.sun_path, "client.sock"); //在本.c路径下生成一个本地socket 文件bind(cfd, (struct sockaddr *)&addr, sizeof(addr));//3主动连接服务器struct sockaddr_un sever_addr;addr.sun_family = AF_LOCAL;strcpy(addr.sun_path, "server.sock"); //指定服务器的文件 这样客户端才可以向这个文件内写内容//(等同对服务器进程的内核区读缓冲区内写数据)int ret = connect(cfd, (struct sockaddr *)&sever_addr, sizeof(sever_addr));if (ret == -1){perror("connect:");exit(-1);}//通信int num = 0;while (1){char buf[128];sprintf(buf, "hello im client:%d\n", num++);send(cfd, buf, strlen(buf) + 1, 0);//接收数据int len = recv(cfd, buf, sizeof(buf), 0);if (len == -1){perror("recv:");exit(-1);}else if (len == 0){printf("sever closed\n");close(cfd);break;}else if (len > 0){printf("sever say:%s\n", buf);}sleep(1);}return 0;}
