linux system socket


原文链接: 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
#include

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
#include
#include
#include
#include
#include
#include
#include
#include
#include

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

}

/tcp:client端/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

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
#include
#include
#include
#include
#include
#include
#include
#include
#include

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
#include
#include
#include
#include
#include
#include
#include
#include
#include

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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

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 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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

#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
#include
#include
#include

// 定义协议的名称结构
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
#include
#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;  

}

`