UDP的socket通讯

简介

      之前已经介绍过UDP简单介绍与通讯模拟。本篇博客是使用socket网络编程,会先介绍一些socket函数的用法,之后实现一个简单的聊天功能,最后拓展了socket中IPV4的地址结构和网络通用地址结构。

函数关系流程图

在这里插入图片描述

函数介绍

socket()

//头文件
#include <sys/socket.h>
//创建socket
int socket(int family, int type, int protocol);
//参数介绍
family       : 协议族 (AF_INET , AF_INET6 , PF_PACKET...)  对应IPV4 , IPV6和链路层编程接口
type           : 套接字类型(SOCK_STREAM , SOCK_DGRAM , SOCK_RAM...)流式套接字 \ 数据报式套接字  \ 原始套接字
protocol   : 协议类型(0 , 	IPPROTO_TCP , IPPROTO_UDP)为0自动寻找 、 
//返回值
得到socket
//NOTE
创建的socket默认是主动的,即默认是客户端主动请求,系统不会分配端口。当服务器主动创建时,会需要进行一定的修改,比如绑定端口。

sendto()

功能:向to指针指向的地址发送UDP数据
ssize sendto(
    	int sockfd, 
     	const void * buf, 
      	size_t nbytes,
       	int flags, 
         	const struct sockaddr *to, 
          	socklen_t addrlen);
参数介绍
    	sockfd		:发送数据使用的套接字
     	buf			    :发送的数据缓冲区
      	nbytes		:发送消息的长度
       	flags		    :一般为0,经典的套接字发送。
        to		        :指向目的主机地址结构体的指针
        addrlen	:to指向的地址长度
NOTE
    	通过to和addrlen确定目的主机
     	可以发送长度为0的数据包
     	如果UDP套接字没有绑定ip和端口信息,那么第一次调用sendto的时候系统会分配本地主机ip和一个临时端口。
RETURN
    	发送数据的字节数,失败返回 -1

example
      发送注意替换IP地址和端口号

#include  <stdio.h>
#include <string.h>
#include  <unistd.h>
#include  <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
int main(void)
{
    //1. create socket
    int sockfd = socket(AF_INET,SOCK_DGRAM,0);
    //2. send data
    //define a IPV4 address
    struct sockaddr_in dst_addr;
    //clear struct,因为socketaddt_in中需要补位0,所以我们可以直接初始赋值为0
    //memset(&dst_addr,0,sizeof(dst_addr));
    bzero(&dst_addr,sizeof(dst_addr));
    //协议
    dst_addr.sin_family = AF_INET;
    // 将主机字节序转换为网络字节序
    dst_addr.sin_port = htons(8080); 
    //将字符串IP转换为32bit整形数据
    inet_pton(AF_INET,"0.0.0.0",&dst_addr.sin_addr.s_addr);
    char data[40] = "Hello,My name is ffy.Nice to meet you!";
    int num = sendto(sockfd,data,strlen(data),0,(struct sockaddr *)&dst_addr, sizeof(dst_addr));
    printf("%d\n\n\n\n\n",num);
    close(sockfd);
    return 0;
}

在这里插入图片描述
bind()

      让套接字绑定一个固定的IP和端口。

int bind(
    int sockfd,
    const struct sockaddr *myaddr,
    socklen_t addrlen
);

参数介绍
    sockfd	    :套接字
    myaddr	:指向特定协议的地址结构指针
    addrlen	:该地址结构的长度
    
返回值
    成功:返回0
    失败:其他

recvfrom()

ssize_t recvfrom(
    int sockfd,
    void *buf,
    size_t nbytes,
    int flags,
    struct sockaddr * from,
    socklen_t *addrlen
);
参数
    sockfd		:从该套接字接收数据
    buf			    :接收数据缓冲区
    nbytes		:接收数据缓冲区大小
    flags		    :套接字标志(常为0)
    from		    :源地址结构体指针,用来保存数据的来源
    addrlen	:from所指内容的长度
NOTE
    from、addrlen用来保存数据的来源信息,可以为空。
RETURN
    成功接受的字符数,出错返回-1。

example
      用上面写的发送函数发送数据,用以下代码接受数据。以下代码绑定了8080端口。

#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include  <unistd.h>
int main(void)
{
    int sockfd = socket(AF_INET,SOCK_DGRAM,0);
    struct sockaddr_in myaddr;
    myaddr.sin_family = AF_INET;
    myaddr.sin_port = htons(8080);
    myaddr.sin_addr .s_addr = htonl(INADDR_ANY);
    bind(sockfd,(struct sockaddr *)&myaddr,sizeof(myaddr));
    while (1)
    {
        /* code */
        char buf[128] = "";
        int len = recvfrom(sockfd, buf, sizeof(buf), 0, NULL,NULL);
        printf("the length is %d \t the buf is %s\n",len,buf);
    }
    close(sockfd);
    return 0;
}

综合案例:聊天程序

NOTE
      需要可以同时收发消息,需要开启多任务。
设计流程:
      1、创建套接字
      2、bind ip与端口信息
      3、创建两个线程:接收(recvfrom)和发送(sendto)
代码

#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <unistd.h>
void *QQSend(void *arg)//arg保存的socket的地址
{
    printf("send channel is created successfully and you can send messages now!\n");
    int sockfd = *(int *)arg;
    struct sockaddr_in dst_addr;
    bzero(&dst_addr,sizeof(dst_addr));
    dst_addr.sin_family = AF_INET;
    socklen_t len_t = sizeof(dst_addr);
    while (1)
    {
        char buf[128] = "";
        fgets(buf,sizeof(buf),stdin);
        buf[strlen(buf)-1] = 0;   //去掉换行符
        //解析地址
        if (strncmp(buf,"sayto",5) == 0)
        {
            //sayto 192.168.31.96 8080
            unsigned short port = 0;
            char ip[16] = "";
            sscanf(buf, "sayto %s %hd",ip,&port);
            dst_addr.sin_port = htons(port);
            inet_pton(AF_INET,ip,&dst_addr.sin_addr.s_addr);
            continue;
        }
        else //要发送的消息 
        {
            sendto(sockfd,buf,strlen(buf),0,(struct sockaddr *)&dst_addr,len_t);
        }
    }
    return NULL;
}
void * QQRecv(void *arg)
{
    printf("receive channel is created successfully also!\n");
    int sockfd = *(int *)arg;
    while (1)
    {
        char buf[128] = "";
        struct sockaddr_in from;
        socklen_t len = sizeof(from);
        recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&from, &len);
        unsigned short port = ntohs(from.sin_port);
        char ip[16] = "";
        inet_ntop(AF_INET, &from.sin_addr.s_addr , ip, 16);
        printf("*******************************\n");
        printf("from %s  :  %s\n",ip,buf);
        printf("*******************************\n");
    }
}
int main(void)
{
    //create a new socket
    int sockfd = socket(AF_INET,SOCK_DGRAM,0);
    unsigned short port = 8000;
    if (sockfd < 0)
    {
        printf("Socket create ERROR!!!\n");
    }
    printf("please enter the port which you can use\n");
    scanf("%hd",&port);
    //bind ip and port
    struct sockaddr_in client;
    bzero(&client,sizeof(client));
    client.sin_family = AF_INET;
    client.sin_port = htons(port);
    client.sin_addr.s_addr = htonl(INADDR_ANY);
    //inet_pton(AF_INET,"192.168.31.96",&client.sin_addr.s_addr);
    //socklen_t client_t = sizeof(client);
    bind(sockfd,(struct sockaddr *) &client, sizeof(client) );
    //创建一个发送线程
    pthread_t tidS, tidR;
    pthread_create(&tidS,NULL,QQSend,(void *) &sockfd);
    pthread_create(&tidR,NULL,QQRecv,(void *) &sockfd);
    pthread_join(tidS,NULL);
    pthread_join(tidR,NULL);
    return 0;
}

在这里插入图片描述

拓展:IPV4地址结构

//头文件:#include <netinet/in.h>
struct in_addr
{
    	in_addr_t  s_addr;     //四个字节
};
//IPv4地址结构
struct sockaddr_in
{
    	sa_family_t sin_family;	//2字节,协议
     	in_port_t sin_port;		//2字节,端口PORT
      	struct in_addr sin_addr;	//4字节,IP地址
       	char sin_zero[8];			//8字节 ,为0,网络中套接字必须为16个字节,作为补位。
};

拓展:网络地址结构

//头文件: #include <netinet.in.h>
struct sockaddr
{
    sa_family_t sa_family;   // 2 byte
    char sa_data[14];
}

以上两种地址结构的使用场景:

      在定义源地址和目的地址的时候使用 struct sockaddr_in;
struct sockaddr_in my_addr;

      在调用编程接口函数的时候,且该函数需要传入地址时,使用struct sockaddr进行强制转换。

bind(sockfd , (struct sockaddr *)my_addr , sizeof(my_addr));
sendto(sockfd,data,strlen(data),0,(struct sockaddr *)&dst_addr, sizeof(dst_addr));
recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&from, &len);
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我要出家当道士

打赏是不可能,这辈子都不可能

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值