linux system socket
一 TCP/IP与套接字
- 套接字是通信端点的抽象
- 套接字同样适用于本地的通信,代替管道
- wirte和read同样适用于套接字
二 TCP通讯
1)tcp使用的过程,
client端,主动连接方叫client。
server端,被动接收方叫server。
=>TCP先连接(三次握手)
client > server:发送SYN a
server > client:回复SYN b,ack a + 1
client > server ack b + 1
==>连接通了,双方可以互相收发消息
===>断开连接
client > server:FIN m
server > client :ack m +1
server > client:FIN n
client > server :ack n + 1
2)一个程序套接字需要执行4个步骤
- 分配套接口和初始化
- 连接
- 发送或连接数据
- 关闭套接字
3)涉及到的调用:
socket(),bind(),listen(),connect(),accept(),recv(),send()
#include int socket(int domain,int type,int protocal); int bind(int sockfd,const struct sockaddr* my_addr,socklen_t addrlen); int listen(int sockfd,int backlog) int accept(int sockfd,struct sockaddr* addr,socklen_t *addrlen); int connect(int sockfd,const struct sockaddr* serv_addr,socklen_t addrlen); ssize_t send(int s, const void * buf,size_t len,int flags) ssize_t recv(int s, const void * buf,size_t len,int flags) int close(int sockfd) int int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen); /tcp:server端/ struct ps{ }; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; int status = 0; void *recvsocket(void *arg)//接收client端socket数据的线程 } void *sendsocket(void *arg)//向client端socket发送数据的线程 } int main(int arg, char *args[]) } /tcp:client端/ void *recvsocket(void *arg) } void *sendsocket(void *arg) } int main(int arg, char *args[]) } 三 UDP通讯 udp/tcp都可用发送函数: udp接收函数: //udp不需要握手,也不需要server端监听listen /UDP发送/ int main(int arg, char *args[]) } /udp接收端/ int main(int arg, char *args[]) } 四 非阻塞socket和epoll 2)非阻塞socket 3)fcntl函数调用 /fcntl设置非阻塞的列子/ } } /fcntl设置阻塞的列子/ } 4)epoll技术 epoll的系统调用函数 int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event) }epoll_data_t; struct epoll_event } events定义: 关于ET,LT两种工作模式 ET(Edge Triggered)是高速工作方式,只支持no-block socket ET和LT的区别: int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout) /epoll例子/ int socket_create(int port)//在port指定端口上建立server端的socket } int socket_accept (int listen_st) } ssize_t socket_recv(int st) } void setnonblocking(int st) } int main(int arg,char *args[]) } 5)select技术 //参数timeout填NULL,永远等待 int socket; FD_ISSET(socket, &fd1);//判断socket是不是在fd1当中 The time structures involved are defined in /select例子/ #define FD_MAX 1024 } 五 特殊问题 } void sockaddr_toa(const struct sockaddr_in *addr,char * IPAddr) } 2)如何将域名转化为IP } 3)如何得到一个socket自身addr,和远程的addr int getsockname(int sockfd, struct sockaddr * addr, socklen_t *addrlen); 六 抓包 // 定义协议的名称结构 } PROTN2T; // 协议数 // IP头结构 } IPHEADER; // UDP头长度 // ICMP头长度 struct TCPPacketHead }; // ICMP包头部结构 }; // UDP包头结构 }; int SnifferReceive(SOCKET &sock, bool ShowByte = false); int SocketCreate(SOCKET &sock, const char *IPAddr, unsigned short Port); /pub.cpp/ #pragma comment(lib,"ws2_32.lib") char *Get_proto_name(unsigned char proto)// 通过struct _PROTN2T结构的proto成员,得到协议名 } void PrintByte(const char *Buf, size_t BufSize)// 将二进制数转化为16进制字符串,打印到屏幕上 } int SnifferReceive(SOCKET &sock, bool ShowByte) } // 指定的IP地址和端口上,建立一个原始的socket。 } /sniffer.cpp/ #include "stdafx.h" int main(int argc, char* argv[]) }
#include
//domain 说明:一般用AF_INET
值 说明
AF_UNIX UNIX内部使用
AF_INET TCP/IP协议
AF_ISO 国际标准组织协议
AF_NS Xerox网络协议
//type 说明
值 说明
SOCK_STREAM 使用TCP可靠连接
SOCK_DGRAM 使用UDP不可靠连接
//protocal 一般填写0
//成功返回0,失败返回-1,并设置errno
//bind 将进程和一个套接口联系起来,bind通常用于服务器进程为接入客户连接建立一个套接口
//参数sockfd是函数socket调用返回的套接口值
//参数my_addr是结构sockaddr的地址
//参数addrlen设置了my_addr能容纳的最大字节数
//成功返回0,失败返回-1,并设置errno
//监听客户端连接
//参数socket是调用socket返回的套接口描述符
//参数backlog设置接入队列的大小,通常设置为最大
//成功返回0,失败返回-1,并设置errno
//accept会返回一个新的套接口,同时原来的套接口继续监听
//参数sockfd是函数socket调用返回的套接口描述符
//参数addr是指向结构sockaddr的地址
//参数addrlen设置了addr能容纳的最大字节数
//成功返回0,失败返回-1,并设置errno
//客户端调用connect与服务端连接
//参数sockfd是函数socket调用返回的套接口描述符
//参数addr是指向结构sockaddr的地址
//参数addrlen设置了addr能容纳的最大字节数
//成功返回0,失败返回-1,并设置errno
//参数s是已经建立的套接字
//参数buf是接收数据内存buffer的地址指针
//参数len指明buffer的大小,单位是字节
//参数flags一般填0
//成功返回0,失败返回-1,并设置errno
//参数s是已经建立的套接字
//参数buf是接收数据内存buffer的地址指针
//参数len指明buffer的大小,单位是字节
//参数flags一般填0
//成功返回收到字节数,如果套接字关闭返回0,失败返回-1,并设置errno
//关闭套接字
//setsockopt函数设置套接口
//常见用法:
//int on = 1;
//setsockopt(st,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))
//由于TCP套接字状态TIME_WAIT引起该套接字关闭后约保留2-4
//分钟,在此期间bind该端口会失败,SO_REUSEADDR设置系统地址可重用
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include int st;
pthread_t *thr;
{struct ps *p = (struct ps *)arg;
int st = p->st;
char s[1024];
while(1)
{
memset(s, 0, sizeof(s));
int rc = recv(st, s, sizeof(s), 0);
if (rc <= 0)//如果recv返回小于等于0,代表socket已经关闭或者出错了
break;
printf("%s\n", s);
}
pthread_mutex_lock(&mutex);
status-- ;
pthread_mutex_unlock(&mutex);
pthread_cancel(*(p->thr));//被cancel掉的线程内部没有使用锁。
return NULL;
{int st = *(int *)arg;
char s[1024];
while(1)
{
memset(s, 0, sizeof(s));
read(STDIN_FILENO, s, sizeof(s));//从键盘读取用户输入信息
send(st, s, strlen(s), 0);
}
return NULL;
{if (arg < 2)
{
return -1;
}
int port = atoi(args[1]);
int st = socket(AF_INET, SOCK_STREAM, 0);//初始化socket
//给TCP设置地址可重用
int on = 1;
if (setsockopt(st, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)
{
printf("setsockopt failed %s\n", strerror(errno));
return EXIT_FAILURE;
}
struct sockaddr_in addr;//定义一个IP地址结构
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;//将addr结构的属性定位为TCP/IP地址
addr.sin_port = htons(port);//将本地字节顺序转化为网络字节顺序。
addr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY代表这个server上所有的地址
//将IP与server程序绑定
if (bind(st, (struct sockaddr *) &addr, sizeof(addr)) == -1)
{
printf("bind failed %s\n", strerror(errno));
return EXIT_FAILURE;
}
//server端开始listen,
if (listen(st, 20) == -1)
{
printf("listen failed %s\n", strerror(errno));
return EXIT_FAILURE;
}
int client_st = 0;//client端socket
//socklen_t len = 0;
struct sockaddr_in client_addr;//表示client端的IP地址
//void *p = &client_addr;
pthread_t thrd1, thrd2;
while (1)
{
memset(&client_addr, 0, sizeof(client_addr));
socklen_t len = sizeof(client_addr);
//accept会阻塞,直到有客户端连接过来,accept返回client的socket描述符
client_st = accept(st, (struct sockaddr *)&client_addr , &len);
pthread_mutex_lock(&mutex);//为全局变量加一个互斥锁,防止与线程函数同时读写变量的冲突
status++;
pthread_mutex_unlock(&mutex);//解锁
if (status > 5)//代表这是下一个socket连接
{
close(client_st);
continue;
}
if (client_st == -1)
{
printf("accept failed %s\n", strerror(errno));
return EXIT_FAILURE;
}
printf("accept by addr:%s\n", inet_ntoa(client_addr.sin_addr));
printf("accept by port:%d\n", ntohs(client_addr.sin_port));
struct ps ps1;
ps1.st = client_st;
ps1.thr = &thrd2;
pthread_create(&thrd1, NULL, recvsocket, &ps1);
pthread_detach(thrd1);//设置线程为可分离
pthread_create(&thrd2, NULL, sendsocket, &client_st);
pthread_detach(thrd2);//设置线程为可分离
}
close(st);//关闭server端listen的socket
return EXIT_SUCCESS;
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
{int st = *(int *)arg;
char s[1024];
while(1)
{
memset(s, 0, sizeof(s));
int rc = recv(st, s, sizeof(s), 0);
if (rc <= 0)
break;
printf("%s\n", s);
}
return NULL;
{int st = *(int *)arg;
char s[1024];
while(1)
{
memset(s, 0, sizeof(s));
read(STDIN_FILENO, s, sizeof(s));
send(st, s, strlen(s), 0);
}
return NULL;
{if (arg < 3)
return -1;
int port = atoi(args[2]);
int st = socket(AF_INET, SOCK_STREAM, 0); //初始化socket,
struct sockaddr_in addr; //定义一个IP地址的结构
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET; //设置结构地址类型为TCP/IP地址
addr.sin_port = htons(port); //指定一个端口号:8080,htons:将short类型从host字节类型到net字节类型转化
addr.sin_addr.s_addr = inet_addr(args[1]); //将字符串类型的IP地址转化为int,赋给addr结构成员.
//调用connect连接到结构addr指定的IP地址和端口号
if (connect(st, (struct sockaddr *) &addr, sizeof(addr)) == -1)
{
printf("connect failed %s\n", strerror(errno));
return EXIT_FAILURE;
}
pthread_t thrd1, thrd2;
pthread_create(&thrd1, NULL, recvsocket, &st);
pthread_create(&thrd2, NULL, sendsocket, &st);
pthread_join(thrd1, NULL);
//pthread_join(thrd2, NULL);
close(st); //关闭socket
return EXIT_SUCCESS;
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
{if (arg < 3)
return -1;
int st = socket(AF_INET, SOCK_DGRAM, 0);//建立socket的时候第二个参数值为SOCK_DGRAM
if (st == -1)
{
printf("socket failed %s\n", strerror(errno));
return 0;
}
int port = atoi(args[2]);
//设置UDP socket可以发送广播消息
int on = 1;
if (setsockopt(st, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)) == -1)
{
printf("setsockopt failed %s\n", strerror(errno));
return EXIT_FAILURE;
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(args[1]);
char buf[1024];
while (1)
{
memset(buf, 0, sizeof(buf));
read(STDIN_FILENO, buf, sizeof(buf));//读取用户键盘输入
if (sendto(st, buf, strlen(buf), 0, (struct sockaddr *) &addr,
sizeof(addr)) == -1)//udp使用sendto发送消息
{
printf("sendto failed %s\n", strerror(errno));
break;
}
}
close(st);
return EXIT_SUCCESS;
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
{if (arg < 2)
return -1;
int st = socket(AF_INET, SOCK_DGRAM, 0);
if (st == -1)
{
printf("socket failed %s\n", strerror(errno));
return 0;
}
int port = atoi(args[1]);
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(st, (struct sockaddr *)&addr, sizeof(addr)) == -1)//UDP接收数据,也需要绑定IP
{
printf("bind failed %s\n", strerror(errno));
return -1;
}
char buf[1024];
struct sockaddr_in client_addr;
socklen_t len = sizeof(client_addr);
while(1)
{
memset(&client_addr, 0, sizeof(client_addr));
memset(buf, 0, sizeof(buf));
if(recvfrom(st, buf, sizeof(buf), 0,
(struct sockaddr *)&client_addr, &len) == -1)
{
printf("recvfrom failed %s\n", strerror(errno));
break;
}else
{
printf("%s recv is %s\n", inet_ntoa(client_addr.sin_addr), buf);
}
}
close(st);
return 0;
1)阻塞socket
函数只有在得到结果之后才会返回
线程并发处理socket也不方便,效率也不高
accept函数返回 -1 ,同时errno值为EAGAIN或者EWOULDBLOCK,这两
个宏定义都为整数11
,同时errno设置为11。如果socket已经关闭,函数返回0
一个SIGPIPE信号,进程必须捕捉这个信号,因为SIGPIPE系统默认处理
为关闭进程
//参数fd为要设置的文件描述符或者socket
//参数cmd,F_GETFL为得到目前状态,F_SETFL为设置状态
//宏定义O_NONBLOCK代表非阻塞,0代表阻塞
//返回值为描述符当前状态
int opts = fcntl(st,F_GETFL);
if(opts < 0)
{printf("fcntl failed %s\n",strerror(errno));
opts = opts | O_NONBLOCK;
if(fcntl(st,F_SETFL,opts ) < 0)
{printf("fcntl failed %s\n",strerror(errno));
if(fcntl(st,F_SETFL,0 < 0)
{printf("fcntl failed %s\n",strerror(errno));
下多路复用IO接口select/poll 的增强版本,它能显著提高程序在大量并
发连接中只有少量活跃情况下的系统CPU利用率
int epoll_create(int size)
//epoll_create用来创建一个epoll文件描述符,即epoll的句柄
//参数size指定epoll所支持的最大句柄数
//函数会返回一个新的epoll句柄,之后的所有操作将通过这个新句柄操作
//操作完之后,用close关闭epoll句柄
//epoll_ctl用来添加/修改/删除需要侦听的文件描述符及其事件
//参数epfd是epoll_create()的返回值
//参数op表示动作,用三个宏表示
EPOLL_CTL_ADD: 注册新的fd到epfd中
EPOLL_CTL_MOD: 修改已经注册到fd的监听事件
EPOLL_CTL_DEL: 从epfd中删除一个fd
//参数fd是要监听的socket描述符
//参数event通知内核需要监听什么事件,struct epoll_event结构如下
typedef union epoll_data
{void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
{__unit32_t events;//Epoll events
epoll_data_t data;//User data variable
这个socket加入到EPOLL队列里
LT(Level triggered)是缺省的工作方式,并且同时支持block和no-block socket
描述符发送更多的就绪通知,直到你做了某些操作导致那个文件为
就绪状态了
不会发送更多通知
的通知你
边缘触发
,或由1到0)时才触发
//epoll_wait接受发生在侦听的描述符上,用户感兴趣的IO事件
//参数epfd是epoll_create()的返回值
//参数events一个epoll_events*指针,当epoll_wait这个函数操作成功之后,epoll_events里面将存储所有的读写事件
//参数maxevents是当前需要监听的所有socket句柄数
//参数timeout是epoll_wait的超时,为0的时候表示马上返回,为-1的时候一直等下去,直到有事件范围,正整数表示等这么长时间
//一般如果网络主循环是单独的线程的话,可以用-1来等,这样可以保证一些效率
//如果是和主逻辑在同一个线程的话,可以用0来保证主循环的效率
//epoll_wait范围之后应该是一个循环,遍历所有的事件
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
{int st = socket(AF_INET,SOCK_STREAM,0);
if (st == 0)
return 0;
int on = 0;
if (setsockopt(st, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)
{
printf("setsockopt failed %s\n", strerror(errno));
return EXIT_FAILURE;
}
struct sockaddr_in addr;//定义一个IP地址结构
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;//将addr结构的属性定位为TCP/IP地址
addr.sin_port = htons(port);//将本地字节顺序转化为网络字节顺序。
addr.sin_addr.s_addr = htonl(INADDR_ANY);
//将IP与server程序绑定
if (bind(st, (struct sockaddr *) &addr, sizeof(addr)) == -1)
{
printf("bind failed %s\n", strerror(errno));
return EXIT_FAILURE;
}
//server端开始listen,
if (listen(st, 200) == -1)
{
printf("listen failed %s\n", strerror(errno));
return EXIT_FAILURE;
}
printf("listen %d success\n",port);
return st;
{struct sockaddr_in client_addr;
socklen_t len = sizeof(client_addr);
memset(&client_addr, 0,sizeof(client_addr));
int client_st = accept(listen_st,(struct sockaddr *)&client_addr,&len);
if (client_st == -1)
{
printf("accept failed %s\n",strerror(errno));
return 0;
}
else
{
printf("accept by %s\n",inet_ntoa(client_addr.sin_addr));
return client_st;
}
{char buf[1024];
memset(buf,0,sizeof(buf));
ssize_t rc = recv(st,buf,sizeof(buf),0);
if(rc <= 0)
{
printf("recv failed %s\n",strerror(errno));
}else
{
printf("recv %s\n",buf);
send(st,buf,rc,0);
}
return rc;
{int opts = fcntl(st,F_GETFL);
if(opts < 0)
{
printf("fcntl failed %s\n",strerror(errno));
}
opts = opts | O_NONBLOCK;
if(fcntl(st,F_SETFL,opts )< 0)
{
printf("fcntl failed %s\n",strerror(errno));
}
{if(arg < 2)
return -1;
int iport = atoi(args[1]);
int listen_st = socket_create(iport);
if(listen_st == EXIT_FAILURE)
return -1;
struct epoll_event ev,events[100];//声明epoll_event结构体的变量,ev用于注册事件,数组用于回传要处理的事件
int epfd = epoll_create(100);//生成用于处理accept的epoll专用文件描述符
setnonblocking(listen_st);//把socket设置为非阻塞方式
ev.data.fd = listen_st;//设置要与处理的事件相关的文件描述符
ev.events = EPOLLIN | EPOLLERR | EPOLLHUP;//设置要处理的事件类型
epoll_ctl(epfd,EPOLL_CTL_ADD,listen_st,&ev);//注册epoll事件
int st = 0;
while(1)
{
int nfds = epoll_wait(epfd,events,100,-1);//等待epoll事件发生
if(nfds == -1)
{
printf("epoll_wait failed %s\n",strerror(errno));
break;
}
//正式流程
int i;
for(i = 0;i < nfds;i++)
{
if(events[i].data.fd < 0)
continue;
//检测到一个socket用户连接到了绑定的socket端口,建立新连接
if(events[i].data.fd == listen_st)
{
st = socket_accept(listen_st);
if(st >= 0)
{
setnonblocking(st);
ev.data.fd = st;
ev.events = EPOLLIN | EPOLLERR | EPOLLHUP;//设置要处理的数据类型
epoll_ctl(epfd,EPOLL_CTL_ADD,st,&ev);
continue;
}
}
//client有socket事件到达
if(events[i].events & EPOLLIN)//socket收到数据
{
st = events[i].data.fd;
if(socket_recv(st) <= 0)
{
close(st);
events[i].data.fd = -1;
}
}
if(events[i].events & EPOLLERR)//socket错误
{
st = events[i].data.fd;
close(st);
events[i].data.fd = -1;
}
if(events[i].events & EPOLLHUP)//socket关闭
{
st = events[i].data.fd;
close(st);
events[i].data.fd = -1;
}
}
}
close(epfd);
return 0;
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
//select函数使用方法
fd_set fd1;
FD_ZERO(&fd1);//初始化fd1
FD_SET(socket, &fd1);//将socket添加到fd1
FD_CLR(socket, &fd1);//将socket从fd1移除. struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
and
struct timespec {
long tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int arg,char *args[])
{if(arg < 2)
return -1;
int iport = atoi(args[1]);
int listen_st = socket_create(iport);//建立一个listen socket
if(listen_st == 0)
return -1;
setnonblocking(listen_st); //把socket设置为非阻塞方式
int i = 0;
int maxfd = 0; //最大的socket,select函数的第一个参数使用
int sockfd = -1;
int client[FD_MAX]; //建立一个socket池,最大存放1024个socket
for(i = 0;i < FD_MAX; i++)
{
client[i] = -1;//将socket池中的每一个成员都初始化为-1
}
maxfd = listen_st;//设置最大编号socket
fd_set allset;
int client_st = -1;
while(1)
{
FD_ZERO(&allset); //初始化allset
FD_SET(listen_st,&allset); //将listen_st放入allset结构中
maxfd = listen_st;
for(i = 0;i < FD_MAX; i++)
{
if(client[i] != -1)
{
FD_SET(client[i],&allset);
if(client[i] > maxfd)
maxfd = client[i];//maxfd永远大于最大的sock
}
}
int rc = select(maxfd + 1,&allset,NULL,NULL,NULL);
if(FD_ISSET(listen_st,&allset))//判断集合中的listen_st描述符是否有的消息到达,代表有client连接
{
client_st = socket_accept(listen_st);
if(client_st >= 0)
{
setnonblocking(client_st);
for(i = 0;i < FD_MAX;i++)
{
if(client[i] == -1)
{
client[i] == client_st;//找到client[]数组中空闲的位置,插入当前client_st
break;
}
}
if(i == FD_MAX)
{
close(client_st); //限制超过FD_MAX,断掉
}
rc--; //处理完一个socket,就将select返回的总数减1
if(rc <= 0)
{
continue;//没有更多的socket可以处理
}
}else
{
break;//如果accept失败,跳出
}
}
for(i = 0,i < FD_MAX; i++)
{
sockfd = client[i];
if(sockfd == -1)
{
continue;
}
if(FD_ISSET(sockfd,&allset))
{
if(socket_recv(sockfd) <= 0)//调用socket_recv接收数据
{
close(sockfd);
//FD_CLR(sockfd,&allset);//将该socket移除
client[i] = -1;
}
rc--;//处理完一个socket,就将select返回的总数减1
}
if(rc < 0)
{
break;//已经没有更多的socket可以创建了,循环break
}
}
}
close(listen_st);
return 0;
1)如何将struct sockaddr_in转化为IP地址?
const char* getIPAddrbyaddr_in(struct sockaddr_in *client_addr)
{return inet_ntoa(client_addr->sin_addr);
{unsigned char *p = (unsigned char *)&(addr->sin_addr.s_addr);
sprintf(IPAddr,"%u,%u,%u,%u",p[0],p[1],p[2],p[3]);
const char *getIPAddrbyHostname(const char * hostname)
{static char s[128];
memset(s,0,sizeof(s));
struct hostent *h;
h = gethostbyname(hostname);
strcpy(s, inet_ntoa(*((struct in_addr*)h->h_addr)));
return s;
int getpeername(int sockfd, struct sockaddr * addr, socklen_t *addrlen);
//得到远端sockaddr
//得到自身sockaddr
/pub.h/
#pragma once
#include
#include
#include
#include
typedef struct _PROTN2T
{int proto;
char *pprototext;
#define PROTO_NUM 11
typedef struct _IPHEADER
{unsigned char header_len:4;// 头长度,4个位长
unsigned char version:4;// 版本号,4个位长
unsigned char tos;// 服务类型(主要定义包的优先级)
unsigned short total_len;// 包整个长度的字节数
unsigned short ident;// 标识,由于IP包发送时候在网络传送时可能还要分割为更小的包,标识唯一确定每个发送的数据包
unsigned short flags;// 综合标志位(前3位:标志,后13位:分块偏移,用来重组分割的IP数据包)
unsigned char ttl;// 生存时间,代表网络上的存活寿命
unsigned char proto;// 协议
unsigned short checksum;// 头校验和,该位确保目的主机接收数据和发送数据的相同
unsigned int sourceIP;// 源IP
unsigned int destIP;// 目的IP
#define UDP_HEAD_LEN 8
#define PSEUDO_HEAD_LEN 12
#define ICMP_HEAD_LEN 8
{WORD SourPort;// 16位源端口
WORD DesPort;// 16目的端口
DWORD SeqMo;// 32位序列号,指出了TCP段数据区其实数据位置
DWORD AckNo;// 32位确认号,指出连接期望从数据流中接收的下一字节数据,例如:如果收到最后一个字节序号为630,那么TCP将发一个为631的确认号
BYTE HLen;// 头长度
BYTE FLag;// 标识位,紧急(URG),确认(ACK),推送(PSH),重置(RST),同步(SYN),完成(FIN)
WORD WndSize;// 16位窗口大小
WORD ChkSum;// 16位TCP校验和
WORD UrgPtr;// 16位紧急指针
struct ICMPPacketHead
{BYTE Type;// 类型
BYTE Code;// 编码
WORD ChkSum;// 16位TCP校验和
struct UDPPacketHead
{WORD SourcePort;// 源端口
WORD DestPort;// 目的端口
WORD Len;// 消息包长度
WORD ChkSum;// 16位TCP校验和
#pragma once
#include "stdafx.h"
#include
#include
#include "pub.h"
{switch (proto)
{
case IPPROTO_IP:
return "IP";
case IPPROTO_ICMP:
return "ICMP";
case IPPROTO_IGMP:
return "IGMP";
case IPPROTO_GGP:
return "GGP";
case IPPROTO_TCP:
return "TCP";
case IPPROTO_PUP:
return "PUP";
case IPPROTO_UDP:
return "UDP";
case IPPROTO_IDP:
return "IDP";
case IPPROTO_ND:
return "ND";
case IPPROTO_RAW:
return "RAW";
case IPPROTO_MAX:
return "MAX";
default:
return "UNKNOW";
}
{for (size_t i = 0; i < BufSize; i++)
{
printf("%.2x", (unsigned char)Buf[i]);
/*
if ((i % 8) == 7)
{
printf(" ");
}
if ((i % 16) == 15)
{
printf("\n")
}*/
}
{IPHEADER *ipHeader = NULL;
TCPPacketHead *tcpHeader = NULL;
ICMPPacketHead *icmpHeader = NULL;
UDPPacketHead *udpHeader = NULL;
BYTE *pData = NULL; // 存放数据的buf
char *pLastBuf = NULL;// 最后一个buf
WORD wSrcPort, wDestPort;// 源端口和目的端口
char sSrcIPAddr[32], sDestIPAddr[32], sProtoName[32];
memset(sSrcIPAddr, 0, sizeof(sSrcIPAddr));
memset(sDestIPAddr, 0, sizeof(sDestIPAddr));
memset(sProtoName, 0, sizeof(sProtoName));
in_addr inaddr;
char sBuf[8192];// Socket默认的Buffer位8K
char *pBuf = sBuf;
memset(sBuf, 0, sizeof(sBuf));
int iRes = recv(sock, sBuf, sizeof(sBuf), 0);
if (iRes == SOCKET_ERROR)
{
return WSAGetLastError();
}
// 得到IP包头
ipHeader = (IPHEADER *)pBuf;
// 得到IP包头总长度
WORD iLen = ntohs(ipHeader->total_len);
while (true)
{
if (iLen <= iRes)
{
// 得到IP包的源地址
inaddr.S_un.S_addr = ipHeader->sourceIP;
strcpy(sSrcIPAddr, inet_ntoa(inaddr));
// 得到IP包的目的地址
inaddr.S_un.S_addr = ipHeader->destIP;
strcpy(sDestIPAddr, inet_ntoa(inaddr));
// 得到包的协议名称
strcpy(sProtoName, Get_proto_name(ipHeader->proto));
// 得到IP包头的长度(因为header_len为4比特的数据,所以需要这样计算)
int iHdrLen = ipHeader->header_len & 0xf;
iHdrLen *= 4;
// 总长度减包头长度得到的数据的长度
int iTotalLen = ntohs(ipHeader->total_len);
iTotalLen -= iHdrLen;
switch (ipHeader->proto)
{
case IPPROTO_ICMP:
{
icmpHeader = (ICMPPacketHead *)(sBuf + iHdrLen);
pData = ((BYTE*)icmpHeader) + ICMP_HEAD_LEN;
iTotalLen -= ICMP_HEAD_LEN;
}
case IPPROTO_TCP:
{
tcpHeader = (TCPPacketHead *)(sBuf + iHdrLen);
// 得到源端口
wSrcPort = ntohs(tcpHeader->SourPort);
// 得到目标端口
wDestPort = ntohs(tcpHeader->DesPort);
iHdrLen = tcpHeader->HLen >> 4;
iHdrLen *= 4;
pData = ((BYTE *)tcpHeader) + iHdrLen;
iTotalLen -= iHdrLen;
break;
}
case IPPROTO_UDP:
{
udpHeader = (UDPPacketHead *)(&sBuf[iHdrLen]);
// 得到源端口
wSrcPort = ntohs(udpHeader->SourcePort);
// 得到目标端口
wDestPort = ntohs(udpHeader->DestPort);
pData = ((BYTE *)udpHeader) + UDP_HEAD_LEN;
iTotalLen -= UDP_HEAD_LEN;
break;
}
}
static unsigned int iSequence = 0;
iSequence++;
/*
Internet 组管理协议(IGMP)是因特网协议家族中的一个组播协议,用于IP主机向人一个相邻的
路由器报告他们的组成员情况
*/
if (strcmp(sProtoName, "IGMP") != 0)// 如果是IGMP协议,就补打印协议内容
{
// 过滤掉广播消息
if (strncmp(sDestIPAddr, "192.168.0.255", strlen("192.168.0.255")) != 0)
{
printf("------------------begin %.10u------------------\n", iSequence);
printf("ProtoName:%s\nSrcAddr:%s\nDestAddr:%s\nSrcPort:%d\nDestPort:%d\n",
sProtoName, sSrcIPAddr, sDestIPAddr, wSrcPort, wDestPort);
if (ShowByte)
{
printf("Byte:\n");
PrintByte((char*)pData, iTotalLen);
}
printf("\nASCII:\n%s\n", (char *)pData);
}
}
//printf("------------------end %d.10u------------------\n\n", iSequence);
if (iLen < iRes)
{
iRes -= iLen;
pBuf += iLen;
ipHeader = (IPHEADER *)pBuf;
}
else
{
break;// 如果ipHeader->total_len == iRes则退出
}
}
else// 已经读到的buffer的最后部分,即包的长度
{
int iLast = iLen - iRes;
if (pLastBuf)
delete []pLastBuf;
pLastBuf = new char[iLen];
int iReaden = iRes;
memcpy(pLastBuf, pBuf, iReaden);
iRes = recv(sock, pLastBuf + iReaden, iLast, 0);
if (iRes == SOCKET_ERROR)
return WSAGetLastError();
pBuf = pLastBuf;
ipHeader = (IPHEADER *)pBuf;
if (iRes == iLast)
iRes = iLen;
else
{
// 读剩余所有的数据
iReaden += iRes;
iLast -= iRes;
while (true)
{
iRes = recv(sock, pLastBuf + iReaden, iLast, 0);
if (iRes == SOCKET_ERROR)
return WSAGetLastError();
iReaden += iRes;
iLast -= iRes;
if (iLast <= 0)
break;
}
}
}
}
return 0;
int SocketCreate(SOCKET &sock, const char *IPAddr, unsigned short Port)
{// 初始化win socket环境
unsigned short wVersion;
WSADATA wsaData;
wVersion = MAKEWORD(1, 1);
int iRes = WSAStartup(wVersion, &wsaData);
if (iRes != 0)
return iRes;
// 创建原始的socket
sock = socket(AF_INET, SOCK_RAW, IPPROTO_IP);
if (sock == INVALID_SOCKET)
return WSAGetLastError();
// 设置超时选项
int iRecTime = 50000; // 50秒,设置接收超时
if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char *)&iRecTime, sizeof(iRecTime)) == SOCKET_ERROR)
return WSAGetLastError();
// 将socket bind到一个具体的断口和地址
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(Port);
addr.sin_addr.s_addr = inet_addr(IPAddr);
if (bind(sock, (LPSOCKADDR)&addr, sizeof(addr)) == SOCKET_ERROR)
{
return WSAGetLastError();
}
// 设置socket模式,当调用WSAIoctl函数的时候为了能让socket能接收网络的所有IP包,
// 传给WSAIoctl函数的socket句柄必须设置成AF_INET, SOCK_RAW,和IPPROTO_IP协议,而且
// 这个socket必须显示的bind到本地的一个端口和地址
DWORD dwBufferInLen = 1;
DWORD dwBufferLen[10];
DWORD dwBytesReturned = 0;
// 调用WSAIoctl, 设置socket可以接收所有的IP包
if (WSAIoctl(sock, SIO_RCVALL, &dwBufferInLen, sizeof(dwBufferInLen),
&dwBufferLen, sizeof(dwBufferLen), &dwBytesReturned, NULL, NULL) == SOCKET_ERROR)
{
return WSAGetLastError();
}
return 0;
// sinffer.cpp : 定义控制台应用程序的入口点。
//
#include "pub.h"
{if (argc < 3)
{
printf("usage: %s IPAddress port [byte]\n", argv[0]);
return 0;
}
printf("sniffer\\nauthor: xhf by 2016-7-15\\nversion is 1.0.0\n\n");
SOCKET sock;
int iPort = atoi(argv[2]);
int iRes = SocketCreate(sock, argv[1], (unsigned short)iPort);
if (iRes != 0)
{
LPVOID lpMsgBuf;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
iRes, // 这个地方放的是lasterror的返回值
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&lpMsgBuf,
0,
NULL
);
printf("Error:%d %s\n", iRes, (LPCTSTR)lpMsgBuf);
LocalFree(lpMsgBuf);
return -1;
}
while (true)
{
if (argc ==4)
{
iRes = SnifferReceive(sock, true);
}
else
{
iRes = SnifferReceive(sock);
}
if (iRes != 0)
{
LPVOID lpMsgBuf;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
iRes, // 这个地方放的是lasterror的返回值
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&lpMsgBuf,
0,
NULL
);
}
}
return 0;