龙空技术网

Linux C Socket 编程

闪念基因 589

前言:

今天各位老铁们对“c语言socket文件”都比较珍视,大家都想要分析一些“c语言socket文件”的相关文章。那么小编也在网上网罗了一些对于“c语言socket文件””的相关内容,希望咱们能喜欢,姐妹们一起来学习一下吧!

本文作者:她爱喝水

本文链接:

1 Socket 是什么

Socket(套接字),就是对 网络上进程通信端点抽象 。一个 Socket 就是网络上进程通信的一端, 提供了应用层进程利用网络协议交换数据的机制

从所处的位置来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信交互的接口。如下图所示:

2 Socket 类型2.1 标准套接字标准套接字是在传输层使用的套接字,分为流式套接字(SOCK_STREAM)和数据报套接字(SOCK_DGRAM)。标准套接字在接收和发送时只能操作数据部分(TCP Payload / UDP Payload),而不能对 IP 首部或TCP 首部和 UDP 首部进行操作。2.1.1 流套接字(SOCK_STREAM)

流套接字(SOCK_STREAM)用于提供 面向连接 (可靠)的数据传输服务。

流套接字保证数据能够实现无差错、无重复发数据,并按顺序接收。

流套接字(SOCK_STREAM)使用 TCP(The Transmission Control Protocol)协议 进行数据的传输

2.1.2 数据报套接字(SOCK_DGRAM)

数据报套接字(SOCK_DGRAM)用于提供 无连接 (不可靠)的数据传输服务。

数据报套接字不保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。

数据报套接字(SOCK_DGRAM)使用 UDP(User DatagramProtocol)协议 进行数据的传输

2.2 原始套接字(SOCK_RAW)

原始套接字(SOCK_RAW)可以做到标准套接字做到的事,更可以做到标准套接字做不到的事。

原始套接字是在传输层及传输层以下使用的套接字。原始套接字在接收和发送时不仅能操作数据部分(TCP Payload / UDP Payload),也能对 IP 首部或TCP 首部和 UDP 首部进行操作。

因此如果我们开发的是更底层的应用,比如发送一个自定义的 IP 包、UDP 包、TCP 包或 ICMP 包,捕获所有经过本机网卡的数据包(sniffer),伪装本机的 IP ,拒绝服务攻击(DOS)等,都可以通过原始套接字(SOCK_RAW)实现。

注意:必须在管理员权限下才能使用原始套接字。

3 Socket() 函数 介绍3.1 功能

分配文件描述符,创建 socket,即创建网络上进程通信的端点。

3.2 头文件

#include <sys/types.h>#include <sys/socket.h>
3.3 函数原型

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

3.4 参数

注意: type 和 protocol 不可以随意组合 ,如 SOCK_STREAM 不可以跟 IPPROTO_UDP 组合。

具体的组合和应用场景可以参考 4 创建 Socket 及其应用场景3.4.1 domain

domain:即协议域,又称为协议族(family),如下所示:

AF_INET / PF_INET(2):IPv4,获取 网络层的数据AF_INET6:IPv6AF_UNIX:UNIX 系统本地通信AF_PACKET / PF_PACKET(17):以太网包,获取 数据链路层的数据

注:

AF = Address Family(地址族),PF = Protocol Family(协议族)理论上建立 socket 时是指定协议,应该用 PF_xxxx,设置地址时应该用 AF_xxxx。当然 AF_xxxx和 PF_xxxx 的值是相同的,混用也不会有太大的问题。3.4.2 type

type:指定 socket 类型,如下所示:

SOCK_STREAM(1):面向连接的流式套接字(TCP)SOCK_DGRAM(2):面向无连接的数据包套接字(UDP)SOCK_RAW(3):接收 底层数据报文 的原始套接字SOCK_PACKET(10):过时类型,可以使用,但是已经废弃,以后不保证还能支持,不推荐使用。3.4.3 protocol

protocol:指定协议,如下所示:

0:自动选择 type 类型对应的默认协议。IPPROTO_IP(0):接受 TCP 类型的数据帧IPPROTO_ICMP(1):接受 ICMP 类型的数据帧IPPROTO_IGMP(2)接受 IGMP 类型的数据帧IPPROTO_TCP(6):接受 TCP 类型的数据帧IPPROTO_UDP(17):接受 UDP 类型的数据帧ETH_P_IP(0x800):接收发往本机 MAC 的 IP 类型的数据帧ETH_P_ARP(0x806):接受发往本机 MAC 的 ARP 类型的数据帧ETH_P_RARP(0x8035):接受发往本机 MAC 的 RARP 类型的数据帧ETH_P_ALL(0x3):接收发往本机 MAC 的所有类型 IP ARP RARP 的数据帧,接收从本机发出的所有类型的数据帧。(混杂模式打开的情况下,会接收到非发往本地 MAC 的数据帧)3.5 返回值成功:返回一个文件描述符失败:返回 -1,并设置 errno3.6 备注

详情查看 man 手册: man 2 socket

4 创建 Socket 及其应用场景5 bind() 函数5.1 功能

将 IP 地址信息绑定到 socket。

5.2 头文件

#include <sys/types.h>          #include <sys/socket.h>
5.3 函数原型

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

5.4 参数5.4.1 sockfd

通信 socket

5.4.2 addr

要绑定的地址信息(包括IP地址,端口号)。

通用地址结构体定义:

struct sockaddr {    sa_family_t sa_family;   // 地址族, AF_xxx    char        sa_data[14]; // 包括 IP 和端口号}

新型的地址结构体定义:(查看新型的结构体信息: gedit /usr/include/linux/in.h )

struct sockaddr_in {  __kernel_sa_family_t    sin_family;    // 地址族,IP 协议。默认:AF_INET  __be16                  sin_port;      // 端口号  struct in_addr          sin_addr;      // 网络 IP 地址  unsigned char           __pad          // 8 位的预留接口};
5.4.3 addrlen

地址信息大小

5.5 返回值成功:返回 0失败:返回 -1,并设置 errno5.6 备注

详细查看 man 手册: man 2 bind

6 listen() 函数6.1 功能

监听指定端口,socket() 创建的 socket 是主动的,调用 listen 使得该 socket 成为 监听 socket ,变主动为被动。

6.2 头文件

#include <sys/socket.h>

6.3 函数原型

int listen(int sockfd, int backlog);

6.4 参数6.4.1 sockfd

通信 socket

6.4.2 backlog

同时能处理的最大连接要求

6.5 返回值成功:返回 0失败:返回 -1,并设置 errno6.6 备注

详细查看 man 手册: man 2 listen

7 accept() 函数7.1 功能

提取出监听 socket 的等待连接队列中 第一个连接请求,创建 一个新的 socket,即 连接 socket

新建立的 连接 socket 用于发送数据和接受数据。7.2 头文件

#include <sys/socket.h>

7.3 函数原型

#include <sys/types.h>          #include <sys/socket.h>
7.4 参数7.4.1 sockfd

监听 socket,即 在 调用 listen() 后的 监听 socket。

7.4.2 addr

(可选)指针,指向一缓冲区,其中接收为通讯层所知的连接实体的地址。Addr参数的实际格式由套接口创建时所产生的地址族确定。

7.4.3 addrlen

(可选)指针,输入参数,配合addr一起使用,指向存有addr地址长度的整型数。

7.5 返回值成功:指向 新的 socket(连接 socket)的文件描述符。失败:返回 -1,并设置 errno7.6 备注

详细查看 man 手册:man 2 listen

8 connect() 函数8.1 功能

发送连接请求

8.2 头文件

#include <sys/types.h>          #include <sys/socket.h>
8.3 函数原型

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

8.4 参数8.4.1 sockfd

通信 socket

8.4.2 addr

要连接的服务器地址

8.4.3 addrlen

地址信息大小

8.5 返回值成功:返回 0失败:返回 -1,并设置 errno8.6 备注

详细查看 man 手册: man 2 connect

9 sendto() 函数9.1 功能

将数据由指定的 socket 传给对方主机

9.2 头文件

#include <sys/types.h>          #include <sys/socket.h>
9.3 函数原型
int sendto (int sockfd , const void * msg, int len, unsigned int flags, conststruct sockaddr * to , int tolen);
9.4 参数9.4.1 sockfd

已建立连接的 socket,如果利用 UDP 协议则不需建立连接。

9.4.2 msg

发送数据的缓冲区。

9.4.3 len

缓冲区长度。

9.4.4 flags

调用方式标志位,一般设为 0 。

9.4.5 to

用来指定要传送的网络地址,结构 sockaddr

9.4.6 tolen

sockaddr 的长度

9.5 返回值成功:返回实际传送出去的字符数失败:返回 -1,并设置 errno9.6 备注

详细查看 man 手册: man 2 sendto

10 recvfrom() 函数10.1 功能

接收远程主机经指定的 socket 传来的数据,并把数据传到由参数 buf 指向的内存空间。

10.2 头文件

#include <sys/types.h>          #include <sys/socket.h>
10.3 函数原型

int recvfrom(int sockfd,void *buf,int len,unsigned int flags, struct sockaddr *from,int *fromlen);

10.4 参数10.4.1 sockfd

已建立连接的 socket,如果利用 UDP 协议则不需建立连接。

10.4.2 buf

接收数据缓冲区。

10.4.3 len

缓冲区长度。

10.4.4 flags

调用方式标志位,一般设为 0 。

10.4.5 from

(可选)指针,指向装有源地址的缓冲区,结构 sockaddr

10.4.6 fromlen

(可选)指针,指向 from 缓冲区长度值,sockaddr 的结构长度

10.5 返回值成功:返回实际接受到的字符数失败:返回 -1,并设置 errno10.6 备注

详细查看 man 手册: man 2 recvfrom

11 字节序

字节序,是 大于一个字节类型的数据在内存中的存放顺序,由 CPU 架构决定,与操作系统无关 。是在跨平台和网络编程中,时常要考虑的问题。

11.1 高低地址

在内存中,栈是向下生长的,以char arr[4]为例,(因为 char 类型数据只有一个字节,不存在字节序的问题)依次输出每个元素的地址,可以发现,arr[0] 的地址最低,arr[3] 的地址最高,如图:

11.2 高低字节

在十进制中靠左边的是高位,靠右边的是低位,在其他进制也是如此。

例如: 0x12345678,从高位到低位的字节依次是 0x12、0x34、0x56 和 0x78。

11.3 字节序分类 - 大小端模式

字节序被分为两类:

大端模式 (Big-endian):内存的 低地址 存放 数据的高字节 ,内存的 高地址 存放 数据的低字节 。(与人类阅读顺序一致)

2.** 小端模式**(Little-endian),是指内存的 低地址 存放 数据的低字节 ,内存的 高地址 存放 数据的高字节

大端模式 CPU 代表是 IBM Power PC,小端模式 CPU 代表是 Intel X86、ARM。

11.4 大小端示例

以 0x12345678 为例,两种模式在内存中的存储情况,如下表所示:

11.5 判断大小端

利用 C 语言 union 联合体所有成员共用同一块内存的特性,可以用联合体快速实现判断大小端。

#include <stdio.h>union u{    char c[4];    int i;};int main(void){    union u test;    int j;    test.i = 0x12345678;    for(j = 0; j < sizeof(test.c); j++)    {        printf("0x%x\n",test.c[j]);    }    return 0;}

运行后结果:

可以看出,我的机器是小端字节序。

11.6 网络字节序与本机字节序

网络字节序(NBO,Network Byte Order),是 TCP/IP 中规定好的一种数据表示格式 。它与具体的 CPU 类型、操作系统等无关,从而可以保证数据在不同主机之间传输时能够被正确解释。

网络字节序采用大端(Big-endian)字节序排序方式。主机字节顺序(HBO,Host Network Order),与机器 CPU 相关,数据的存储顺序由 CPU 决定。11.6.1 转换函数

socket 编程中经常会用到 4 个网络字节顺序与本地字节顺序之间的转换函数:htons()、ntohl()、 ntohs()、htons()。

htonl()--"Host to Network Long"        // 长整型数据主机字节顺序转网络字节顺序ntohl()--"Network to Host Long"        // 长整型数据网络字节顺序转主机字节顺序htons()--"Host to Network Short"       // 短整型数据主机字节顺序转网络字节顺序ntohs()--"Network to Host Short"       // 短整型数据网络字节顺序转主机字节顺序

在使用小端字节序的系统中,这些函数会把字节序进行转换。

在使用大端字节序的系统中,这些函数会定义成空宏。

12 代码示例12.1 标准套接字(SOCK_STREAM - TCP)12.1.1 TCP Socket 通信过程12.1.1.1 服务器1. 建立连接阶段调用 socket(),分配文件描述符,创建 服务器 socket调用 bind(),将 socket 与本地 IP 地址和端口绑定调用 listen(),监听指定端口,socket() 创建的 socket 是主动的,调用 listen 使得该 socket 成为监听 socket ,变主动为被动调用 accept(),获得 连接 socket,阻塞等待客户端发起连接2. 数据交互阶段调用 read(),阻塞等待客户端发送的数据请求,收到请求后从 read() 返回,处理客户端请求调用 write(),将数据发送给客户端3. 关闭连接当 read() 返回 0 的时候,说明客户端发来了 FIN 数据包,即关闭连接,调用 close() 关闭 连接 socket 和 监听 socket12.1.1.2 客户端1. 建立连接阶段调用 socket(),分配文件描述符,创建 客户端 socket调用 connect(),向服务器发送建立连接请求2. 数据交互阶段调用 write(),向服务器发送数据调用 read(),阻塞等待服务器应答3. 关闭连接当没有数据发送的时候,调用 close() 关闭 客户端 socket ,即关闭连接,向服务器发送 FIN 数据报12.1.2 单个客户端单个服务器的 TCP 通信

Linux-C TCP简单例子 - nanfeibuyi - - 例子1

12.1.3 多线程实现 - 单个客户端单个服务器的 TCP 通信

Linux-C TCP简单例子 - nanfeibuyi - - 例子2

12.1.4 多路复用实现 - 单个客户端单个服务器的 TCP 通信

Linux-C TCP简单例子 - nanfeibuyi - - 例子3

12.1.5 多个客户端单个服务器的 TCP 通信

Linux-C TCP简单例子 - nanfeibuyi - - 例子4

12.1.6 多线程实现 - 多个客户端单个服务器的 TCP 通信

Linux-C TCP简单例子 - nanfeibuyi - - 例子5

12.1.7 多路复用实现 - 多个客户端单个服务器的 TCP 通信

Linux-C TCP简单例子 - nanfeibuyi - - 例子6

12.2 标准套接字(SOCK_DGRAM- UDP)12.2.1 UDP Socket 通信过程12.2.1.1 服务器1. 建立连接阶段调用 socket(),分配文件描述符,创建 服务器 socket调用 bind(),将 socket 与本地 IP 地址和端口绑定2. 数据交互阶段调用 recvfrom(),阻塞,接受客户端的数据调用 sendto(),将数据发送给客户端3. 关闭连接调用 close() 关闭 服务器 socket12.2.1.2 客户端1. 建立连接阶段调用 socket(),分配文件描述符,创建 客户端 socket2. 数据交互阶段调用 sendto(),向服务器发送数据调用 recvfrom(),阻塞,接受服务器的数据3. 关闭连接调用 close() 关闭 客户端 socket ,即关闭连接。12.2.2 单个客户端单个服务器的 UDP 通信

代码来源:Linux-C UDP简单例子 - nanfeibuyi - - 例子1

12.2.3 多线程实现 - 单个客户端单个服务器的 UDP 通信

代码来源:Linux-C UDP简单例子 - nanfeibuyi - - 例子2

12.2.4 多路复用实现 - 单个客户端单个服务器的 UDP 通信

代码来源:Linux-C UDP简单例子 - nanfeibuyi - - 例子3

12.2.4 UDP 通信组播

代码来源:Linux-C UDP简单例子 - nanfeibuyi - - 例子4

12.2.4 UDP 通信广播

代码来源:Linux-C UDP简单例子 - nanfeibuyi - - 例子5

12.3 原始套接字12.3.1 抓取以太网上的所有数据帧

代码来源:GitHub - zhouyingjiu -

/*  *        sniffer.c  *  *        功能:  *                linux rawSocket 抓取以太网上的所有数据帧  *  *        参数:  *                无  *  *  注意:  *      执行该程序需要 root 权限 sudo ./   */  #include <stdio.h> #include <stdlib.h> #include <string.h>  #ifdef __linux__         #include <unistd.h>         #include <errno.h>         #include <sys/socket.h>         #include <sys/types.h>         #include <netinet/in.h>         #include <netinet/ip.h>         #include <netinet/tcp.h>         #include <netinet/udp.h>         #include <netinet/ip_icmp.h>         #include <net/if_arp.h>         #include <netinet/if_ether.h>         #include <net/if.h>         #include <sys/ioctl.h> #elif __win32__         #include <windows.h>  #endif  void UnpackARP(char *buff); void UnpackIP(char *buff); void UnpackTCP(char *buff); void UnpackUDP(char *buff); void UnpackICMP(char *buff); void UnpackIGMP(char *buff);  int main(int argc, char **argv) {         int sockfd, i;         char buff[2048];                /*        *   监听以太网上的所有数据帧        */         if(0 > (sockfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL))))                 {                 perror("socket error!");                          exit(-1);         }          while(1)         {                 memset(buff, 0, 2048);                                  int n = recvfrom(sockfd, buff, 2048, 0, NULL, NULL);                  printf("%s\n",buff);                                  printf("开始解析数据包============\n");                                  printf("大小: %d\n", n);                                  struct ethhdr *eth = (struct ethhdr*)buff;                                  char *nextStack = buff + sizeof(struct ethhdr);                                  int protocol = ntohs(eth->h_proto);                 switch(protocol)                  {                         case ETH_P_IP:                                 UnpackIP(nextStack);                                 break;                                                  case ETH_P_ARP:                                 UnpackARP(nextStack);                                 break;                 }                                  printf("解析结束=================\n\n");         }          return 0; }  void getAddress(long saddr, char *str)  {         sprintf(str, "%d.%d.%d.%d",                         \                         ((unsigned char*)&saddr)[0],         \                         ((unsigned char*)&saddr)[1],         \                         ((unsigned char*)&saddr)[2],         \                         ((unsigned char*)&saddr)[3]); }  void UnpackARP(char *buff)  {         printf("ARP数据包\n"); }  void UnpackIP(char *buff)  {         struct iphdr *ip = (struct iphdr*)buff;         char *nextStack = buff + sizeof(struct iphdr);         int protocol = ip->protocol;         char data[20];          getAddress(ip->saddr, data);         printf("来源ip %s\n", data);                  bzero(data, sizeof(data));          getAddress(ip->daddr, data);         printf("目标ip %s\n", data);          switch(protocol)          {                 case 0x06:                         UnpackTCP(nextStack);                         break;                  case 0x17:                         UnpackUDP(nextStack);                         break;                                  case 0x01:                         UnpackICMP(nextStack);                         break;                  case 0x02:                         UnpackIGMP(nextStack);                         break;                  default:                         printf("unknown protocol\n");                         break;         } }  void UnpackTCP(char *buff)  {         struct tcphdr *tcp = (struct tcphdr*)buff;                  printf("传输层协议:tcp\n");              printf("来源端口:%d\n", ntohs(tcp->source));         printf("目标端口:%d\n", ntohs(tcp->dest)); }  void UnpackUDP(char *buff)  {         struct udphdr *udp = (struct udphdr*)buff;                  printf("传输层协议:udp\n");              printf("来源端口:%d\n", ntohs(udp->source));         printf("目的端口:%d\n", ntohs(udp->dest)); }  void UnpackICMP(char *buff)  {         printf("ICMP数据包\n");         }  void UnpackIGMP(char *buff)  {         printf("IGMP数据包\n"); }
12.3.2 抓取以太网上的所有数据帧,匹配 HTTP 协议并发送 TCP RST

代码来源:我的 Github -

13 参考资料套接字 - 百度百科 - 套接字/9637606?fromtitle=socket&fromid=281150&fr=aladdinRAW SOCKET - 百度百科 - SOCKET/995623?fromtitle=原始套接字&fromid=23692610&fr=aladdin#ref_[1]_4263346原始套接字简介 - chengqiuming - 原始套接字概述 - anton_99 - Linux 原始套接字抓取底层报文 - 2603898260 - Linux-C TCP 简单例子 - nanfeibuyi - 【Linux网络编程】socket编程“网络字节顺序”和“主机字节顺序” - qq_20553613 - 网络字节序 - 百度百科 - 网络字节序/12610557?fr=aladdin字节序(大小端)理解 - sunflower_della - 理解大小端字节序 - fan-yuan - linux网络编程之TCP/IP的TCP socket通信过程(含实例代码) - 知乎 - linux服务器开发专栏 - Linux C Socket UDP编程详解及实例分享 - 知乎 - linux服务器开发专栏 - Linux-C UDP简单例子 - nanfeibuyi - 《图解 TCP/IP》(第 5 版)[日]竹下隆史 /[日]村山公保/ [日]荒井透 / [日]苅田幸雄浅谈linux下原始套接字 SOCK_RAW 的内幕及其应用 - 知乎 - linux服务器开发专栏 - GitHub - zhouyingjiu -

本文作者:她爱喝水

本文链接:

标签: #c语言socket文件