网络编程
一、UDP广播通信
1、广播
单播:数据包发送方式只有一个接受方
广播:同时发给局域网中的所有主机
2、特点
只有用户数据报套接字(使用UDP协议)才能广播
3、广播地址
以192.168.63.0网段为例:…***.255 代表该网段的广播地址。发送给该地址的数据包被所有主机接收
比如我们当前教室的局域网段 是 192.168.63.0 ,那么广播地址就是 192.168.63.255
sendto("你好",192.168.14.255);
4、实现广播的过程(一定是使用UDP协议)
广播发送端:
1、创建数据报套接字 UDP
int socketfd = socket(AF_INET,SOCK_DGRAM,0);
2、设置socketfd套接字文件描述符的属性为 广播 。(也就是允许发送广播数据包)
SO_BROADCAST -----》使用广播方式传送
int on=1;
setsockopt(sockfd , SOL_SOCKET,SO_BROADCAST,&on, sizeof(on));
3、发送数据 ,指定接收方为广播地址
struct sockaddr_in sendAddr;
sendAddr.sin_family = AF_INET;
sendAddr.sin_port = htons(10000);
sendAddr.sin_addr.s_addr = inet_addr("192.168.14.255");//一定是广播地址,广播发送
sendto(sockfd,buf,strlen(buf),0,(struct sockaddr*)&sendAddr,sizeof(sendAddr));
4、关闭
close();
广播接收方:
1、创建用户数据报套接字
int socketfd = socket(AF_INET,SOCK_DGRAM,0);
2、绑定(192.168.14.255)广播IP地址和端口号 (10000)
注意:绑定的端口必须和发送方指定的端口相同
struct sockaddr_in ownAddr;
ownAddr.sin_family = AF_INET;
ownAddr.sin_port = htons(10000);
//uint32_t htonl(uint32_t hostlong); 将 主机IP转为 网络IP
ownAddr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY(0.0.0.0) 代表本机所有的地址 //inet_addr("192.168.63.255");
3、接收数据
recvfrom();
4、关闭
close();
说明:
广播端与接收端的端口号要一致,广播端的地址是INADDR_ANY或者192.168.63.255
发送端:
#include<stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
int main()
{
printf("广播发送端(UDP协议)...............\n");
//1、创建数据报套接字
int socketfd = socket(AF_INET,SOCK_DGRAM,0);
if(socketfd == -1)
{
perror("socket error");
return -1;
}
//2、设置套接字文件描述符socketfd的属性为广播(也就是允许发送广播数据包)
int on=1;
setsockopt(socketfd,SOL_SOCKET,SO_BROADCAST,&on,sizeof(on));
//3、发送数据,并且指定接收方为广播地址
struct sockaddr_in sendAddr;//IPV4地址结构体变量
sendAddr.sin_family = AF_INET;
sendAddr.sin_port = htons(10000);
sendAddr.sin_addr.s_addr = inet_addr("192.168.14.255");//一定是广播地址,广播发送
while(1)
{
char buf[1024]={0};
printf("data:");
scanf("%s",buf); //100+"hello"
sendto(socketfd,buf,strlen(buf),0,( struct sockaddr *)&sendAddr,sizeof(sendAddr));
}
close(socketfd);
return 0;
}
接收端:
#include<stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
int main()
{
printf(“广播接收端(UDP协议)…\n”);
//1、创建数据报套接字
int socketfd = socket(AF_INET,SOCK_DGRAM,0);
if(socketfd == -1)
{
perror("socket error");
return -1;
}
//2、绑定(192.168.14.255)广播IP地址和端口号 (10000)
struct sockaddr_in ownAddr;
ownAddr.sin_family = AF_INET;
ownAddr.sin_port = htons(10000); //s unsigned short int
ownAddr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY(0.0.0.0) 代表本机所有的地址 //inet_addr("192.168.14.255");
bind(socketfd, (struct sockaddr *)&ownAddr,sizeof(ownAddr));
struct sockaddr_in otherAddr;
int len = sizeof(struct sockaddr_in);
while(1)
{
char buf[1024]={0};
recvfrom(socketfd,buf,sizeof(buf),0, (struct sockaddr *)&otherAddr,&len);
printf("来自 %s:%u recv:%s\n",inet_ntoa(otherAddr.sin_addr),ntohs(otherAddr.sin_port),buf);
}
close(socketfd);
return 0;
}
二、UDP组播(群聊)
1、概念
组播是介于单播与广播之间,在一个局域网内,将某些主机添加到组中,并设置一个组地址.我们只需要将数据发送到组播地址即可,加入到该组的所有主机都能接收到数据
2、组播特点
- 需要给组播设置IP地址,该IP必须是D类地址
- 只有UDP才能设置组播
3、IP地址分类
IP地址 = 网络号 + 主机号
网络号:指的是不同的网络
主机号:指的是同一个网段下用来识别不同的主机。那也就是说,主机号所占的位数越多,在该网段下的主机数越多。
A类地址 :保留给政府机构使用
A类IP地址就由1字节的网络地址和3字节主机地址组成,网络地址的最高位必须是“0”
A类地址范围 1.0.0.1 - 126.255.255.254
B类地址 :分配给中等规模的公司
B类IP地址就由2字节的网络地址和2字节主机地址组成,网络地址的最高位必须是“10”。
B类地址范围 128.0.0.1 - 191.255.255.254
C类地址 :分配给任何需要的人
C类IP地址就由3字节的网络地址和1字节主机地址组成,网络地址的最高位必须是“110”。
r C类地址范围 192.0.0.1 - 223.255.255.254 //192.168.14.2
D类地址 :用于组播
D类地址范围 224.0.0.1 - 239.255.255.254 //224.0.0.10
E类地址 :用于实验
E类地址范围 240.0.0.1 - 255.255.255.254
特殊地址:
每一个字节都为0的地址(“0.0.0.0”)对应于当前主机; INADDR_ANY -->代表当前主机所有的地址
127.0.0.1 回环地址 --》在当前主机内部自动形成闭环的网络 --》主要用于 主机内部不同的应用程序通信
如果你已经确定当前客户端 和 服务器 都是在同一台主机上运行,那么可以使用这个地址
4、接收端接收组播消息 -->需要加入组播属性的套接字
#define IP_ADD_MEMBERSHIP 加入组播
// usr/include/linux/in.h
struct ip_mreq {
struct in_addr imr_multiaddr; /* 多播组的IP地址 224.0.0.10/
struct in_addr imr_interface; /* 需要加入到多组的IP地址 192.168.53.134 */
};
5、组播通信的过程
发送端:
1、创建UDP数据报套接字
2、发送数据,往组播地址(224.0.0.10 )里面发送数据
3、关闭
接收端:(要把接收端的IP地址加入到组播里面)
1、创建UDP数据报套接字
2、定义组播结构体
struct ip_mreq vmreq;
3、设置组播ip(初始化 组播结构体)
inet_pton(AF_INET,"224.0.0.10",&vmreq.imr_multiaddr); // 组播地址
inet_pton(AF_INET,"192.168.63.2",&vmreq.imr_interface); // 需要添加到组的ip
#ininclude <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
参数:
af : 你要选择哪一种协议族 IPV4 --》AF_INET 还是 IPV6–》AF_INET6
src:本地IP地址
dst:将本地IP地址转为网络IP地址存储到这里
作用:
将本地IP地址转为网络IP地址
4)加入组播属性(也就是设置这个套接字 可以接收组播信息)
setsockopt(socketfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&vmreq,sizeof(vmreq));
5)绑定地址
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(atoi(argv[1]));
saddr.sin_addr.s_addr = htonl(INADDR_ANY); //htonl(INADDR_ANY) 代表 主机所有的地址
bind(socketfd,(struct sockaddr *)&saddr,sizeof(saddr
6)接收数据
recvfrom(......)
代码说明:
发送端:
#include<stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#define GROUPADDR "224.0.0.10" //组播地址
#define GROUPPORT 10000
int main()
{
printf("组播发送端.....\n");
//1、创建UDP数据报套接字
int socketfd = socket(AF_INET,SOCK_DGRAM,0);
if(socketfd == -1)
{
perror("socket error");
return -1;
}
//2、发送数据,往组播地址(224.0.0.10 )里面发送数据
struct sockaddr_in sendAddr;//IPV4地址结构体变量
sendAddr.sin_family = AF_INET;
sendAddr.sin_port = htons(GROUPPORT);
sendAddr.sin_addr.s_addr = inet_addr(GROUPADDR);//一定是组播地址
while(1)
{
char buf[1024]={0};
printf("data:");
scanf("%s",buf);
sendto(socketfd,buf,strlen(buf),0,( struct sockaddr *)&sendAddr,sizeof(sendAddr));
}
//3、关闭
close(socketfd);
return 0;
}
接收端:
#include<stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#define OWNADDR "192.168.112.109" //接收端的IP地址 自己ubuntu的IP地址
#define GROUPADDR "224.0.0.10" //组播地址
#define GROUPPORT 10000
int main()
{
//1、创建UDP数据报套接字
int socketfd = socket(AF_INET,SOCK_DGRAM,0);
if(socketfd == -1)
{
perror("socket error");
return -1;
}
//2、定义组播结构体
struct ip_mreq vmreq;
//3、设置组播ip(初始化 组播结构体)
inet_pton(AF_INET,GROUPADDR,&vmreq.imr_multiaddr); // 组播地址
inet_pton(AF_INET,OWNADDR,&vmreq.imr_interface); // 需要添加到组的ip
//4)加入组播属性(也就是设置这个套接字 可以接收组播信息)
setsockopt(socketfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&vmreq,sizeof(vmreq));
//5)绑定地址
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(GROUPPORT);
saddr.sin_addr.s_addr = htonl(INADDR_ANY); //htonl(INADDR_ANY) 代表 主机所有的地址
bind(socketfd,(struct sockaddr *)&saddr,sizeof(saddr));
//6)接收数据
struct sockaddr_in otherAddr;
int len = sizeof(struct sockaddr_in);
while(1)
{
char buf[1024]={0};
recvfrom(socketfd,buf,sizeof(buf),0, (struct sockaddr *)&otherAddr,&len);
printf("来自 %s:%u recv:%s\n",inet_ntoa(otherAddr.sin_addr),ntohs(otherAddr.sin_port),buf);
}
//关闭
close(socketfd);
return 0;
}
三、多进程并发服务器
使用多进程并发服务器时要考虑以下几点:
1.父进程最大文件描述个数(父进程中需要close关闭accept返回的新文件描述符)
2.系统内创建进程个数(与内存大小相关)
3.进程创建过多是否降低整体服务性能(进程调度)
1、建立套接字
int socketFd = socket
2、绑定
3、设置监听
while(1){
4、阻塞等待客户端的连接......
int newClientFd = accept();
//创建一个子进程,子进程 负责接收每一个连接上来的客户端数据
pid_t id = fork();
if(id == 0) //子进程
{
//关闭 socketFd 文件描述符
while(1) { //接收 客户端的数据
int ret = read(newClientFd,buf,sizeof(buf));
if(ret == 0)
break;
}
//子进程退出的时候 ,会自动发出来一个SIGCHLD信号
break;
}
else if(id >0) //父进程
{
//关闭 newClientFd 文件描述符
//捕捉 SIGCHLD信号 执行 信号响应函数,在该函数中 回收子进程
continue;
}
}
#include<stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include<stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <sys/wait.h>
#define SERVER_PORT 9999
//多进程并发服务器
void sys_err(const char*err)
{
fprintf(stderr,"%s\n",strerror(errno));
exit(0);
}
//信号响应函数
void sig_child(int signum)
{
//当子进程退出的时候,会自动触发SIGCHLD信号,然后在该信号响应函数中回收子进程的资源
//避免子进程变成 僵尸进程
/* while(1){
int ret = waitpid(-1,NULL,WNOHANG); //非阻塞等待子进程的退出 WNOHANG
if(ret < 0)
break;
} */
printf("父进程 回收子进程的资源 [%d].....\n",getpid());
while(waitpid(-1,NULL,WNOHANG) > 0);
}
int main()
{
int ret;
struct sockaddr_in clientAddr;//存储连接上来的客户端的IP地址和端口号
int len = sizeof(struct sockaddr_in);
printf("服务器 Port:%d\n",SERVER_PORT);
//1、建立套接字
int socketFd = socket(AF_INET,SOCK_STREAM, 0);
if(socketFd == -1){
sys_err("socket error");
}
//端口复用
int optval = 1;
setsockopt(socketFd,SOL_SOCKET,SO_REUSEADDR,&optval, sizeof(optval));
//2、绑定自己的IP地址和端口号
struct sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(SERVER_PORT);//short
serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
ret = bind(socketFd,(struct sockaddr*)&serverAddr,sizeof(struct sockaddr_in));
if(ret == -1){
sys_err("bind error");
}
//3、设置监听
listen(socketFd,128);
//信号的捕捉 当子退出的时候,会发出SIGCHLD信号
signal(SIGCHLD,sig_child);
while(1){
//4、阻塞接收新的客户端连接....
int newClientFd = accept(socketFd, (struct sockaddr*)&clientAddr,&len);
printf("有新的客户端连接上来 IP:%s Port:%hu\n",
inet_ntoa(clientAddr.sin_addr),
ntohs(clientAddr.sin_port));
//每次来一个客户端,就创建一个子进程,子进程接收客户端的数据
pid_t id = fork();
if(id == -1) //出错
{
sys_err("fork error");
}else if(id == 0) //子进程
{
//关闭监听 套接字文件描述符
close(socketFd);
while(1)
{
char buf[1024] = {0};
int n = read(newClientFd,buf,sizeof(buf));
if(n == 0){ //说明客户端断开了.....
printf("客户端断开 IP:%s Port:%hu\n",
inet_ntoa(clientAddr.sin_addr),
ntohs(clientAddr.sin_port));
break;
}
printf("客户端IP:%s Port:%hu 数据:%s\n",
inet_ntoa(clientAddr.sin_addr),
ntohs(clientAddr.sin_port),
buf);
}
close(newClientFd);
exit(0); //子进程结束
}else if(id >0) //父进程
{
close(newClientFd);
}
}
//关闭
close(socketFd);
return 0;
}
四、多线程并发服务器
#include<stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include<stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <sys/wait.h>
#include <pthread.h>
#define SERVER_PORT 9999
typedef struct {
int newClientFd;
struct sockaddr_in clientAddr;
}ClientInfo_t;
//多线程并发服务器
void sys_err(const char*err)
{
fprintf(stderr,"%s\n",strerror(errno));
exit(0);
}
void *start_routine (void *arg)
{
ClientInfo_t *pCurClient = (ClientInfo_t*)arg;
while(1)
{
char buf[1024] = {0};
int n = read(pCurClient->newClientFd,buf,sizeof(buf));
if(n == 0){ //说明客户端断开了.....
printf("客户端断开 IP:%s Port:%hu\n",
inet_ntoa(pCurClient->clientAddr.sin_addr),
ntohs(pCurClient->clientAddr.sin_port));
break;
}
printf("客户端IP:%s Port:%hu 数据:%s\n",
inet_ntoa(pCurClient->clientAddr.sin_addr),
ntohs(pCurClient->clientAddr.sin_port),
buf);
}
}
int main()
{
int ret,i=0;
struct sockaddr_in clientAddr;//存储连接上来的客户端的IP地址和端口号
int len = sizeof(struct sockaddr_in);
ClientInfo_t client[1024]; //存储所有的客户端的 套接字文件描述符 和 IP地址 和 端口号
printf("服务器 Port:%d PID:%d \n",SERVER_PORT,getpid());
//1、建立套接字
int socketFd = socket(AF_INET,SOCK_STREAM, 0);
if(socketFd == -1){
sys_err("socket error");
}
//端口复用
int optval = 1;
setsockopt(socketFd,SOL_SOCKET,SO_REUSEADDR,&optval, sizeof(optval));
//2、绑定自己的IP地址和端口号
struct sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(SERVER_PORT);//short
//serverAddr.sin_addr.s_addr = inet_addr("192.168.63.2");
serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
ret = bind(socketFd,(struct sockaddr*)&serverAddr,sizeof(struct sockaddr_in));
if(ret == -1){
sys_err("bind error");
}
//3、设置监听
listen(socketFd,128);
while(1){
//4、阻塞接收新的客户端连接....
int newClientFd = accept(socketFd, (struct sockaddr*)&clientAddr,&len);
printf("有新的客户端连接上来 IP:%s Port:%hu newClientFd:%d\n",
inet_ntoa(clientAddr.sin_addr),
ntohs(clientAddr.sin_port),
newClientFd);
client[i].newClientFd = newClientFd;
client[i].clientAddr = clientAddr;
//开启一条子线程,接收新的客户端的数据
pthread_t thread;
pthread_create(&thread, NULL,start_routine,&client[i]);
//设置子线程的分离属性 --自己回收自己
pthread_detach(thread);
i++;
}
//关闭
close(socketFd);
return 0;
}