在上片文章已经讲过了TCP协议的基本结构和构成并举例,也粗略的讲过了SOCKET,但是讲解的并不完善,这里详细讲解下关于SOCKET的编程的I/O复用函数。

1、I/O复用:selec函数

  在介绍socket编程之前,首先要熟悉下I/O多路转接技术,尽管SOCKET通信编程有很多模型,但是,在UNIX环境下,使用I/O多路转接模型无疑是一种更好的选择,UNIX下有5种I/0模型,分别是阻塞式I/O.非阻塞式I/O、I/O复用(select和poll)、信号驱动式I/O,异步I/O。这5种方式都可用SOCKET编程,这里只介绍阻塞式I/O和I/O复用,如果向详细了解I/O模型的,可以参考《UNIX网络编程卷一:套接字联网API》,或着查看

Socket模型详解(转)

  (1)阻塞式I/O

  阻塞式I/O很好理解,一个线程利用recvfrom系统接收来自一个socket上的数据,没有数据的时候就等待,一直等到有数据,将数据交给应用去处理,之前降到的connect、accept、recv、recvfrom都是属于阻塞程序(所谓阻塞方式block,顾名思义,就是进程或是线程执行到这些函数时必须等待某个事件的发生,如果事件没有发生,进程或线程就被阻塞,函数不能立即返回)。

  (2)I/O复用

  对于只监控一个socket的应用程序来说,阻塞式I/O模型会工作的很好,但是如果需要监控多个socket的话,阻塞式I/O处理起来就比较麻烦了,这时候就可以使用I/O复用很好的解决这个问题。使用I/O复用时,所有的程序将需要监控的socket交给select函数,当某个socket上有可用数据时,select函数会返回通知应用程序。

  (3)select函数

1、select函数原型(UNIX中提供POLL函数与之类似)

  select函数允许进程指示内核等待多个事件中的任何一个发生,并只在一个或多个事件发生或经历一段指定时间后才唤醒它。其原型如下:

int select(int maxfd,  fd_set *readfds,  fd_set *writefds,  fe_set  *esceptfds,  const struct  timeval  *timeout);

2、select函数参数

  第一个参数是需要监控的描述符个数,他的值是最大描述符加1。中间三个参数是要让内核测试读、写和异常条件的描述符,参数readfds指定了被读监控的文件描述集;参数writefds指定了被写监控的文件描述符集,而参数exceptfds被指定了异常监控的文件描述符集,如果我们对某一个条件不感兴趣,就可以设置为空指针。

  这里异常条件中包含了TCP的带外数据到达,大多数嵌入的API返回一个int返回代码。如果该码是<0,则一个错误已发生。错误的性质可以利用所谓的“错误号”,然而,在没有全局变量的多任务工作 环境中,socket领域,我们可以带哦用以下代码来查询错误。

int espx_last_socket_errno(int socket) {

   int ret = 0;

   u32_t optlen = sizeof(ret);

   getsockopt(socket, SOL_SOCKET, SO_ERROR, &ret, &optlen);

   return ret;

}

  

   参数timeout起到了定时器的作用,到来指定的时间,无论是否有设备准备好,都返回调用,timeval的结构定义如下:

struct timeval
{
long tv_sec;//秒
long tv_usec;//微秒
};

  timeout 取不同值时,该调用就表现不同的性质:

  1)、timeout为0,即两个成员的取值必须为0;select调用立即返回,这样就类似为轮询。

  2)、timeout为NULL,select()调用就阻塞,也就是永远等待,直到有描述符就绪。

  3)、timeout为整数,就是等待一段时间,在这段时间内如果有描述符准备就绪,即立即返回,如果超过时间不管有没有准备符也立即返回。

  对于这个参数在UNIX系统下的使用值得注意的是:当timeout设置为永远等待或者等待一段时间的模式时,select函数可能会被信号中断,有些unix的内核在这种情况下,不会再次重启select,因此程序中应该要有处理select返回EINTR错误的准备。

  另外,尽管timeout参数可以表示很大的数值,但部分系统不一定会至此,可能会返回错误。

  如果,select函数中间三个参数都为空的话,select就成为了一个比sleep更为精确的休眠函数。

3、select函数返回值

  select函数调用返回时,除了那些已经就绪的描述符外,select将清除readfds、writerfds、和exceptfds中的没有就绪的描述符。这个情况是值得注意的,在一个典型的socket接收数据的程序中,当select检测到有数据过来,select函数返回,称故乡继续处理接收到的数据,数据处理结束,然后继续调用select监测,这个时候应该使用FD系列(windows和UNIX下)宏重新设置描述符集,在Solaris下,不重新设置的话,有时还能正常工作,但是在部分linux下就不会正常工作。

  select的返回值有如下情况:

1)正常情况下返回就绪的文件描述符个数。

2)经历过timeout时长后仍无设备准备好,返回0;

3)、如果select被某个信号中断,他将返回-1并设置error为EINTR.

4)如果出错,返回-1并设置相应的errno;

2、FD系列宏

  系统提供了4个宏对描述符集进行操作:

  1)void FD_SET(int fd,fd_set *fdset);//宏FD_SET设置文件描述符集fdset中对应的fd的位(设置为1)

  2)void FD_CLR(int fd,fd_set *fdset);//宏FD_CLR清除文件描述符集fdset中对应于文件描述符fd的位(设置为0)

  3)void FD_ISSET(int fd,fd_set *fdset);//宏FD_ZERO清除文件描述符集fdset中的所有位(即把所有位都设置为0)

  4)void FD_ZERO(fd_set *fdset);//检测文件描述符集fdset中对应于文件描述符fd的位是否被设置。

  前三个宏在调用select前被描述符屏蔽位,在调用select后使用FD_ISSET进行检测。

3、select函数应用举例

1、通过前面的学习,我们可以编程如下来首先完成一个客户端的连接:

/*
文件名称:socket.c
功能说明:通过selct实现TCP服务器的非阻塞模式
使用说明:复制此文件,对argc赋值2,argv[1]先赋值1运行启动服务器,再赋值2启动客户端发送数据
编写日期:2017-11-18
修改历史:无
*/
//包含文件申明
#include <stdio.h>
#include<io.h>
#include <string.h>
#include <WinSock2.h> #pragma comment(lib, "ws2_32.lib") //全局变量定义初始化
#define S_ADDR S_un.S_addr
#define CLOSE closesocket
struct socketaddr_in
{
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[];
}; /************************************************************
函数名称:socket_init
函数功能:打开windows下的socket,UNIX下不用
参数:
数据类型 输入/输出描述 说明:socket设置失败返回1,socket设置成功返回0
************************************************************/
void socket_init()//为了在应用程序当中调用任何一个Winsock API函数,首先第一件事情就是必须通过WSAStartup函数完成对Winsock服务的初始化,因此需要调用WSAStartup函数。
{
WSADATA wsa; printf("\n初始化中Initialising Winsock...\n");
if (WSAStartup(MAKEWORD(, ), &wsa) != )
{
printf("Failed. Error Code : %d", WSAGetLastError());
return ;
}
printf("初始化成功Initialised.\n");
} /************************************************************
函数名称:tcp_client
函数功能:创建一个tcp客户端,给IP为"192.168.191.1"的服务器7777端口发送数据Hi,TCP
参数:
数据类型 输入/输出描述 说明:IP地址0"192.168.191.1"是本机现在的网络环境,不同的网络环境的IP不同。可用IPCONFIG在windows的cmd下查询
************************************************************/
void tcp_client()
{
int sockfd;
struct sockaddr_in ServAddr;
int ret;
char* SendStr = "Hi,Tcp"; //定义一个发送的指针,指针指向要发送的数据的地址
int len;
sockfd = socket(AF_INET, SOCK_STREAM, );//打开网络通信端口,为了执行I/O操作,第一件事就是要调用socket函数 ServAddr.sin_family = AF_INET;
ServAddr.sin_port = htons();
ServAddr.sin_addr.S_ADDR = inet_addr("192.168.191.1");//配置ServAddr结构体参数 ret = connect(sockfd, (struct sockaddr*)&ServAddr, sizeof(ServAddr));//connect有TCP客户端用来和TCP服务器建立连接的 if (ret != )
{
return;
}
len = send(sockfd, SendStr, strlen(SendStr), );
//发送数据到服务器
if (len <= )
{
printf("send data error\n");
}
CLOSE(sockfd);//关闭socket函数
return;
}
/************************************************************
函数名称:tcp_server
函数功能:创建一个tcp服务器,接收任何IP地址的客户端给其7777端口发送的数据并显示
参数:
数据类型 输入/输出描述 说明:无
************************************************************/
void tcp_server()
{
int listensockfd;
int connfd;
struct sockaddr_in ServAddr;
fd_set RecvdFd;
struct timeval timeout;
int ret;
char RecvBuf[];
int len;
struct sockaddr ConnAddr;
int ConnAddrLen = sizeof(struct sockaddr);
int maxfd; listensockfd = socket(AF_INET, SOCK_STREAM, );//打开网络通信端口,为了执行I/O操作,第一件事就是要调用socket函数 maxfd = listensockfd; ServAddr.sin_family = AF_INET;
ServAddr.sin_port = htons();
ServAddr.sin_addr.S_ADDR = htonl(INADDR_ANY);//配置ServAddr数据,所有iP的设备都可以通过7777端口将数据传输到这个服务器
//
if (bind(listensockfd, (struct sockaddr*)&ServAddr, sizeof(ServAddr)) != )//
//将本地的一个IP地址和套接字绑定在一起
{
printf("bind error\n");
return;
}
if (listen(listensockfd, ) != )
//listen由TCP服务器调起,监听客户发起的connect,如果监听到connect,则和客户进行三次握手
{
printf("listen error\n ");
return;
}
timeout.tv_sec = ;
timeout.tv_usec = ; FD_ZERO(&RecvdFd);
//清除监听select函数描述符的监听符
for (;;)
{
FD_SET(listensockfd, &RecvdFd);
//设置文件描述符集,将监听socket的描述符加入到select的监控中
memset(RecvBuf, , sizeof(RecvBuf));
//分配接受数据的内存
ret = select(maxfd + , &RecvdFd, NULL, NULL, &timeout);
//配置select函数监控设定的描述符,并且只对“读”事件关心
if (- == ret)
{
continue;
} if (FD_ISSET(listensockfd, &RecvdFd))
//select函数返回,通知有时间,判断是否是监听的socket描述符中的读事件发生
//
{
connfd = accept(listensockfd, &ConnAddr, &ConnAddrLen);
//如果是,则调用accept函数进行事件处理,
if (- == connfd)
{
printf("accept error\n");
continue;
}
FD_SET(connfd, &RecvdFd);
//将accept返回的客户连接描述符加入到select监控位
}
if (FD_ISSET(connfd, &RecvdFd))
//select函数返回,通知有事件,判断是否是客户连接成功的描述符上有事件发生
{
len = recv(connfd, RecvBuf, , );
//如果是,调用recv函数进行数据接收
if (len <= )
//如果recv接受长度小于0,则表示对端关闭
{
FD_CLR(connfd, &RecvdFd);
CLOSE(connfd);
//调用socket关闭函数,关闭本端socket
continue;
}
printf("%s\n", RecvBuf);
//将接收的数据显示出来
}
}
return;
} /************************************************************
函数名称:main
函数功能:主函数
参数:
        数据类型 输入/输出描述
argc int 输入的数据argc
argv char* 输入的数组argv[]
说明:argc=2,且argv[1]为1的时候为服务器,2的时候为客户端,打开两个程序,先编译运行1,再编译运行2
************************************************************/
void main(int argc, char* argv[])
{
argc = ;//是2是为了保证我们设定参数
argv[] = "";//为1的时候为服务器,2的时候为客户端,打开两个程序,先编译运行1,再编译运行2
int type; socket_init();
if (argc != )
{
printf("Usage:< s% [Mode 1|2]>\n", argv[]);
return;
}
type = strtol(argv[], NULL, );
if ( == type)
{
printf("Server Mode Run!\n ");
tcp_server();
return;
}
if ( == type)
{
printf("Client Mode Run!\n");
tcp_client();
return;
} return;
}

实验现象:

先运行argv[1]赋值为1的程序

再运行argv[1]赋值为2的程序,可以看到如下结果

可以看到数据发送成功。

2、为了体现select函数的优越性,将程序进行修改,让客户端发送一系列字符串,服务器端将字符串打印出来,并且让其同时处理两个客户端连接。

/*
文件名称:socket.c
功能说明:通过selct实现TCP服务器的非阻塞模式
使用说明:复制此文件,对argc赋值2,argv[1]先赋值1运行启动服务器,再赋值2启动客户端发送数据
编写日期:2017-11-18
修改历史:
2017-11-18 修改发送数据,使client发送字符串,优化服务器,使服务器可处理多个客户端数据
*/
//包含文件申明
#include <stdio.h>
#include<io.h>
#include <string.h>
#include <WinSock2.h> #pragma comment(lib, "ws2_32.lib") //全局变量定义初始化
#define S_ADDR S_un.S_addr
#define CLOSE closesocket
struct socketaddr_in
{
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[];
}; /************************************************************
函数名称:socket_init
函数功能:打开windows下的socket,UNIX下不用
参数:
数据类型 输入/输出描述 说明:socket设置失败返回1,socket设置成功返回0
************************************************************/
void socket_init()//为了在应用程序当中调用任何一个Winsock API函数,首先第一件事情就是必须通过WSAStartup函数完成对Winsock服务的初始化,因此需要调用WSAStartup函数。
{
WSADATA wsa; printf("\n初始化中Initialising Winsock...\n");
if (WSAStartup(MAKEWORD(, ), &wsa) != )
{
printf("Failed. Error Code : %d", WSAGetLastError());
return ;
}
printf("初始化成功Initialised.\n");
} /************************************************************
函数名称:tcp_client
函数功能:创建一个tcp客户端,给IP为"192.168.191.1"的服务器7777端口发送数据Hi,TCP
参数:
数据类型 输入/输出描述 说明:IP地址0"192.168.191.1"是本机现在的网络环境,不同的网络环境的IP不同。可用IPCONFIG在windows的cmd下查询
修改历史:
2017-11-18 将字符发送优化为字符串发送
************************************************************/
void tcp_client()
{
int sockfd;
struct sockaddr_in ServAddr;
int ret;
char* SendStr[] =
{
"Hi,Tcp",
"HI,TCP,nice to meet you",
"HI,TCP,are you ok???" };//定义一个发送的指针,指针指向要发送的数据的地址
unsigned int SendStrlen = ;
unsigned int SendLen = ;
int loop = ;
int len; sockfd = socket(AF_INET, SOCK_STREAM, ); ServAddr.sin_family = AF_INET;
ServAddr.sin_port = htons();
ServAddr.sin_addr.S_ADDR = inet_addr("192.168.191.1");//配置ServAddr结构体参数 ret = connect(sockfd, (struct sockaddr*)&ServAddr, sizeof(ServAddr));//connect有TCP客户端用来和TCP服务器建立连接的 if (ret != )
{
return;
}
for (loop = ; loop < ; loop++)
{
SendStrlen = strlen(SendStr[loop]);
SendLen = htonl(SendStrlen);
len = send(sockfd, (char*)&SendLen, sizeof(SendLen), );
if (len <= )
{
printf("send data error\n");
break;
}
len = send(sockfd, SendStr[loop], SendStrlen, );
if (len <= )
{
printf("send data error\n");
break;
}
}
CLOSE(sockfd);//关闭socket函数
return;
}
/************************************************************
函数名称:tcp_server
函数功能:创建一个tcp服务器,接收任何IP地址的客户端给其7777端口发送的数据并显示
参数:
数据类型 输入/输出描述 说明:无
************************************************************/
void tcp_server()
{
int listensockfd;
int connfd;
struct sockaddr_in ServAddr;
fd_set RecvdFd;
struct timeval timeout;
int ret;
char RecvBuf[];
int len;
struct sockaddr ConnAddr;
int ConnAddrLen = sizeof(struct sockaddr);
int maxfd; int maxconn = ;
int clientfd[] = { -,- };
int loop = ;
unsigned int RecvStrlen = ;
unsigned int RecvedTotallen = ; listensockfd = socket(AF_INET, SOCK_STREAM, );//打开网络通信端口,为了执行I/O操作,第一件事就是要调用socket函数 maxfd = listensockfd; ServAddr.sin_family = AF_INET;
ServAddr.sin_port = htons();
ServAddr.sin_addr.S_ADDR = htonl(INADDR_ANY);//配置ServAddr数据,所有iP的设备都可以通过7777端口将数据传输到这个服务器
//
if (bind(listensockfd, (struct sockaddr*)&ServAddr, sizeof(ServAddr)) != )//
//将本地的一个IP地址和套接字绑定在一起
{
printf("bind error\n");
return;
}
if (listen(listensockfd, ) != )
//listen由TCP服务器调起,监听客户发起的connect,如果监听到connect,则和客户进行三次握手
{
printf("listen error\n ");
return;
}
timeout.tv_sec = ;
timeout.tv_usec = ; FD_ZERO(&RecvdFd);
//清除监听select函数描述符的监听符
for (;;)
{
FD_SET(listensockfd, &RecvdFd);
//设置文件描述符集,将监听socket的描述符加入到select的监控中
memset(RecvBuf, , sizeof(RecvBuf));
//分配接受数据的内存
ret = select(maxfd + , &RecvdFd, NULL, NULL, &timeout);
//配置select函数监控设定的描述符,并且只对“读”事件关心
if (- == ret)
{
continue;
} if (FD_ISSET(listensockfd, &RecvdFd))
//select函数返回,通知有时间,判断是否是监听的socket描述符中的读事件发生
{
connfd = accept(listensockfd, &ConnAddr, &ConnAddrLen);
//如果是,则调用accept函数进行事件处理,
if (- == connfd)
{
printf("accept error=%d\n", GetLastError());
continue;
}
for (loop = ; loop < ; loop++)//最多允许两个客户输入,并将客户连接的socket描述符保存好。
{
if (- == clientfd[loop])
{
FD_SET(connfd, &RecvdFd);
//将accept返回的客户连接描述符加入到select监控位
clientfd[loop] = connfd;
maxfd = (maxfd > connfd ? maxfd : connfd);
break;
}
}
if (loop >= )
{
printf("Max connect reached.\n");
}
}
for (loop = ; loop < ; loop++)//循环检查来两个客户端的socket描述符,看是否有数据可读。
{
if (FD_ISSET(clientfd[loop], &RecvdFd))
//select函数返回,通知有事件,判断是否是客户连接成功的描述符上有事件发生
{
RecvStrlen = ;
len = recv(connfd, (char*)&RecvStrlen, sizeof(RecvStrlen), );
//如果是,调用recv函数进行数据接收
if (len <= )
//如果recv接受长度小于0,则表示对端关闭
{
FD_CLR(connfd, &RecvdFd);
CLOSE(connfd);
//调用socket关闭函数,关闭本端socket
continue;//先接收四个字节长度的字符串长度
}
memset(RecvBuf, , sizeof(RecvBuf));
RecvStrlen = ntohl(RecvStrlen);//将接收到的长度进行字节转换,转换成本机序
RecvedTotallen = ;
while ()//循环接收每次发送的字符串,直到制定长度
{
len = recv(connfd, &RecvBuf[RecvedTotallen], RecvStrlen, );
if (len <= )
{
FD_CLR(clientfd[loop], &RecvdFd);
closesocket(clientfd[loop]);
break;
}
RecvedTotallen += len; if (RecvedTotallen == RecvStrlen)
{
break;
}
}
printf("%s\n", RecvBuf);
}
}
}
return;
} /************************************************************
函数名称:main
函数功能:主函数
参数:
数据类型 输入/输出描述
argc int 输入的数据argc
argv char* 输入的数组argv[]
说明:argc=2,且argv[1]为1的时候为服务器,2的时候为客户端,打开两个程序,先编译运行1,再编译运行2
************************************************************/
void main(int argc, char* argv[])
{
argc = ;//是2是为了保证我们设定参数
argv[] = "";//为1的时候为服务器,2的时候为客户端,打开两个程序,先编译运行1,再编译运行2
int type; socket_init();
if (argc != )
{
printf("Usage:< s% [Mode 1|2]>\n", argv[]);
return;
}
type = strtol(argv[], NULL, );
if ( == type)
{
printf("Server Mode Run!\n ");
tcp_server();
return;
}
if ( == type)
{
printf("Client Mode Run!\n");
tcp_client();
return;
} return;
}

同样的方法进行调用结果如下:

当第二个客户端连接到服务器时,有如下输出

3、通过之前的编程和优化,基本上已经实现了大多数TCP服务的应用场景,但是此处还有一个问题,就是当服务器正在接收数据时,另一个客户端又发来数据了该如何处理,这里就设计到TCP的并发处理了,每当accept一个连接时就创建一个线程去单独处理这个连接,下面对前面的程序进行修改如下

/*
文件名称:socket.c
功能说明:通过selct实现TCP服务器的非阻塞模式
使用说明:复制此文件,对argc赋值2,argv[1]先赋值1运行启动服务器,再赋值2启动客户端发送数据
编写日期:2017-11-18
修改历史:
2017-11-18 修改发送数据,使client发送字符串,优化服务器,使服务器可处理多个客户端数据
*/
//包含文件申明
#include <stdio.h>
#include<io.h>
#include <string.h>
#include <WinSock2.h>
#include <conio.h>
#include <process.h> #pragma comment(lib, "ws2_32.lib") //全局变量定义初始化
#define S_ADDR S_un.S_addr
#define CLOSE closesocket
struct socketaddr_in
{
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[];
}; /************************************************************
函数名称:socket_init
函数功能:打开windows下的socket,UNIX下不用
参数:
数据类型 输入/输出描述 说明:socket设置失败返回1,socket设置成功返回0
************************************************************/
void socket_init()//为了在应用程序当中调用任何一个Winsock API函数,首先第一件事情就是必须通过WSAStartup函数完成对Winsock服务的初始化,因此需要调用WSAStartup函数。
{
WSADATA wsa; printf("\n初始化中Initialising Winsock...\n");
if (WSAStartup(MAKEWORD(, ), &wsa) != )
{
printf("Failed. Error Code : %d", WSAGetLastError());
return ;
}
printf("初始化成功Initialised.\n");
} /************************************************************
函数名称:tcp_client
函数功能:创建一个tcp客户端,给IP为"192.168.191.1"的服务器7777端口发送数据Hi,TCP
参数:
数据类型 输入/输出描述 说明:IP地址0"192.168.191.1"是本机现在的网络环境,不同的网络环境的IP不同。可用IPCONFIG在windows的cmd下查询
修改历史:
2017-11-18 将字符发送优化为字符串发送
************************************************************/
void tcp_client()
{
int sockfd;
struct sockaddr_in ServAddr;
int ret;
char* SendStr[] =
{
"Hi,Tcp",
"HI,TCP,nice to meet you",
"HI,TCP,are you ok???" };//定义一个发送的指针,指针指向要发送的数据的地址
unsigned int SendStrlen = ;
unsigned int SendLen = ;
int loop = ;
int len; sockfd = socket(AF_INET, SOCK_STREAM, ); ServAddr.sin_family = AF_INET;
ServAddr.sin_port = htons();
ServAddr.sin_addr.S_ADDR = inet_addr("192.168.191.1");//配置ServAddr结构体参数 ret = connect(sockfd, (struct sockaddr*)&ServAddr, sizeof(ServAddr));//connect有TCP客户端用来和TCP服务器建立连接的 if (ret != )
{
return;
}
for (loop = ; loop < ; loop++)
{
SendStrlen = strlen(SendStr[loop]);
SendLen = htonl(SendStrlen);
len = send(sockfd, (char*)&SendLen, sizeof(SendLen), );
if (len <= )
{
printf("send data error\n");
break;
}
len = send(sockfd, SendStr[loop], SendStrlen, );
if (len <= )
{
printf("send data error\n");
break;
}
}
CLOSE(sockfd);//关闭socket函数
return;
}
/************************************************************
函数名称:tcp_server
函数功能:创建一个tcp服务器,接收任何IP地址的客户端给其7777端口发送的数据并显示
参数:
数据类型 输入/输出描述 说明:无
************************************************************/
void tcp_server()
{
int listensockfd;
int connfd;
struct sockaddr_in ServAddr;
fd_set RecvdFd;
struct timeval timeout;
int ret;
char RecvBuf[];
int len;
struct sockaddr ConnAddr;
int ConnAddrLen = sizeof(struct sockaddr); listensockfd = socket(AF_INET, SOCK_STREAM, );//打开网络通信端口,为了执行I/O操作,第一件事就是要调用socket函数 ServAddr.sin_family = AF_INET;
ServAddr.sin_port = htons();
ServAddr.sin_addr.S_ADDR = htonl(INADDR_ANY);//配置ServAddr数据,所有iP的设备都可以通过7777端口将数据传输到这个服务器
//
if (bind(listensockfd, (struct sockaddr*)&ServAddr, sizeof(ServAddr)) != )//
//将本地的一个IP地址和套接字绑定在一起
{
printf("bind error\n");
return;
}
if (listen(listensockfd, ) != )
//listen由TCP服务器调起,监听客户发起的connect,如果监听到connect,则和客户进行三次握手
{
printf("listen error\n ");
return;
}
timeout.tv_sec = ;
timeout.tv_usec = ; FD_ZERO(&RecvdFd);
//清除监听select函数描述符的监听符
for (;;)
{
FD_SET(listensockfd, &RecvdFd);
//设置文件描述符集,将监听socket的描述符加入到select的监控中
ret = select(listensockfd + , &RecvdFd, NULL, NULL, &timeout);
//配置select函数监控设定的描述符,并且只对“读”事件关心,只监控监听描述符,不再监控客户连接进来的描述符。
if (- == ret)
{
continue;
} if (FD_ISSET(listensockfd, &RecvdFd))
//select函数返回,通知有时间,判断是否是监听的socket描述符中的读事件发生
{
connfd = accept(listensockfd, &ConnAddr, &ConnAddrLen);
//如果是,则调用accept函数进行事件处理,
if (- == connfd)
{
printf("accept error=%d\n", GetLastError());
continue;
}
/*创建线程*/
tcp_create_thread(connfd);
}
}
return;
} unsigned int _stdcall tcp_conn_process_thread(void *args)//window下用这个定义
//unix下定义为void* tcp_conn_process_thread(void* args)
{
int connfd;
fd_set RecvdFd;
struct timeval timeout;
char RecvBuf[];
unsigned int RecvStrlen = ;
unsigned int RecvedTotallen = ;
int ret;
int len; timeout.tv_sec = ;
timeout.tv_usec = ; connfd = *((int*)args);
FD_ZERO(&RecvdFd);
//清除监听select函数描述符的监听符
for (;;)
{
FD_SET(connfd, &RecvdFd);
//设置文件描述符集,将监听socket的描述符加入到select的监控中
memset(RecvBuf, , sizeof(RecvBuf));
//分配接受数据的内存
ret = select(connfd + , &RecvdFd, NULL, NULL, &timeout);
//配置select函数监控设定的描述符,并且只对“读”事件关心
if (- == ret)
{
continue;
} if (FD_ISSET(connfd, &RecvdFd))
//select函数返回,通知有时间,判断是否是监听的socket描述符中的读事件发生
{
RecvStrlen = ;
len = recv(connfd, (char*)&RecvStrlen, sizeof(RecvStrlen), );
//如果是,调用recv函数进行数据接收
if (len <= )
//如果recv接受长度小于0,则表示对端关闭
{
CLOSE(connfd);
//调用socket关闭函数,关闭本端socket
break;
}
memset(RecvBuf, , sizeof(RecvBuf));
RecvStrlen = ntohl(RecvStrlen);//将接收到的长度进行字节转换,转换成本机序
RecvedTotallen = ;
while ()//循环接收每次发送的字符串,直到制定长度
{
len = recv(connfd, &RecvBuf[RecvedTotallen], RecvStrlen, );
if (len <= )
{
CLOSE(connfd);
break;
}
RecvedTotallen += len; if (RecvedTotallen == RecvStrlen)
{
break;
}
}
printf("%s\n", RecvBuf);
}
}
return ; } int g_connfd;
int tcp_create_thread(int sockfd)
{
g_connfd = sockfd;
//声明一个全局变量,作为参数传递给线程,如果使用局部变量的话,由于主线程与新创线程运行的时间关系,在线创建线程需要
//使用参数地址时,主线程函数调用已经推出,从而导致传递的局部变量的地址已经被释放。
unsigned long threadId;
threadId = _beginthreadex(NULL, , (unsigned(_stdcall*)(void*))tcp_conn_process_thread, &g_connfd, , NULL);
//windows下创建一个线程
/***********************************************************************
UNIX下:
pthread_t threadId;
pthread_create(&threaId,NULL,tcp_conn_process_thread,(void*)&g_connfd);
***********************************************************************/
return;
} /************************************************************
函数名称:main
函数功能:主函数
参数:
数据类型 输入/输出描述
argc int 输入的数据argc
argv char* 输入的数组argv[]
说明:argc=2,且argv[1]为1的时候为服务器,2的时候为客户端,打开两个程序,先编译运行1,再编译运行2
************************************************************/
void main(int argc, char* argv[])
{
argc = ;//是2是为了保证我们设定参数
argv[] = "";//为1的时候为服务器,2的时候为客户端,打开两个程序,先编译运行1,再编译运行2
int type; socket_init();
if (argc != )
{
printf("Usage:< s% [Mode 1|2]>\n", argv[]);
return;
}
type = strtol(argv[], NULL, );
if ( == type)
{
printf("Server Mode Run!\n ");
tcp_server();
return;
}
if ( == type)
{
printf("Client Mode Run!\n");
tcp_client();
return;
} return;
}

运行程序1、2后结果如下:

到这里,一个功能完善的TCP服务器就搭建完成了。

下一篇文章:

基于visual studio的UDP编程

28、vSocket模型详解及select应用详解的更多相关文章

  1. linux select函数详解

    linux select函数详解 在Linux中,我们可以使用select函数实现I/O端口的复用,传递给 select函数的参数会告诉内核: •我们所关心的文件描述符 •对每个描述符,我们所关心的状 ...

  2. mysql基础篇 - SELECT 语句详解

    基础篇 - SELECT 语句详解         SELECT语句详解 一.实验简介 SQL 中最常用的 SELECT 语句,用来在表中选取数据,本节实验中将通过一系列的动手操作详细学习 SELEC ...

  3. 数据挖掘模型中的IV和WOE详解

    IV: 某个特征中 某个小分组的 响应比例与未响应比例之差 乘以 响应比例与未响应比例的比值取对数 数据挖掘模型中的IV和WOE详解 http://blog.csdn.net/kevin7658/ar ...

  4. MySQL之SELECT 语句详解

    本文参考实验楼的SELECT 语句详解结合自己操作部分而写成. 注意:大多数系统中,SQL语句都是不区分大小写的,但是出于严谨和便于区分保留字和变量名,在书写的时,保留字应大写,而变量名应小写.所谓的 ...

  5. IE8“开发人员工具”使用详解上(各级菜单详解)

    来源: http://www.cnblogs.com/JustinYoung/archive/2009/03/24/kaifarenyuangongju.html IE8“开发人员工具”使用详解上(各 ...

  6. iOS 开发之照片框架详解之二 —— PhotoKit 详解(下)

    本文链接:http://kayosite.com/ios-development-and-detail-of-photo-framework-part-three.html 这里接着前文<iOS ...

  7. linux驱动由浅入深系列:高通sensor架构实例分析之三(adsp上报数据详解、校准流程详解)【转】

    本文转载自:https://blog.csdn.net/radianceblau/article/details/76180915 本系列导航: linux驱动由浅入深系列:高通sensor架构实例分 ...

  8. ViewPager 详解(二)---详解四大函数

    前言:上篇中我们讲解了如何快速实现了一个滑动页面,但问题在于,PageAdapter必须要重写的四个函数,它们都各有什么意义,在上节的函数内部为什么要这么实现,下面我们就结合Android的API说明 ...

  9. iOS 开发之照片框架详解之二 —— PhotoKit 详解(上)

    转载自:http://kayosite.com/ios-development-and-detail-of-photo-framework-part-two.html 一. 概况 本文接着 iOS 开 ...

随机推荐

  1. BOM心得

    Brower Objects Model浏览器对象模型 ps: 到现在也没个正式标准.............. window是BOM的顶级对象,但一般可以省略 一.Location对象 相当于浏览器 ...

  2. (转)在WinForm中选择本地文件

    相信很多朋友在日常的编程中总会遇到各钟各样的问题,关于在WinForm中选择本地文件就是很多朋友们都认为很难的一个学习.net的难点, 在WebForm中提供了FileUpload控件来供我们选择本地 ...

  3. java有车有房有能力最基本运用

    public class yunsuan { public static void main(String[] args) { // 1是有,0是没有 int i = 1, l = 0;// 有房 i ...

  4. Android.FamousBlogs

    1. cyrilmottier http://cyrilmottier.com/ http://cyrilmottier.com/about/ 2. greenrobot http://greenro ...

  5. linux系统中的进程状态分析

    转载地址:https://blog.csdn.net/shenwansangz/article/details/51981459 linux是一个多用户,多任务的系统,可以同时运行多个用户的多个程序, ...

  6. robot framework测试驱动无法定位页面元素

    robot framework错误提示: [ WARN ] Keyword 'Capture Page Screenshot' could not be run on failure: NoSuchW ...

  7. 枚举之后define

    经常会看到类似下边的code写法,觉得这么写没什么意义. enum { AA, BB, CC, }; #define AA AA #define BB BB #define CC CC 尝试下边代码, ...

  8. PS快捷键大全,记住这些就够了!

    希望能帮到大家更好的学习.

  9. PhpStorm 为 Laravel 搭建 PhpUnit 单元测试环境

    1.PhpStorm 中打开项目的路径为 Laravel 安装的根目录 2.点击右下角 EventLog 提示按钮, 初始化 Composer 的设置 3.打开单元单测试示例类,按提示点击 Fix . ...

  10. 转:Struts2返回JSON数据的具体应用范例

    http://blog.csdn.net/jspamd/article/details/8810109 纠错: <result type="json" name=" ...