套接字编程(一)( 接口介绍与UDP通信简单实现 )

目录

一、前情提要

① 五元组

源端IP 

端口号

② 客户端与服务端

客户端:

服务端:

关系:

二、UDP通信的两端流程

对于服务端来说:

1、创建套接字

2、为套接字绑定地址信息

3、接受数据

4、发送数据

5、关闭套接字

三、相关接口使用

1、创建套接字

① int socket(int domain, int type, int protocol);

② domain:表示地址域类型

③ type 套接字类型

④ protocol 协议类型(默认为0,表示使用默认对应协议)

⑤ 返回值 创建成功返回一个套接字描述符,失败返回-1

2、为套接字绑定地址信息

①int bind(int sockfd, struct scokaddr *addr, socklen_t len);

② sockfd: 创建套接字返回的套接字描述符

③ addr 要绑定的地址信息(不同的地址域类型,有不同的地址结构)

 ④ 成功返回0, 失败返回-1

 3、发送数据

 ① ssize_t sendto(int sockfd,  void *buf,  size_t dlen, 

                                int flag,  struct sockaddr *peer,  socklen_t alen);

4、接收数据

① ssize_t recvfrom(int sockfd,  void *buf,  size_t dlen, 

                                  int flag,  struct sockaddr *peer,  socklen_t  *alen);

5、关闭套接字、释放资源

① int close(int fd)

6、字节序相关接口

五、UDP通信的简单实现


一、前情提要

① 五元组

网络通信中的数据都具备完整的五元组(源端IP、对端IP、源端端口、对端端口、协议)

源端IP 

在IP数据包头部中, 有两个IP地址, 分别叫做源IP地址, 和目的IP地址,说白了就是为了确定发送用户的IP地址。

有了IP地址之后,是用户主机上的那个进程或者说是应用程序呢?

端口号

有了IP地址+端口号 可以唯一确认某一台主机上的某一个进程

例如 192.168.2.2.1234

uint16_t  端口号是一个二字节16位的整数

一个端口号只能被一个进程占用,如果另外一个进程也进行访问这个端口号,就会存在端口冲突的风险

源端端口和对端端口分辨描述了数据是谁发送的,是要发给谁。

② 客户端与服务端

网络通信通常是两台主机之间的通信(俩台主机分别为客户端、服务端)

客户端:

部署用户这边,通常主动发起请求,按下普攻键,给服务端发送一个我需要攻击的目标以及我的攻击力状态,传递一些信息

服务端:

部署在应用服务提供商一端,被动接受请求,提供服务,收到了你发送的攻击请求,然后对相应的对象进行数据的修改,减掉红Buff野怪35滴血……

关系:

 网络通信都是客户端与服务端之间的通信,不存在客户端与客户端,服务端与服务端。后者显而易见,前者我攻击一个对象,我不是直接将对方的状态进行修改,而是将这个数据发送给服务端,服务端进行修改之后反映给我攻击的对象的客户端,起到一个中间桥梁的作用。

客户端能够访问的服务端地址信息必须是已知的,才能主动发起请求,并且服务端的信息轻易不会进行修改

二、UDP通信的两端流程

因为客户端所访问的服务端比如是存在、已知、且轻易不变的,所以先进行服务端的创建

对于服务端来说:

1、创建套接字

建立进程与网卡之间的关系,在内核中生成一个套接字结构体(struct scoket)

2、为套接字绑定地址信息

给创建的套接字结构体,内部存入我需要发送数据的IP地址和端口

在数据传输中通过IP的确认访问到对应的主机网卡,在通过端口信息访问到对应主机的进程或应用程序

例如 192.168.2.3对应的主机进程端口444  发送一个数据给-->192.168.2.2对应主机的端口1234

                192.168.2.3.444-->192.168.2.2.1234

 struct socket 存放着我们的数据信息还有一个输入缓冲区一个输出缓冲区

 3、接受数据

从内核的socket结构体中接受缓冲区中取出数据

4、发送数据

将数据放到内核结构体的发送缓冲区

5、关闭套接字

释放资源

三、相关接口使用

1、创建套接字

① int socket(int domain, int type, int protocol);

② domain:表示地址域类型

(域间通信、ipv4通信、ipv6通信……)其中IPV4(AF_INET)

③ type 套接字类型

SOCK_STREAM 流式套接字  提供字节流传输,对应TCP协议

SOCK_DGRAM 数据报套接字 提供数据报传输,对应UDP协议

④ protocol 协议类型(默认为0,表示使用默认对应协议)

IPPROTO_TCP 值为6

IPPROTO_UDP 值为17

 

⑤ 返回值 创建成功返回一个套接字描述符,失败返回-1

2、为套接字绑定地址信息

①int bind(int sockfd, struct scokaddr *addr, socklen_t len);

② sockfd: 创建套接字返回的套接字描述符

③ addr 要绑定的地址信息(不同的地址域类型,有不同的地址结构)

但是如何通过一个接口完成各种不同地址域类型的地址信息绑定呢?

我们知道ipv4 为uint32_t 具有32位的整形存储  ipv6 uint8_t ip[16] 128位数据  还有其他地址域类型比如域间通信……

 原来他们的存储地址空间不同,但是他们都有相同的格式约定,前俩个字节都保存的是地址域类型,当bind进行绑定信息时,会自动识别这俩个字节的地址域类型,然后采取哪种地址结构进行解析了。

那这一过程如何具体实现呢?

使用不同种类型的地址结构时,选择对应的地址类型进行强转传入,例如ipv4的地址结构类型为struct sockaddr_in进行强转。

const char *addr = "192.168.2.2.1234";

(struct sockaddr_in)addr(进行传入即可)

 ④ 成功返回0, 失败返回-1

 3、发送数据

 ① ssize_t sendto(int sockfd,  void *buf,  size_t dlen, 

                                int flag,  struct sockaddr *peer,  socklen_t alen);

② sockfd socket返回的套接字描述符

③ buf 发送数据的起始地址

④ dlen 发送数据的长度,从buf地址开始,发送dlen长度的数据

⑤ flag 默认为0,阻塞发送(发送缓冲区满了就等着, 当发送缓冲区有空间了进行发送)

⑥ peer 对端地址信息,描述了数据要发送给谁

        描述了信息送往哪里    (这里存放的是接收方的信息)

⑦ alen 对端地址信息长度

⑧ 返回值: 成功返回实际发送数据字节长度,失败返回-1;

4、接收数据

① ssize_t recvfrom(int sockfd,  void *buf,  size_t dlen, 

                                  int flag,  struct sockaddr *peer,  socklen_t  *alen);

② sockfd socket创建的套接字描述符

③ 一块地址空间, 里面用来存放我接收得到的数据

④ dlen 接收数据的字节长度

flag 默认为0,当socket接收缓冲区为空的时候进行等待,有数据了就进行接收

⑥ peer 对端地址信息,描述了接收的数据来自哪里。(这里存放的是来自发送方的地址信息)

⑦ alen  地址信息长度,里面即存放了想要接收的数据长度也存放了实际接收的数据长度

⑧ 成功返回实际接收的数据长度,失败返回-1

5、关闭套接字、释放资源

① int close(int fd)

6、字节序相关接口

①由于网络字节序为大端字节序,所有的网络通信都要遵守这一标准,所以:

                如果主机为小端字节序,即进行对应处理,然后进行发送

                如果主机为大端字节序,不进行处理

② erhost -->  network:

                uint32_t htonl(uint32_t hostlong); (四字节)

                uint16_t htons(uint16_t hostshort);(二字节)

③ network  -->  host

                uint32_t ntohl(uint32_t netlong);(四字节)

                uint16_t ntohs(uint16_t netshort);(二字节)

④ 将点分十进制转换为网络字节序整数

typedef uint32_t in_addr_t;

in_addr_t  inet_addr(const char *ip);

例如:     const char *ip = "192.168.2.2"  ==》  0xC0A80202  (192 -> C0  16进制)

⑤ 将网络字节序整数转换为点分十进制

typedef uint32_t in_addr_t;

const char *inet_ntoa(struct in_addr addr);

结构体in_addr内部只有一个变量为 s_addr

 

五、UDP通信的简单实现

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<stdint.h>
  5 #include<sys/socket.h>
  6 #include<string.h>
  7 #include<arpa/inet.h>
  8 #include<netinet/in.h>
  9 
 10 
 11 
 12 int main(int argc, char *argv[])
 13 {
 14   if(argc != 3)
 15   {
 16     printf("./udp_srv 192.168.2.2 9000\n");
 17     return -1;
 18   }
 19   uint16_t port = atoi(argv[2]);
 20   char *ip = argv[1];
 21 
 22   // 1、创建套接字 int socket(int domain, int type, int protocol)
 23   // 参数分别为 地址域格式使用ipv4,使用SOCK_DGRAM数据报套接字类型,使用对应的IPPROTO_UDP    协议
 24   int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
 25   if(sockfd < 0)
 26   {
 27     perror("socket error");
 28     return -1;
 29   }
 30 
 31   // 2、为套接字绑定地址信息
 32   // int bind(int s
 33   // ockfd, struct sockaddr* addr, socklen_t len);
 34   struct sockaddr_in addr;// 定义一个ipv4的地址结构出来
 35   addr.sin_family = AF_INET;                                                             
 36   addr.sin_port = htons(port); // 因为定义的port为俩字节 所以使用htons
 37   addr.sin_addr.s_addr = inet_addr(ip);// 将ip点分十进制格式转换为整数形式存储到addr结构>    体中的sin_addr结构体中的变量s_addr
 38   socklen_t len = sizeof(struct sockaddr_in);
 39   int ret = bind(sockfd, (struct sockaddr*)&addr, len);
 40   if(ret == -1)
 41   {
 42     perror("bind error");
 43     return -1;
 44   }
 45 
 46   // 3、循环接收发送数据
 47   while(1)
 48   {
 49     // 3.1 接收数据
 50     // ssize_t recvfrom(int sockfd, void *buf, int len, int flag, struct sockaddr *peer,     socklen_t *len)
 51     char buf[1024] = {0};
 52     struct sockaddr_in peer;
 53     socklen_t len = sizeof(struct sockaddr_in);
 54     // peer 中的地址信息是系统进行设置的,数据是谁发的,设置的就是谁的地址
 55     ssize_t ret = recvfrom(sockfd, buf, 1023, 0, (struct sockaddr*)&peer, &len);
 56     if(ret < 0)
 57     {
 58       perror("revcfrom error");
 59       return -1;
 60     }
 61     char *peerip = inet_ntoa(peer.sin_addr);// 将网络字节序转化为字符串
 62     uint16_t peerport = ntohs(peer.sin_port);// 将网络字节序端口转化为主机字节序端口
 63     printf("client[%s:%d] say:%s\n", peerip, peerport, buf);
 64 
 65     // 4、发送数据
 66     char data[1024] = {0};
 67     printf("sever say:");
 68     fflush(stdout);
 69     scanf("%s", data);
 70     ret = sendto(sockfd, data, strlen(data), 0, (struct sockaddr*)&peer, len);
 71     if(ret < 0)
 72     {
 73       perror("sendto error");
 74       return -1;
 75     }
 76 
 77   }
 78   close(sockfd);
 79   return 0;
 80 }

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值