Hi,大家好,我是编程小6,很荣幸遇见你,我把这些年在开发过程中遇到的问题或想法写出来,今天说一说Linux C Socket 编程,希望能够帮助你!!!。
本文作者:她爱喝水
本文链接:https://www.cnblogs.com/PikapBai/p/13964866.html
Socket(套接字),就是对 网络上进程通信 的 端点 的 抽象 。一个 Socket 就是网络上进程通信的一端, 提供了应用层进程利用网络协议交换数据的机制 。
从所处的位置来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信交互的接口。如下图所示:
流套接字(SOCK_STREAM)用于提供 面向连接 (可靠)的数据传输服务。
流套接字保证数据能够实现无差错、无重复发数据,并按顺序接收。
流套接字(SOCK_STREAM)使用 TCP(The Transmission Control Protocol)协议 进行数据的传输 。
数据报套接字(SOCK_DGRAM)用于提供 无连接 (不可靠)的数据传输服务。
数据报套接字不保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。
数据报套接字(SOCK_DGRAM)使用 UDP(User DatagramProtocol)协议 进行数据的传输 。
原始套接字(SOCK_RAW)可以做到标准套接字做到的事,更可以做到标准套接字做不到的事。
因此如果我们开发的是更底层的应用,比如发送一个自定义的 IP 包、UDP 包、TCP 包或 ICMP 包,捕获所有经过本机网卡的数据包(sniffer),伪装本机的 IP ,拒绝服务攻击(DOS)等,都可以通过原始套接字(SOCK_RAW)实现。
注意:必须在管理员权限下才能使用原始套接字。
分配文件描述符,创建 socket,即创建网络上进程通信的端点。
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol)
注意: type 和 protocol 不可以随意组合 ,如 SOCK_STREAM 不可以跟 IPPROTO_UDP 组合。
domain:即协议域,又称为协议族(family),如下所示:
注:
type:指定 socket 类型,如下所示:
protocol:指定协议,如下所示:
详情查看 man 手册: man 2 socket
将 IP 地址信息绑定到 socket。
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
通信 socket
要绑定的地址信息(包括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 位的预留接口
};
地址信息大小
详细查看 man 手册: man 2 bind
监听指定端口,socket() 创建的 socket 是主动的,调用 listen 使得该 socket 成为 监听 socket ,变主动为被动。
#include <sys/socket.h>
int listen(int sockfd, int backlog);
通信 socket
同时能处理的最大连接要求
详细查看 man 手册: man 2 listen
提取出监听 socket 的等待连接队列中 第一个连接请求,创建 一个新的 socket,即 连接 socket
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/socket.h>
监听 socket,即 在 调用 listen() 后的 监听 socket。
(可选)指针,指向一缓冲区,其中接收为通讯层所知的连接实体的地址。Addr参数的实际格式由套接口创建时所产生的地址族确定。
(可选)指针,输入参数,配合addr一起使用,指向存有addr地址长度的整型数。
详细查看 man 手册:man 2 listen
发送连接请求
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
通信 socket
要连接的服务器地址
地址信息大小
详细查看 man 手册: man 2 connect
将数据由指定的 socket 传给对方主机
#include <sys/types.h>
#include <sys/socket.h>
int sendto (int sockfd , const void * msg, int len, unsigned int flags, const
struct sockaddr * to , int tolen);
已建立连接的 socket,如果利用 UDP 协议则不需建立连接。
发送数据的缓冲区。
缓冲区长度。
调用方式标志位,一般设为 0 。
用来指定要传送的网络地址,结构 sockaddr
sockaddr 的长度
详细查看 man 手册: man 2 sendto
接收远程主机经指定的 socket 传来的数据,并把数据传到由参数 buf 指向的内存空间。
#include <sys/types.h>
#include <sys/socket.h>
int recvfrom(int sockfd,void *buf,int len,unsigned int flags, struct sockaddr *from,int *fromlen);
已建立连接的 socket,如果利用 UDP 协议则不需建立连接。
接收数据缓冲区。
缓冲区长度。
调用方式标志位,一般设为 0 。
(可选)指针,指向装有源地址的缓冲区,结构 sockaddr
(可选)指针,指向 from 缓冲区长度值,sockaddr 的结构长度
详细查看 man 手册: man 2 recvfrom
字节序,是 大于一个字节类型的数据在内存中的存放顺序,由 CPU 架构决定,与操作系统无关 。是在跨平台和网络编程中,时常要考虑的问题。
在内存中,栈是向下生长的,以char arr[4]为例,(因为 char 类型数据只有一个字节,不存在字节序的问题)依次输出每个元素的地址,可以发现,arr[0] 的地址最低,arr[3] 的地址最高,如图:
在十进制中靠左边的是高位,靠右边的是低位,在其他进制也是如此。
例如: 0x12345678,从高位到低位的字节依次是 0x12、0x34、0x56 和 0x78。
字节序被分为两类:
2.** 小端模式**(Little-endian),是指内存的 低地址 存放 数据的低字节 ,内存的 高地址 存放 数据的高字节 。
大端模式 CPU 代表是 IBM Power PC,小端模式 CPU 代表是 Intel X86、ARM。
以 0x12345678 为例,两种模式在内存中的存储情况,如下表所示:
利用 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;
}
运行后结果:
可以看出,我的机器是小端字节序。
网络字节序(NBO,Network Byte Order),是 TCP/IP 中规定好的一种数据表示格式 。它与具体的 CPU 类型、操作系统等无关,从而可以保证数据在不同主机之间传输时能够被正确解释。
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" // 短整型数据网络字节顺序转主机字节顺序
在使用小端字节序的系统中,这些函数会把字节序进行转换。
在使用大端字节序的系统中,这些函数会定义成空宏。
Linux-C TCP简单例子 - nanfeibuyi - https://blog.csdn.net/nanfeibuyi/article/details/88540144 - 例子1
Linux-C TCP简单例子 - nanfeibuyi - https://blog.csdn.net/nanfeibuyi/article/details/88540144 - 例子2
Linux-C TCP简单例子 - nanfeibuyi - https://blog.csdn.net/nanfeibuyi/article/details/88540144 - 例子3
Linux-C TCP简单例子 - nanfeibuyi - https://blog.csdn.net/nanfeibuyi/article/details/88540144 - 例子4
Linux-C TCP简单例子 - nanfeibuyi - https://blog.csdn.net/nanfeibuyi/article/details/88540144 - 例子5
Linux-C TCP简单例子 - nanfeibuyi - https://blog.csdn.net/nanfeibuyi/article/details/88540144 - 例子6
代码来源:Linux-C UDP简单例子 - nanfeibuyi - https://blog.csdn.net/nanfeibuyi/article/details/88540233 - 例子1
代码来源:Linux-C UDP简单例子 - nanfeibuyi - https://blog.csdn.net/nanfeibuyi/article/details/88540233 - 例子2
代码来源:Linux-C UDP简单例子 - nanfeibuyi - https://blog.csdn.net/nanfeibuyi/article/details/88540233 - 例子3
代码来源:Linux-C UDP简单例子 - nanfeibuyi - https://blog.csdn.net/nanfeibuyi/article/details/88540233 - 例子4
代码来源:Linux-C UDP简单例子 - nanfeibuyi - https://blog.csdn.net/nanfeibuyi/article/details/88540233 - 例子5
代码来源:GitHub - zhouyingjiu - https://github.com/zouyingjiu/sniffer
/*
* 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");
}
代码来源:我的 Github - https://github.com/PikapBai/sniffer_cmpHTTP_sendTCP
本文作者:她爱喝水
本文链接:https://www.cnblogs.com/PikapBai/p/13964866.html
今天的分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。
上一篇
已是最后文章
下一篇
已是最新文章