UDP套接字-C实现

介绍套接字

套接字是互联网用户层的接口,可以实现不同终端间的数据传递;在C中套接字的各种函数和数据结构分布在很多库里,在此不详细说每一个函数在哪个库中,使用到的程序中使用到套接字的函数都包括在以下库中

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

在这个实验中使用UDP套接字.UDP协议和TCP协议不同,UDP不能保证每传送的数据对方能够接收到,而TCP可以.但是UDP可以比较快的传输数据,因此在实时通讯,视频会议等领域有应用.

运行环境

运行环境为Windows子系统Ubuntu 18.04 LTS

程序作用

程序分为两个部分:客户端程序和服务器程序

客户端程序把字符串yyyyyyyyyy传送给服务器,然后接受服务器传送的时间并显示

服务器程序接受客户端传送的数据.显示客户端的IP地址,端口号,客户端传来的字符串.最终把系统时间传回客户端.

客户端程序

下面分块介绍客户端程序

首先声明嵌套字,实际上嵌套字就是一个int型数据,使用socket函数初始化即可.下面socket函数传递的第一个参数为IPv4协议(PF_INET);第二个参数为无连接通讯(SOCK_DGRAM);第三个参数为UDP协议(IPPROTO_UDP)

// 创建套接字,参数:IPv4协议,连接类型,udp协议
int client_fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);

接下来要确定服务器的地址.地址是用一个结构体表示的.struct socket_addr_in有四个元素,如下:

struct sockaddr_in {
short int sin_family;  //协议簇
unsigned short int sin_port; //端口号
struct in_addr sin_addr; //里面储存IP地址
unsigned char sin_zero[8]; //没用
};

服务器地址只是用了三个元素,协议簇初始为AF_INET,表示IPv4;sin_addr.s_addr这个元素可以看到是IP地址,但是在这里要把用字符串表示的IP地址通过inet_addr()转成需要的格式;最后一个端口号赋值为12000

:这个结构体是继承struct sockaddr的结构体,两者大小一样,一般要使用的时候需要将struct sockaddr_in *转成struct sockaddr *

    // 服务器的地址
    struct sockaddr_in server_addr;
    int addr_size = sizeof(struct sockaddr);
    
    // 清空服务器地址
	memset(&server_addr, 0, addr_size);
    // 地址为IP地址
	server_addr.sin_family = AF_INET;
    //服务器IP地址,127.0.0.1是本机,也就是传给自己电脑
	server_addr.sin_addr.s_addr=inet_addr("127.0.0.1");
    //服务器端口号,这个可以随意1024-65535
	server_addr.sin_port=htons(12000); 

接下来开始对服务器传送字符串buf;这里用到函数sendto();第一个参数是客户端的套接字client_fd;第二个参数是要传送的字符串buf;第三个参数是缓冲区大小,即字符串的最大长度;第四个参数不用管,传0;第五个参数传递服务器的地址,但是这里要转成基类的指针;第六个参数传递地址结构的大小,这个在之前已经计算得出为addr_size,这里也可写为sizeof(struct sockaddr)

    // 初始化要传送的字符串
    memset(buf, 'y', 10);
	printf("sending: '%s'\n",buf);
	
	// 传送数据,参数如下
    // 客户套接字,字符串,字符串最大长度,0,服务器地址,地址大小
    sendto(client_fd, buf, MAXBUF, 0, (struct sockaddr *)&server_addr, addr_size);
    

最后要接受从服务器传来的信息,这里使用recvfrom()函数,revcfrom函数和sendto差不多,要注意的是它的返回值.当成功接收时返回值为读取到字符串的长度,在程序中储存cc里面,之后的程序用到cc给字符串加上结束符’\0’.具体程序如下:

    int cc = recvfrom(client_fd, buf, MAXBUF, 0,
        (struct sockaddr *)&server_addr, &addr_size);
    buf[cc] = '\0';
    printf("%s", buf);

整个客户端完整代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#define MAXBUF 20000
 
int main()
{
    // 客户嵌套字
	int client_fd;
    // 地址大小,当参数使用
    int addr_size = sizeof(struct sockaddr);
    // 服务器的地址
    struct sockaddr_in server_addr;
    // 要传送的字符串
	char buf[MAXBUF];

    // 创建套接字,参数:IPv4协议,连接类型,udp协议
    client_fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);

    // 清空服务器地址
	memset(&server_addr, 0, addr_size);
    // 地址为IP地址
	server_addr.sin_family = AF_INET;
    //服务器IP地址,127.0.0.1是本机,也就是传给自己电脑
	server_addr.sin_addr.s_addr=inet_addr("127.0.0.1");
    //服务器端口号,这个可以随意1024-65535
	server_addr.sin_port=htons(12000); 

    // 初始化要传送的字符串
    memset(buf, 'y', 10);
	printf("sending: '%s'\n",buf);
	
	// 传送数据,参数如下
    // 客户套接字,字符串,字符串最大长度,0,服务器地址,地址大小
    sendto(client_fd, buf, MAXBUF, 0, (struct sockaddr *)&server_addr, addr_size);
    
    // 接受服务器数据,参数和传送的一样
    // 返回值为接收到字符串长度
    int cc = recvfrom(client_fd, buf, MAXBUF, 0,
        (struct sockaddr *)&server_addr, &addr_size);
    buf[cc] = '\0';
    printf("%s", buf);

	return 0;
}

服务器程序

服务器程序和客户端很像,只需要讨论其中的一些差别即可

服务器端程序要建立套接字并绑定到服务器的地址上,绑定的函数使用bind函数;这个函数的第一个参数就是服务器的套接字;第二个参数是服务器的地址(转成基类);第三个参数是地址大大小

    // 创建套接字并绑定
    int server_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    // 清空服务器地址
    memset(&server_addr, 0, addr_size);
    // 地址为IP地址
    server_addr.sin_family = AF_INET;
    // INADDR_ANY表示可以接受任意的IP地址发来的消息
    server_addr.sin_addr.s_addr = INADDR_ANY;
    // 端口
    server_addr.sin_port = htons(12000);
    // 绑定套接字,和地址
    bind(server_sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr));

服务器程序还要接受多个客户程序的请求,因此在这里设置了一个死循环,每次服务器程序接受客户信息并且发送时间之后,都等待另一个客户的信息,程序最终需要在命令行中输入Ctrl+c结束,循环的过程如下:

    printf("The server is ready\n-----------------------\n");
    while(1)
    {
        // 接受用户传来的信息,参数见用户程序
        // 注意这个函数可以获得用户的地址
        cc = recvfrom(server_sock, buf, MAXBUF, 0, 
            (struct sockaddr *)&client_addr, &addr_size);

        // 输出用户的IP地址
        printf("client ip: %s\n",inet_ntoa(client_addr.sin_addr));
        // 输出用户的端口号
        printf("client port: %d\n", ntohs(client_addr.sin_port));
        // 输出用户传来的信息
        buf[cc] = '\0';
        printf("The message is: %s\n", buf);
        printf("end\n-----------------------\n");

        // 获得系统时间
        time_t now = time(0);
        char *buffer_send = ctime(&now);
        // 给用户传递时间
        sendto(server_sock, buffer_send, strlen(buffer_send),
                0, (struct sockaddr *)&client_addr, addr_size);
    }

总客户程序代码如下:

#include <sys/socket.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <time.h>
#define MAXBUF 20000

int main(int argc, char **argv)
{
    // 服务器地址
    struct sockaddr_in server_addr;
    // 客户端地址
    struct sockaddr_in client_addr;
    // 地址结构的大小,用作参数
    int addr_size = sizeof(client_addr);
    // 
    char buf[MAXBUF];
    int cc;
    
    // 创建套接字并绑定
    int server_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    // 清空服务器地址
    memset(&server_addr, 0, addr_size);
    // 地址为IP地址
    server_addr.sin_family = AF_INET;
    // INADDR_ANY表示可以接受任意的IP地址发来的消息
    server_addr.sin_addr.s_addr = INADDR_ANY;
    // 端口
    server_addr.sin_port = htons(12000);
    // 绑定套接字,和地址
    bind(server_sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr));

    printf("The server is ready\n-----------------------\n");
    while(1)
    {
        // 接受用户传来的信息,参数见用户程序
        // 注意这个函数可以获得用户的地址
        cc = recvfrom(server_sock, buf, MAXBUF, 0, 
            (struct sockaddr *)&client_addr, &addr_size);

        // 输出用户的IP地址
        printf("client ip: %s\n",inet_ntoa(client_addr.sin_addr));
        // 输出用户的端口号
        printf("client port: %d\n", ntohs(client_addr.sin_port));
        // 输出用户传来的信息
        buf[cc] = '\0';
        printf("The message is: %s\n", buf);
        printf("end\n-----------------------\n");

        // 获得系统时间
        time_t now = time(0);
        char *buffer_send = ctime(&now);
        // 给用户传递时间
        sendto(server_sock, buffer_send, strlen(buffer_send),
                0, (struct sockaddr *)&client_addr, addr_size);
    }
    return 0;
}

运行结果

先启动服务器程序,可以看到如下界面

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wTWXUC5y-1584117892512)(C:\Users\yangxr\AppData\Roaming\Typora\typora-user-images\image-20200314003100989.png)]

接下来启动客户端程序,有如下界面;在这里可以看到客户程序从服务器程序返回的数据中获得了时间

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hrZcmW9P-1584117892518)(C:\Users\yangxr\AppData\Roaming\Typora\typora-user-images\image-20200314003342153.png)]

再次查看服务器端结果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hvw7Sf1J-1584117892520)(C:\Users\yangxr\AppData\Roaming\Typora\typora-user-images\image-20200314003504127.png)]

可以看到服务器程序已经取出IP地址,端口号和客户传来的字符串

评论

感觉C语言编写套接字真的很多细节要注意,只是很粗略讲一下都是好几千字,下次将TCP套接字估计更长,可能考虑以后写一些python的代码这样看起来比较方便

  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值