一 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 <sys/types.h>
#include <sys/socket.h>
int socket(int domain,int type,int protocal);
//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
int bind(int sockfd,const struct sockaddr* my_addr,socklen_t addrlen);
//bind 将进程和一个套接口联系起来,bind通常用于服务器进程为接入客户连接建立一个套接口
//参数sockfd是函数socket调用返回的套接口值
//参数my_addr是结构sockaddr的地址
//参数addrlen设置了my_addr能容纳的最大字节数
//成功返回0,失败返回-1,并设置errno
int listen(int sockfd,int backlog)
//监听客户端连接
//参数socket是调用socket返回的套接口描述符
//参数backlog设置接入队列的大小,通常设置为最大
//成功返回0,失败返回-1,并设置errno
int accept(int sockfd,struct sockaddr* addr,socklen_t *addrlen);
//accept会返回一个新的套接口,同时原来的套接口继续监听
//参数sockfd是函数socket调用返回的套接口描述符
//参数addr是指向结构sockaddr的地址
//参数addrlen设置了addr能容纳的最大字节数
//成功返回0,失败返回-1,并设置errno
int connect(int sockfd,const struct sockaddr* serv_addr,socklen_t addrlen);
//客户端调用connect与服务端连接
//参数sockfd是函数socket调用返回的套接口描述符
//参数addr是指向结构sockaddr的地址
//参数addrlen设置了addr能容纳的最大字节数
//成功返回0,失败返回-1,并设置errno
ssize_t send(int s, const void * buf,size_t len,int flags)
//参数s是已经建立的套接字
//参数buf是接收数据内存buffer的地址指针
//参数len指明buffer的大小,单位是字节
//参数flags一般填0
//成功返回0,失败返回-1,并设置errno
ssize_t recv(int s, const void * buf,size_t len,int flags)
//参数s是已经建立的套接字
//参数buf是接收数据内存buffer的地址指针
//参数len指明buffer的大小,单位是字节
//参数flags一般填0
//成功返回收到字节数,如果套接字关闭返回0,失败返回-1,并设置errno
int close(int sockfd)
//关闭套接字
int int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
//setsockopt函数设置套接口
//常见用法:
//int on = 1;
//setsockopt(st,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))
//由于TCP套接字状态TIME_WAIT引起该套接字关闭后约保留2-4
//分钟,在此期间bind该端口会失败,SO_REUSEADDR设置系统地址可重用
/*tcp:server端*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
struct ps{
int st;
pthread_t *thr;
};
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int status = 0;
void *recvsocket(void *arg)//接收client端socket数据的线程
{
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;
}
void *sendsocket(void *arg)//向client端socket发送数据的线程
{
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;
}
int main(int arg, char *args[])
{
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 %s\n", inet_ntoa(client_addr.sin_addr));
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;
}
/*tcp:client端*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
void *recvsocket(void *arg)
{
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;
}
void *sendsocket(void *arg)
{
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;
}
int main(int arg, char *args[])
{
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;
}
三 UDP通讯
- UDP处理细节比TCP少
- UDP不能保证消息被传送到目的地
- UDP不能保证数据包传递顺序
- UDP面向无连接
udp/tcp都可用发送函数:
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
udp接收函数:
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
//udp不需要握手,也不需要server端监听listen
/*UDP发送*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
int main(int arg, char *args[])
{
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;
}
/*udp接收端*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
int main(int arg, char *args[])
{
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;
}
四 非阻塞socket和epoll
1)阻塞socket
- 阻塞调用是指调用结果返回之前,当前线程会被挂起。
函数只有在得到结果之后才会返回
- 对于文件操作read、fread函数将会把线程阻塞
- 对于socket、accept、与recv、recvfrom函数调用将会将线程阻塞
- 为了避免整个进程被阻塞后挂起,所以在阻塞模式下,往往需要采用多线程继续
- 一个进程中可以并发的线程总数总是有限的,在处理大量客户端socket连接,
线程并发处理socket也不方便,效率也不高
2)非阻塞socket
- 非阻塞调用是指调用立即返回
- 在非阻塞模式下,accept与recv、recvfrom函数调用就会立刻返回
- 在nonblocking状态下调用accept函数,如果没有客户端请求,那么
accept函数返回 -1 ,同时errno值为EAGAIN或者EWOULDBLOCK,这两
个宏定义都为整数11
- 在nonblocking状态下调用recv、recvfrom函数,如果没有数据,返回-1
,同时errno设置为11。如果socket已经关闭,函数返回0
- 在nonblocking状态下对一个已经关闭的socket调用send函数,将引发
一个SIGPIPE信号,进程必须捕捉这个信号,因为SIGPIPE系统默认处理
为关闭进程
3)fcntl函数调用
- fcntl函数可以将文件或者socket描述符设置为阻塞或非阻塞
- int fcntl(int fd,int cmd,.../*arg*/)
//参数fd为要设置的文件描述符或者socket
//参数cmd,F_GETFL为得到目前状态,F_SETFL为设置状态
//宏定义O_NONBLOCK代表非阻塞,0代表阻塞
//返回值为描述符当前状态
/*fcntl设置非阻塞的列子*/
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));
}
/*fcntl设置阻塞的列子*/
if(fcntl(st,F_SETFL,0 < 0)
{
printf("fcntl failed %s\n",strerror(errno));
}
4)epoll技术
- epoll是linux内核为处理大批量文件描述符而做了改进的poll,是linux
下多路复用IO接口select/poll 的增强版本,它能显著提高程序在大量并
发连接中只有少量活跃情况下的系统CPU利用率
- epoll文件描述符用完后,需要用close关闭
- 每次添加/修改/删除文件描述符都要调用epoll_ctl,所以要尽量少地调用epoll_ctl
epoll的系统调用函数
int epoll_create(int size)
//epoll_create用来创建一个epoll文件描述符,即epoll的句柄
//参数size指定epoll所支持的最大句柄数
//函数会返回一个新的epoll句柄,之后的所有操作将通过这个新句柄操作
//操作完之后,用close关闭epoll句柄
int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event)
//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;
}epoll_data_t;
struct epoll_event
{
__unit32_t events;//Epoll events
epoll_data_t data;//User data variable
}
events定义:
- EPOLLIN:表示对应的文件描述符可以读(包括对端socket正常关闭)
- EPOLLOUT:表示对应的文件描述符可以写
- EPOLLPRI:表示对应的文件描述符有紧急数据可读
- EPOLLERR:表示对应的文件描述符发生错误
- EPOLLHUP:表示对应的文件描述符被挂断(是否被close)
- EPOLLET:将EPOLL设为边缘触发(Edge Triggered)模式 ,这是相对于水平触发(Level Triggered)来说的
- EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把
这个socket加入到EPOLL队列里
关于ET,LT两种工作模式
LT(Level triggered)是缺省的工作方式,并且同时支持block和no-block socket
- 在LT模式中,内核通知一个文件描述符是否就绪了,然后可以对这个就读的fd进行IO操作
- 如果你不做任何操作,内核还是会继续通知你的,所以,这种模式编程出错错误可能要小一点
ET(Edge Triggered)是高速工作方式,只支持no-block socket
- 在ET模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你
- ET模式会假设你知道文件描述符已经就绪,并且不会再为那个文件
描述符发送更多的就绪通知,直到你做了某些操作导致那个文件为
就绪状态了
- 如果一直不对这个fd作IO操作(从而导致他再次变成未就绪),内核
不会发送更多通知
ET和LT的区别:
- LT事件不会丢弃,而是只要读buffer里面有数据可以让用户读,则不断
的通知你
- ET则只在世纪爱发生之时通知.可以简单理解为LT是水平触发,而ET是
边缘触发
- LT模式只要有事件为处理就会触发,而ET则只在高低电平变换(即由0到1
,或由1到0)时才触发
int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout)
//epoll_wait接受发生在侦听的描述符上,用户感兴趣的IO事件
//参数epfd是epoll_create()的返回值
//参数events一个epoll_events*指针,当epoll_wait这个函数操作成功之后,epoll_events里面将存储所有的读写事件
//参数maxevents是当前需要监听的所有socket句柄数
//参数timeout是epoll_wait的超时,为0的时候表示马上返回,为-1的时候一直等下去,直到有事件范围,正整数表示等这么长时间
//一般如果网络主循环是单独的线程的话,可以用-1来等,这样可以保证一些效率
//如果是和主逻辑在同一个线程的话,可以用0来保证主循环的效率
//epoll_wait范围之后应该是一个循环,遍历所有的事件
/*epoll例子*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/epoll.h>
int socket_create(int port)//在port指定端口上建立server端的socket
{
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;
}
int socket_accept (int listen_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;
}
}
ssize_t socket_recv(int 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;
}
void setnonblocking(int st)
{
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));
}
}
int main(int arg,char *args[])
{
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;
}
5)select技术
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
//参数timeout填NULL,永远等待
//select函数使用方法
fd_set fd1;
FD_ZERO(&fd1);//初始化fd1
int socket;
FD_SET(socket, &fd1);//将socket添加到fd1
FD_CLR(socket, &fd1);//将socket从fd1移除.
FD_ISSET(socket, &fd1);//判断socket是不是在fd1当中
The time structures involved are defined in <sys/time.h> and look like
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
and
struct timespec {
long tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
/*select例子*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/select.h>
#define FD_MAX 1024
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);
}
void sockaddr_toa(const struct sockaddr_in *addr,char * IPAddr)
{
unsigned char *p = (unsigned char *)&(addr->sin_addr.s_addr);
sprintf(IPAddr,"%u,%u,%u,%u",p[0],p[1],p[2],p[3]);
}
2)如何将域名转化为IP
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;
}
3)如何得到一个socket自身addr,和远程的addr
int getpeername(int sockfd, struct sockaddr * addr, socklen_t *addrlen);
//得到远端sockaddr
int getsockname(int sockfd, struct sockaddr * addr, socklen_t *addrlen);
//得到自身sockaddr
六 抓包
/*pub.h*/
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wtypes.h>
// 定义协议的名称结构
typedef struct _PROTN2T
{
int proto;
char *pprototext;
} PROTN2T;
// 协议数
#define PROTO_NUM 11
// IP头结构
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
} IPHEADER;
// UDP头长度
#define UDP_HEAD_LEN 8
#define PSEUDO_HEAD_LEN 12
// ICMP头长度
#define ICMP_HEAD_LEN 8
struct TCPPacketHead
{
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位紧急指针
};
// ICMP包头部结构
struct ICMPPacketHead
{
BYTE Type;// 类型
BYTE Code;// 编码
WORD ChkSum;// 16位TCP校验和
};
// UDP包头结构
struct UDPPacketHead
{
WORD SourcePort;// 源端口
WORD DestPort;// 目的端口
WORD Len;// 消息包长度
WORD ChkSum;// 16位TCP校验和
};
int SnifferReceive(SOCKET &sock, bool ShowByte = false);
int SocketCreate(SOCKET &sock, const char *IPAddr, unsigned short Port);
/*pub.cpp*/
#pragma once
#include "stdafx.h"
#include <winsock2.h>
#include <MSTcpIP.h>
#include "pub.h"
#pragma comment(lib,"ws2_32.lib")
char *Get_proto_name(unsigned char proto)// 通过struct _PROTN2T结构的proto成员,得到协议名
{
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";
}
}
void PrintByte(const char *Buf, size_t BufSize)// 将二进制数转化为16进制字符串,打印到屏幕上
{
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")
}*/
}
}
int SnifferReceive(SOCKET &sock, bool ShowByte)
{
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;
}
// 指定的IP地址和端口上,建立一个原始的socket。
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;
}
/*sniffer.cpp*/
// sinffer.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "pub.h"
int main(int argc, char* argv[])
{
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;
}
Linux下socket编程
最新推荐文章于 2025-05-22 21:28:22 发布