一、传输层socket(四层socket,普通socket)
可参考本人以下博客:
Windows Socket编程之UDP实现大文件的传输:http://blog.csdn.net/luchengtao11/article/details/71016222
Windows Socket编程之TCP实现大文件的传输:http://blog.csdn.net/luchengtao11/article/details/71012580
(1)创建
-
socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);//TCP
-
//或者
-
socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);//UDP
AF_INEF表示TCP/IP族
第三个参数可以为0,由操作系统自行选择
(2)发送
sendto(sd,buffer,BUFSIZ,0,(SOCKADDR*)&addrServ,sizeof(SOCKADDR));//UDP
send(sd, buffer, BUFSIZ, 0); //TCP
(3)接收
recvfrom(sd,buffer,BUFSIZ,0,(SOCKADDR*)&addrClient,sizeof(SOCKADDR));//UDP
recv(sd, buffer, BUFSIZ, 0);//TCP
sd为socket标识符,buffer为接受/发送缓冲区,BUFSIZ为接受/发送缓冲区。后两个参数为发送或接受的对方地址,可以为NULL
二、网络层socket(三层socket)
可以参考本人一下博客或代码:
Linux下的raw Socket(原始套接字)编程:http://blog.csdn.net/luchengtao11/article/details/73878760
(1)创建
socket(AF_INET, SOCK_RAW, IPPROTO_UDP );//第三个参数可以是UDP,TCP或者ICMP
(2)接收
recvfrom(sd, buffer, sizeof(buffer), 0,(struct sockaddr *)&client_addr, &addrlen));//
后两个参数可以为null
接受的报文是从IP数据报的第一个字节开始的。
当内核有一个需要传递到原始套接字的IP数据报时,它将检查所有进程上的原始套接字,以寻找所有匹配的套接字。每个匹配懂得套接字将被递送以该iP数据报的一个副本。(事实证明,如果进程过多,匹配的套接字过多,内核会忙于数据报的软过滤和分发,而实际的套接字却空闲,导致性能下降)
内核对每个原始套接字均执行如下3个测试,只有这三个测试为真,内核才把接收到的数据报递送到这个套接字。
【1】如果创建这个套接字时制订了非0的协议参数(socket的第三个参数),那么接受到的数据报的协议字段必须匹配该值,否则数据报不递送到这个套接字
【2】如果这个原始套接字已由bind调用绑定了某个本地IP地址,那么接受到的数据报的目的IP地址必须匹配这个绑定的地址,否则该数据报不递送到这个套接字。
【3】如果这个原始套接字已由connect调用指定了某个外地IP地址,那么接受到的数据报的源IP地址必须匹配这个已连接地址,否则该数据报不递送到这个套接字。
注意,如果一个原始套接字以0值协议参数创建的,而且没有bind和connect,那么该套接字将接收可由内核传递到原始套接字的每个原始数据报的一个副本。
(3)发送
-
/*
-
如果IP_HDRINCL未开启,由进程让内核发送的数据是从IP首部之后的第一个字节开始的,内核会自动构造合适的IP
-
如果IP_HDRINGL开启,进程需要自行构造IP包
-
*/
-
/*
-
if (setsockopt(sd, IPPROTO_IP, IP_HDRINCL, val, sizeof(int)))
-
{
-
perror("setsockopt() error");
-
exit(-1);
-
}
-
*/
-
sendto(sd, buffer, request_length, 0, (sockaddr *)&client_addr, addrlen);
三、数据链路层scoket(二层socket)
代码可以参考:
raw_socket_数据链路层收发实例:https://github.com/Wuchenwcf/MyCode/blob/master/C%2B%2B/Linux/computer%20network/raw_socket_%E6%95%B0%E6%8D%AE%E9%93%BE%E8%B7%AF%E5%B1%82%E6%94%B6%E5%8F%91%E5%AE%9E%E4%BE%8B.cpp
(1)创建
sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP));//第三个参数可以为ETH_P_ALL ETH_P_IP ETH_P_ARP等
(2)接受
-
struct sockaddr_ll client;
-
socklen_t addr_length = sizeof(sockaddr_ll);
-
recvfrom(sock, buffer, 2048, 0, (sockaddr *)&client, &addr_length);//此时的地址是数据链路层的地址
接受的报文是从以太网的帧开始的
(3)发送
sendto(sock, sendbuffer, n, 0, (struct sockaddr *) &client, sizeof(client));
四、小结
引用:http://blog.csdn.net/firefoxbug/article/details/7561159
网卡对该数据帧进行硬过滤(根据网卡的模式不同会有不同的动作,如果设置了promisc混杂模式的话,则不做任何过滤直接交给下一层输入例程,否则非本机mac或者广播mac会被直接丢弃).按照上面的例子,如果成功的话,会进入ip输入例程.但是在进入ip输入例程之前,系统会检查系统中是否有通过socket(AF_PACKET, SOCK_RAW, ..)创建的套接字.如果有的话并且协议相符,在这个例子中就是需要ETH_P_IP或者ETH_P_ALL类型.系统就给每个这样的socket接收缓冲区发送一个数据帧拷贝.然后进入下一步.
其次,进入了ip输入例程(ip层会对该数据包进行软过滤,就是检查校验或者丢弃非本机ip或者广播ip的数据包等,具体要参考源代码),例子中就是如果成功的话会进入udp输入例程.但是在交给udp输入例程之前,系统会检查系统中是否有通过socket(AF_INET, SOCK_RAW, ..)创建的套接字.如果有的话并且协议相符,在这个例子中就是需要IPPROTO_UDP类型.系统就给每个这样的socket接收缓冲区发送一个数据帧拷贝.然后进入下一步。
最后,进入udp输入例程 ...
ps:如果校验和出错的话,内核会直接丢弃该数据包的.而不会拷贝给sock_raw的套接字,因为校验和都出错了,数据肯定有问题的包括所有信息都没有意义了.