通常我们看到的socket是拆包后的,只能看到用户数据,原始套接字raw就是给你看最原始的,以方便你进行干预。从用户的角度来看,SOCK_STREAM、SOCK_DGRAM这两类套接字似乎的确涵盖了TCP/IP应用的全部,因为基于TCP/IP的应用,从 协议栈的层次上讲,在传输层的确只可能建立于TCP或UDP协议之上(图),而SOCK_STREAM、SOCK_DGRAM又分别对应于TCP和 UDP,所以几乎所有的应用都可以用这两类套接字实现。
Raw Socket与标准套接字(SOCK_STREAM、SOCK_DGRAM)的区别在于前者直接置"根"于操作系统网络核心(Network Core),而SOCK_STREAM、SOCK_DGRAM则"悬浮"于TCP和UDP协议的外围,如图所示:
当我们使用Raw Socket的时候,可以完全自定义IP包,一切形式的包都可以"制造"出来。如果用原始套接字来传输数据的话,就要自己来写TCP或者UDP包的头部。
1、客户端程序
#include<stdio.h>
#include<netinet/tcp.h>
#include<netinet/ip.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include<netinet/udp.h>
#include <string.h>
#define DESTPORT 1234
#define SOURCEPORT 8888
#define SOURCEADDR "127.0.0.1"
#define MAXDATASIZE 100
int main(int argc,char *argv[])
{
int sockfd,num;
struct sockaddr_in addr,peer;
int on;
on = 1;
int mm;
char buf[MAXDATASIZE];
int peerlen = sizeof(peer);
char msg[] = "welcome\n";
int msglen = strlen(msg);
char * shuju;
int nn;
if(argc !=2)
{
printf("Usage:%s<IP Address>\n",argv[0]);
exit(1);
}
bzero(&addr,sizeof(struct sockaddr_in));
addr.sin_family=AF_INET;
addr.sin_port=htons(DESTPORT);
inet_aton(argv[1],&addr.sin_addr);
/*创建套接字*/
if((sockfd=socket(AF_INET,SOCK_RAW,IPPROTO_TCP))==-1)
{
perror("socket() error.");
exit(1);
}
/*设置IP_HDRINCL选项,构造自己的IP头部*/
setsockopt(sockfd,IPPROTO_IP,IP_HDRINCL,&on,sizeof(on));
char buffer[100];
struct ip *ip;
struct tcphdr *tcp;
int head_len;
int i;
head_len = sizeof(struct ip) + sizeof(struct tcphdr) + msglen;
bzero(buffer,100);
/*初始化IP的头部*/
ip = (struct ip *)buffer;
ip->ip_v = IPVERSION;
ip->ip_hl = sizeof(struct ip) >> 2;
ip->ip_tos = 0;
ip->ip_len = htons(head_len);
ip->ip_id = 0;
ip->ip_off = 0;
ip->ip_ttl = MAXTTL;
ip->ip_p = IPPROTO_TCP;
ip->ip_sum = 0;
ip->ip_dst = addr.sin_addr;
ip->ip_src.s_addr = inet_addr(SOURCEADDR);
printf("dest address is %s\n",inet_ntoa(addr.sin_addr));
/*初始化TCP的头部*/
tcp = (struct tcphdr *)(buffer + sizeof(struct ip));
tcp->source = htons(SOURCEPORT);
tcp->dest = htons(DESTPORT);
tcp->seq = random();
tcp->ack_seq = 0;
tcp->doff = 5;
tcp->psh = 1;
tcp->check = 0;
/*让shuju指针指向数据存储处*/
shuju =&buffer[sizeof(struct ip) + sizeof( struct tcphdr)];
memcpy(shuju,msg,msglen);
/* 发送数据*/
mm = sendto(sockfd,buffer,head_len,0,(struct sockaddr *)&addr,sizeof(struct sockaddr));
close(sockfd);
}
2、服务器端程序
#include<stdio.h>
#include<netinet/tcp.h>
#include<netinet/ip.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include<netinet/udp.h>
#include <string.h>
#define DESTPORT 1234
#define SOURCEPORT 8888
#define SOURCEADDR "127.0.0.1"
#define MAXDATASIZE 100
int main()
{
int sockfd,num;
struct sockaddr_in addr,peer;
int on;
on = 1;
int mm;
char buf[MAXDATASIZE];
int peerlen = sizeof(peer);
char * shuju;
int nn;
/*创建一个TCP的原始套接字*/
if((sockfd=socket(AF_INET,SOCK_RAW,IPPROTO_TCP))==-1)
{
perror("socket() error.");
exit(1);
}
/*设置IP_HDRINCL选项,以便构造自己的IP头部*/
setsockopt(sockfd,IPPROTO_IP,IP_HDRINCL,&on,sizeof(on));
char buffer[100];
struct ip *ip;
struct tcphdr *tcp;
int head_len;
int i;
/*初始化缓冲区*/
bzero(buffer,100);
/*初始化IP头部*/
ip = (struct ip *)buffer;
ip->ip_v = IPVERSION;
ip->ip_hl = sizeof(struct ip) >> 2;
ip->ip_tos = 0;
ip->ip_len = htons(head_len);
ip->ip_id = 0;
ip->ip_off = 0;
ip->ip_ttl = MAXTTL;
ip->ip_p = IPPROTO_TCP;
ip->ip_sum = 0;
ip->ip_dst.s_addr = inet_addr(SOURCEADDR);
ip->ip_src.s_addr = inet_addr(SOURCEADDR);
/*初始化TCP头部*/
tcp = (struct tcphdr *)(buffer + sizeof(struct ip));
tcp->source = htons(SOURCEPORT);
tcp->dest = htons(DESTPORT);
tcp->seq = random();
tcp->ack_seq = 0;
tcp->doff = 5;
tcp->psh = 1;
tcp->check = 0;
/*让shuju指针指向数据存储处*/
shuju =&buffer[sizeof(struct ip) + sizeof( struct tcphdr)];
/*接收客户端发来的数据*/
while(1)
{
if((num = recvfrom(sockfd,buffer,MAXDATASIZE,0,(struct sockaddr *)&peer,&peerlen)) == -1)
{
printf("recvfrom() error\n");
exit(1);
}
/*输出客户端的数据*/
printf("client message: %s\n",shuju);
break;
}
/*关闭套接字*/
close(sockfd);
}
4、Linux系统下运行结果
客服端发送一字符串“welcome”服务器端,结果如下:
客户端为:
服务器端为;