Linux下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 <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则只在高低电平变换(即由01
  ,或由10)时才触发


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;  
}  


























评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

少言才不会咸

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值