百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程网 > 正文

网络编程-2、TCP&UDP编程 udp网络编程的一般步骤

yuyutoo 2024-12-22 21:48 3 浏览 0 评论

1、UDP编程

1.1、UDP编程-创建套接字

#include <sys/socket.h>
int socket(int family,int type,int protocol);

功能

创建一个用于网络通信的socket套接字(描述符)

参数

family:协议族(AF_INET、AF_INET6、PF_PACKET等)

type:套接字类(SOCK_STREAM、SOCK_DGRAM、SOCK_RAW等)

protocol:协议类别(0、IPPROTO_TCP、IPPROTO_UDP等

返回值:

套接字

特点

创建套接字时,系统不会分配端口

创建的套接字默认属性是主动的,即主动发起服务的请求;当作为服务器时,往往需要修改为被动的

1.2、UDP编程-发送数据

ssize_t sendto(int sockfd,const void *buf,size_t nbytes,int flags,const struct sockaddr *to,socklen_t addrlen);

功能:

向to结构体指针中指定的ip,发送UDP数据

参数:

sockfd:套接字

buf: 发送数据缓冲区

nbytes: 发送数据缓冲区的大小

flags:一般为0

to: 指向目的主机地址结构体的指针

addrlen:to所指向内容的长度

返回值:

成功:发送数据的字符数

失败: -1

注意:

通过to和addrlen确定目的地址

可以发送0长度的UDP数据包


1.3、UDP编程-绑定端口

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

功能:将本地协议地址与sockfd绑定

参数:

sockfd: socket套接字

myaddr: 指向特定协议的地址结构指针

addrlen:该地址结构的长度

返回值 :

成功:返回0

失败:其他

1.4、UDP编程-接收数据

ssize_t recvfrom(int sockfd, void *buf,size_t nbytes,int flags,struct sockaddr *from,socklen_t *addrlen);

功能 :

接收UDP数据,并将源地址信息保存在from指向的结构中

参数 :

sockfd:套接字

buf: 接收数据缓冲区

nbytes:接收数据缓冲区的大小

flags: 套接字标志(常为0)

from: 源地址结构体指针,用来保存数据的来源

addrlen: from所指内容的长度

返回值:

成功:接收到的字符数

失败: -1

注意:

通过from和addrlen参数存放数据来源信息

from和addrlen可以为NULL, 表示不保存数据来源


1.5、UDP编程-客户端示例


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

#define SERVER_IP  "192.168.0.108"
#define SERVER_PORT   8080

int main(int argc, char *argv[])
{   
    int sockfd;
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);        //创建UDP套接字
    if(sockfd < 0)
    {
        perror("socket");
        exit(-1);
    }
    
    struct sockaddr_in dest_addr;
    bzero(&dest_addr, sizeof(dest_addr));
    dest_addr.sin_family = AF_INET;
    dest_addr.sin_port   = htons(SERVER_PORT);
    inet_pton(AF_INET, SERVER_IP, &dest_addr.sin_addr);

    printf("UDP server %s:%d\n", SERVER_IP, SERVER_PORT);
    
    while(1)
    {
        char send_buf[512] = "";
        fgets(send_buf, sizeof(send_buf), stdin);//获取输入
        send_buf[strlen(send_buf)-1] = '\0';
        sendto(sockfd, send_buf, strlen(send_buf), 0, (struct sockaddr*)&dest_addr, sizeof(dest_addr));//发送数据
    }
    
    close(sockfd);
    return 0;
}

1.6、UDP编程-服务端示例


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

#define SERVER_IP  "192.168.0.104"
#define SERVER_PORT   8080

int main(int argc, char *argv[])
{
    int sockfd;
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sockfd < 0)
    {
        perror("socket");
        exit(-1);
    }
        
    struct sockaddr_in my_addr;
    bzero(&my_addr, sizeof(my_addr));
    my_addr.sin_family = AF_INET;
    my_addr.sin_port   = htons(SERVER_PORT);
    my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    
    printf("server bind port: %d\n", SERVER_PORT);
    int ret = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr));
    if(ret != 0)
    {
        perror("bind");
        close(sockfd);      
        exit(-1);
    }
    printf("receive data:\n");
    while(1)
    {
        int recv_len;
        char recv_buf[512] = "";
        struct sockaddr_in client_addr;
        char client_ip[INET_ADDRSTRLEN] = "";//INET_ADDRSTRLEN=16
        socklen_t cliaddr_len = sizeof(client_addr);
        
        recv_len = recvfrom(sockfd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr*)&client_addr, &cliaddr_len);
        inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN);
        //char *client_ip_pr=inet_ntoa(client_addr.sin_addr);
        printf("client_ip:%s ,client_port:%d\n",client_ip, ntohs(client_addr.sin_port));
        printf("data(%d):%s\n",recv_len,recv_buf);
    }
    close(sockfd);
    return 0;
}

1.7、UDP广播

广播:由一台主机向该主机所在局域网内的所有主机发送数据的方式 ,广播只能用UDP或原始IP实现,不能用TCP

广播的用途:

单个服务器与多个客户主机通信时减少分组流通

地址解析协议(ARP)

动态主机配置协议(DHCP)

网络时间协议(NTP)

广播的特点:

处于同一子网的所有主机都必须处理数据

UDP数据包会沿协议栈向上一直到UDP层

运行音视频等较高速率工作的应用,会带来大负

局限于局域网内使用


UDP广播地址

{网络ID,主机ID}

网络ID表示由子网掩码中1覆盖的连续位

主机ID表示由子网掩码中0覆盖的连续位

定向广播地址:主机ID全1

例:对于192.168.220.0/24,其定向广播地址为 192.168.220.255

通常路由器不会转发该广播

受限广播地址:255.255.255.255

路由器从不转发该广播


套接字选项

int setsockopt(int sockfd, int level,int optname,const void *optval,socklen_t optlen);

功能:

设置套接字选项值

参数:

sockfd:套接字

level: 被设置的选项的级别,如果想要在套接字级别上设置选项,就必须把level设置为 SOL_SOCKET

optname:准备设置的选项,option_name可以有哪些取值,这取决于level

optval:类型

optlen:

返回值:

成功:0

失败: -1

示例

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

#define SERVER_IP  "192.168.0.104"
#define SERVER_PORT   8080

int main(int argc, char *argv[])
{
    int sockfd=0;
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sockfd < 0)
    {
        perror("socket");
        exit(-1);
    }
        
    struct sockaddr_in server_addr;
    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port   = htons(SERVER_PORT);

    
    printf("server bind port: %d\n", SERVER_PORT);
    
    int opt=1;
    setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&opt,sizeof(opt));
    
    char send_buf[1024]={0};
    strcpy(send_buf,"this is broadbast msg\n");

    int len = sendto(sockfd,send_buf,strlen(send_buf),0,(struct sockaddr *)&server_addr,sizeof(server_addr));
    if(len<0)
    {
        printf("send error\n");
        close(sockfd);
        return -1;
    }
    printf("send success\n");

    close(sockfd);
    return 0;
}

1.8、UDP多播(组播)

多播: 数据的收发仅仅在同一分组中进行多播的特点:

多播地址标示一组接口

多播可以用于广域网使用

在IPv4中,多播是可选的

UDP多播地址

IPv4的D类地址是多播地址

十进制:224.0.0.1 - 239.255.255.254

十六进制:E0.00.00.01 - EF.FF.FF.FE 特殊的IP地址多播地址:224.0.0.1:是所有主机组,子网上所有具有多播能力的节点(主机、路由器、打印机)必须在所有具有多播能力的接口上加入该组。224.0.0.2:是所有路由器组,子网上所有多播路由器必须在所有具有多播能力的接口上加入该组。

多播地址分类

224.0.0.0 -- 224.0.255 链路局部的多播地址,是低级拓扑和维护协议保留的,多播路由器不转发以这些地址为目的地址的数据报

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: 为本地管理组播地址,仅在特定的本地范围内有效


  • 在IPv4因特网域(AF_INET)中,多播地址结构体用如下结构体ip_mreq表示

struct in_addr{
    in_addr_t    s_addr;                              
};
 
struct ip_mreq{
    struct in_addr imr_multiaddr;//多播组ip
    struct in_addr imr_interface;//将要添加到的多播组ip
};

套接口选项

int setsockopt(int sockfd, int level,int optname,const void *optval, socklen_t optlen);

功能:

设置套接字选项值

参数:

sockfd:套接字

level: 被设置的选项的级别,IPPROTO_IP

optname: IP_ADD_MEMBERSHIP :加入多播组 ,IP_DROP_MEMBERSHIP :离开多播组

optval:类型 ip_mreq{}

optlen:

返回值:

成功:0

失败: -1


为什么要用组播

单播和组播是寻址方案的两个极端(要么单个、要么全部),多播则是两者之间的一种折中的方案,多播数据报只应该由对它感兴趣的接口接收。另外,广播一般局限于局域网内使用,多播则既可用于局域网,也可跨广域网使用。

2、TCP编程

2.1、TCP介绍

  • 作为客户端需要具备的条件

(1) 知道服务器的ip、port
(2) 主动连接 服务器

  • 需要用到的函数

socket : 创建TCP套接字(主动)

connect:连接服务器

send:发送数据到服务器

recv: 接受服务器的响应

close:关闭连接

2.2、TCP客户端相关函数

2.2.1 创建TCP套接字函数

	int sockfd;
	sockfd = socket(AF_INET, SOCK_STREAM, 0);// 创建通信端点:套接字

2.2.2 连接服务器函数

int connect(int sockfd,const struct sockaddr *addr,socklen_t len);

功能:主动跟服务器建立链接
参数:

sockfd:socket套接字
addr: 连接的服务器地址结构
len: 地址结构体长度

返回值:

成功:0 失败:其他

注意:

? connect建立连接之后不会产生新的套接字
? 连接成功后才可以开始传输TCP数据

2.2.3 TCP发送数据

#include <sys/socket.h>
ssize_t send(int sockfd, const void* buf,size_t nbytes, int flags);

功能:用于发送数据
参数:

sockfd: 已建立连接的套接字
buf: 发送数据的地址
nbytes: 发送缓数据的大小(以字节为单位)
flags: 套接字标志(常为0)

注意:不能用TCP协议发送0长度的数据包

2.2.4 TCP接收数据

#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf,size_t nbytes, int flags);

功能:用于接收网络数据
参数:

? sockfd:套接字
? buf: 接收网络数据的缓冲区的地址
? nbytes:接收缓冲区的大小(以字节为单位)
? flags: 套接字标志(常为0)

返回值:成功接收到字节数

2.2.5 客户端代码

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

#define SERVER_IP  "192.168.0.107"
#define SERVER_PORT   8080

int main(int argc, char *argv[])
{
    int sockfd;
    sockfd = socket(AF_INET, SOCK_STREAM, 0);// 创建通信端点:套接字
    if(sockfd < 0)
    {
        perror("socket");
        exit(-1);
    }
    
    struct sockaddr_in server_addr;
    bzero(&server_addr,sizeof(server_addr)); // 初始化服务器地址
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT);
    inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr);
    
    int err_log = connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));      // 主动连接服务器
    if(err_log != 0)
    {
        perror("connect");
        close(sockfd);
        exit(-1);
    }
    
    char send_buf[1024] = "";
    printf("send data to [%s:%d]\n",SERVER_IP,SERVER_PORT);
    while(1)
    {
        printf("send:");
        fgets(send_buf,sizeof(send_buf),stdin);
        send_buf[strlen(send_buf)-1]='\0';
        send(sockfd, send_buf, strlen(send_buf), 0);   // 向服务器发送信息
    }


    close(sockfd);
    return 0;
}

2.3、TCP服务端相关函数

做为TCP服务器需要具备的条件

? 具备一个可以确知的地址
? 让操作系统知道是一个服务器,而不是客户端
? 等待连接的到来

对于面向连接的TCP协议来说,连接的建立才真正意味着数据通信的开始

2.3.1、bind函数

bind用法和UDP服务端使用一样,

	struct sockaddr_in my_addr;
	bzero(&my_addr, sizeof(my_addr));	     
	my_addr.sin_family = AF_INET;
	my_addr.sin_port   = htons(port);
	my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	
	int err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr));
	if( err_log != 0)
	{
		perror("binding");
		close(sockfd);		
		exit(-1);
	}

2.3.2、listen函数

#include <sys/socket.h>
int listen(int sockfd, int backlog);

功能:

? 将套接字由主动修改为被动
? 使操作系统为该套接字设置一个连接队列,用来记录所有连接到该套接字的连接

参数:

? sockfd: socket监听套接字
? backlog:连接队列的长度

返回值:

? 成功:返回0
? 失败:其他

2.3.3、accept 函数

#include <sys/socket.h>
int accept(int sockfd,struct sockaddr *cliaddr,socklen_t *addrlen);

功能:从已连接队列中取出一个已经建立的连接,如果没有任何连接可用,则进入睡眠等待(阻塞)

参数:

? sockfd: socket监听套接字

? cliaddr: 用于存放客户端套接字地址结构

? addrlen:套接字地址结构体长度的地址

返回值:已连接套接字

注意:返回的是一个已连接套接字,这个套接字代表当前这个连接

2.3.4、服务端的代码

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

#define SERVER_PORT   8080

int main(int argc, char *argv[])
{   //创建套接字 
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);   
    if(sockfd < 0)
    {
        perror("socket");
        exit(-1);
    }
    
    struct sockaddr_in my_addr;
    bzero(&my_addr, sizeof(my_addr));        
    my_addr.sin_family = AF_INET;
    my_addr.sin_port   = htons(SERVER_PORT);
    my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    
    int err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr));
    if( err_log != 0)
    {
        perror("binding");
        close(sockfd);      
        exit(-1);
    }
    
    err_log = listen(sockfd, 10);
    if(err_log != 0)
    {
        perror("listen");
        close(sockfd);      
        exit(-1);
    }   
    
    printf("listen client port=%d...\n",SERVER_PORT);

    while(1)
    {   
    
        struct sockaddr_in client_addr;        
        char cli_ip[INET_ADDRSTRLEN] = "";     
        socklen_t cliaddr_len = sizeof(client_addr);    
        
        int connfd;
        connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len);       
        if(connfd < 0)
        {
            perror("accept");
            continue;
        }

        inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);
        printf("----------------------------------------------\n");
        printf("client ip=%s,port=%d\n", cli_ip,ntohs(client_addr.sin_port));
        
        char recv_buf[2048] = "";
        while( recv(connfd, recv_buf, sizeof(recv_buf), 0) > 0 )
        {
            printf("\nrecv data:\n");
            printf("%s\n",recv_buf);
        }
        
        close(connfd);     //关闭已连接套接字
        printf("client closed!\n");
    }
    close(sockfd);         //关闭监听套接字
    return 0;
}

相关推荐

牛逼哄哄的数据库连接池,底层原理是个啥?

作者:敦格来源:https://blog.csdn.net/shuaihj/article/details/14223015这次我们采取技术演进的方式来谈谈数据库连接池的技术出现过程及其原理,以及当下...

如何实现一个连接池?一文带你深入浅出,彻底搞懂

-前言-【2w1h】是技术领域中一种非常有效的思考和学习方式,即What、Why和How;坚持【2w1h】,可以快速提升我们的深度思考能力。今天我们通过【2w1h】方式来讨论“连接池”:什么是连接...

什么是连接池?如何实现一个Java连接池?

什么是连接池?结构连接池对外提供接口:获得连接归还连接并暴露客户端可配置的参数:...

什么是数据库连接池? 什么是数据库连接池?如何使用

在JDBC编程中,每次创建和断开Connection对象都会消耗一定的时间和IO资源。这是因为在Java程序与数据库之间建立连接时,数据库端要验证用户名和密码,并且要为这个连接分配资源,Java程序则...

男生怎么插入对方话题,分享这2点,可以快速搭讪

在日常生活中,男生怎么插入对方的话题呢?来分享一下几个小妙招,让男生快速搭讪。1、同女性聊天.普通的女性可以先有礼貌的打招呼,询问对方聊天内容,对方的话题适不适合自己发表观点,如果不适合可以等着对方说...

PDF文件如何插入、删除、替换、旋转页面

Hello,各位小伙伴们大家好,我是爱学习的小助手。...

SQL——INSERT:插入数据 sqlinsert怎么插入

INSERT语句用于向数据库表中插入数据。1、插入一条数据insert into adm_user(login_name,password,name,mobile_no)&...

近乎变态的USB发展史,命名混乱到了极致,一篇文章让你读懂

USB3.0经历过多次改名,到最后没有任何规律!特别是USB3.0居然“升格”到USB3.2,搞得人头大!同1个蓝色USB口好像说3.0/3.1/3.2的都有,究竟谁对?今天为大家缕一缕。点赞收藏!【...

女大学生把30厘米“兔兔管”插入体内:上瘾性行为,害了多少人?

轻触关注,打开知识的大门,我在这里将与您共享更多精彩内容!女大学生小玲身高160厘米,体重却高达170斤。她曾尝试过节许多减肥方法,但无一例外均以失败告终。...

大年初二铁棍插入10岁女童脑内,上海值班医生手术取出

澎湃新闻记者陈斯斯来自浙江嘉兴的10岁女童逛商场遭遇意外,一根铁棍插入其脑内,随后被120急救车急送上海交大医学院附属新华医院(简称上海新华医院)实施急诊手术。2月3日,澎湃新闻(www.thep...

插入语和状语的区别 where引导的定语从句和状语从句的区别

1.插入语是在一个句子中插入一个成分,它不是句子的成分,而是表示说话人的态度或进行解释补充说明等。...

用户数据之存量——DAU/MAU 用户数据到底是什么

编辑导语:数据分析是设计师了解用户行为的一个重要手段。本文作者分享了不同指标的核心含义,从用户数据的存量、Active活跃度、User用户展开分析,一起来学习一下吧,希望对你有帮助。随着设计师对产品设...

键盘上这些被厂商抠掉的功能键,到底有多没用?

最近呢,办公室流行起了客制化键盘这个东西,简单来讲就是可以根据自己的需求和喜好来定制独一无二的键盘。...

如何制作Windows系统安装U盘 最简单的方法 有手就行

前段时间一位朋友问我,能不能给他安装系统,他随后补了一句:给你钱。其实安装Windows系统是一件非常简单的事情,所以我想着,不如告诉大家最简单的操作方法,让大家能够省一笔安装系统的钱。该教程会分为两...

大模型给你的答案,也要插入广告了?

英伟达创始人CEO黄仁勋曾在被问到他是否使用ChatGPT或Bard时,他回答称,“我一般用Perplexity,而且几乎每天都在用。”Perplexity旨在用AI技术打造一个没有广告的“谷歌搜索...

取消回复欢迎 发表评论: