网络编程03

网络编程




一、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 -->代表当前主机所有的地址
127001  回环地址  --》在当前主机内部自动形成闭环的网络 --》主要用于 主机内部不同的应用程序通信        
    如果你已经确定当前客户端 和 服务器 都是在同一台主机上运行,那么可以使用这个地址

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;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值